Chapter picker: tap the chapter badge to jump to any chapter
The CH badge by the scrubber is now a button opening a list of every chapter (number, title, start time); the playing one is highlighted and scrolled into view, a tap seeks there (and resumes if paused). Works with shared chapters and YouTube's.
This commit is contained in:
@@ -3273,7 +3273,7 @@ function fitLyricLines(list, { maxShare = 0.17, maxPx = 160, minPx = 18, uniform
|
||||
const Notes = (() => {
|
||||
const LC = window.LyricsCore;
|
||||
const panel = $('notesPanel');
|
||||
const noop = { load() {}, tick() {}, addBookmark() {}, handleSyncKey() { return false; }, serviceRefresh() {}, chapterAt() { return null; }, jumpChapter() {} };
|
||||
const noop = { load() {}, tick() {}, addBookmark() {}, handleSyncKey() { return false; }, serviceRefresh() {}, chapterAt() { return null; }, jumpChapter() {}, openChapterPicker() {} };
|
||||
if (!panel || !LC) return noop;
|
||||
|
||||
const CACHE_KEY = 'ytpNotesCache';
|
||||
@@ -3892,6 +3892,42 @@ const Notes = (() => {
|
||||
await saveShared('chapters', { items: [...chapters().map((c) => ({ t: c.t, title: c.title })), v] }, baseRev);
|
||||
}
|
||||
|
||||
// Chapter picker: every chapter with its number and start time; the one
|
||||
// playing is marked and scrolled into view. Tapping a row jumps there.
|
||||
function openChapterPicker() {
|
||||
const cs = chapters().slice().sort((a, b) => a.t - b.t);
|
||||
if (!cs.length) { toast('No chapters for this video'); return; }
|
||||
const cur = (Player.master && Player.master.currentTime) || 0;
|
||||
let active = -1;
|
||||
cs.forEach((c, i) => { if (c.t <= cur + 0.5) active = i; });
|
||||
const body = document.createElement('div');
|
||||
body.className = 'ch-picker';
|
||||
if (autoChapters()) {
|
||||
const note = document.createElement('p');
|
||||
note.className = 'ch-picker-src';
|
||||
note.textContent = 'Chapters from YouTube';
|
||||
body.appendChild(note);
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.className = 'ch-picker-list';
|
||||
list.setAttribute('role', 'listbox');
|
||||
cs.forEach((c, i) => {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'ch-pick' + (i === active ? ' on' : '');
|
||||
b.setAttribute('role', 'option');
|
||||
b.setAttribute('aria-selected', String(i === active));
|
||||
b.innerHTML = `<span class="ch-pick-n">${String(i + 1).padStart(2, '0')}</span><span class="ch-pick-title"></span><span class="ch-pick-t">${LC.fmtClock(c.t)}</span>`;
|
||||
b.querySelector('.ch-pick-title').textContent = c.title || `Chapter ${i + 1}`;
|
||||
b.onclick = () => { Player.seek(c.t); if (Player.master && Player.master.paused) Player.toggle(); closeModal(); };
|
||||
list.appendChild(b);
|
||||
});
|
||||
body.appendChild(list);
|
||||
showModal(`Chapters · ${cs.length}`, body, [{ label: 'Close', onClick: closeModal }]);
|
||||
const on = list.querySelector('.ch-pick.on');
|
||||
if (on) requestAnimationFrame(() => { on.scrollIntoView({ block: 'center' }); on.focus({ preventScroll: true }); });
|
||||
}
|
||||
|
||||
// [ / ] — previous / next chapter. "Previous" restarts the current chapter
|
||||
// first when more than 3 s into it, like a CD player.
|
||||
function jumpChapter(dir) {
|
||||
@@ -4443,6 +4479,7 @@ const Notes = (() => {
|
||||
load, tick, addBookmark, addNote, handleSyncKey, serviceRefresh, refreshFlags: () => { sv.flagsKey = ''; renderServiceButtons(); setPanelFlagMode(sv.panelFlag && flagsEnabled()); paintFlags(); loadFlags(); },
|
||||
stageLyrics: { render: renderStage, set: setStageLyrics, on: stageOn },
|
||||
jumpChapter,
|
||||
openChapterPicker,
|
||||
chapterAt: (t) => { const c = chapters().slice().sort((a, b) => a.t - b.t); let k = -1; c.forEach((x, i) => { if (x.t <= t) k = i; }); return k < 0 ? null : { n: k + 1, of: c.length, title: c[k].title || '' }; },
|
||||
};
|
||||
})();
|
||||
@@ -11176,6 +11213,7 @@ document.querySelectorAll('.chip').forEach((c) => {
|
||||
// Mini now-playing bar
|
||||
$('miniPlayBtn').addEventListener('click', (e) => { e.stopPropagation(); Player.toggle(); });
|
||||
$('updateIconBtn').addEventListener('click', updateFromIcon);
|
||||
$('chBadge').addEventListener('click', (e) => { e.stopPropagation(); Notes.openChapterPicker(); });
|
||||
$('miniBar').addEventListener('click', () => {
|
||||
setHomeOverPlayer(false);
|
||||
hideMiniBar();
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<span id="durTime" class="time">0:00</span>
|
||||
<span id="chBadge" class="ch-badge" hidden></span>
|
||||
<button id="chBadge" type="button" class="ch-badge" hidden title="Chapters — tap to jump" aria-haspopup="dialog"></button>
|
||||
<button id="fsMini" class="fs-mini" type="button" title="Fullscreen" aria-label="Fullscreen"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg></button>
|
||||
</div>
|
||||
<button id="waveLoud" class="wave-loud hidden" type="button">⚡ Jump to the loudest part</button>
|
||||
|
||||
@@ -4314,3 +4314,20 @@ html.ios-nozoom, html.ios-nozoom body { touch-action: manipulation; }
|
||||
/* Classic-layout SVG nav icons: `hidden` is not reliably honoured on SVG, so
|
||||
hide them explicitly everywhere; theme-classic.css shows them in Classic. */
|
||||
.classic-nav-icon { display: none; }
|
||||
|
||||
/* Chapter picker (tap the chapter badge) */
|
||||
.ch-picker-src { margin: 0 0 8px; font-size: 12.5px; color: var(--text-dim); }
|
||||
.ch-picker-list { display: flex; flex-direction: column; gap: 4px; max-height: min(60vh, 460px); overflow-y: auto; margin: 0 -4px; padding: 2px 4px; }
|
||||
.ch-pick {
|
||||
display: grid; grid-template-columns: 2.2em 1fr auto; align-items: center; gap: 10px;
|
||||
width: 100%; min-height: 44px; padding: 8px 12px; border-radius: 12px; text-align: left; cursor: pointer;
|
||||
border: 1px solid transparent; background: var(--bg-2); color: var(--text); font: 600 14px var(--ui);
|
||||
transition: border-color .15s, background .15s;
|
||||
}
|
||||
.ch-pick:hover { border-color: var(--accent); }
|
||||
.ch-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||
.ch-pick-n { font: 600 12px var(--mono, ui-monospace, monospace); color: var(--text-dim); }
|
||||
.ch-pick-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.ch-pick-t { font: 500 12.5px var(--mono, ui-monospace, monospace); color: var(--text-dim); font-variant-numeric: tabular-nums; }
|
||||
.ch-pick.on { background: color-mix(in srgb, var(--accent) 16%, var(--bg-2)); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
|
||||
.ch-pick.on .ch-pick-n, .ch-pick.on .ch-pick-t { color: var(--accent-bright); }
|
||||
|
||||
@@ -380,3 +380,8 @@ html[data-layout="glass-stage"] .svc-tile { min-height: 58px; padding: 8px 4px 7
|
||||
html[data-layout="glass-stage"] .svc-tile .svc-ico { width: 20px; height: 20px; }
|
||||
html[data-layout="glass-stage"] .svc-font { height: 36px; }
|
||||
html[data-layout="glass-stage"] .svc-font button { width: 28px; height: 30px; }
|
||||
|
||||
/* Chapter badge opens the chapter picker */
|
||||
html[data-layout="glass-stage"] #controls .ch-badge { cursor: pointer; font-family: var(--mono); transition: background .15s, border-color .15s; }
|
||||
html[data-layout="glass-stage"] #controls .ch-badge:hover { background: rgba(255, 255, 255, 0.12); border-color: var(--accent); }
|
||||
html[data-layout="glass-stage"] #controls .ch-badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
Reference in New Issue
Block a user