Share works in the iOS app on an episode of a private feed (#452)

With no ipx-play link (a private feed, or no ipx-play set up), Share fell
back to the share sheet after the request for the link, which WebKit
refuses once an await has come between the press and it, and a Patreon
address went through confirm() first, which the iOS app answers Cancel
without showing anything (#453). Taps sent the request and nothing
appeared. That address now goes through the same menu as an ipx-play
link, so the sheet opens from a press of its own, and the warning about
an address that carries your access is written in the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-13 16:57:25 +00:00
parent c133adf398
commit b3636e648f
4 changed files with 39 additions and 19 deletions

View File

@@ -524,8 +524,8 @@ const shareAt=(url: string, secs: number)=>secs>=5 ? `${url}?t=${Math.floor(secs
/// An episode shared as Overcast shares one (#447): a link to its page on ipx-play, where anyone
/// can play it, from where you are in it if you like. A private feed, a post, or a server with no
/// ipx-play shares the item's own address, as before.
async function shareEpisode(e: Entry, encId: number|null, btn?: HTMLElement){
/// ipx-play shares the item's own address, through the same menu.
async function shareEpisode(e: Entry, encId: number|null){
const name=entryName(e).text;
const own=encId!=null ? e.enclosures.find(x=>x.id===encId)?.url : e.link;
let link: {url: string|null, why: string|null}={url:null, why:null};
@@ -533,26 +533,26 @@ async function shareEpisode(e: Entry, encId: number|null, btn?: HTMLElement){
link=await api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/share`,
{method:'POST', body:JSON.stringify(encId!=null ? {enc:encId} : {})});
}catch{}
if(!link.url){
if(own) await share(name, own, btn); else toast('There is nothing to share for this item.', true);
return;
}
const url=link.url;
// Where you are: the player's clock when it has this one, else where you left off.
const at=player.guid===e.guid && player.feed===e.feed_id ? audio.currentTime : e.position;
// A menu rather than the share sheet at once: the sheet needs a press of its own, and the
// request above has used up the one that opened this.
const url=link.url ?? own;
if(!url){ toast('There is nothing to share for this item.', true); return; }
// Where you are: the player's clock when it has this one, else where you left off. Only
// ipx-play's page reads a time.
const at=!link.url ? 0 : player.guid===e.guid && player.feed===e.feed_id ? audio.currentTime : e.position;
// A menu rather than the share sheet at once, for the item's own address too: the sheet needs a
// press of its own, and the request above has used up the one that opened this (#452). The
// warning is written here, not asked with confirm(), which the iOS app answers Cancel (#453).
openModal(`<button class="iconbtn cardx" onclick="closeModal()" title="Close" aria-label="Close">${ICON.close}</button>
<h3>Share</h3><p class="hint">${esc(name)}</p>
${!link.url && carriesAccess(url) ? `<p class="hint">${esc(ACCESS_WARNING)}</p>` : ''}
<div class="sharemenu">
<button class="btn primary" id="shlink">${ICON.share} Share link</button>
${at>=5?`<button class="btn" id="shat">${ICON.share} Share from ${clock(at)}</button>`:''}
<button class="btn" id="shcopy">${ICON.copy} Copy link</button>
</div>
<p class="hint">Anyone with the link can play it, and subscribe in their own podcast app.</p>`);
$<HTMLButtonElement>('#shlink').onclick=()=>{ closeModal(); share(name, url); };
${link.url ? '<p class="hint">Anyone with the link can play it, and subscribe in their own podcast app.</p>' : ''}`);
$<HTMLButtonElement>('#shlink').onclick=()=>{ closeModal(); sendLink(name, url); };
const timed=$<HTMLButtonElement>('#shat');
if(timed) timed.onclick=()=>{ closeModal(); share(name, shareAt(url, at)); };
if(timed) timed.onclick=()=>{ closeModal(); sendLink(name, shareAt(url, at)); };
$<HTMLButtonElement>('#shcopy').onclick=async()=>{ await copyText(url, $<HTMLButtonElement>('#shcopy')); toast('Link copied'); closeModal(); };
}
@@ -564,7 +564,7 @@ async function epAction(a: string, e: Entry, el: HTMLElement|null, encId?: numbe
try{
if(a==='play'){ haptic('tap'); play(e, encId!=null ? enc : undefined); }
if(a==='offline' && enc) await toggleKept(enc, entryName(e).text);
if(a==='share') await shareEpisode(e, encId??null, el??undefined);
if(a==='share') await shareEpisode(e, encId??null);
if(a==='flag'){ e.flagged=!e.flagged; await api(path+'/flags',{method:'POST',body:JSON.stringify({flagged:e.flagged})}); haptic('tap'); redraw(); }
if(a==='read'){ await setRead(e,!e.read); haptic(e.read?'done':'tap'); redraw(); }
if(a==='get'){