Movies & TV: arrows and drag to slide rows (no scrollbar needed), Trailer button (Plex extras, else a YouTube search)
build-container / build (push) Successful in 11m18s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-23 10:30:24 -04:00
1 parent d598501393
commit 7198efaccd
5 files changed
+46 -2

No files matched your search

+14 -1
View File
@@ -170,6 +170,18 @@ async function videoItem(id) {
jfetch('/api/plex/status').then(st => { // Plex only lets its owner edit details, so the button is offered only then
if (st.canEdit && $('#vact') && !$('#vedit')) { $('#vact').insertAdjacentHTML('beforeend', '<button class="btn ghost" id="vedit">Edit</button>'); $('#vedit').onclick = () => videoEdit(m); }
}).catch(() => {});
if (m.type === 'movie' || m.type === 'show') px.get(`/library/metadata/${id}/extras`).catch(() => ({})).then(mc => { // Trailer: one from Plex when it has any, otherwise a YouTube search
const box = $('#vact'); if (!box || $('#vtr')) return;
const ex = (mc.Metadata || []).filter(x => (x.Media || []).length), tr = ex.filter(x => (x.subtype || '') === 'trailer'), list = tr.length ? tr : ex;
box.insertAdjacentHTML('beforeend', `<button class="btn ghost" id="vtr">${icon('play')}Trailer</button>`);
$('#vtr').onclick = () => {
const yt = () => window.open('https://www.youtube.com/results?search_query=' + vdEnc(`${m.title} ${m.year || ''} ${m.type === 'show' ? 'tv series' : 'movie'} official trailer`), '_blank', 'noopener');
if (!list.length) return yt();
const go = x => { store.set('video_back', location.hash); location.hash = '#/video/watch/' + x.ratingKey; };
if (list.length === 1) return go(list[0]);
actionSheet('Trailers', m.title, [...list.slice(0, 8).map(x => ({ label: x.title || 'Trailer', icon: 'play', run: () => go(x) })), { label: 'Search YouTube instead', icon: 'search', run: yt }]);
};
});
if (m.type === 'movie' || m.type === 'show') wlItems().then(list => { // Watch later
const on = list.some(x => x.ratingKey === String(id)), box = $('#vact'); if (!box || $('#vwl')) return;
box.insertAdjacentHTML('beforeend', `<button class="btn ghost" id="vwl">${on ? '✓ On Watch later' : '+ Watch later'}</button>`);
@@ -230,7 +242,7 @@ const vdTitle = m => m.type === 'episode' ? `${m.grandparentTitle} · S${m.paren
function videoMount() {
if (!vp) return; videoMini(false);
const v = $('#view'), m = vp.m, id = vp.id, ep = m.type === 'episode', title = vdTitle(m), el = vp.el;
document.body.classList.add('onvideo');
document.body.classList.add('onvideo'); // (for a trailer or other extra, Back returns to the page you came from)
const b = (idn, ic, label, extra = '') => `<button class="vb${extra}" id="${idn}" aria-label="${label}" title="${label}">${ic}</button>`;
v.innerHTML = `<div class="vwrap"><div class="vstage" id="vstage">
<div class="vtop"><a class="btn ghost" id="vback" href="${ep ? '#/video/item/' + (m.parentRatingKey || m.grandparentRatingKey) : '#/video/item/' + id}">${icon('back')}Back</a><nav class="vcrumbs vtc" aria-label="Where you are">${vdCrumbs(m)}</nav><b class="vtitle">${esc(title)}</b>
@@ -269,6 +281,7 @@ function videoMount() {
} else { if (e.detail === 2) { videoFullscreen(); return; } setTimeout(() => { if (Date.now() - lastDbl > 350) { el.paused ? el.play().catch(() => {}) : el.pause(); } }, 0); wake(); }
});
stage.addEventListener('pointermove', e => { if (e.pointerType !== 'touch') wake(); }); stage.addEventListener('focusin', wake);
if (m.type === 'clip' || m.extraType) { const bk = $('#vback'); if (bk) bk.setAttribute('href', store.get('video_back') || '#/video'); } // a trailer: Back returns to its movie
vdUi(); wake();
}
/** Media events, bound once to the video element (which outlives the player page while the mini player is showing). */