Phone reader: back and the item's actions float over the text (#456)
On a phone they sat above the title and scrolled away with it, so marking, pinning or sharing a long post meant scrolling back to its top. Now back sits alone on the left and the actions in a capsule on the right, sticky at the top of the reader, as Gmail's iOS reader does. Each theme's own panel, frosted, so no theme changes colour; Glass gets its sheen and a denser fill, since the text scrolls right under them, and Classic its toolbar metal. Read and pin redraw the reader, which now keeps its scroll position, since they are pressed mid-post. The desktop reader is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqkrVA8TdTxe556JB3sR1n
This commit is contained in:
@@ -429,9 +429,19 @@ function detailHtml(e: Entry){
|
||||
const narrow=!!window.matchMedia?.('(max-width:820px)')?.matches;
|
||||
const f=S.feeds.find(x=>x.id===e.feed_id);
|
||||
const num=[e.season?`S${e.season}`:'',e.episode?`E${e.episode}`:''].filter(Boolean).join('');
|
||||
const back=`<button class="btn ico" id="dback" title="Back to the items" aria-label="Back to the items">${ICON.left}</button>`;
|
||||
const acts=`<span class="dacts"><button class="btn ico" data-a="read" title="Mark ${e.read?'unread':'read'}"
|
||||
aria-label="Mark ${e.read?'unread':'read'}" aria-pressed="${!!e.read}">${e.read?ICON.check:ICON.unread}</button>
|
||||
<button class="btn ico" data-a="flag" title="${e.flagged?'Pinned: never deleted. Unpin':'Pin, so it is never deleted'}"
|
||||
aria-label="${e.flagged?'Unpin':'Pin'}" aria-pressed="${!!e.flagged}">${e.flagged?ICON.pinOn:ICON.pin}</button>
|
||||
${webUrl(e.link)?`<a class="btn ico" href="${esc(webUrl(e.link))}" target="_blank" rel="noopener noreferrer"
|
||||
title="Open the original" aria-label="Open the original">${ICON.open}</a>
|
||||
<button class="btn ico" data-a="share" title="Share" aria-label="Share">${ICON.share}</button>`:''}</span>`;
|
||||
// description was sanitized server-side with ammonia before it ever reached here
|
||||
return `
|
||||
<button class="btn ico" id="dback" title="Back to the items" aria-label="Back to the items">${ICON.left}</button>
|
||||
${/* On a phone, back and the item's actions float over the text as it scrolls, so they are
|
||||
at hand anywhere in a long post, not only back at its top. */
|
||||
narrow ? `<div class="dfloat">${back}${acts}</div>` : back}
|
||||
${/* A post without a title starts with its text: its own first words as a heading above
|
||||
themselves would read as a mistake. */ e.title&&e.title.trim() ? `<h3 class="dt">${esc(e.title)}</h3>` : ''}
|
||||
<div class="dmeta">
|
||||
@@ -441,13 +451,7 @@ function detailHtml(e: Entry){
|
||||
.filter(Boolean).map(s=>`<span>${s}</span>`).join('<span class="dot"></span>')}
|
||||
${e.markdown?`<select class="tview" aria-label="Show the text as">${TEXT_VIEWS.map(([v,label])=>
|
||||
`<option value="${v}"${v===textView?' selected':''}>${label}</option>`).join('')}</select>`:''}
|
||||
<span class="dacts"><button class="btn ico" data-a="read" title="Mark ${e.read?'unread':'read'}"
|
||||
aria-label="Mark ${e.read?'unread':'read'}" aria-pressed="${!!e.read}">${e.read?ICON.check:ICON.unread}</button>
|
||||
<button class="btn ico" data-a="flag" title="${e.flagged?'Pinned: never deleted. Unpin':'Pin, so it is never deleted'}"
|
||||
aria-label="${e.flagged?'Unpin':'Pin'}" aria-pressed="${!!e.flagged}">${e.flagged?ICON.pinOn:ICON.pin}</button>
|
||||
${webUrl(e.link)?`<a class="btn ico" href="${esc(webUrl(e.link))}" target="_blank" rel="noopener noreferrer"
|
||||
title="Open the original" aria-label="Open the original">${ICON.open}</a>
|
||||
<button class="btn ico" data-a="share" title="Share" aria-label="Share">${ICON.share}</button>`:''}</span>
|
||||
${narrow?'':acts}
|
||||
</div>
|
||||
${narrow?encs:''}
|
||||
<div class="dbody">${textOf(e)}</div>`;
|
||||
@@ -575,7 +579,14 @@ async function shareEpisode(e: Entry, encId: number|null){
|
||||
|
||||
async function epAction(a: string, e: Entry, el: HTMLElement|null, encId?: number){
|
||||
// Swap the row in place and refresh the text below when it is the one being read.
|
||||
const redraw=()=>{ swapRow(e); if(!el || S.sel===entryKey(e)) showDetail(e); };
|
||||
// The reader keeps its place: on a phone read and pin are pressed anywhere in a long post.
|
||||
const redraw=()=>{
|
||||
swapRow(e);
|
||||
if(el && S.sel!==entryKey(e)) return;
|
||||
const box=$('#detail'), top=box?.scrollTop??0;
|
||||
showDetail(e);
|
||||
if(box) box.scrollTop=top;
|
||||
};
|
||||
const enc=(encId!=null && e.enclosures.find(x=>x.id===encId)) || e.enclosures[0];
|
||||
const path=`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}`;
|
||||
try{
|
||||
|
||||
Reference in New Issue
Block a user