chore: format

This commit is contained in:
2026-05-26 22:49:00 +02:00
parent 95ca4dd892
commit a5fdd8b999
4 changed files with 149 additions and 101 deletions

View File

@@ -166,88 +166,128 @@ const initPlayer = (): void => {
onLoadedMetadata(); onLoadedMetadata();
} }
state.video.addEventListener('waiting', () => { state.video.addEventListener(
if (loading) { 'waiting',
loading.style.display = 'flex'; () => {
} if (loading) {
}, { signal }); loading.style.display = 'flex';
state.video.addEventListener('playing', () => { }
if (loading) { },
loading.style.display = 'none'; { signal }
} );
}, { signal }); state.video.addEventListener(
'playing',
() => {
if (loading) {
loading.style.display = 'none';
}
},
{ signal }
);
// update progress bar during buffering // update progress bar during buffering
state.video.addEventListener('progress', () => { state.video.addEventListener(
updateTimeline(state.video.currentTime); 'progress',
}, { signal }); () => {
updateTimeline(state.video.currentTime);
},
{ signal }
);
// main loop: update progress, subtitles, skip buttons // main loop: update progress, subtitles, skip buttons
state.video.addEventListener('timeupdate', () => { state.video.addEventListener(
updateTimeline(state.video.currentTime); 'timeupdate',
updateSubtitleRender(displayTimeFromAbsolute(state.video.currentTime)); () => {
updateSkipButton(state.video.currentTime); updateTimeline(state.video.currentTime);
}, { signal }); updateSubtitleRender(displayTimeFromAbsolute(state.video.currentTime));
updateSkipButton(state.video.currentTime);
},
{ signal }
);
state.video.addEventListener('ended', () => { state.video.addEventListener(
goToNextEpisode(); 'ended',
}, { signal }); () => {
goToNextEpisode();
},
{ signal }
);
// click/drag to seek (pointer events are more consistent across fullscreen/mobile) // click/drag to seek (pointer events are more consistent across fullscreen/mobile)
progressWrap?.addEventListener('pointerdown', e => { progressWrap?.addEventListener(
// ignore right/middle click 'pointerdown',
if ('button' in e && e.button !== 0) return; e => {
state.isScrubbing = true; // ignore right/middle click
try { if ('button' in e && e.button !== 0) return;
(e.currentTarget as HTMLElement).setPointerCapture((e as PointerEvent).pointerId); state.isScrubbing = true;
} catch {} try {
const rect = progressWrap.getBoundingClientRect(); (e.currentTarget as HTMLElement).setPointerCapture((e as PointerEvent).pointerId);
state.video.currentTime = absoluteTimeFromRatio( } catch {}
Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)) const rect = progressWrap.getBoundingClientRect();
); state.video.currentTime = absoluteTimeFromRatio(
updateTimeline(state.video.currentTime); Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
updateSkipButton(state.video.currentTime); );
showControls(); updateTimeline(state.video.currentTime);
}, { signal }); updateSkipButton(state.video.currentTime);
showControls();
},
{ signal }
);
// hover to preview time // hover to preview time
progressWrap?.addEventListener('pointermove', e => { progressWrap?.addEventListener(
const rect = progressWrap.getBoundingClientRect(); 'pointermove',
updatePreviewUI(Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))); e => {
}, { signal }); const rect = progressWrap.getBoundingClientRect();
updatePreviewUI(Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)));
},
{ signal }
);
progressWrap?.addEventListener('pointerleave', hidePreviewPopover, { signal }); progressWrap?.addEventListener('pointerleave', hidePreviewPopover, { signal });
progressWrap?.addEventListener('pointerup', () => { progressWrap?.addEventListener(
// ensure we finish the seek even if no window mousemove fired 'pointerup',
if (!progressWrap) return; () => {
state.isScrubbing = false; // ensure we finish the seek even if no window mousemove fired
}, { signal }); if (!progressWrap) return;
state.isScrubbing = false;
},
{ signal }
);
// dragging outside progress bar while scrubbing // dragging outside progress bar while scrubbing
window.addEventListener('pointermove', e => { window.addEventListener(
if (!state.isScrubbing || !progressWrap) return; 'pointermove',
const rect = progressWrap.getBoundingClientRect(); e => {
state.video.currentTime = absoluteTimeFromRatio( if (!state.isScrubbing || !progressWrap) return;
Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)) const rect = progressWrap.getBoundingClientRect();
); state.video.currentTime = absoluteTimeFromRatio(
updateTimeline(state.video.currentTime); Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
updateSkipButton(state.video.currentTime); );
}, { signal }); updateTimeline(state.video.currentTime);
updateSkipButton(state.video.currentTime);
},
{ signal }
);
// track next-episode links outside the player so they start fresh after finishing an episode // track next-episode links outside the player so they start fresh after finishing an episode
document.addEventListener('click', e => { document.addEventListener(
const target = e.target; 'click',
if (!(target instanceof Element)) return; e => {
const anchor = target.closest('a[href]'); const target = e.target;
if (!(anchor instanceof HTMLAnchorElement)) return; if (!(target instanceof Element)) return;
const url = new URL(anchor.href, location.origin); const anchor = target.closest('a[href]');
if (url.origin !== location.origin) return; if (!(anchor instanceof HTMLAnchorElement)) return;
const parts = url.pathname.split('/').filter(Boolean); const url = new URL(anchor.href, location.origin);
if (parts[0] !== 'anime' || parts[2] !== 'watch') return; if (url.origin !== location.origin) return;
if (Number.parseInt(parts[1], 10) !== state.malID) return; const parts = url.pathname.split('/').filter(Boolean);
const nextEpisode = Number.parseInt(url.searchParams.get('ep') ?? '1', 10); if (parts[0] !== 'anime' || parts[2] !== 'watch') return;
const currentEpisode = Number.parseInt(state.currentEpisode, 10); if (Number.parseInt(parts[1], 10) !== state.malID) return;
if (nextEpisode === currentEpisode + 1) markEpisodeTransition(nextEpisode); const nextEpisode = Number.parseInt(url.searchParams.get('ep') ?? '1', 10);
}, { signal }); const currentEpisode = Number.parseInt(state.currentEpisode, 10);
if (nextEpisode === currentEpisode + 1) markEpisodeTransition(nextEpisode);
},
{ signal }
);
state.video.addEventListener('click', showControls, { signal }); state.video.addEventListener('click', showControls, { signal });
@@ -256,40 +296,48 @@ const initPlayer = (): void => {
let searchDebounce: number | undefined; let searchDebounce: number | undefined;
if (searchInput) { if (searchInput) {
searchInput.addEventListener('input', () => { searchInput.addEventListener(
clearTimeout(searchDebounce); 'input',
// debounce to avoid excessive range switches while typing () => {
searchDebounce = window.setTimeout(() => { clearTimeout(searchDebounce);
const val = searchInput.value.replace(/\D/g, ''); // debounce to avoid excessive range switches while typing
if (!val) { searchDebounce = window.setTimeout(() => {
// clear: jump to current episode range const val = searchInput.value.replace(/\D/g, '');
const cur = Number.parseInt(state.currentEpisode, 10); if (!val) {
switchEpisodeRange(Math.floor((cur - 1) / 100)); // clear: jump to current episode range
updateEpisodeHighlight(cur); const cur = Number.parseInt(state.currentEpisode, 10);
return; switchEpisodeRange(Math.floor((cur - 1) / 100));
} updateEpisodeHighlight(cur);
const ep = Number.parseInt(val, 10); return;
if (!ep || ep <= 0) return; }
const maxEp = state.totalEpisodes > 0 ? state.totalEpisodes : 500; const ep = Number.parseInt(val, 10);
const clamped = Math.min(ep, maxEp); if (!ep || ep <= 0) return;
searchInput.value = String(clamped); const maxEp = state.totalEpisodes > 0 ? state.totalEpisodes : 500;
if (state.episodeGrid) { const clamped = Math.min(ep, maxEp);
switchEpisodeRange(Math.floor((clamped - 1) / 100)); searchInput.value = String(clamped);
updateEpisodeHighlight(clamped); if (state.episodeGrid) {
} switchEpisodeRange(Math.floor((clamped - 1) / 100));
}, 300); updateEpisodeHighlight(clamped);
}, { signal }); }
}, 300);
},
{ signal }
);
} }
// range buttons (100s of episodes) // range buttons (100s of episodes)
if (dropdown) { if (dropdown) {
dropdown.querySelectorAll('.episode-range-btn').forEach(btn => { dropdown.querySelectorAll('.episode-range-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener(
const idx = Number.parseInt((btn as HTMLElement).dataset.rangeIndex ?? '0', 10); 'click',
switchEpisodeRange(idx); () => {
const dd = btn.closest('ui-dropdown'); const idx = Number.parseInt((btn as HTMLElement).dataset.rangeIndex ?? '0', 10);
if (isClosableDropdown(dd)) dd.close(); switchEpisodeRange(idx);
}, { signal }); const dd = btn.closest('ui-dropdown');
if (isClosableDropdown(dd)) dd.close();
},
{ signal }
);
}); });
} }

View File

@@ -172,8 +172,7 @@ export const initState = (c: HTMLElement): boolean => {
const isSubtitleItemArray = (v: unknown): v is { lang: string; token: string }[] => const isSubtitleItemArray = (v: unknown): v is { lang: string; token: string }[] =>
Array.isArray(v) && Array.isArray(v) &&
v.every( v.every(
item => item => isRecord(item) && typeof item.lang === 'string' && typeof item.token === 'string'
isRecord(item) && typeof item.lang === 'string' && typeof item.token === 'string'
); );
const parseModeSources = (v: unknown): Record<string, ModeSource> => { const parseModeSources = (v: unknown): Record<string, ModeSource> => {

View File

@@ -37,4 +37,3 @@ export const safeLocalStorage = {
} }
}, },
}; };

View File

@@ -190,7 +190,9 @@ const sortVisibleWatchlistItems = (sortBy: WatchlistSort, desc: boolean): void =
grids.push(singleGrid); grids.push(singleGrid);
} }
document.querySelectorAll<HTMLElement>('.watchlist-section .grid').forEach(grid => grids.push(grid)); document
.querySelectorAll<HTMLElement>('.watchlist-section .grid')
.forEach(grid => grids.push(grid));
const sortItemsInGrid = (grid: HTMLElement): void => { const sortItemsInGrid = (grid: HTMLElement): void => {
const items = Array.from(grid.querySelectorAll<HTMLElement>('.watchlist-item')); const items = Array.from(grid.querySelectorAll<HTMLElement>('.watchlist-item'));