diff --git a/.xo-config.cjs b/.xo-config.cjs index f6a950da..ced7c7b3 100644 --- a/.xo-config.cjs +++ b/.xo-config.cjs @@ -27,7 +27,7 @@ module.exports = { "import/extensions": "off", "n/file-extension-in-import": "off", // 不限制循环方式 - "unicorn/no-array-for-each": "off", + // "unicorn/no-array-for-each": "off", // 不限制注释首字母大小写 "capitalized-comments": "off", // 不强制使用 addEventListener diff --git a/metaHeader.ts b/metaHeader.ts index 5bfd8380..f3fb0079 100644 --- a/metaHeader.ts +++ b/metaHeader.ts @@ -40,10 +40,10 @@ const resource = { dev: {} as Record, prod: {} as Record, }; -Object.entries(resourceList).forEach(([k, v]) => { +for (const [k, v] of Object.entries(resourceList)) { resource.prod[k] = v.at(0); resource.dev[k] = v.at(-1); -}); +} /** 根据 index.ts 的注释获取支持站点列表 */ const getSupportSiteList = () => { @@ -143,11 +143,11 @@ export const getMetaData = (isDevMode: boolean) => { const _metaData: typeof metaData = JSON.parse(JSON.stringify(metaData)); // 将 @resource 中的 / 替换为 |,以兼容 ios 的油猴扩展 - Object.keys(_metaData.resource).forEach((key) => { - if (!key.includes('/')) return; + for (const key of Object.keys(_metaData.resource)) { + if (!key.includes('/')) continue; _metaData.resource[key.replaceAll('/', '|')] = _metaData.resource[key]; Reflect.deleteProperty(_metaData.resource, key); - }); + } const metaText = Object.entries(_metaData) .filter(([, val]) => val) diff --git a/src/components/Manga/actions/abreastScroll.ts b/src/components/Manga/actions/abreastScroll.ts index c3f43036..0ff19a44 100644 --- a/src/components/Manga/actions/abreastScroll.ts +++ b/src/components/Manga/actions/abreastScroll.ts @@ -2,11 +2,9 @@ import { createRootMemo, createThrottleMemo } from 'helper/solidJs'; import { createSignal } from 'solid-js'; import { clamp } from 'helper'; -import classes from '../index.module.css'; import { store } from '../store'; import { abreastColumnWidth, isAbreastMode } from './memo/common'; -import { rootSize } from './memo/observer'; /** 并排卷轴模式下的全局滚动填充 */ export const [abreastScrollFill, _setAbreastScrollFill] = createSignal(0); @@ -28,7 +26,7 @@ export const abreastArea = createThrottleMemo( const position: Area['position'] = {}; let length = 0; - const rootHeight = rootSize().height; + const rootHeight = store.rootSize.height; if (!rootHeight || store.imgList.length === 0) return { columns, position, length }; @@ -99,7 +97,7 @@ export const abreastArea = createThrottleMemo( /** 头尾滚动的限制值 */ const scrollFillLimit = createRootMemo( - () => abreastArea().length - rootSize().height, + () => abreastArea().length - store.rootSize.height, ); export const setAbreastScrollFill = (val: number) => _setAbreastScrollFill(clamp(-scrollFillLimit(), val, scrollFillLimit())); @@ -113,7 +111,7 @@ export const abreastShowColumn = createThrottleMemo(() => { start = abreastArea().columns.length - 1; let end = Math.floor( - (store.page.offset.x.px + rootSize().width) / abreastColumnWidth(), + (store.page.offset.x.px + store.rootSize.width) / abreastColumnWidth(), ); if (end >= abreastArea().columns.length) end = abreastArea().columns.length - 1; @@ -130,19 +128,17 @@ export const abreastContentWidth = createRootMemo( /** 并排卷轴模式下的最大滚动距离 */ export const abreastScrollWidth = createRootMemo( - () => abreastContentWidth() - rootSize().width, + () => abreastContentWidth() - store.rootSize.width, ); -/** 卷轴模式下每个图片所在位置的样式 */ +/** 并排卷轴模式下每个图片所在位置的样式 */ export const imgAreaStyle = createRootMemo(() => { if (!isAbreastMode() || store.gridMode) return ''; let styleText = ''; - const selector = (index: number, imgNum = 0) => { - const indexText = `${index}${imgNum === 0 ? '' : `-${imgNum}`}`; - return `#mangaFlow > .${classes.img}[data-index="${indexText}"]`; - }; + const selector = (index: number, imgNum = 0) => + `#_${index}${imgNum === 0 ? '' : `-${imgNum}`}`; for (const index of store.imgList.keys()) { let imgNum = 0; diff --git a/src/components/Manga/actions/helper.ts b/src/components/Manga/actions/helper.ts index e0390061..c35fb6ec 100644 --- a/src/components/Manga/actions/helper.ts +++ b/src/components/Manga/actions/helper.ts @@ -1,7 +1,8 @@ import { scheduleIdle } from '@solid-primitives/scheduled'; +import { onCleanup } from 'solid-js'; import { difference, byPath } from 'helper'; -import { type State, store, setState, refs } from '../store'; +import { type State, store, setState, refs, _setState } from '../store'; import { type Option } from '../store/option'; /** 触发 onOptionChange */ @@ -29,6 +30,20 @@ export const bindRef = (e: T) => Reflect.set(refs, name, e); +export const watchDomSize = ( + name: keyof State, + e: T, +) => { + const resizeObserver = new ResizeObserver(([{ contentRect }]) => { + if (!contentRect.width || !contentRect.height) return; + const size = { width: contentRect.width, height: contentRect.height }; + _setState(name as any, size); + }); + resizeObserver.disconnect(); + resizeObserver.observe(e); + onCleanup(() => resizeObserver.disconnect()); +}; + /** 将界面恢复到正常状态 */ export const resetUI = (state: State) => { state.show.toolbar = false; diff --git a/src/components/Manga/actions/hotkeys.ts b/src/components/Manga/actions/hotkeys.ts index 1fc14ac7..9bc1dbc1 100644 --- a/src/components/Manga/actions/hotkeys.ts +++ b/src/components/Manga/actions/hotkeys.ts @@ -45,12 +45,12 @@ export const hotkeysMap = createRootMemo(() => /** 删除指定快捷键 */ export const delHotkeys = (code: string) => { - Object.entries(store.hotkeys).forEach(([name, keys]) => { + for (const [name, keys] of Object.entries(store.hotkeys)) { const i = keys.indexOf(code); if (i === -1) return; const newKeys = [...store.hotkeys[name]]; newKeys.splice(i, 1); setHotkeys(name, newKeys); - }); + } }; diff --git a/src/components/Manga/actions/imageSize.ts b/src/components/Manga/actions/imageSize.ts index 9753dce8..fb2a5f47 100644 --- a/src/components/Manga/actions/imageSize.ts +++ b/src/components/Manga/actions/imageSize.ts @@ -3,12 +3,7 @@ import { getImgSize, requestIdleCallback, singleThreaded } from 'helper'; import { type State, store, setState } from '../store'; -import { - rootSize, - abreastColumnWidth, - isAbreastMode, - placeholderSize, -} from './memo'; +import { abreastColumnWidth, isAbreastMode, placeholderSize } from './memo'; import { updateImgType } from './imageType'; let height = 0; @@ -30,8 +25,8 @@ const getImgDisplaySize = (state: State, index: number) => { if (!state.option.scrollMode.enabled) return { height, width }; if (isAbreastMode()) return setWidth(abreastColumnWidth()); - if (state.option.scrollMode.fitToWidth || width > rootSize().width) - return setWidth(rootSize().width); + if (state.option.scrollMode.fitToWidth || width > state.rootSize.width) + return setWidth(state.rootSize.width); height *= state.option.scrollMode.imgScale; width *= state.option.scrollMode.imgScale; @@ -61,8 +56,8 @@ createEffectOn( () => store.option.scrollMode.fitToWidth, () => store.option.scrollMode.imgScale, () => store.imgList, + () => store.rootSize, placeholderSize, - rootSize, ], ([isScrollMode]) => { if (!isScrollMode) return; diff --git a/src/components/Manga/actions/imageType.ts b/src/components/Manga/actions/imageType.ts index e091ea07..23530c8e 100644 --- a/src/components/Manga/actions/imageType.ts +++ b/src/components/Manga/actions/imageType.ts @@ -4,7 +4,6 @@ import { type State, setState, store } from '../store'; import { isWideImg } from '../handleComicData'; import { resetImgState, updatePageData } from './image'; -import { rootSize } from './memo'; /** 根据比例判断图片类型 */ export const getImgType = ( @@ -20,7 +19,7 @@ export const getImgType = ( /** 更新图片类型。返回是否修改了图片类型 */ const _updateImgType = (state: State, draftImg: ComicImg) => { - if (!rootSize().width || !rootSize().height) return false; + if (!state.rootSize.width || !state.rootSize.height) return false; const { type } = draftImg; if (!draftImg.width || !draftImg.height) return false; draftImg.type = getImgType(draftImg as Required, state); @@ -108,7 +107,7 @@ export const updateImgType = (state: State, i: number) => { // 处理显示窗口的长宽变化 createEffectOn( - rootSize, + () => store.rootSize, ({ width, height }) => setState((state) => { state.proportion.单页比例 = Math.min(width / 2 / height, 1); @@ -125,5 +124,4 @@ createEffectOn( if (isEdited) resetImgState(state); updatePageData(state); }), - { defer: true }, ); diff --git a/src/components/Manga/actions/memo/observer.ts b/src/components/Manga/actions/memo/observer.ts index 59b43b43..b9df60db 100644 --- a/src/components/Manga/actions/memo/observer.ts +++ b/src/components/Manga/actions/memo/observer.ts @@ -1,9 +1,8 @@ -import { createRoot, createSignal } from 'solid-js'; +import { createSignal } from 'solid-js'; import { inRange } from 'helper'; import { createEffectOn, createRootMemo } from 'helper/solidJs'; import { store, _setState } from '../../store'; -import { useDomSize } from '../../hooks/useDomSize'; import { isAbreastMode } from './common'; @@ -11,15 +10,12 @@ import { isAbreastMode } from './common'; export const imgPageMap = createRootMemo(() => { const map: Record = {}; for (let i = 0; i < store.pageList.length; i++) { - store.pageList[i].forEach((imgIndex) => { + for (const imgIndex of store.pageList[i]) if (imgIndex !== -1) map[imgIndex] = i; - }); } return map; }); -export const [rootSize, watchRootSize] = useDomSize(); -export const [flowSize, watchFlowSize] = useDomSize(); const [_scrollTop, setScrollTop] = createSignal(0); /** 卷轴模式下的滚动距离 */ @@ -35,6 +31,7 @@ export const bindScrollTop = (dom: HTMLElement) => { }; // 窗口宽度小于800像素时,标记为移动端 -createEffectOn(rootSize, ({ width }) => - _setState('isMobile', inRange(1, width, 800)), +createEffectOn( + () => store.rootSize, + ({ width }) => _setState('isMobile', inRange(1, width, 800)), ); diff --git a/src/components/Manga/actions/operate.ts b/src/components/Manga/actions/operate.ts index 89af8b25..064355aa 100644 --- a/src/components/Manga/actions/operate.ts +++ b/src/components/Manga/actions/operate.ts @@ -7,7 +7,6 @@ import { abreastColumnWidth, isAbreastMode, isScrollMode, - rootSize, scrollTop, } from './memo'; import { @@ -48,7 +47,9 @@ export const handleMouseDown: EventHandler['on:mousedown'] = (e) => { /** 卷轴模式下的页面滚动 */ const scrollModeScrollPage = (dir: 'next' | 'prev') => { if (!store.show.endPage) { - scrollTo(scrollTop() + rootSize().height * 0.8 * (dir === 'next' ? 1 : -1)); + scrollTo( + scrollTop() + store.rootSize.height * 0.8 * (dir === 'next' ? 1 : -1), + ); _setState('flag', 'scrollLock', true); } @@ -131,10 +132,14 @@ export const handleKeyDown = (e: KeyboardEvent) => { if (isAbreastMode()) { switch (hotkeysMap()[code]) { case 'scroll_up': - setAbreastScrollFill(abreastScrollFill() - rootSize().height * 0.02); + setAbreastScrollFill( + abreastScrollFill() - store.rootSize.height * 0.02, + ); return; case 'scroll_down': - setAbreastScrollFill(abreastScrollFill() + rootSize().height * 0.02); + setAbreastScrollFill( + abreastScrollFill() + store.rootSize.height * 0.02, + ); return; case 'scroll_left': @@ -143,9 +148,9 @@ export const handleKeyDown = (e: KeyboardEvent) => { return scrollTo(scrollProgress() - abreastColumnWidth()); case 'page_up': - return scrollTo(scrollProgress() - rootSize().width * 0.8); + return scrollTo(scrollProgress() - store.rootSize.width * 0.8); case 'page_down': - return scrollTo(scrollProgress() + rootSize().width * 0.8); + return scrollTo(scrollProgress() + store.rootSize.width * 0.8); case 'jump_to_home': return scrollTo(0); diff --git a/src/components/Manga/actions/pointer.ts b/src/components/Manga/actions/pointer.ts index 972f803c..cde36d0f 100644 --- a/src/components/Manga/actions/pointer.ts +++ b/src/components/Manga/actions/pointer.ts @@ -6,7 +6,7 @@ import { type UseDrag } from '../hooks/useDrag'; import { store, setState, refs } from '../store'; import { resetUI } from './helper'; -import { imgPageMap, rootSize } from './memo'; +import { imgPageMap } from './memo'; import { resetPage } from './show'; import { zoom } from './zoom'; import { turnPageFn, turnPageAnimation, turnPage } from './turnPage'; @@ -223,10 +223,10 @@ export const handleTrackpadWheel = (e: WheelEvent) => { } // 滚动过一页时 - if (dy <= -rootSize().height) { - if (turnPageFn(state, 'next')) dy += rootSize().height; - } else if (dy >= rootSize().height && turnPageFn(state, 'prev')) - dy -= rootSize().height; + if (dy <= -state.rootSize.height) { + if (turnPageFn(state, 'next')) dy += state.rootSize.height; + } else if (dy >= state.rootSize.height && turnPageFn(state, 'prev')) + dy -= state.rootSize.height; state.page.vertical = true; state.isDragMode = true; diff --git a/src/components/Manga/actions/renderPage.ts b/src/components/Manga/actions/renderPage.ts index c5772988..a72509f1 100644 --- a/src/components/Manga/actions/renderPage.ts +++ b/src/components/Manga/actions/renderPage.ts @@ -5,7 +5,7 @@ import { type State, setState, store, _setState } from '../store'; import { abreastArea, abreastShowColumn } from './abreastScroll'; import { scrollLength } from './scroll'; -import { rootSize, scrollTop } from './memo/observer'; +import { scrollTop } from './memo/observer'; import { contentHeight, imgTopList } from './imageSize'; /** 找到普通卷轴模式下指定高度上的图片 */ @@ -56,9 +56,9 @@ export const updateShowRange = (state: State) => { } else { // 普通卷轴模式 const top = scrollTop(); - const bottom = scrollTop() + rootSize().height; - const renderTop = top - rootSize().height; - const rednerBottom = bottom + rootSize().height; + const bottom = scrollTop() + state.rootSize.height; + const renderTop = top - state.rootSize.height; + const rednerBottom = bottom + state.rootSize.height; const renderTopImg = findTopImg(renderTop); const topImg = findTopImg(top, renderTopImg); @@ -77,9 +77,9 @@ createEffectOn( () => store.option.scrollMode.enabled, () => store.activePageIndex, () => store.option.scrollMode.abreastMode, + () => store.rootSize, abreastShowColumn, scrollTop, - rootSize, ], throttle(() => setState(updateShowRange)), // 两种卷轴模式下都可以通过在每次滚动后记录 diff --git a/src/components/Manga/actions/scroll.ts b/src/components/Manga/actions/scroll.ts index 1b72b614..31ac7c05 100644 --- a/src/components/Manga/actions/scroll.ts +++ b/src/components/Manga/actions/scroll.ts @@ -10,7 +10,7 @@ import { } from './abreastScroll'; import { isScrollMode, isAbreastMode, abreastColumnWidth } from './memo/common'; import { contentHeight, imgTopList } from './imageSize'; -import { scrollTop, rootSize } from './memo/observer'; +import { scrollTop } from './memo/observer'; import { setOption } from './helper'; /** 滚动内容的长度 */ @@ -35,8 +35,8 @@ export const scrollPercentage = createRootMemo( /** 滚动条滑块长度 */ export const sliderHeight = createRootMemo(() => { let itemLength = 1; - if (isScrollMode()) itemLength = rootSize().height; - if (isAbreastMode()) itemLength = rootSize().width; + if (isScrollMode()) itemLength = store.rootSize.height; + if (isAbreastMode()) itemLength = store.rootSize.width; return itemLength / scrollLength(); }); diff --git a/src/components/Manga/actions/scrollbar.ts b/src/components/Manga/actions/scrollbar.ts index 6ee5bb92..4e575a98 100644 --- a/src/components/Manga/actions/scrollbar.ts +++ b/src/components/Manga/actions/scrollbar.ts @@ -1,11 +1,9 @@ -import { createRoot, createSignal } from 'solid-js'; import { clamp } from 'helper'; -import { createEffectOn, createRootMemo } from 'helper/solidJs'; +import { createRootMemo } from 'helper/solidJs'; import { type PointerState, type UseDrag } from '../hooks/useDrag'; import { type State, store, refs, _setState } from '../store'; -import { rootSize } from './memo'; import { scrollLength, scrollPercentage, @@ -13,9 +11,10 @@ import { sliderHeight, } from './scroll'; -const [_scrollDomLength, setScrollDomLength] = createSignal(0); /** 滚动条元素的长度 */ -export const scrollDomLength = _scrollDomLength; +export const scrollDomLength = createRootMemo(() => + Math.max(store.scrollbarSize.width, store.scrollbarSize.height), +); /** 滚动条滑块的中心点高度 */ export const sliderMidpoint = createRootMemo( @@ -109,17 +108,3 @@ export const handlescrollbarSlider: UseDrag = ({ type, xy, initial }, e) => { _setState('activePageIndex', newPageIndex); } }; - -createRoot(() => { - // 更新 scrollLength - createEffectOn([scrollPosition, rootSize], () => { - if (!refs.scrollbar) return; - // 部分情况下,在窗口大小改变后滚动条大小不会立刻跟着修改,需要等待一帧渲染 - // 比如打开后台标签页后等一会再切换过去 - requestAnimationFrame(() => - setScrollDomLength( - Math.max(refs.scrollbar.clientWidth, refs.scrollbar.clientHeight), - ), - ); - }); -}); diff --git a/src/components/Manga/actions/translation/index.ts b/src/components/Manga/actions/translation/index.ts index 382f68a9..60c3cc9e 100644 --- a/src/components/Manga/actions/translation/index.ts +++ b/src/components/Manga/actions/translation/index.ts @@ -69,7 +69,7 @@ const translationAll = singleThreaded(async (): Promise => { /** 开启或关闭指定图片的翻译 */ export const setImgTranslationEnbale = (list: number[], enbale: boolean) => { setState((state) => { - list.forEach((i) => { + for (const i of list) { const img = state.imgList[i]; if (!img) return; @@ -107,7 +107,7 @@ export const setImgTranslationEnbale = (list: number[], enbale: boolean) => { } } } - }); + } }); return translationAll(); diff --git a/src/components/Manga/components/ComicImg.tsx b/src/components/Manga/components/ComicImg.tsx index 79ac3c7e..3ad862c6 100644 --- a/src/components/Manga/components/ComicImg.tsx +++ b/src/components/Manga/components/ComicImg.tsx @@ -59,9 +59,7 @@ export const ComicImg: Component = (img) => { { - onMount(() => watchFlowSize(refs.mangaFlow)); - const { hiddenMouse, onMouseMove } = useHiddenMouse(); const handleDrag: UseDrag = (state, e) => { @@ -125,11 +121,11 @@ export const ComicImgFlow: Component = () => { '--zoom-y': () => `${store.zoom.offset.y}px`, '--page-x'() { if (isScrollMode()) return '0px'; - const x = `${store.page.offset.x.pct * rootSize().width + store.page.offset.x.px}px`; + const x = `${store.page.offset.x.pct * store.rootSize.width + store.page.offset.x.px}px`; return store.option.dir === 'rtl' ? x : `calc(${x} * -1)`; }, '--page-y': () => - `${store.page.offset.y.pct * rootSize().height + store.page.offset.y.px}px`, + `${store.page.offset.y.pct * store.rootSize.height + store.page.offset.y.px}px`, 'touch-action'() { if (store.gridMode) return 'auto'; if (store.zoom.scale !== 100) { diff --git a/src/components/Manga/components/Scrollbar.tsx b/src/components/Manga/components/Scrollbar.tsx index 4a198608..60beffa7 100644 --- a/src/components/Manga/components/Scrollbar.tsx +++ b/src/components/Manga/components/Scrollbar.tsx @@ -25,6 +25,7 @@ import { isAbreastMode, abreastArea, sliderTop, + watchDomSize, } from '../actions'; import classes from '../index.module.css'; @@ -38,6 +39,7 @@ export const Scrollbar: Component = () => { handleDrag: handlescrollbarSlider, easyMode: () => isScrollMode() && store.option.scrollbar.easyScroll, }); + watchDomSize('scrollbarSize', refs.scrollbar); }); // 在被滚动时使自身可穿透,以便在卷轴模式下触发页面的滚动 @@ -61,7 +63,7 @@ export const Scrollbar: Component = () => { .map((column) => column.map(getPageTip)); if (store.option.dir === 'rtl') { columns.reverse(); - columns.forEach((column) => column.reverse()); + for (const column of columns) column.reverse(); } return columns.map((column) => column.join(', ')).join(' | '); }); diff --git a/src/components/Manga/components/SettingHotkeys.tsx b/src/components/Manga/components/SettingHotkeys.tsx index 6c03b56f..36a3f1db 100644 --- a/src/components/Manga/components/SettingHotkeys.tsx +++ b/src/components/Manga/components/SettingHotkeys.tsx @@ -77,7 +77,7 @@ export const SettingHotkeys: Component = () => ( title={t('setting.hotkeys.restore')} on:click={() => { const newKeys = defaultHotkeys[name] ?? []; - newKeys.forEach(delHotkeys); + for (const code of defaultHotkeys[name]) delHotkeys(code); setHotkeys(name, newKeys); }} > diff --git a/src/components/Manga/handleComicData.test.ts b/src/components/Manga/handleComicData.test.ts index cea8e708..5ec306fe 100644 --- a/src/components/Manga/handleComicData.test.ts +++ b/src/components/Manga/handleComicData.test.ts @@ -16,7 +16,12 @@ const mock = ( const fillEffect = { '-1': true, ...initFillEffect }; const pageList = handleComicData( imgTypeList.map( - (type): ComicImg => ({ type, loadType: 'loaded', src: '' }), + (type): ComicImg => ({ + type, + loadType: 'loaded', + src: '', + size: { height: 0, width: 0 }, + }), ), fillEffect, ); diff --git a/src/components/Manga/helper.ts b/src/components/Manga/helper.ts index 6b4679cc..1b1ee01d 100644 --- a/src/components/Manga/helper.ts +++ b/src/components/Manga/helper.ts @@ -4,8 +4,10 @@ export const stopPropagation = (e: Event) => { }; /** 从头开始播放元素的动画 */ -export const playAnimation = (e?: HTMLElement) => - e?.getAnimations().forEach((animation) => { +export const playAnimation = (e?: HTMLElement) => { + if (!e) return; + for (const animation of e.getAnimations()) { animation.cancel(); animation.play(); - }); + } +}; diff --git a/src/components/Manga/hooks/useDomSize.ts b/src/components/Manga/hooks/useDomSize.ts deleted file mode 100644 index 03f95680..00000000 --- a/src/components/Manga/hooks/useDomSize.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createSignal, onCleanup } from 'solid-js'; - -export const useDomSize = () => { - const [domSize, setDomSize] = createSignal( - { width: 0, height: 0 }, - // 宽高为零时不触发变更 - { equals: (_, { width, height }) => !width || !height }, - ); - - const initResizeObserver = (dom: HTMLElement) => { - setDomSize({ width: dom.scrollWidth, height: dom.scrollHeight }); - const resizeObserver = new ResizeObserver(([{ contentRect }]) => - setDomSize({ width: contentRect.width, height: contentRect.height }), - ); - resizeObserver.disconnect(); - resizeObserver.observe(dom); - onCleanup(() => resizeObserver.disconnect()); - }; - - return [domSize, initResizeObserver] as const; -}; diff --git a/src/components/Manga/hooks/useInit.ts b/src/components/Manga/hooks/useInit.ts index 6beb0a4a..1fb8f13d 100644 --- a/src/components/Manga/hooks/useInit.ts +++ b/src/components/Manga/hooks/useInit.ts @@ -8,7 +8,7 @@ import { defaultHotkeys, defaultImgType, focus, - watchRootSize, + watchDomSize, resetImgState, scrollTo, updatePageData, @@ -25,7 +25,7 @@ const createComicImg = (url: string): ComicImg => ({ }); export const useInit = (props: MangaProps) => { - watchRootSize(refs.root); + watchDomSize('rootSize', refs.root); const watchProps: Partial< Record unknown> @@ -100,14 +100,14 @@ export const useInit = (props: MangaProps) => { state.commentList = props.commentList; }, }; - Object.entries(watchProps).forEach(([key, fn]) => + for (const [key, fn] of Object.entries(watchProps)) { createEffect( on( () => props[key as keyof MangaProps], () => setState(fn), ), - ), - ); + ); + } const handleImgList = () => { setState((state) => { diff --git a/src/components/Manga/store/other.ts b/src/components/Manga/store/other.ts index b25ea0c0..ff5bd122 100644 --- a/src/components/Manga/store/other.ts +++ b/src/components/Manga/store/other.ts @@ -18,4 +18,7 @@ export const OtherState = { */ scrollLock: false, }, + + rootSize: { width: 0, height: 0 }, + scrollbarSize: { width: 0, height: 0 }, }; diff --git a/src/components/Toast/ToastItem.tsx b/src/components/Toast/ToastItem.tsx index fc33d041..2835e1ee 100644 --- a/src/components/Toast/ToastItem.tsx +++ b/src/components/Toast/ToastItem.tsx @@ -62,12 +62,12 @@ export const ToastItem: Component = (props) => { let scheduleRef: HTMLDivElement; createEffect(() => { if (!props.update) return; - resetToastUpdate(props.id); - scheduleRef?.getAnimations().forEach((animation) => { + if (!scheduleRef) return; + for (const animation of scheduleRef.getAnimations()) { animation.cancel(); animation.play(); - }); + } }); const handleClick: EventHandler['on:click'] = (e) => { diff --git a/src/helper/dmzjApi.ts b/src/helper/dmzjApi.ts index eb55e171..93f940a0 100644 --- a/src/helper/dmzjApi.ts +++ b/src/helper/dmzjApi.ts @@ -110,9 +110,8 @@ const getComicDetail_v4Api = async (comicId: string): Promise => { comicInfo: { last_update_chapter_id, last_updatetime, chapters, title }, } = dmzjDecrypt(res.responseText); - Object.values(chapters).forEach((chapter) => { + for (const chapter of Object.values(chapters)) chapter.data.sort((a, b) => a.chapter_order - b.chapter_order); - }); return { title, diff --git a/src/helper/i18n.ts b/src/helper/i18n.ts index b63e74c2..4631abe7 100644 --- a/src/helper/i18n.ts +++ b/src/helper/i18n.ts @@ -35,9 +35,9 @@ export const t = createRoot(() => { return (keys: string, variables?: Record) => { let text = byPath(locales(), keys) ?? ''; if (variables) - Object.entries(variables).forEach(([k, v]) => { + for (const [k, v] of Object.entries(variables)) text = text.replaceAll(`{{${k}}}`, `${String(v)}`); - }); + if (isDevMode && !text) log.warn('unknown i18n key', keys); return text; }; diff --git a/src/helper/index.ts b/src/helper/index.ts index 994c7405..bc3ea813 100644 --- a/src/helper/index.ts +++ b/src/helper/index.ts @@ -31,9 +31,7 @@ export const approx = (val: number, target: number, range: number) => /** 根据传入的条件列表的真假,对 val 进行取反 */ export const ifNot = (val: unknown, ...conditions: boolean[]) => { let res = Boolean(val); - conditions.forEach((v) => { - if (v) res = !res; - }); + for (const v of conditions) if (v) res = !res; return res; }; @@ -478,9 +476,8 @@ export const requestIdleCallback = ( */ export const autoUpdate = (update: () => Promise) => { const refresh = singleThreaded(update); - ['click', 'popstate'].forEach((eventName) => - window.addEventListener(eventName, refresh, { capture: true }), - ); + for (const eventName of ['click', 'popstate']) + window.addEventListener(eventName, refresh, { capture: true }); refresh(); }; diff --git a/src/helper/triggerLazyLoad.ts b/src/helper/triggerLazyLoad.ts index 8a60003f..9b4f2f27 100644 --- a/src/helper/triggerLazyLoad.ts +++ b/src/helper/triggerLazyLoad.ts @@ -105,8 +105,8 @@ const handleTrigged = (e: HTMLImageElement) => { }; /** 监视图片是否被显示的 Observer */ -imgShowObserver = new IntersectionObserver((entries) => - entries.forEach((img) => { +imgShowObserver = new IntersectionObserver((entries) => { + for (const img of entries) { const ele = img.target as HTMLImageElement; if (img.isIntersecting) { imgMap.set(ele, { @@ -117,8 +117,8 @@ imgShowObserver = new IntersectionObserver((entries) => const timeoutID = imgMap.get(ele)?.observerTimeout; if (timeoutID) window.clearTimeout(timeoutID); - }), -); + } +}); const turnPageScheduled = createScheduled((fn) => throttle(fn, 1000)); /** 触发翻页 */ diff --git a/src/index.ts b/src/index.ts index 5bf6807d..b6b5eeba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -432,7 +432,7 @@ try { dynamicUpdate(async (setImg) => { for (let i = 0; i < imgNum; i++) { const newImgs = await getPageImg(i + 1); - newImgs.forEach((url) => setImg(i, url)); + for (const url of newImgs) setImg(i, url); } }, imgNum)(), onNext: handlePrevNext( diff --git a/src/site/dmzj.tsx b/src/site/dmzj.tsx index 2652a78b..f126e8e6 100644 --- a/src/site/dmzj.tsx +++ b/src/site/dmzj.tsx @@ -84,7 +84,7 @@ import { getComicId, getViewpoint, useComicDetail } from '../helper/dmzjApi'; querySelector('.cartoon_online_border')!.innerHTML = '获取漫画数据中'; // 删掉原有的章节 dom - querySelectorAll('.odd_anim_title ~ *').forEach((e) => e.remove()); + for (const e of querySelectorAll('.odd_anim_title ~ *')) e.remove(); const { comicId } = await getId(); diff --git a/src/site/dmzj_phone.tsx b/src/site/dmzj_phone.tsx index 00ccf356..ce7e1855 100644 --- a/src/site/dmzj_phone.tsx +++ b/src/site/dmzj_phone.tsx @@ -85,7 +85,7 @@ import { document.title = title; insertNode(document.body, `

${title}

`); - Object.values(chapters).forEach((chapter) => { + for (const chapter of Object.values(chapters)) { // 手动构建添加章节 dom let temp = `

${chapter.title}

`; let i = chapter.data.length; @@ -100,7 +100,7 @@ import { : '' }>${chapter.data[i].chapter_title}`; insertNode(document.body, temp); - }); + } document.body.childNodes[0].remove(); await GM.addStyle( diff --git a/src/site/ehentai.tsx b/src/site/ehentai.tsx index a4773705..67d8c668 100644 --- a/src/site/ehentai.tsx +++ b/src/site/ehentai.tsx @@ -179,13 +179,13 @@ declare const selected_tagname: string; /** 缩略图元素列表 */ const thumbnailEleList: HTMLImageElement[] = []; - querySelectorAll('#gdt img').forEach((e) => { + for (const e of querySelectorAll('#gdt img')) { const index = Number(e.alt) - 1; if (Number.isNaN(index)) return; thumbnailEleList[index] = e; // 根据当前显示的图片获取一部分文件名 [, ehImgFileNameList[index]] = e.title.split(/:|: /); - }); + } // 先根据文件名判断一次 await getAdPageByFileName(ehImgFileNameList, mangaProps.adList); // 不行的话再用缩略图识别 diff --git a/src/site/kemono.tsx b/src/site/kemono.tsx index 9abf35ab..59f8b7ca 100644 --- a/src/site/kemono.tsx +++ b/src/site/kemono.tsx @@ -27,13 +27,13 @@ import { createEffectOn, querySelectorAll, useInit } from 'main'; // 加上跳转至 pwa 的链接 const zipExtension = new Set(['zip', 'rar', '7z', 'cbz', 'cbr', 'cb7']); - querySelectorAll('.post__attachment a').forEach((e) => { - if (!zipExtension.has(e.href.split('.').pop()!)) return; + for (const e of querySelectorAll('.post__attachment a')) { + if (!zipExtension.has(e.href.split('.').pop()!)) continue; const a = document.createElement('a'); a.href = `https://comic-read.pages.dev/?url=${encodeURIComponent(e.href)}`; a.textContent = e.textContent!.replace('Download ', 'ComicReadPWA - '); a.className = e.className; a.style.opacity = '.6'; e.parentNode!.insertBefore(a, e.nextElementSibling); - }); + } })(); diff --git a/src/site/nhentai.tsx b/src/site/nhentai.tsx index 38414ecd..62db67ce 100644 --- a/src/site/nhentai.tsx +++ b/src/site/nhentai.tsx @@ -64,9 +64,8 @@ declare const gallery: { num_pages: number; media_id: string; images: Images }; // 在漫画浏览页 if (document.getElementsByClassName('gallery').length > 0) { if (options.open_link_new_page) - querySelectorAll('a:not([href^="javascript:"])').forEach((e) => - e.setAttribute('target', '_blank'), - ); + for (const e of querySelectorAll('a:not([href^="javascript:"])')) + e.setAttribute('target', '_blank'); const blacklist: number[] = (unsafeWindow?._n_app ?? unsafeWindow?.n) ?.options?.blacklisted_tags; @@ -129,7 +128,7 @@ declare const gallery: { num_pages: number; media_id: string; images: Images }; let comicDomHtml = ''; - result.forEach((comic) => { + for (const comic of result) { const blacklisted = comic.tags.some((tag) => blacklist?.includes(tag.id), ); @@ -146,7 +145,7 @@ declare const gallery: { num_pages: number; media_id: string; images: Images }; }" src="https://t.nhentai.net/galleries/${comic.media_id}/thumb.${ fileType[comic.images.thumbnail.t] }">
${comic.title.english}
`; - }); + } // 构建页数按钮 if (comicDomHtml) { diff --git a/src/site/yamibo.tsx b/src/site/yamibo.tsx index 11acb64e..3ba8b60c 100644 --- a/src/site/yamibo.tsx +++ b/src/site/yamibo.tsx @@ -106,9 +106,8 @@ interface History { // 判断当前页是帖子 if (/thread(-\d+){3}|mod=viewthread/.test(document.URL)) { // 修复微博图床的链接 - querySelectorAll('img[file*="sinaimg.cn"]').forEach((e) => - e.setAttribute('referrerpolicy', 'no-referrer'), - ); + for (const e of querySelectorAll('img[file*="sinaimg.cn"]')) + e.setAttribute('referrerpolicy', 'no-referrer'); const fid: number = unsafeWindow.fid ?? @@ -194,7 +193,8 @@ interface History { // 如果帖子内有设置目录 if (querySelector('#threadindex')) { let id: number; - querySelectorAll('#threadindex li').forEach((dom) => { + for (const dom of querySelectorAll('#threadindex li')) { + // eslint-disable-next-line @typescript-eslint/no-loop-func dom.addEventListener('click', () => { if (id) return; id = window.setInterval(() => { @@ -210,7 +210,7 @@ interface History { window.clearInterval(id); }, 100); }); - }); + } } const tagDom = querySelector('.ptg.mbm.mtn > a'); @@ -325,9 +325,8 @@ interface History { trigger.target as HTMLElement, ); if (triggerIndex === -1) return; - watchFloorList - .splice(0, triggerIndex + 1) - .forEach((e) => observer.unobserve(e)); + for (const e of watchFloorList.splice(0, triggerIndex + 1)) + observer.unobserve(e); // 储存数据 debounceSave({ @@ -339,7 +338,7 @@ interface History { }, { rootMargin: '-160px' }, ); - watchFloorList.forEach((e) => observer.observe(e)); + for (const e of watchFloorList) observer.observe(e); } return; @@ -376,10 +375,11 @@ interface History { `forum.php?mod=viewthread&tid=${tid}&extra=page%3D1&mobile=2&page=${data.lastPageNum}#${data.lastAnchor}`; } - querySelectorAll(listSelector).forEach((e) => { + for (const e of querySelectorAll(listSelector)) { const tid = getTid(e); render( + // eslint-disable-next-line @typescript-eslint/no-loop-func () => { const [data, setData] = createSignal(); @@ -432,7 +432,7 @@ interface History { }, isMobile ? e.children[3] : e.getElementsByTagName('th')[0], ); - }); + } // 切换回当前页时更新提示 document.addEventListener('visibilitychange', updateHistoryTag); diff --git a/src/site/yurifans.tsx b/src/site/yurifans.tsx index d25b35e1..daa0b1a6 100644 --- a/src/site/yurifans.tsx +++ b/src/site/yurifans.tsx @@ -76,7 +76,7 @@ declare const b2token: string; }); }; - querySelectorAll('.xControl > a').forEach((a, i) => { + for (const [i, a] of querySelectorAll('.xControl > a').entries()) { const imgRoot = a.parentElement!.nextElementSibling! as HTMLElement; imgListMap.push(imgRoot.getElementsByTagName('img')); @@ -89,7 +89,7 @@ declare const b2token: string; ) return loadChapterImg(i); }); - }); + } return; }