From 4c6b1a8458abdabb7fcf1a7f25f8fab1024b6991 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Mon, 26 Oct 2026 15:59:41 -0400 Subject: [PATCH] Board shows list items and note text; fix white bar under read chat messages - Family notes card lists each shared checklist's items (with who added them) and each note's text - The read tick in Chat and Signal had the same class name as the book reader's page style, drawing a white bar; renamed Co-Authored-By: Claude Sonnet 5.5 --- notes.js | 2 +- public/board.html | 1 + public/board.js | 2 +- public/chat-view.js | 2 +- public/signal-view.js | 2 +- public/style.css | 4 ++-- public/sw.js | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/notes.js b/notes.js index e7e5ecb..4393147 100644 --- a/notes.js +++ b/notes.js @@ -66,5 +66,5 @@ function search(me, q) { return out.slice(0, 8); } /** Notes and lists shared with the whole family, for the family board. */ -const familyNotes = () => all().filter(n => n.share === 'family' && !n.archived).sort((a, b) => (b.pinned - a.pinned) || b.updated - a.updated).slice(0, 20).map(n => ({ id: n.id, type: n.type, title: n.title || (n.type === 'list' ? 'Checklist' : 'Note'), pinned: n.pinned, total: n.items.length, done: n.items.filter(i => i.done).length, next: (n.items.find(i => !i.done) || {}).text || '', body: n.type === 'note' ? n.body.slice(0, 120) : '' })); +const familyNotes = () => all().filter(n => n.share === 'family' && !n.archived).sort((a, b) => (b.pinned - a.pinned) || b.updated - a.updated).slice(0, 20).map(n => ({ id: n.id, type: n.type, title: n.title || (n.type === 'list' ? 'Checklist' : 'Note'), pinned: n.pinned, total: n.items.length, done: n.items.filter(i => i.done).length, next: (n.items.find(i => !i.done) || {}).text || '', items: n.type === 'list' ? n.items.slice(0, 80).map(i => ({ text: i.text, done: !!i.done, by: i.addedBy || '' })) : [], body: n.type === 'note' ? n.body.slice(0, 400) : '' })); module.exports = { handle, search, familyNotes }; diff --git a/public/board.html b/public/board.html index e8a43e1..b2f50a4 100644 --- a/public/board.html +++ b/public/board.html @@ -67,6 +67,7 @@ .err { color: var(--danger); min-height: 20px; } /* touch screen: things scroll by dragging, with no scroll bars */ .card, .chl, #ovb div { scrollbar-width: none; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; } .card::-webkit-scrollbar, .chl::-webkit-scrollbar, #ovb div::-webkit-scrollbar { display: none; } + .nt { margin-top: 6px; } .nti { padding: 3px 0; border: 0; font-size: 16px; } .nti .chk { width: 20px; height: 20px; border: 2px solid var(--line); border-radius: 6px; display: grid; place-items: center; flex: none; font-size: 13px; } .nti.dn .chk { background: var(--accent); border-color: var(--accent); color: #fff; } .nti.dn .g { text-decoration: line-through; opacity: .6; } .ntbody { margin: 2px 0 6px; color: var(--muted); white-space: pre-wrap; font-size: 15px; } .pbtn { width: 34px; height: 34px; border-radius: 50%; background: var(--accent); color: var(--accent-t, #fff); border: 0; font-size: 24px; line-height: 1; cursor: pointer; flex: none; } #ovb input[type=text], #ovb input[type=date], #ovb input[type=time], #ovb select { width: 100%; min-height: 48px; padding: 0 14px; border-radius: 12px; border: 1px solid var(--line); background: var(--panel2); color: var(--text); font-size: 17px; } .pill { display: inline-flex; align-items: center; gap: 6px; margin: 3px 6px 3px 0; padding: 6px 12px; border-radius: 16px; background: var(--panel2); border: 1px solid var(--line); color: var(--text); font: inherit; font-size: 15px; cursor: pointer; } .pill.pool { border-color: var(--accent); } .pill small { color: var(--muted); font-size: 12px; } .pill.more { background: none; border-style: dashed; color: var(--muted); cursor: default; } diff --git a/public/board.js b/public/board.js index 5df6d66..16a4ee8 100644 --- a/public/board.js +++ b/public/board.js @@ -26,7 +26,7 @@ function draw() { + card('Today and tomorrow', day('Today', t0) + day('Tomorrow', t1), 'event') + card('This week', weekCard(d.week)) + card('Shopping list', d.shopping.map(i => `
${esc(i.text)}${i.by ? `${esc(i.by)}` : ''}
`).join('') || none('Nothing to buy'), 'shopping') + card('Family tasks', d.tasks.map(t => `
${esc(t.title)}${t.by ? ` ${esc(t.by)}` : ''}${t.due === t0 ? 'Today' : esc(t.due)}
`).join('') || none('Nothing due'), 'task') - + card('Family notes and lists', d.notes.map(n => `
${esc(n.title)}${n.type === 'list' ? ` ${n.done} of ${n.total} done${n.next ? ' · next: ' + esc(n.next) : ''}` : ` ${esc(n.body)}`}
`).join('') || none('No shared notes'), d.notes.some(n => n.type === 'list') ? 'item' : ''); + + card('Family notes and lists', d.notes.map(n => `
${esc(n.title)}${n.type === 'list' ? ` ${n.done} of ${n.total} done` : ''}
${n.type === 'list' ? n.items.map(i => `
${i.done ? '✓' : ''}${esc(i.text)}${i.by ? ` ${esc(i.by)}` : ''}
`).join('') || '

Empty

' : `

${esc(n.body)}

`}
`).join('') || none('No shared notes'), d.notes.some(n => n.type === 'list') ? 'item' : ''); wireChores(); } function poolAll() { diff --git a/public/chat-view.js b/public/chat-view.js index b2e4dac..d94172d 100644 --- a/public/chat-view.js +++ b/public/chat-view.js @@ -83,7 +83,7 @@ function chBubble(m, c, prev) { const reacts = {}; Object.values(m.react || {}).forEach(e => { reacts[e] = (reacts[e] || 0) + 1; }); const day = !prev || new Date(prev.ts).toDateString() !== new Date(m.ts).toDateString() ? `
${new Date(m.ts).toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric' })}
` : ''; if (m.sys) return `${day}
${esc(m.text)}
`; - const tick = m.out && m.status ? `${m.status === 'sent' ? '✓' : '✓✓'}` : ''; + const tick = m.out && m.status ? `${m.status === 'sent' ? '✓' : '✓✓'}` : ''; return `${day}
${m.fwd ? `↪ Forwarded from ${esc(m.fwd.name)}` : ''}${c.type === 'group' && !m.out ? `${esc(m.name)}` : ''} ${m.poll ? chPoll(m) : ''}${m.quote ? `
${esc(m.quote.name)}${esc(m.quote.text || 'Message')}
` : ''}${att} ${m.deleted ? 'This message was deleted' : m.text ? `
${chMention(sgLink(esc(m.text)).replace(/\n/g, '
'))}
` : ''} diff --git a/public/signal-view.js b/public/signal-view.js index 2e98215..c6362c8 100644 --- a/public/signal-view.js +++ b/public/signal-view.js @@ -104,7 +104,7 @@ function sgBubble(m, c, prev) { return `${day}
${c.type === 'group' && !m.out ? `${esc(m.name || nm(m.from))}` : ''} ${m.quote ? `
${esc(nm(m.quote.author))}${esc(m.quote.text || '')}
` : ''}${att} ${m.deleted ? 'This message was deleted' : m.text ? `
${sgLink(esc(m.text)).replace(/\n/g, '
')}
` : ''} - ${m.edited ? 'edited · ' : ''}${new Date(m.ts).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}${tick ? ` ${tick}` : ''} + ${m.edited ? 'edited · ' : ''}${new Date(m.ts).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}${tick ? ` ${tick}` : ''} ${Object.keys(reacts).length ? `${Object.entries(reacts).map(([e, n]) => e + (n > 1 ? ' ' + n : '')).join(' ')}` : ''}
`; } async function sgDrawThread(first) { diff --git a/public/style.css b/public/style.css index 87bf6bc..4c1e655 100644 --- a/public/style.css +++ b/public/style.css @@ -369,8 +369,8 @@ body:not(.editing) #view { padding-top: 8px; } .sgb .who { display: block; font-weight: 700; font-size: 12px; margin-bottom: 2px; } .sgt a { color: inherit; text-decoration: underline; } .sgmeta { display: block; text-align: right; font-size: 11px; opacity: .7; margin-top: 2px; } -.sgmeta .rd { color: #8be28b; font-weight: 700; } -.sgb.out .sgmeta .rd { color: #fff; } +.sgmeta .sgrd { color: #8be28b; font-weight: 700; } +.sgb.out .sgmeta .sgrd { color: #fff; } .sgimg { display: block; max-width: 100%; max-height: 320px; border-radius: 10px; margin-bottom: 4px; } .sgb audio, .sgb video { max-width: 100%; display: block; margin-bottom: 4px; } .sgfile { display: flex; align-items: center; gap: 8px; text-decoration: underline; margin-bottom: 4px; } diff --git a/public/sw.js b/public/sw.js index 6442389..ac7f04b 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ // App-shell cache only; Seafile/API traffic always goes to the network. -const CACHE = 'hub-v83'; +const CACHE = 'hub-v84'; const SHARE = 'hub-share'; // files shared into the app from other apps, held until the person picks a folder const SHELL = ['/icon.svg', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/apple-touch-icon.png'];