diff options
| -rw-r--r-- | src/features/messages.js | 20 | ||||
| -rw-r--r-- | src/i18n.js | 1 | ||||
| -rw-r--r-- | src/style.css | 3 | ||||
| -rw-r--r-- | tools/live-stream-test.js | 8 |
4 files changed, 15 insertions, 17 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index eecdb79..0974839 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -8835,13 +8835,9 @@ export function messagesFeature(ctx) { h('div', { class: 'live-tile-title' }, liveTitle(ev) || t('liveUntitled')), h('div', { class: 'row gap6 live-tile-host' }, avatar(host, 'chat-avatar mini', false), h('span', { class: 'small muted' }, displayName(host)))); } - // a profile's past streams, a row to scroll through + // a profile's Streams tab: their past streams, as the Live chip's tiles function pastStreams(pk) { - const past = pastOf(pk).slice(0, 20); - if (!past.length) return null; - return h('div', { class: 'col', style: 'gap:6px', 'data-key': 'live-past-of:' + pk }, - h('div', { class: 'small muted' }, t('livePast')), - h('div', { class: 'live-past-row' }, ...past.map(liveTile))); + return h('div', { class: 'live-grid live-past', 'data-key': 'live-past-of:' + pk }, ...pastOf(pk).slice(0, 60).map(liveTile)); } // a profile's "on now" line function liveBanner(pk) { @@ -12752,7 +12748,6 @@ export function messagesFeature(ctx) { // the hat pitch lives beside the avatar it decorates mine ? hook('hatShopEntry') : null)), ui.profEdit ? null : liveBanner(pk), - ui.profEdit ? null : pastStreams(pk), mine && !ui.profEdit && canGoLive() && !liveNowOf(pk) ? h('button', { class: 'btn-sm go-live-btn', style: 'align-self:flex-start', onClick: openGoLive }, h('span', { class: 'go-live-dot' }), t('goLive')) : null, // no spinner while the kind 0 loads — prefetch keeps this rare, and @@ -12942,7 +12937,9 @@ export function messagesFeature(ctx) { const c = notesFor(pk); // Posts | Replies: the replies are their side of other people's // conversations, apart from what they posted themselves - const tab = profTab(pk); + // Streams joins the tabs once they've streamed + const streamed = pastOf(pk).length > 0; + const tab = profTab(pk) === 'streams' && !streamed ? 'posts' : profTab(pk); const shown = c.notes.filter((e) => (tab === 'replies') === replyish(e)); // a tab that holds only a few of the pages fetched so far (someone // who mostly replies, or mostly posts) asks for older ones by itself: @@ -12950,15 +12947,16 @@ export function messagesFeature(ctx) { // (five pages at most per tab: someone who never replies must not // have their whole history paged through looking for one) const fill = c.autoFill && c.autoFill.tab === tab ? c.autoFill : (c.autoFill = { tab, n: 0 }); - if (c.status === 'ready' && shown.length < 12 && !c.end && !c.loadingMore && fill.n < 5) { + if (tab !== 'streams' && c.status === 'ready' && shown.length < 12 && !c.end && !c.loadingMore && fill.n < 5) { fill.n++; queueMicrotask(() => loadOlderNotes(pk).catch(() => {})); } const tabs = h('div', { class: 'seg prof-tabs', style: 'display:flex;width:100%' }, - ...['posts', 'replies'].map((k) => h('button', { + ...['posts', 'replies', ...(streamed ? ['streams'] : [])].map((k) => h('button', { type: 'button', class: (tab === k ? 'active ' : '') + 'grow', 'data-tab': k, onClick: () => { if (tab === k) return; ui.profTab = { pk, tab: k }; c.win = null; render(); }, - }, t(k === 'posts' ? 'profTabPosts' : 'profTabReplies')))); + }, t(k === 'posts' ? 'profTabPosts' : k === 'replies' ? 'profTabReplies' : 'profTabStreams')))); + if (tab === 'streams') return h('div', { class: 'col', style: 'gap:8px' }, tabs, pastStreams(pk)); // while loading with nothing to show: an invisible copy of the // empty-state line holds the height, so the page doesn't jump when // the answer lands (no spinner). With posts already in hand — from diff --git a/src/i18n.js b/src/i18n.js index ecc5bb4..9b921f1 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -893,6 +893,7 @@ const STR = { profNotesNone: 'No recent posts found.', profTabPosts: 'Posts', profTabReplies: 'Replies', + profTabStreams: 'Streams', profRepliesNone: 'No recent replies found.', profReplyTag: 'reply', msgSearching: 'Searching…', diff --git a/src/style.css b/src/style.css index a7a176e..e8a5379 100644 --- a/src/style.css +++ b/src/style.css @@ -896,9 +896,6 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; } color: #fff; background: #e0245e; box-shadow: 0 0 0 2px var(--paper); text-transform: none; } .live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px 10px; padding: 2px 0 4px; } .live-past-title { margin: 6px 0 0; font-size: 15px; } -.live-past-row { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; scrollbar-width: none; } -.live-past-row::-webkit-scrollbar { display: none; } -.live-past-row .live-tile { flex: 0 0 160px; } .live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #e0245e; margin-right: 5px; vertical-align: 1px; } .live-tile { display: flex; flex-direction: column; gap: 5px; padding: 0; text-align: left; background: none; border: 0; color: inherit; cursor: pointer; min-width: 0; } diff --git a/tools/live-stream-test.js b/tools/live-stream-test.js index 4cd8337..46fb7e9 100644 --- a/tools/live-stream-test.js +++ b/tools/live-stream-test.js @@ -259,15 +259,17 @@ try { await page.evaluate(() => document.querySelector('.notes-feed .live-card').click()); check('the card in the post opens the stream too', await waitFor(() => !!document.querySelector('.live-page'), 5000) && await waitFor(playing, 20000)); await page.evaluate(() => { const a = [...document.querySelectorAll('.live-page a.quote-name')].find((x) => /Stream Host/.test(x.textContent)); a.click(); }); - check('the host\'s profile lists their past streams', await waitFor(() => [...document.querySelectorAll('.live-past-row .live-tile')].some((t) => /Yesterday on coinos/.test(t.innerText)), 10000)); - await page.evaluate(() => [...document.querySelectorAll('.live-past-row .live-tile')].find((t) => /Zelda on Rumble/.test(t.innerText)).click()); + check('the host\'s profile has a Streams tab', await waitFor(() => !!document.querySelector('.prof-tabs [data-tab="streams"]'), 10000) && !(await page.$('.live-past'))); + await page.evaluate(() => document.querySelector('.prof-tabs [data-tab="streams"]').click()); + check('...listing their past streams', await waitFor(() => [...document.querySelectorAll('.live-past .live-tile')].some((t) => /Yesterday on coinos/.test(t.innerText)), 10000)); + await page.evaluate(() => [...document.querySelectorAll('.live-past .live-tile')].find((t) => /Zelda on Rumble/.test(t.innerText)).click()); check('a recording kept as a web page: ended, and a link to watch it there, no player', await waitFor(() => !!document.querySelector('.live-page a.live-rec-link[href="https://rumble.com/v7gt2va-zelda.html"]'), 5000) && await page.evaluate(() => /This stream has ended/.test(document.body.innerText) && !document.querySelector('.live-page video.live-video'))); await page.goBack(); await waitFor(() => !document.querySelector('.live-page'), 5000); check('the host\'s profile says they\'re on', await waitFor(() => !!document.querySelector('.live-banner'), 10000) && await waitText('Building coinos live', 2000)); check('...the player stopped when the page went', await page.evaluate(() => [...document.querySelectorAll('video')].every((v) => v.paused || !v.isConnected))); - await page.evaluate(() => [...document.querySelectorAll('.live-past-row .live-tile')].find((t) => /Yesterday on coinos/.test(t.innerText)).click()); + await page.evaluate(() => [...document.querySelectorAll('.live-past .live-tile')].find((t) => /Yesterday on coinos/.test(t.innerText)).click()); check('a past stream opens on its recording, playing', await waitFor(() => !!document.querySelector('.live-page'), 5000) && await waitFor(playing, 20000) && await page.evaluate(() => !document.body.innerText.includes('This stream has ended'))); check('no page errors', !page.errs.length, page.errs.join(' | ')); |
