您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Universal comic reader
当前为
此脚本不应直接安装,它是供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/417893/877253/vim%20comic%20viewer.js
// ==UserScript== // @name vim comic viewer // @description Universal comic reader // @version 1.2.0 // @namespace https://gf.qytechs.cn/en/users/713014-nanikit // @exclude * // @match http://unused-field.space/ // @author nanikit // @license MIT // ==/UserScript== "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var react = require("react"); var react$1 = require("@stitches/react"); var reactDom = require("react-dom"); const useFullscreenElement = () => { const [element, setElement] = react.useState( document.fullscreenElement || undefined, ); react.useEffect(() => { const notify = () => setElement(document.fullscreenElement || undefined); document.addEventListener("fullscreenchange", notify); return () => document.removeEventListener("fullscreenchange", notify); }, []); return element; }; const usePageNavigator = () => { const [observer, setObserver] = react.useState(); const [pages] = react.useState({ entries: [], isSorted: true, }); const sortAndGetAnchor = react.useCallback(() => { const first = pages.entries?.[0]?.target; if (!pages.isSorted && !!first) { const children = [ ...first.parentElement.children, ]; pages.entries.sort((a, b) => { const aRatio = Math.round(a.intersectionRatio * 10); const bRatio = Math.round(b.intersectionRatio * 10); const aIndex = children.indexOf(a.target); const bIndex = children.indexOf(b.target); return (bRatio - aRatio) * 10 + (bIndex - aIndex); }); pages.isSorted = true; } return pages.entries?.[0]?.target; }, [ pages, ]); const goNext = react.useCallback(() => { const anchor = sortAndGetAnchor(); if (!anchor) { return; } let cursor = anchor; const originBound = cursor.getBoundingClientRect(); while (cursor.nextElementSibling) { const next = cursor.nextElementSibling; const nextBound = next.getBoundingClientRect(); if (originBound.bottom < nextBound.top) { next.scrollIntoView({ block: "center", }); break; } cursor = next; } }, [ sortAndGetAnchor, ]); const goPrevious = react.useCallback(() => { const anchor = sortAndGetAnchor(); if (!anchor) { return; } let cursor = anchor; const originBound = cursor.getBoundingClientRect(); while (cursor.previousElementSibling) { const previous = cursor.previousElementSibling; const previousBound = previous.getBoundingClientRect(); if (previousBound.bottom < originBound.top) { previous.scrollIntoView({ block: "center", }); break; } cursor = previous; } }, [ sortAndGetAnchor, ]); const restore = react.useCallback(() => { const anchor = sortAndGetAnchor(); if (!anchor) { return; } anchor.scrollIntoView({ block: "center", }); }, [ pages, sortAndGetAnchor, ]); react.useEffect(() => { const newObserver = new IntersectionObserver((entries) => { let newIntersections = pages.entries; for (const entry of entries) { newIntersections = newIntersections.filter((item) => item.target !== entry.target ); if (entry.isIntersecting) { newIntersections.push(entry); } } pages.entries = newIntersections; }, { threshold: [ 0.01, 0.5, ], }); setObserver(newObserver); window.addEventListener("resize", restore); return () => { newObserver.disconnect(); window.removeEventListener("resize", restore); }; }, [ pages, ]); return react.useMemo(() => ({ goNext, goPrevious, restore, observer, }), [ goNext, goPrevious, restore, observer, ]); }; const { styled, css } = react$1.createStyled({}); const init = (source) => { if (typeof source === "string") { return { src: source, iterator: (function* () { })(), }; } if (Array.isArray(source)) { return { src: source[0], iterator: (function* () { for (const url of source.slice(1)) { yield url; } })(), }; } throw new Error("unknown image source"); }; const reducer = (state, action) => { if (action !== "next") { return init(action); } if (state.iterator == null) { return state; } const result = state.iterator.next(); if (result.done === true) { return {}; } return { ...state, src: result.value, }; }; const usePageReducer = (source) => { const [state, dispatch] = react.useReducer(reducer, source, init); const onError = react.useCallback(() => { dispatch("next"); }, []); return { src: state.src, onError, }; }; const Image = styled("img", { height: "100vh", maxWidth: "100vw", objectFit: "contain", margin: "4px 1px", }); const Page = ({ source, observer, ...props }) => { const { src, onError } = usePageReducer(source); const ref = react.useRef(); react.useEffect(() => { const target = ref.current; if (target && observer) { observer.observe(target); return () => observer.unobserve(target); } }, [ observer, ref.current, ]); return react.createElement( Image, Object.assign({ ref: ref, src: src, onError: onError, loading: "lazy", }, props), ); }; const ImageContainer = styled("div", { backgroundColor: "#eee", display: "flex", justifyContent: "center", alignItems: "center", flexFlow: "row-reverse wrap", overflowY: "auto", }); const Viewer = ({ source, ...props }) => { const [images, setImages] = react.useState(); const [status, setStatus] = react.useState("loading"); const navigator = usePageNavigator(); const ref = react.useRef(); const fullscreenElement = useFullscreenElement(); const handleNavigation = react.useCallback((event) => { switch (event.key) { case "j": navigator.goNext(); break; case "k": navigator.goPrevious(); break; case "o": window.close(); break; } }, [ navigator, ]); const fetchSource = react.useCallback(async () => { try { setImages(await source()); setStatus("complete"); } catch (error) { setStatus("error"); console.log(error); throw error; } }, [ source, ]); react.useEffect(() => { const globalKeyHandler = async (event) => { if (event.key === "i") { if (document.fullscreenElement) { await document.exitFullscreen(); } else { await ref?.current?.requestFullscreen?.(); } } }; window.addEventListener("keydown", globalKeyHandler); return () => window.removeEventListener("keydown", globalKeyHandler); }, [ navigator, ref.current, ]); react.useEffect(() => { ref.current?.focus?.(); }, [ ref.current, ]); react.useEffect(() => { if (!ref.current) { return; } const style = ref.current.style; const fullscreenStyle = { display: "flex", position: "fixed", top: 0, bottom: 0, overflow: "auto", }; if (fullscreenElement && style.position !== "fixed") { Object.assign(style, fullscreenStyle); navigator.restore(); ref.current.focus(); } else if (!fullscreenElement && style.position === "fixed") { for (const property of Object.keys(fullscreenStyle)) { style.removeProperty(property); } navigator.restore(); } }, [ ref.current, fullscreenElement, navigator, ]); react.useEffect(() => { fetchSource(); }, [ fetchSource, ]); return react.createElement( ImageContainer, Object.assign({ ref: ref, className: "vim_comic_viewer", tabIndex: -1, onKeyDown: handleNavigation, }, props), status === "complete" ? images?.map?.((image, index) => react.createElement(Page, { key: index, source: image, observer: navigator.observer, }) ) || false : react.createElement( "p", null, status === "error" ? "에러가 발생했습니다" : "로딩 중...", ), ); }; const timeout = (millisecond) => new Promise((resolve) => setTimeout(resolve, millisecond)); const waitDomContent = (document) => document.readyState === "loading" ? new Promise((r) => document.addEventListener("readystatechange", r, { once: true, }) ) : true; const insertCss = (css) => { const style = document.createElement("style"); style.innerHTML = css; document.head.append(style); }; var utils = /*#__PURE__*/ Object.freeze({ __proto__: null, timeout: timeout, waitDomContent: waitDomContent, insertCss: insertCss, }); var types = /*#__PURE__*/ Object.freeze({ __proto__: null, }); /** @jsx createElement */ /// <reference lib="dom" /> const initializeWithSource = async (source) => { const root = document.createElement("div"); while (true) { if (document.body) { document.body.append(root); reactDom.render( react.createElement(Viewer, { source: source, }), root, ); break; } await timeout(1); } }; const initialize = async (sourceOrSources) => { if (Array.isArray(sourceOrSources)) { const source = sourceOrSources.find((x) => x.isApplicable()); if (source) { await initializeWithSource(source.comicSource); } } else { await initializeWithSource(sourceOrSources.comicSource); } }; exports.initialize = initialize; exports.types = types; exports.utils = utils;
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址