aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-14 00:15:58 +0000
committerAdam Soltys <[email protected]>2026-10-14 00:15:58 +0000
commit8a5118839502adb52cd504867bb8ac602160a48c (patch)
treee24375a6c9ae89939bde000c35db761cc8f003f4
parent9ce87d8742ea44918e73625cc79a0fc184b4597c (diff)
downloadcoinosv3-8a5118839502adb52cd504867bb8ac602160a48c.tar.gz
coinosv3-8a5118839502adb52cd504867bb8ac602160a48c.zip
Feed clips: the player's full-screen button works again, and opens the pager
controlslist=nofullscreen kept Chrome's full screen (no next clip) off the feed, but greyed the button out. The button is back; pressing it backs out of Chrome's full screen and opens the pager on that clip instead. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
-rw-r--r--src/features/messages.js18
-rw-r--r--tools/fullscreen-notice-test.js12
2 files changed, 24 insertions, 6 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 55a4376..16ae3a2 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -7662,7 +7662,17 @@ export function messagesFeature(ctx) {
return true;
}
if (typeof document !== 'undefined') document.addEventListener('fullscreenchange', () => {
- if (fsElement()) return;
+ const el = fsElement();
+ // The player's own full-screen button on a feed clip: the pager, not
+ // Chrome's full screen (no next clip there). The button stays on the bar
+ // (left off, Chrome greys it out) and is taken over here.
+ if (el && !pager && el.matches && el.matches('.notes-feed video.note-video') && el.dataset.clip) {
+ const url = el.dataset.clip;
+ Promise.resolve((document.exitFullscreen || document.webkitExitFullscreen || (() => {})).call(document)).catch(() => {})
+ .then(() => { if (!pager && document.contains(el)) openClip(el, url); });
+ return;
+ }
+ if (el) return;
// out of the pager's full screen some other way (Back on Android, Escape
// on a computer): out of the pager too, in the one step
if (pagerFs) { pagerFs = false; if (pager) dismissPager(); }
@@ -7676,10 +7686,8 @@ export function messagesFeature(ctx) {
// play button: no seek bar, no sound button. Without autoplay the
// player's own controls are the way in, so they stay.
const idle = autoplayOk() && !wokeClips.has(url);
- // In the feed a tap on the clip opens the pager, so the player's own
- // full-screen button (Chrome's full screen: no next clip, and its notice)
- // is left off its bar there.
- const v = h('video', { src: playSrc(url), class: 'note-video', controls: idle ? undefined : true, controlslist: ui.chatOpen && ui.msgView === 'feed' && !ui.noteThread && !ui.profilePk ? 'nofullscreen' : undefined, poster: videoThumbs.get(url) || (videoInfo.get(url) || {}).image || serverThumb(url),
+ // (data-clip: which clip it is, for the full-screen button's pager)
+ const v = h('video', { src: playSrc(url), class: 'note-video', controls: idle ? undefined : true, 'data-clip': url, poster: videoThumbs.get(url) || (videoInfo.get(url) || {}).image || serverThumb(url),
preload: 'none', playsinline: true, muted: loud ? undefined : true,
style: stable ? videoBoxStyle(feedMedia(url)) : undefined,
// learned now, used by the next paint of any row with this clip
diff --git a/tools/fullscreen-notice-test.js b/tools/fullscreen-notice-test.js
index c5658ea..cf15f8c 100644
--- a/tools/fullscreen-notice-test.js
+++ b/tools/fullscreen-notice-test.js
@@ -93,7 +93,7 @@ async function run(lockOk) {
await waitText('receive', 20000);
await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click());
await page.waitForSelector('.notes-feed video.note-video', { timeout: 20000 });
- check('a feed clip\'s bar has no full-screen button', await waitFor(() => [...document.querySelectorAll('.notes-feed video.note-video')].every((v) => v.getAttribute('controlslist') === 'nofullscreen'), null, 5000));
+ check('a feed clip\'s bar keeps its full-screen button (not greyed out)', await page.evaluate(() => [...document.querySelectorAll('.notes-feed video.note-video')].every((v) => !/nofullscreen/.test(v.getAttribute('controlslist') || ''))));
await openClip('a wide one');
await sleep(1200);
let s = await st();
@@ -103,6 +103,16 @@ async function run(lockOk) {
await page.keyboard.press('ArrowDown'); await sleep(1200);
await page.keyboard.press('Escape');
check('Escape closes it', await waitFor(() => !document.querySelector('.media-pager') && !document.fullscreenElement, null, 3000));
+ // the player's own full-screen button: the pager, on that clip
+ await page.evaluate(() => { const r = [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes('a tall one')); r.querySelector('video.note-video').scrollIntoView({ block: 'center' }); });
+ await sleep(400);
+ await page.evaluate(() => [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes('a tall one')).querySelector('video.note-video').requestFullscreen());
+ check('its full-screen button opens the pager', await waitFor(() => !!document.querySelector('.media-pager.videos'), null, 4000), JSON.stringify(await st()));
+ check('…on that clip', await waitFor(() => { const s = [...document.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return !!s && /tall/.test(s.getAttribute('data-k') || ''); }, null, 3000));
+ check('…and the clip itself isn\'t left full screen', await waitFor(() => !(document.fullscreenElement && document.fullscreenElement.matches('video.note-video')), null, 3000));
+ await page.keyboard.press('Escape');
+ check('Escape closes that too', await waitFor(() => !document.querySelector('.media-pager'), null, 3000));
+ await sleep(600);
await openClip('a tall one'); await sleep(1200);
s = await st();
check('a tall clip: the pager, no full screen, the screen left alone', s.open && !s.fs && !s.orient.some((x) => x.startsWith('lock')), JSON.stringify(s));