// ==UserScript== // @name EhPeek // @version 260902.1404 // @description A touch-optimized E-H/ExH viewer // @description:ja タッチ操作向けの E-H/ExH リーダー // @description:zh-CN 为触屏优化的 E-H/ExH 阅读器 // @icon https://raw.githubusercontent.com/yamipot/ehpeek/master/icon.svg // @icon64 https://raw.githubusercontent.com/yamipot/ehpeek/master/icon.svg // @license MIT // @namespace https://github.com/yamipot/ehpeek // @homepage https://github.com/yamipot/ehpeek // @supportURL https://github.com/yamipot/ehpeek/issues // @match *://exhentai.org/* // @match *://exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/* // @match *://e-hentai.org/* // @match *://*.exhentai.org/* // @match *://*.exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/* // @match *://*.e-hentai.org/* // @match *://*.hath.network/* // @exclude *://forums.e-hentai.org/* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_registerMenuCommand // @grant GM_download // @run-at document-start // @updateURL https://github.com/yamipot/ehpeek/raw/build-stable/ehpeek.user.js // @downloadURL https://github.com/yamipot/ehpeek/raw/build-stable/ehpeek.user.js // ==/UserScript== "use strict"; (() => { var __defProp = Object.defineProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key != "symbol" ? key + "" : key, value), __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); // node_modules/.pnpm/solid-js@1.9.14/node_modules/solid-js/dist/solid.js var sharedConfig = { context: void 0, registry: void 0, effects: void 0, done: !1, getContextId() { return getContextId(this.context.count); }, getNextContextId() { return getContextId(this.context.count++); } }; function getContextId(count) { let num = String(count), len = num.length - 1; return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num; } function setHydrateContext(context) { sharedConfig.context = context; } function nextHydrateContext() { return { ...sharedConfig.context, id: sharedConfig.getNextContextId(), count: 0 }; } var IS_DEV = !1, equalFn = (a, b) => a === b, $PROXY = /* @__PURE__ */ Symbol("solid-proxy"), SUPPORTS_PROXY = typeof Proxy == "function", $TRACK = /* @__PURE__ */ Symbol("solid-track"); var signalOptions = { equals: equalFn }, ERROR = null, runEffects = runQueue, STALE = 1, PENDING = 2, UNOWNED = { owned: null, cleanups: null, context: null, owner: null }; var Owner = null, Transition = null, Scheduler = null, ExternalSourceConfig = null, Listener = null, Updates = null, Effects = null, ExecCount = 0; function createRoot(fn, detachedOwner) { let listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : { owned: null, cleanups: null, context: current ? current.context : null, owner: current }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root))); Owner = root, Listener = null; try { return runUpdates(updateFn, !0); } finally { Listener = listener, Owner = owner; } } function createSignal(value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; let s = { value, observers: null, observerSlots: null, comparator: options.equals || void 0 }, setter = (value2) => (typeof value2 == "function" && (Transition && Transition.running && Transition.sources.has(s) ? value2 = value2(s.tValue) : value2 = value2(s.value)), writeSignal(s, value2)); return [readSignal.bind(s), setter]; } function createRenderEffect(fn, value, options) { let c = createComputation(fn, value, !1, STALE); Scheduler && Transition && Transition.running ? Updates.push(c) : updateComputation(c); } function createEffect(fn, value, options) { runEffects = runUserEffects; let c = createComputation(fn, value, !1, STALE), s = SuspenseContext && useContext(SuspenseContext); s && (c.suspense = s), (!options || !options.render) && (c.user = !0), Effects ? Effects.push(c) : updateComputation(c); } function createMemo(fn, value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; let c = createComputation(fn, value, !0, 0); return c.observers = null, c.observerSlots = null, c.comparator = options.equals || void 0, Scheduler && Transition && Transition.running ? (c.tState = STALE, Updates.push(c)) : updateComputation(c), readSignal.bind(c); } function batch(fn) { return runUpdates(fn, !1); } function untrack(fn) { if (!ExternalSourceConfig && Listener === null) return fn(); let listener = Listener; Listener = null; try { return ExternalSourceConfig ? ExternalSourceConfig.untrack(fn) : fn(); } finally { Listener = listener; } } function onMount(fn) { createEffect(() => untrack(fn)); } function onCleanup(fn) { return Owner === null || (Owner.cleanups === null ? Owner.cleanups = [fn] : Owner.cleanups.push(fn)), fn; } function getListener() { return Listener; } function getOwner() { return Owner; } function runWithOwner(o, fn) { let prev = Owner, prevListener = Listener; Owner = o, Listener = null; try { return runUpdates(fn, !0); } catch (err) { handleError(err); } finally { Owner = prev, Listener = prevListener; } } function startTransition(fn) { if (Transition && Transition.running) return fn(), Transition.done; let l = Listener, o = Owner; return Promise.resolve().then(() => { Listener = l, Owner = o; let t; return (Scheduler || SuspenseContext) && (t = Transition || (Transition = { sources: /* @__PURE__ */ new Set(), effects: [], promises: /* @__PURE__ */ new Set(), disposed: /* @__PURE__ */ new Set(), queue: /* @__PURE__ */ new Set(), running: !0 }), t.done || (t.done = new Promise((res) => t.resolve = res)), t.running = !0), runUpdates(fn, !1), Listener = Owner = null, t ? t.done : void 0; }); } var [transPending, setTransPending] = /* @__PURE__ */ createSignal(!1); function createContext(defaultValue, options) { let id2 = /* @__PURE__ */ Symbol("context"); return { id: id2, Provider: createProvider(id2), defaultValue }; } function useContext(context) { let value; return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue; } function children(fn) { let children2 = createMemo(fn), memo2 = createMemo(() => resolveChildren(children2())); return memo2.toArray = () => { let c = memo2(); return Array.isArray(c) ? c : c != null ? [c] : []; }, memo2; } var SuspenseContext; function readSignal() { let runningTransition = Transition && Transition.running; if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this); else { let updates = Updates; Updates = null, runUpdates(() => lookUpstream(this), !1), Updates = updates; } if (Listener) { let observers = this.observers; if (!observers || observers[observers.length - 1] !== Listener) { let sSlot = observers ? observers.length : 0; Listener.sources ? (Listener.sources.push(this), Listener.sourceSlots.push(sSlot)) : (Listener.sources = [this], Listener.sourceSlots = [sSlot]), observers ? (observers.push(Listener), this.observerSlots.push(Listener.sources.length - 1)) : (this.observers = [Listener], this.observerSlots = [Listener.sources.length - 1]); } } return runningTransition && Transition.sources.has(this) ? this.tValue : this.value; } function writeSignal(node, value, isComp) { let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value; if (!node.comparator || !node.comparator(current, value)) { if (Transition) { let TransitionRunning = Transition.running; (TransitionRunning || !isComp && Transition.sources.has(node)) && (Transition.sources.add(node), node.tValue = value), TransitionRunning || (node.value = value); } else node.value = value; node.observers && node.observers.length && runUpdates(() => { for (let i = 0; i < node.observers.length; i += 1) { let o = node.observers[i], TransitionRunning = Transition && Transition.running; TransitionRunning && Transition.disposed.has(o) || ((TransitionRunning ? !o.tState : !o.state) && (o.pure ? Updates.push(o) : Effects.push(o), o.observers && markDownstream(o)), TransitionRunning ? o.tState = STALE : o.state = STALE); } if (Updates.length > 1e6) throw Updates = [], new Error(); }, !1); } return value; } function updateComputation(node) { if (!node.fn) return; cleanNode(node); let time = ExecCount; runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time), Transition && !Transition.running && Transition.sources.has(node) && queueMicrotask(() => { runUpdates(() => { Transition && (Transition.running = !0), Listener = Owner = node, runComputation(node, node.tValue, time), Listener = Owner = null; }, !1); }); } function runComputation(node, value, time) { let nextValue, owner = Owner, listener = Listener; Listener = Owner = node; try { nextValue = node.fn(value); } catch (err) { return node.pure && (Transition && Transition.running ? (node.tState = STALE, node.tOwned && node.tOwned.forEach(cleanNode), node.tOwned = void 0) : (node.state = STALE, node.owned && node.owned.forEach(cleanNode), node.owned = null)), node.updatedAt = time + 1, handleError(err); } finally { Listener = listener, Owner = owner; } (!node.updatedAt || node.updatedAt <= time) && (node.updatedAt != null && "observers" in node ? writeSignal(node, nextValue, !0) : Transition && Transition.running && node.pure ? (Transition.sources.has(node) || (node.value = nextValue), Transition.sources.add(node), node.tValue = nextValue) : node.value = nextValue, node.updatedAt = time); } function createComputation(fn, init, pure, state2 = STALE, options) { let c = { fn, state: state2, updatedAt: null, owned: null, sources: null, sourceSlots: null, cleanups: null, value: init, owner: Owner, context: Owner ? Owner.context : null, pure }; if (Transition && Transition.running && (c.state = 0, c.tState = state2), Owner === null || Owner !== UNOWNED && (Transition && Transition.running && Owner.pure ? Owner.tOwned ? Owner.tOwned.push(c) : Owner.tOwned = [c] : Owner.owned ? Owner.owned.push(c) : Owner.owned = [c]), ExternalSourceConfig && c.fn) { let sourceFn = c.fn, [track, trigger] = createSignal(void 0, { equals: !1 }), ordinary = ExternalSourceConfig.factory(sourceFn, trigger); onCleanup(() => ordinary.dispose()); let inTransition, triggerInTransition = () => startTransition(trigger).then(() => { inTransition && (inTransition.dispose(), inTransition = void 0); }); c.fn = (x) => (track(), Transition && Transition.running ? (inTransition || (inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition)), inTransition.track(x)) : ordinary.track(x)); } return c; } function runTop(node) { let runningTransition = Transition && Transition.running; if ((runningTransition ? node.tState : node.state) === 0) return; if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node); if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node); let ancestors = [node]; for (; (node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount); ) { if (runningTransition && Transition.disposed.has(node)) return; (runningTransition ? node.tState : node.state) && ancestors.push(node); } for (let i = ancestors.length - 1; i >= 0; i--) { if (node = ancestors[i], runningTransition) { let top = node, prev = ancestors[i + 1]; for (; (top = top.owner) && top !== prev; ) if (Transition.disposed.has(top)) return; } if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node); else if ((runningTransition ? node.tState : node.state) === PENDING) { let updates = Updates; Updates = null, runUpdates(() => lookUpstream(node, ancestors[0]), !1), Updates = updates; } } } function runUpdates(fn, init) { if (Updates) return fn(); let wait = !1; init || (Updates = []), Effects ? wait = !0 : Effects = [], ExecCount++; try { let res = fn(); return completeUpdates(wait), res; } catch (err) { wait || (Effects = null), Updates = null, handleError(err); } } function completeUpdates(wait) { if (Updates && (Scheduler && Transition && Transition.running ? scheduleQueue(Updates) : runQueue(Updates), Updates = null), wait) return; let res; if (Transition) { if (!Transition.promises.size && !Transition.queue.size) { let sources = Transition.sources, disposed = Transition.disposed; Effects.push.apply(Effects, Transition.effects), res = Transition.resolve; for (let e2 of Effects) "tState" in e2 && (e2.state = e2.tState), delete e2.tState; Transition = null, runUpdates(() => { for (let d of disposed) cleanNode(d); for (let v of sources) { if (v.value = v.tValue, v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]); v.tOwned && (v.owned = v.tOwned), delete v.tValue, delete v.tOwned, v.tState = 0; } setTransPending(!1); }, !1); } else if (Transition.running) { Transition.running = !1, Transition.effects.push.apply(Transition.effects, Effects), Effects = null, setTransPending(!0); return; } } let e = Effects; Effects = null, e.length && runUpdates(() => runEffects(e), !1), res && res(); } function runQueue(queue) { for (let i = 0; i < queue.length; i++) runTop(queue[i]); } function scheduleQueue(queue) { for (let i = 0; i < queue.length; i++) { let item = queue[i], tasks = Transition.queue; tasks.has(item) || (tasks.add(item), Scheduler(() => { tasks.delete(item), runUpdates(() => { Transition.running = !0, runTop(item); }, !1), Transition && (Transition.running = !1); })); } } function runUserEffects(queue) { let i, userLength = 0; for (i = 0; i < queue.length; i++) { let e = queue[i]; e.user ? queue[userLength++] = e : runTop(e); } if (sharedConfig.context) { if (sharedConfig.count) { sharedConfig.effects || (sharedConfig.effects = []), sharedConfig.effects.push(...queue.slice(0, userLength)); return; } setHydrateContext(); } for (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count) && (queue = [...sharedConfig.effects, ...queue], userLength += sharedConfig.effects.length, delete sharedConfig.effects), i = 0; i < userLength; i++) runTop(queue[i]); } function lookUpstream(node, ignore) { let runningTransition = Transition && Transition.running; runningTransition ? node.tState = 0 : node.state = 0; for (let i = 0; i < node.sources.length; i += 1) { let source = node.sources[i]; if (source.sources) { let state2 = runningTransition ? source.tState : source.state; state2 === STALE ? source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount) && runTop(source) : state2 === PENDING && lookUpstream(source, ignore); } } } function markDownstream(node) { let runningTransition = Transition && Transition.running; for (let i = 0; i < node.observers.length; i += 1) { let o = node.observers[i]; (runningTransition ? !o.tState : !o.state) && (runningTransition ? o.tState = PENDING : o.state = PENDING, o.pure ? Updates.push(o) : Effects.push(o), o.observers && markDownstream(o)); } } function cleanNode(node) { let i; if (node.sources) for (; node.sources.length; ) { let source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers; if (obs && obs.length) { let n = obs.pop(), s = source.observerSlots.pop(); index < obs.length && (n.sourceSlots[s] = index, obs[index] = n, source.observerSlots[index] = s); } } if (node.tOwned) { for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]); delete node.tOwned; } if (Transition && Transition.running && node.pure) reset(node, !0); else if (node.owned) { for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i](); node.cleanups = null; } Transition && Transition.running ? node.tState = 0 : node.state = 0; } function reset(node, top) { if (top || (node.tState = 0, Transition.disposed.add(node)), node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]); } function castError(err) { return err instanceof Error ? err : new Error(typeof err == "string" ? err : "Unknown error", { cause: err }); } function runErrors(err, fns, owner) { try { for (let f of fns) f(err); } catch (e) { handleError(e, owner && owner.owner || null); } } function handleError(err, owner = Owner) { let fns = ERROR && owner && owner.context && owner.context[ERROR], error = castError(err); if (!fns) throw error; Effects ? Effects.push({ fn() { runErrors(error, fns, owner); }, state: STALE }) : runErrors(error, fns, owner); } function resolveChildren(children2) { if (typeof children2 == "function" && !children2.length) return resolveChildren(children2()); if (Array.isArray(children2)) { let results = []; for (let i = 0; i < children2.length; i++) { let result = resolveChildren(children2[i]); if (Array.isArray(result)) if (result.length < 32768) results.push.apply(results, result); else for (let j = 0; j < result.length; j++) results.push(result[j]); else results.push(result); } return results; } return children2; } function createProvider(id2, options) { return function(props) { let res; return createRenderEffect(() => res = untrack(() => (Owner.context = { ...Owner.context, [id2]: props.value }, children(() => props.children))), void 0), res; }; } var FALLBACK = /* @__PURE__ */ Symbol("fallback"); function dispose(d) { for (let i = 0; i < d.length; i++) d[i](); } function mapArray(list, mapFn, options = {}) { let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null; return onCleanup(() => dispose(disposers)), () => { let newItems = list() || [], newLen = newItems.length, i, j; return newItems[$TRACK], untrack(() => { let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item; if (newLen === 0) len !== 0 && (dispose(disposers), disposers = [], items = [], mapped = [], len = 0, indexes && (indexes = [])), options.fallback && (items = [FALLBACK], mapped[0] = createRoot((disposer) => (disposers[0] = disposer, options.fallback())), len = 1); else if (len === 0) { for (mapped = new Array(newLen), j = 0; j < newLen; j++) items[j] = newItems[j], mapped[j] = createRoot(mapper); len = newLen; } else { for (temp = new Array(newLen), tempdisposers = new Array(newLen), indexes && (tempIndexes = new Array(newLen)), start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++) ; for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) temp[newEnd] = mapped[end], tempdisposers[newEnd] = disposers[end], indexes && (tempIndexes[newEnd] = indexes[end]); for (newIndices = /* @__PURE__ */ new Map(), newIndicesNext = new Array(newEnd + 1), j = newEnd; j >= start; j--) item = newItems[j], i = newIndices.get(item), newIndicesNext[j] = i === void 0 ? -1 : i, newIndices.set(item, j); for (i = start; i <= end; i++) item = items[i], j = newIndices.get(item), j !== void 0 && j !== -1 ? (temp[j] = mapped[i], tempdisposers[j] = disposers[i], indexes && (tempIndexes[j] = indexes[i]), j = newIndicesNext[j], newIndices.set(item, j)) : disposers[i](); for (j = start; j < newLen; j++) j in temp ? (mapped[j] = temp[j], disposers[j] = tempdisposers[j], indexes && (indexes[j] = tempIndexes[j], indexes[j](j))) : mapped[j] = createRoot(mapper); mapped = mapped.slice(0, len = newLen), items = newItems.slice(0); } return mapped; }); function mapper(disposer) { if (disposers[j] = disposer, indexes) { let [s, set] = createSignal(j); return indexes[j] = set, mapFn(newItems[j], s); } return mapFn(newItems[j]); } }; } var hydrationEnabled = !1; function createComponent(Comp, props) { if (hydrationEnabled && sharedConfig.context) { let c = sharedConfig.context; setHydrateContext(nextHydrateContext()); let r = untrack(() => Comp(props || {})); return setHydrateContext(c), r; } return untrack(() => Comp(props || {})); } function trueFn() { return !0; } var propTraps = { get(_, property, receiver) { return property === $PROXY ? receiver : _.get(property); }, has(_, property) { return property === $PROXY ? !0 : _.has(property); }, set: trueFn, deleteProperty: trueFn, getOwnPropertyDescriptor(_, property) { return { configurable: !0, enumerable: !0, get() { return _.get(property); }, set: trueFn, deleteProperty: trueFn }; }, ownKeys(_) { return _.keys(); } }; function resolveSource(s) { return (s = typeof s == "function" ? s() : s) ? s : {}; } function resolveSources() { for (let i = 0, length = this.length; i < length; ++i) { let v = this[i](); if (v !== void 0) return v; } } function mergeProps(...sources) { let proxy = !1; for (let i = 0; i < sources.length; i++) { let s = sources[i]; proxy = proxy || !!s && $PROXY in s, sources[i] = typeof s == "function" ? (proxy = !0, createMemo(s)) : s; } if (SUPPORTS_PROXY && proxy) return new Proxy({ get(property) { for (let i = sources.length - 1; i >= 0; i--) { let v = resolveSource(sources[i])[property]; if (v !== void 0) return v; } }, has(property) { for (let i = sources.length - 1; i >= 0; i--) if (property in resolveSource(sources[i])) return !0; return !1; }, keys() { let keys = []; for (let i = 0; i < sources.length; i++) keys.push(...Object.keys(resolveSource(sources[i]))); return [...new Set(keys)]; } }, propTraps); let sourcesMap = {}, defined = /* @__PURE__ */ Object.create(null); for (let i = sources.length - 1; i >= 0; i--) { let source = sources[i]; if (!source) continue; let sourceKeys = Object.getOwnPropertyNames(source); for (let i2 = sourceKeys.length - 1; i2 >= 0; i2--) { let key = sourceKeys[i2]; if (key === "__proto__" || key === "constructor") continue; let desc = Object.getOwnPropertyDescriptor(source, key); if (!defined[key]) defined[key] = desc.get ? { enumerable: !0, configurable: !0, get: resolveSources.bind(sourcesMap[key] = [desc.get.bind(source)]) } : desc.value !== void 0 ? desc : void 0; else { let sources2 = sourcesMap[key]; sources2 && (desc.get ? sources2.push(desc.get.bind(source)) : desc.value !== void 0 && sources2.push(() => desc.value)); } } } let target = {}, definedKeys = Object.keys(defined); for (let i = definedKeys.length - 1; i >= 0; i--) { let key = definedKeys[i], desc = defined[key]; desc && desc.get ? Object.defineProperty(target, key, desc) : target[key] = desc ? desc.value : void 0; } return target; } function splitProps(props, ...keys) { let len = keys.length; if (SUPPORTS_PROXY && $PROXY in props) { let blocked = len > 1 ? keys.flat() : keys[0], res = keys.map((k) => new Proxy({ get(property) { return k.includes(property) ? props[property] : void 0; }, has(property) { return k.includes(property) && property in props; }, keys() { return k.filter((property) => property in props); } }, propTraps)); return res.push(new Proxy({ get(property) { return blocked.includes(property) ? void 0 : props[property]; }, has(property) { return blocked.includes(property) ? !1 : property in props; }, keys() { return Object.keys(props).filter((k) => !blocked.includes(k)); } }, propTraps)), res; } let objects = []; for (let i = 0; i <= len; i++) objects[i] = {}; for (let propName of Object.getOwnPropertyNames(props)) { let keyIndex = len; for (let i = 0; i < keys.length; i++) if (keys[i].includes(propName)) { keyIndex = i; break; } let desc = Object.getOwnPropertyDescriptor(props, propName); !desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable ? objects[keyIndex][propName] = desc.value : Object.defineProperty(objects[keyIndex], propName, desc); } return objects; } var narrowedError = (name) => `Stale read from <${name}>.`; function For(props) { let fallback = "fallback" in props && { fallback: () => props.fallback }; return createMemo(mapArray(() => props.each, props.children, fallback || void 0)); } function Show(props) { let keyed = props.keyed, conditionValue = createMemo(() => props.when, void 0, void 0), condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a == !b }); return createMemo(() => { let c = condition(); if (c) { let child = props.children; return typeof child == "function" && child.length > 0 ? untrack(() => child(keyed ? c : () => { if (!untrack(condition)) throw narrowedError("Show"); return conditionValue(); })) : child; } return props.fallback; }, void 0, void 0); } // node_modules/.pnpm/solid-js@1.9.14/node_modules/solid-js/web/dist/web.js var booleans = [ "allowfullscreen", "async", "alpha", "autofocus", "autoplay", "checked", "controls", "default", "disabled", "formnovalidate", "hidden", "indeterminate", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "seamless", "selected", "adauctionheaders", "browsingtopics", "credentialless", "defaultchecked", "defaultmuted", "defaultselected", "defer", "disablepictureinpicture", "disableremoteplayback", "preservespitch", "shadowrootclonable", "shadowrootcustomelementregistry", "shadowrootdelegatesfocus", "shadowrootserializable", "sharedstoragewritable" ], Properties = /* @__PURE__ */ new Set([ "className", "value", "readOnly", "noValidate", "formNoValidate", "isMap", "noModule", "playsInline", "adAuctionHeaders", "allowFullscreen", "browsingTopics", "defaultChecked", "defaultMuted", "defaultSelected", "disablePictureInPicture", "disableRemotePlayback", "preservesPitch", "shadowRootClonable", "shadowRootCustomElementRegistry", "shadowRootDelegatesFocus", "shadowRootSerializable", "sharedStorageWritable", ...booleans ]), ChildProperties = /* @__PURE__ */ new Set(["innerHTML", "textContent", "innerText", "children"]), Aliases = /* @__PURE__ */ Object.assign(/* @__PURE__ */ Object.create(null), { className: "class", htmlFor: "for" }), PropAliases = /* @__PURE__ */ Object.assign(/* @__PURE__ */ Object.create(null), { class: "className", novalidate: { $: "noValidate", FORM: 1 }, formnovalidate: { $: "formNoValidate", BUTTON: 1, INPUT: 1 }, ismap: { $: "isMap", IMG: 1 }, nomodule: { $: "noModule", SCRIPT: 1 }, playsinline: { $: "playsInline", VIDEO: 1 }, readonly: { $: "readOnly", INPUT: 1, TEXTAREA: 1 }, adauctionheaders: { $: "adAuctionHeaders", IFRAME: 1 }, allowfullscreen: { $: "allowFullscreen", IFRAME: 1 }, browsingtopics: { $: "browsingTopics", IMG: 1 }, defaultchecked: { $: "defaultChecked", INPUT: 1 }, defaultmuted: { $: "defaultMuted", AUDIO: 1, VIDEO: 1 }, defaultselected: { $: "defaultSelected", OPTION: 1 }, disablepictureinpicture: { $: "disablePictureInPicture", VIDEO: 1 }, disableremoteplayback: { $: "disableRemotePlayback", AUDIO: 1, VIDEO: 1 }, preservespitch: { $: "preservesPitch", AUDIO: 1, VIDEO: 1 }, shadowrootclonable: { $: "shadowRootClonable", TEMPLATE: 1 }, shadowrootdelegatesfocus: { $: "shadowRootDelegatesFocus", TEMPLATE: 1 }, shadowrootserializable: { $: "shadowRootSerializable", TEMPLATE: 1 }, sharedstoragewritable: { $: "sharedStorageWritable", IFRAME: 1, IMG: 1 } }); function getPropAlias(prop, tagName) { let a = PropAliases[prop]; return typeof a == "object" ? a[tagName] ? a.$ : void 0 : a; } var DelegatedEvents = /* @__PURE__ */ new Set(["beforeinput", "click", "dblclick", "contextmenu", "focusin", "focusout", "input", "keydown", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "pointerdown", "pointermove", "pointerout", "pointerover", "pointerup", "touchend", "touchmove", "touchstart"]), SVGElements = /* @__PURE__ */ new Set([ "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hkern", "image", "line", "linearGradient", "marker", "mask", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "stop", "svg", "switch", "symbol", "text", "textPath", "tref", "tspan", "use", "view", "vkern" ]), SVGNamespace = { xlink: "http://www.w3.org/1999/xlink", xml: "http://www.w3.org/XML/1998/namespace" }; var memo = (fn) => createMemo(() => fn()); function reconcileArrays(parentNode, a, b) { let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null; for (; aStart < aEnd || bStart < bEnd; ) { if (a[aStart] === b[bStart]) { aStart++, bStart++; continue; } for (; a[aEnd - 1] === b[bEnd - 1]; ) aEnd--, bEnd--; if (aEnd === aStart) { let node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after; for (; bStart < bEnd; ) parentNode.insertBefore(b[bStart++], node); } else if (bEnd === bStart) for (; aStart < aEnd; ) (!map || !map.has(a[aStart])) && a[aStart].remove(), aStart++; else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { let node = a[--aEnd].nextSibling; parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling), parentNode.insertBefore(b[--bEnd], node), a[aEnd] = b[bEnd]; } else { if (!map) { map = /* @__PURE__ */ new Map(); let i = bStart; for (; i < bEnd; ) map.set(b[i], i++); } let index = map.get(a[aStart]); if (index != null) if (bStart < index && index < bEnd) { let i = aStart, sequence = 1, t; for (; ++i < aEnd && i < bEnd && !((t = map.get(a[i])) == null || t !== index + sequence); ) sequence++; if (sequence > index - bStart) { let node = a[aStart]; for (; bStart < index; ) parentNode.insertBefore(b[bStart++], node); } else parentNode.replaceChild(b[bStart++], a[aStart++]); } else aStart++; else a[aStart++].remove(); } } } var $$EVENTS = "_$DX_DELEGATE"; function render(code, element, init, options = {}) { let disposer; return createRoot((dispose2) => { disposer = dispose2, element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init); }, options.owner), () => { disposer(), element.textContent = ""; }; } function template(html, isImportNode, isSVG, isMathML) { let node, create = () => { let t = isMathML ? document.createElementNS("http://www.w3.org/1998/Math/MathML", "template") : document.createElement("template"); return t.innerHTML = html, isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild; }, fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), !0)) : () => (node || (node = create())).cloneNode(!0); return fn.cloneNode = fn, fn; } function delegateEvents(eventNames, document2 = window.document) { let e = document2[$$EVENTS] || (document2[$$EVENTS] = /* @__PURE__ */ new Set()); for (let i = 0, l = eventNames.length; i < l; i++) { let name = eventNames[i]; e.has(name) || (e.add(name), document2.addEventListener(name, eventHandler)); } } function setAttribute(node, name, value) { isHydrating(node) || (value == null ? node.removeAttribute(name) : node.setAttribute(name, value)); } function setAttributeNS(node, namespace, name, value) { isHydrating(node) || (value == null ? node.removeAttributeNS(namespace, name) : node.setAttributeNS(namespace, name, value)); } function setBoolAttribute(node, name, value) { isHydrating(node) || (value ? node.setAttribute(name, "") : node.removeAttribute(name)); } function className(node, value) { isHydrating(node) || (value == null ? node.removeAttribute("class") : node.className = value); } function addEventListener(node, name, handler, delegate) { if (delegate) Array.isArray(handler) ? (node[`$$${name}`] = handler[0], node[`$$${name}Data`] = handler[1]) : node[`$$${name}`] = handler; else if (Array.isArray(handler)) { let handlerFn = handler[0]; node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e)); } else node.addEventListener(name, handler, typeof handler != "function" && handler); } function classList(node, value, prev = {}) { let classKeys = Object.keys(value || {}), prevKeys = Object.keys(prev), i, len; for (i = 0, len = prevKeys.length; i < len; i++) { let key = prevKeys[i]; !key || key === "undefined" || value[key] || (toggleClassKey(node, key, !1), delete prev[key]); } for (i = 0, len = classKeys.length; i < len; i++) { let key = classKeys[i], classValue = !!value[key]; !key || key === "undefined" || prev[key] === classValue || !classValue || (toggleClassKey(node, key, !0), prev[key] = classValue); } return prev; } function style(node, value, prev) { if (!value) return prev ? setAttribute(node, "style") : value; let nodeStyle = node.style; if (typeof value == "string") return nodeStyle.cssText = value; typeof prev == "string" && (nodeStyle.cssText = prev = void 0), prev || (prev = {}), value || (value = {}); let v, s; for (s in prev) value[s] == null && nodeStyle.removeProperty(s), delete prev[s]; for (s in value) v = value[s], v !== prev[s] && (nodeStyle.setProperty(s, v), prev[s] = v); return prev; } function setStyleProperty(node, name, value) { value != null ? node.style.setProperty(name, value) : node.style.removeProperty(name); } function spread(node, props = {}, isSVG, skipChildren) { let prevProps = {}; return skipChildren || createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children)), createRenderEffect(() => typeof props.ref == "function" && use(props.ref, node)), createRenderEffect(() => assign(node, props, isSVG, !0, prevProps, !0)), prevProps; } function use(fn, element, arg) { return untrack(() => fn(element, arg)); } function insert(parent, accessor, marker, initial) { if (marker !== void 0 && !initial && (initial = []), typeof accessor != "function") return insertExpression(parent, accessor, initial, marker); createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial); } function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = !1) { props || (props = {}); for (let prop in prevProps) if (!(prop in props)) { if (prop === "children") continue; prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef, props); } for (let prop in props) { if (prop === "children") { skipChildren || insertExpression(node, props.children); continue; } let value = props[prop]; prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef, props); } } function getNextElement(template2) { let node, key; return !isHydrating() || !(node = sharedConfig.registry.get(key = getHydrationKey())) ? template2() : (sharedConfig.completed && sharedConfig.completed.add(node), sharedConfig.registry.delete(key), node); } function isHydrating(node) { return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected); } function toPropertyName(name) { return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase()); } function toggleClassKey(node, key, value) { let classNames = key.trim().split(/\s+/); for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value); } function assignProp(node, prop, value, prev, isSVG, skipRef, props) { let isCE, isProp, isChildProp, propAlias, forceProp; if (prop === "style") return style(node, value, prev); if (prop === "classList") return classList(node, value, prev); if (value === prev) return prev; if (prop === "ref") skipRef || value(node); else if (prop.slice(0, 3) === "on:") { let e = prop.slice(3); prev && node.removeEventListener(e, prev, typeof prev != "function" && prev), value && node.addEventListener(e, value, typeof value != "function" && value); } else if (prop.slice(0, 10) === "oncapture:") { let e = prop.slice(10); prev && node.removeEventListener(e, prev, !0), value && node.addEventListener(e, value, !0); } else if (prop.slice(0, 2) === "on") { let name = prop.slice(2).toLowerCase(), delegate = DelegatedEvents.has(name); if (!delegate && prev) { let h = Array.isArray(prev) ? prev[0] : prev; node.removeEventListener(name, h); } (delegate || value) && (addEventListener(node, name, value, delegate), delegate && delegateEvents([name])); } else if (prop.slice(0, 5) === "attr:") setAttribute(node, prop.slice(5), value); else if (prop.slice(0, 5) === "bool:") setBoolAttribute(node, prop.slice(5), value); else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-") || "is" in props)) { if (forceProp) prop = prop.slice(5), isProp = !0; else if (isHydrating(node)) return value; prop === "class" || prop === "className" ? className(node, value) : isCE && !isProp && !isChildProp ? node[toPropertyName(prop)] = value : node[propAlias || prop] = value; } else { let ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]]; ns ? setAttributeNS(node, ns, prop, value) : setAttribute(node, Aliases[prop] || prop, value); } return value; } function eventHandler(e) { if (sharedConfig.registry && sharedConfig.events && sharedConfig.events.find(([el, ev]) => ev === e)) return; let node = e.target, key = `$$${e.type}`, oriTarget = e.target, oriCurrentTarget = e.currentTarget, retarget = (value) => Object.defineProperty(e, "target", { configurable: !0, value }), handleNode = () => { let handler = node[key]; if (handler && !node.disabled) { let data = node[`${key}Data`]; if (data !== void 0 ? handler.call(node, data, e) : handler.call(node, e), e.cancelBubble) return; } return node.host && typeof node.host != "string" && !node.host._$host && node.contains(e.target) && retarget(node.host), !0; }, walkUpTree = () => { for (; handleNode() && (node = node._$host || node.parentNode || node.host); ) ; }; if (Object.defineProperty(e, "currentTarget", { configurable: !0, get() { return node || document; } }), sharedConfig.registry && !sharedConfig.done && (sharedConfig.done = _$HY.done = !0), e.composedPath) { let path = e.composedPath(); retarget(path[0]); for (let i = 0; i < path.length - 2 && (node = path[i], !!handleNode()); i++) { if (node._$host) { node = node._$host, walkUpTree(); break; } if (node.parentNode === oriCurrentTarget) break; } } else walkUpTree(); retarget(oriTarget); } function insertExpression(parent, value, current, marker, unwrapArray) { let hydrating = isHydrating(parent); if (hydrating) { !current && (current = [...parent.childNodes]); let cleaned = []; for (let i = 0; i < current.length; i++) { let node = current[i]; node.nodeType === 8 && node.data.slice(0, 2) === "!$" ? node.remove() : cleaned.push(node); } current = cleaned; } for (; typeof current == "function"; ) current = current(); if (value === current) return current; let t = typeof value, multi = marker !== void 0; if (parent = multi && current[0] && current[0].parentNode || parent, t === "string" || t === "number") { if (hydrating || t === "number" && (value = value.toString(), value === current)) return current; if (multi) { let node = current[0]; node && node.nodeType === 3 ? node.data !== value && (node.data = value) : node = document.createTextNode(value), current = cleanChildren(parent, current, marker, node); } else current !== "" && typeof current == "string" ? current = parent.firstChild.data = value : current = parent.textContent = value; } else if (value == null || t === "boolean") { if (hydrating) return current; current = cleanChildren(parent, current, marker); } else { if (t === "function") return createRenderEffect(() => { let v = value(); for (; typeof v == "function"; ) v = v(); current = insertExpression(parent, v, current, marker); }), () => current; if (Array.isArray(value)) { let array = [], currentArray = current && Array.isArray(current); if (normalizeIncomingArray(array, value, current, unwrapArray)) return createRenderEffect(() => current = insertExpression(parent, array, current, marker, !0)), () => current; if (hydrating) { if (!array.length) return current; if (marker === void 0) return current = [...parent.childNodes]; let node = array[0]; if (node.parentNode !== parent) return current; let nodes = [node]; for (; (node = node.nextSibling) !== marker; ) nodes.push(node); return current = nodes; } if (array.length === 0) { if (current = cleanChildren(parent, current, marker), multi) return current; } else currentArray ? current.length === 0 ? appendNodes(parent, array, marker) : reconcileArrays(parent, current, array) : (current && cleanChildren(parent), appendNodes(parent, array)); current = array; } else if (value.nodeType) { if (hydrating && value.parentNode) return current = multi ? [value] : value; if (Array.isArray(current)) { if (multi) return current = cleanChildren(parent, current, marker, value); cleanChildren(parent, current, null, value); } else current == null || current === "" || !parent.firstChild ? parent.appendChild(value) : parent.replaceChild(value, parent.firstChild); current = value; } } return current; } function normalizeIncomingArray(normalized, array, current, unwrap2) { let dynamic = !1; for (let i = 0, len = array.length; i < len; i++) { let item = array[i], prev = current && current[normalized.length], t; if (!(item == null || item === !0 || item === !1)) if ((t = typeof item) == "object" && item.nodeType) normalized.push(item); else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic; else if (t === "function") if (unwrap2) { for (; typeof item == "function"; ) item = item(); dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic; } else normalized.push(item), dynamic = !0; else { let value = String(item); prev && prev.nodeType === 3 && prev.data === value ? normalized.push(prev) : normalized.push(document.createTextNode(value)); } } return dynamic; } function appendNodes(parent, array, marker = null) { for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker); } function cleanChildren(parent, current, marker, replacement) { if (marker === void 0) return parent.textContent = ""; let node = replacement || document.createTextNode(""); if (current.length) { let inserted = !1; for (let i = current.length - 1; i >= 0; i--) { let el = current[i]; if (node !== el) { let isParent = el.parentNode === parent; !inserted && !i ? isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker) : isParent && el.remove(); } else inserted = !0; } } else parent.insertBefore(node, marker); return [node]; } function getHydrationKey() { return sharedConfig.getNextContextId(); } var SVG_NAMESPACE = "http://www.w3.org/2000/svg"; function createElement(tagName, isSVG = !1, is = void 0) { return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName, { is }); } function Portal(props) { let { useShadow } = props, marker = document.createTextNode(""), mount = () => props.mount || document.body, owner = getOwner(), content, hydrating = !!sharedConfig.context; return createEffect(() => { hydrating && (getOwner().user = hydrating = !1), content || (content = runWithOwner(owner, () => createMemo(() => props.children))); let el = mount(); if (el instanceof HTMLHeadElement) { let [clean, setClean] = createSignal(!1), cleanup = () => setClean(!0); createRoot((dispose2) => insert(el, () => clean() ? dispose2() : content(), null)), onCleanup(cleanup); } else { let container = createElement(props.isSVG ? "g" : "div", props.isSVG), renderRoot = useShadow && container.attachShadow ? container.attachShadow({ mode: "open" }) : container; Object.defineProperty(container, "_$host", { get() { return marker.parentNode; }, configurable: !0 }), insert(renderRoot, content), el.appendChild(container), props.ref && props.ref(container), onCleanup(() => el.contains(container) && el.removeChild(container)); } }, void 0, { render: !hydrating }), marker; } function createDynamic(component, props) { let cached = createMemo(component); return createMemo(() => { let component2 = cached(); switch (typeof component2) { case "function": return untrack(() => component2(props)); case "string": let isSvg = SVGElements.has(component2), el = sharedConfig.context ? getNextElement() : createElement(component2, isSvg, untrack(() => props.is)); return spread(el, props, isSvg), el; } }); } function Dynamic(props) { let [, others] = splitProps(props, ["component"]); return createDynamic(() => props.component, others); } // src/eh/url.ts var EXHENTAI_HOST = "exhentai.org", EXHENTAI_ONION_HOST = "exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion", SEARCH_CATEGORY_PATH = /^\/(?:doujinshi|manga|artistcg|gamecg|western|non-h|imageset|cosplay|asianporn|misc)\/?$/i; function ehSiteTheme(url = window.location.href) { let hostname = new URL(url, window.location.href).hostname; return hostname === EXHENTAI_HOST || hostname.endsWith(`.${EXHENTAI_HOST}`) || hostname === EXHENTAI_ONION_HOST || hostname.endsWith(`.${EXHENTAI_ONION_HOST}`) ? "exhentai" : "e-hentai"; } function galleryIdentityFromUrl(url = window.location.href) { try { let match = new URL(url, window.location.href).pathname.match(/^\/g\/(\d+)\/([^/]+)/i), galleryId = Number(match?.[1]), token = match?.[2]; return token && Number.isSafeInteger(galleryId) && galleryId > 0 ? { galleryId, token } : null; } catch { return null; } } function urlPath(url) { try { return new URL(url, window.location.href).pathname.toLowerCase(); } catch { return ""; } } function galleryTagNameFromUrl(url) { let encodedName = urlPath(url).match(/^\/tag\/(.+?)\/?$/i)?.[1]; try { return encodedName ? decodeURIComponent(encodedName.replace(/\+/g, " ")) : null; } catch { return null; } } function isFullImageUrl(url) { return urlPath(url).includes("/fullimg"); } function extractPageType(url = window.location.href) { try { let parsed = new URL(url, window.location.href), hash = new URLSearchParams(parsed.hash.replace(/^#/, "")); if (parsed.pathname === "/popular" && hash.has("ehpeek_history")) { let requestedPage = Number(hash.get("page") ?? "0"); return { type: "readHistory", url: parsed.href, pageIndex: Number.isSafeInteger(requestedPage) && requestedPage >= 0 ? requestedPage : 0 }; } let galleryMatch = parsed.pathname.match(/^\/g\/(\d+)\/([^/]+)\/?$/i); if (galleryMatch) { let galleryId = Number(galleryMatch[1]), token = galleryMatch[2]; if (token && Number.isFinite(galleryId) && galleryId > 0) return { type: "gallery", url: parsed.href, galleryId, token, previewIndex: previewPageIndex(parsed.href), peekPage: peekPageFromHash(parsed.hash) }; } let imageMatch = parsed.pathname.match(/^\/s\/[^/]+\/(\d+)-(\d+)\/?$/i); if (imageMatch) { let galleryId = Number(imageMatch[1]), pageNum = Number(imageMatch[2]); if (Number.isFinite(galleryId) && galleryId > 0 && Number.isFinite(pageNum) && pageNum > 0) return { type: "image", url: parsed.href, galleryId, pageNum }; } return parsed.pathname === "/favorites.php" ? { type: "favorites", url: parsed.href } : /^\/mytags\/?$/.test(parsed.pathname) ? { type: "myTags", url: parsed.href } : parsed.pathname === "/uconfig.php" ? { type: "settings", url: parsed.href } : parsed.pathname === "/" || SEARCH_CATEGORY_PATH.test(parsed.pathname) || parsed.pathname.startsWith("/tag/") || parsed.pathname.startsWith("/uploader/") || /^\/(?:popular|watched)\/?$/.test(parsed.pathname) ? { type: "search", url: parsed.href } : { type: "other", url: parsed.href }; } catch { return { type: "other", url }; } } function readHistoryUrl(pageIndex = 0) { let url = new URL("/popular", window.location.href); return url.hash = pageIndex > 0 ? `ehpeek_history&page=${pageIndex}` : "ehpeek_history", url.href; } function galleryPageNumber(url) { let page2 = extractPageType(url); return page2.type === "image" ? page2.pageNum : void 0; } function previewPageIndex(url = window.location.href) { try { let value = Number(new URL(url).searchParams.get("p") || "0"); return Number.isFinite(value) && value >= 0 ? value : 0; } catch { return 0; } } function previewUrlForIndex(previewIndex, pageUrl = window.location.href) { let url = new URL(pageUrl); return previewIndex <= 0 ? url.searchParams.delete("p") : url.searchParams.set("p", String(previewIndex)), url.hash = "", url.href; } function previewPageIndexForGalleryPage(galleryPage, pageSize, maxPreviewIndex) { let previewIndex = Math.max(0, Math.floor((galleryPage - 1) / pageSize)); return Math.min(previewIndex, maxPreviewIndex); } function peekPageFromHash(hash = window.location.hash) { let params = new URLSearchParams(hash.replace(/^#/, "")), page2 = Number(params.get("peek_page") || ""); return Number.isFinite(page2) && page2 > 0 ? page2 : null; } function clearPeekLocation() { let url = new URL(window.location.href), params = new URLSearchParams(url.hash.replace(/^#/, "")); params.has("peek_page") && (params.delete("peek_page"), url.hash = params.toString(), window.history.replaceState(window.history.state, "", url.href)); } function updatePeekLocation(pageNumber, pageSize, maxPreviewIndex) { if (!pageNumber || pageNumber <= 0) return; let url = new URL(window.location.href), params = new URLSearchParams(window.location.hash.replace(/^#/, "")), nextValue = String(pageNumber), nextPreviewIndex = previewPageIndexForGalleryPage(pageNumber, pageSize, maxPreviewIndex), changed = !1; nextPreviewIndex === 0 ? url.searchParams.has("p") && (url.searchParams.delete("p"), changed = !0) : url.searchParams.get("p") !== String(nextPreviewIndex) && (url.searchParams.set("p", String(nextPreviewIndex)), changed = !0), params.get("peek_page") !== nextValue && (params.set("peek_page", nextValue), changed = !0), changed && (url.hash = params.toString(), window.history.replaceState(window.history.state, "", url.href)); } // src/eh/request.ts async function requestPage(url, options = {}) { let controller = new AbortController(), abort = () => controller.abort(), timeoutMs = options.timeoutMs === void 0 ? 3e4 : options.timeoutMs, timeout = timeoutMs === null ? null : window.setTimeout(abort, timeoutMs); options.signal?.aborted ? controller.abort() : options.signal?.addEventListener("abort", abort, { once: !0 }); try { let response = await fetch(url, { method: options.method ?? "GET", body: options.body, credentials: "include", headers: options.headers, signal: controller.signal }); if (!response.ok) throw new Error(`HTTP ${response.status}`); let html = await response.text(); return { document: new DOMParser().parseFromString(html, "text/html"), url: response.url || url }; } finally { timeout !== null && window.clearTimeout(timeout), options.signal?.removeEventListener("abort", abort); } } async function updateGalleryFavorite(actionUrl, value, note) { let body = new URLSearchParams(); body.set("favcat", value), body.set("favnote", note), body.set("apply", "Apply Changes"), body.set("update", "1"), await requestPage(actionUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }); } async function addMyTag(tagName, tagSet, mode) { let body = new URLSearchParams(); body.set("usertag_action", "add"), body.set("tagname_new", tagName), body.set("tagcolor_new", ""), body.set("tagweight_new", "10"), mode === "watched" ? body.set("tagwatch_new", "on") : mode === "hidden" && body.set("taghide_new", "on"); let url = new URL("/mytags", window.location.origin); return url.searchParams.set("tagset", tagSet), requestPage(url.href, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }); } async function deleteMyTag(tagId, tagSet) { let body = new URLSearchParams(); body.set("usertag_action", "mass"), body.set("usertag_target", "0"), body.append("modify_usertags[]", tagId); let url = new URL("/mytags", window.location.origin); return url.searchParams.set("tagset", tagSet), requestPage(url.href, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }); } // ehpeek-spectrum-ui-sizes:ehpeek:spectrum-ui-sizes var ehpeek_spectrum_ui_sizes_default = { control: { xs: "24px", sm: "32px", md: "40px", lg: "48px", xl: "56px" }, font: { xs: "10px", sm: "14px", md: "16px", lg: "20px", xl: "28px" }, icon: { xs: "14px", sm: "16px", md: "20px", lg: "22px", xl: "26px" }, space: { xs: "4px", sm: "8px", md: "12px", lg: "16px", xl: "24px" }, radius: { xs: "3px", sm: "4px", md: "6px", lg: "8px", xl: "10px" } }; // src/ui.ts var UI_ROOT_CLASS = "ehpeek-ui-root", UI_STATE_STYLE_ID = "ehpeek-ui-state", UI_SCALE_SELECTOR = ".ehpeek-ui-root, body.ehpeek-touch-gallery-page, .ehpeek-external-autocomplete", UI_SCALE_FACTORS = { xsmall: 0.8, small: 1, medium: 1.25, large: 1.5, xlarge: 1.8 }, UI_SCALE_NAMES = Object.freeze( Object.keys(UI_SCALE_FACTORS) ), uiState; function configureUi(options) { uiState = { ...options }, applyUiStateClasses(); } function markUiRoot(root) { root.parentElement?.closest(`.${UI_ROOT_CLASS}`) || root.classList.add(UI_ROOT_CLASS); } function setUiPointer(pointer) { let state2 = requireUiState(); state2.pointer = pointer, applyUiStateClasses(); } function nextUiScale(scale) { let index = UI_SCALE_NAMES.indexOf(scale); return UI_SCALE_NAMES[(index + 1) % UI_SCALE_NAMES.length]; } function uiScaleLevel(scale) { return UI_SCALE_NAMES.indexOf(scale) + 1; } function applyUiScale(scale, root, pixelFactor = 1) { let factor = UI_SCALE_FACTORS[scale] * pixelFactor; if (!root) { applyGlobalUiScale(factor); return; } for (let [property, value] of uiScaleDeclarations(factor)) root.style.setProperty(property, value); } function applyGlobalUiScale(factor) { let style2 = uiStateStyle(), declarations = uiScaleDeclarations(factor).map(([property, value]) => `${property}:${value}`).join(";"); style2.textContent = `${UI_SCALE_SELECTOR}{${declarations}}`; } function applyUiStateClasses() { let state2 = requireUiState(), style2 = uiStateStyle(); style2.classList.toggle("ehpeek-pointer-mouse", state2.pointer === "mouse"), style2.classList.toggle("ehpeek-site-e-hentai", state2.site === "e-hentai"), style2.classList.toggle("ehpeek-site-exhentai", state2.site === "exhentai"); } function uiStateStyle() { let style2 = document.getElementById(UI_STATE_STYLE_ID); return style2 || (style2 = document.createElement("style"), style2.id = UI_STATE_STYLE_ID, (document.head ?? document.documentElement).append(style2)), style2; } function requireUiState() { if (!uiState) throw new Error("UI must be configured before it is initialized."); return uiState; } function uiScaleDeclarations(factor) { return [ ...sizeScaleDeclarations("--ui-control-size", ehpeek_spectrum_ui_sizes_default.control, factor), ...hitSizeScaleDeclarations(ehpeek_spectrum_ui_sizes_default.control, factor), ...sizeScaleDeclarations("--ui-font-size", ehpeek_spectrum_ui_sizes_default.font, factor), ...sizeScaleDeclarations("--ui-icon-size", ehpeek_spectrum_ui_sizes_default.icon, factor), ...sizeScaleDeclarations("--ui-space", ehpeek_spectrum_ui_sizes_default.space, factor), ...sizeScaleDeclarations("--ui-radius", ehpeek_spectrum_ui_sizes_default.radius, factor) ]; } function sizeScaleDeclarations(prefix, values, factor) { return Object.entries(values).map(([name, value]) => [ `${prefix}-${name}`, `${scaledPixelValue(value, factor)}px` ]); } function hitSizeScaleDeclarations(values, factor) { return Object.entries(values).map(([name, value]) => [ `--ui-hit-size-${name}`, `${Math.max(32, scaledPixelValue(value, factor))}px` ]); } function scaledPixelValue(value, factor) { return Math.round(pixelValue(value) * factor * 1e3) / 1e3; } function pixelValue(value) { let pixels = /^([\d.]+)px$/.exec(value)?.[1]; if (pixels === void 0) throw new Error(`Expected a pixel UI size, received: ${value}`); return Number(pixels); } // src/eh/dom/external.ts var AUTOCOMPLETE_INPUT_SELECTOR = "#f_search, #newtagfield", AUTOCOMPLETE_ROOT_CLASS = "ehpeek-external-autocomplete", AUTOCOMPLETE_ITEM_CLASS = "ehpeek-external-autocomplete-item", AUTOCOMPLETE_TEXT_CLASS = "ehpeek-external-autocomplete-text", externalDom = { retainedOriginalPageSelector: ".eh-syringe-ignore", tagLabelAttribute: "ehs-tag" }, EXTERNAL_AUTOCOMPLETE_SOURCES = [ { items: ".auto-complete-item", root: ".eh-syringe-lite-auto-complete-list", text: ".auto-complete-text" }, { items: ".lolicon-autocomplete-item", root: ".lolicon-autocomplete-dropdown", text: null } ]; function initializeExternalAutocompleteUi() { let observer = null, mark = () => { for (let source of EXTERNAL_AUTOCOMPLETE_SOURCES) document.querySelectorAll(source.root).forEach((root) => { root.classList.add(AUTOCOMPLETE_ROOT_CLASS), root.querySelectorAll(source.items).forEach((item) => { item.classList.add(AUTOCOMPLETE_ITEM_CLASS); }), source.text && root.querySelectorAll(source.text).forEach((text) => { text.classList.add(AUTOCOMPLETE_TEXT_CLASS); }); }); }, stopObserving = () => { observer?.disconnect(), observer = null; }, startObserving = (event) => { !(event.target instanceof Element) || !event.target.matches(AUTOCOMPLETE_INPUT_SELECTOR) || (stopObserving(), mark(), observer = new MutationObserver(mark), observer.observe(document.body, { childList: !0, subtree: !0 })); }, stopWhenInputBlurs = (event) => { event.target instanceof Element && event.target.matches(AUTOCOMPLETE_INPUT_SELECTOR) && stopObserving(); }; document.addEventListener("focusin", startObserving), document.addEventListener("focusout", stopWhenInputBlurs); } // src/eh/dom/core.ts var HIDDEN_ORIGINAL_DOM_NODE_CLASS = "ehpeek-hide-original-node", EHPEEK_ANCHOR_ATTRIBUTE = "data-ehpeek-anchor", mountedNodes = /* @__PURE__ */ new WeakMap(); var managedBody = null, emptyDomApply = {}, emptyDomChilds = {}; function defineDomNode() { return (selector, options = {}) => { let childs = options.childs ?? emptyDomChilds; return Object.assign({ apply: options.apply ?? emptyDomApply, childs, kind: "node", selector }, childs); }; } var query = defineDomNode(), anchor = defineDomNode(), area = defineDomNode(), button = defineDomNode(), cell = defineDomNode(), control = defineDomNode(), form = defineDomNode(), image = defineDomNode(), input = defineDomNode(), option = defineDomNode(), row = defineDomNode(), script = defineDomNode(), select = defineDomNode(), table = defineDomNode(), textarea = defineDomNode(); function cls(name, options = {}) { return query(`.${name}`, options); } function id(name, options = {}) { return query(`#${name}`, options); } function tag(name, options = {}) { return defineDomNode()(name, options); } function domSelector(source) { return typeof source == "string" ? source : source.selector; } function originalPageNode(node) { return node.closest(externalDom.retainedOriginalPageSelector) === null; } function retainedOriginalPageNode(node) { return node.closest(externalDom.retainedOriginalPageSelector) !== null; } function anyDomNode() { return !0; } function createAnchor(name) { let selector = `[${EHPEEK_ANCHOR_ATTRIBUTE}="${CSS.escape(name)}"]`; if (document.querySelector(selector)) return null; let anchor2 = document.createElement("div"); return anchor2.setAttribute(EHPEEK_ANCHOR_ATTRIBUTE, name), DomNode.from(anchor2).inplace(); } function createManagedElement(tagName, apply = emptyDomApply) { return ManagedDomNode.from(document.createElement(tagName), apply); } function documentBody() { return managedBody ?? (managedBody = DomNode.from(document.body).inplace()), managedBody; } var _node, _DomNode = class _DomNode { constructor(node) { __privateAdd(this, _node); __privateSet(this, _node, node); } static from(node) { return new _DomNode(node); } use(description) { return bindDom(description, () => [this]).bound; } one(source, filter = originalPageNode) { return Array.from( __privateGet(this, _node).querySelectorAll(domSelector(source)), _DomNode.from ).find(filter) ?? null; } all(source, filter = originalPageNode) { return Array.from(__privateGet(this, _node).querySelectorAll(domSelector(source))).map(_DomNode.from).filter(filter); } parent() { let parent = __privateGet(this, _node).parentElement; return parent ? _DomNode.from(parent) : null; } children() { return Array.from(__privateGet(this, _node).children, (child) => _DomNode.from(child)); } closest(source) { let element = __privateGet(this, _node).closest(domSelector(source)); return element ? _DomNode.from(element) : null; } matches(source) { return __privateGet(this, _node).matches(domSelector(source)); } previous() { let previous = __privateGet(this, _node).previousElementSibling; return previous instanceof HTMLElement ? _DomNode.from(previous) : null; } form() { return __privateGet(this, _node).form ? _DomNode.from(__privateGet(this, _node).form) : null; } childElementCount() { return __privateGet(this, _node).childElementCount; } text() { return __privateGet(this, _node).textContent?.trim() ?? ""; } attribute(name) { return __privateGet(this, _node).getAttribute(name); } hasAttribute(name) { return __privateGet(this, _node).hasAttribute(name); } attributeNames() { return __privateGet(this, _node).getAttributeNames(); } hasClass(className2) { return __privateGet(this, _node).classList.contains(className2); } computedStyle() { return window.getComputedStyle(__privateGet(this, _node)); } imageSize() { return { height: __privateGet(this, _node).naturalHeight || __privateGet(this, _node).height || Number(__privateGet(this, _node).getAttribute("height") || ""), width: __privateGet(this, _node).naturalWidth || __privateGet(this, _node).width || Number(__privateGet(this, _node).getAttribute("width") || "") }; } inputValue() { return __privateGet(this, _node).value; } checked() { return __privateGet(this, _node).checked; } selected() { return __privateGet(this, _node).selected; } sameNode(other) { return __privateGet(this, _node) === __privateGet(other, _node); } observe(source, onObserved, options = { childList: !0, subtree: !0 }) { let seen = [], cleanups = [], scan = () => { for (let node of this.all(domSelector(source))) { if (seen.some((candidate) => candidate.sameNode(node))) continue; seen.push(node); let cleanup = onObserved(node); cleanup && cleanups.push(cleanup); } }, observer = new MutationObserver(scan); return scan(), observer.observe(__privateGet(this, _node), options), () => { observer.disconnect(), cleanups.forEach((cleanup) => cleanup()); }; } inplace(apply = emptyDomApply) { return ManagedDomNode.from(__privateGet(this, _node), apply); } move(apply = emptyDomApply) { let managed = this.inplace(apply); return managed.remove(), managed; } clone(applyOrDeep = emptyDomApply, deep = !0) { let apply = typeof applyOrDeep == "boolean" ? emptyDomApply : applyOrDeep, cloneDeep = typeof applyOrDeep == "boolean" ? applyOrDeep : deep; return ManagedDomNode.from( __privateGet(this, _node).cloneNode(cloneDeep), apply ); } }; _node = new WeakMap(); var DomNode = _DomNode; function bindDom(description, scopes) { let children2 = [], invalidateChildren = () => { for (let child of children2) child.invalidate(); }, definition = isDomDefinition(description) ? description : null, nodeController = definition ? bindDomNode(definition, scopes, invalidateChildren) : null, bound = nodeController?.bound ?? {}, childScopes = definition ? () => nodeController?.bound.all() ?? [] : scopes, childs = definition?.childs ?? description; for (let [name, child] of Object.entries(childs)) { if (name in bound) throw new Error(`Original DOM child name is reserved: ${name}`); let childController = bindDom(child, childScopes); children2.push(childController), Object.assign(bound, { [name]: childController.bound }); } return { bound, invalidate: nodeController?.invalidate ?? invalidateChildren }; } function isDomDefinition(description) { return "kind" in description && description.kind === "node"; } function bindDomNode(description, scopes, invalidateChildren) { let cached, queryNodes = () => scopes().flatMap((scope) => scope.all(description.selector)), resolve = () => (cached ?? (cached = queryNodes()), cached); return { bound: { all: () => [...resolve()], clone: () => resolve()[0]?.clone(description.apply) ?? null, cloneAll: () => resolve().map((node) => node.clone(description.apply)), inplace: () => resolve()[0]?.inplace(description.apply) ?? null, inplaceAll: () => resolve().map((node) => node.inplace(description.apply)), move: () => resolve()[0]?.move(description.apply) ?? null, moveAll: () => resolve().map((node) => node.move(description.apply)), one: () => resolve()[0] ?? null, requery: () => (cached = queryNodes(), invalidateChildren(), [...cached]) }, invalidate: () => { cached = void 0, invalidateChildren(); } }; } var _apply, _node2, _ManagedDomNode = class _ManagedDomNode { constructor(element, apply) { __privateAdd(this, _apply); __privateAdd(this, _node2); __privateSet(this, _apply, apply), __privateSet(this, _node2, element), this.Component = () => __privateGet(this, _node2); } static from(element, apply = emptyDomApply) { return new _ManagedDomNode(element, apply); } apply(...names) { let classes = names.map((name) => { let className2 = __privateGet(this, _apply)[name]; if (!className2) throw new Error(`Unknown original DOM application: ${name}`); return className2; }); return __privateGet(this, _node2).classList.add(...classes), this; } all(source) { let apply = typeof source == "string" ? emptyDomApply : source.apply; return Array.from( __privateGet(this, _node2).querySelectorAll(domSelector(source)), (node) => _ManagedDomNode.from(node, apply) ); } rect() { return __privateGet(this, _node2).getBoundingClientRect(); } readAttribute(name) { return __privateGet(this, _node2).getAttribute(name); } imageSize() { return { height: __privateGet(this, _node2).naturalHeight || __privateGet(this, _node2).height || Number(__privateGet(this, _node2).getAttribute("height") || ""), width: __privateGet(this, _node2).naturalWidth || __privateGet(this, _node2).width || Number(__privateGet(this, _node2).getAttribute("width") || "") }; } setAttributes(values) { for (let [name, value] of Object.entries(values)) __privateGet(this, _node2).setAttribute(name, value); return this; } removeAttributes(...names) { for (let name of names) __privateGet(this, _node2).removeAttribute(name); return this; } addClasses(...names) { return __privateGet(this, _node2).classList.add(...names), this; } removeClasses(...names) { return __privateGet(this, _node2).classList.remove(...names), this; } replaceClasses(value) { return __privateGet(this, _node2).className = value, this; } styles(values, priority = "") { for (let [property, value] of Object.entries(values)) __privateGet(this, _node2).style.setProperty(property, value, priority); return this; } removeStyles(...properties) { for (let property of properties) __privateGet(this, _node2).style.removeProperty(property); return this; } removeAllStyles() { return __privateGet(this, _node2).removeAttribute("style"), this; } attribute(name, value) { return __privateGet(this, _node2).setAttribute(name, value), this; } click() { __privateGet(this, _node2).click(); } mount(view) { mountedNodes.get(__privateGet(this, _node2))?.(), __privateGet(this, _node2).replaceChildren(), markUiRoot(__privateGet(this, _node2)), mountedNodes.set(__privateGet(this, _node2), render(view, __privateGet(this, _node2))); } remove() { mountedNodes.get(__privateGet(this, _node2))?.(), mountedNodes.delete(__privateGet(this, _node2)), __privateGet(this, _node2).remove(); } replaceWith(replacement) { __privateGet(this, _node2).replaceWith( replacement instanceof _ManagedDomNode ? __privateGet(replacement, _node2) : replacement ); } before(sibling) { __privateGet(this, _node2).before(sibling instanceof _ManagedDomNode ? __privateGet(sibling, _node2) : sibling); } after(sibling) { __privateGet(this, _node2).after(sibling instanceof _ManagedDomNode ? __privateGet(sibling, _node2) : sibling); } append(...children2) { return __privateGet(this, _node2).append(...children2.map((child) => __privateGet(child, _node2))), this; } prepend(child) { __privateGet(this, _node2).prepend(child instanceof _ManagedDomNode ? __privateGet(child, _node2) : child); } setTextUnlessInput(text) { __privateGet(this, _node2) instanceof HTMLInputElement || (__privateGet(this, _node2).textContent = text); } setHidden(hidden) { return __privateGet(this, _node2).hidden = hidden, this; } hideOriginal() { return __privateGet(this, _node2).classList.add(HIDDEN_ORIGINAL_DOM_NODE_CLASS), this; } replaceChildren(...children2) { __privateGet(this, _node2).replaceChildren(...children2.map((child) => child instanceof _ManagedDomNode ? __privateGet(child, _node2) : child)); } listen(type, listener, options) { return __privateGet(this, _node2).addEventListener(type, listener, options), () => __privateGet(this, _node2).removeEventListener(type, listener, options); } observe(onChange, options = { childList: !0, subtree: !0 }) { let observer = new MutationObserver(onChange); return observer.observe(__privateGet(this, _node2), options), () => observer.disconnect(); } focus() { __privateGet(this, _node2).focus(); } scrollIntoView(options) { __privateGet(this, _node2).scrollIntoView(options); } isNode(node) { return __privateGet(this, _node2) === node; } contains(node) { return __privateGet(this, _node2).contains(node); } matches(source) { return __privateGet(this, _node2).matches(domSelector(source)); } copyAttributesTo(target) { for (let attribute of Array.from(__privateGet(this, _node2).attributes)) __privateGet(target, _node2).setAttribute(attribute.name, attribute.value); } setInputValue(value) { __privateGet(this, _node2).value = value; } inputValue() { return __privateGet(this, _node2).value; } setSelected(selected) { __privateGet(this, _node2).selected = selected; } dispatchInput() { __privateGet(this, _node2).dispatchEvent(new Event("input", { bubbles: !0 })); } mirrorContentTo(target) { let update = () => { target.replaceChildren( ...Array.from(__privateGet(this, _node2).childNodes, (node) => node.cloneNode(!0)) ); let language = __privateGet(this, _node2).getAttribute("lang"); language ? target.setAttribute("lang", language) : target.removeAttribute("lang"); }; return update(), this.observe(update, { attributes: !0, attributeFilter: ["lang"], characterData: !0, childList: !0, subtree: !0 }); } }; _apply = new WeakMap(), _node2 = new WeakMap(); var ManagedDomNode = _ManagedDomNode; // src/eh/dom/domClass.ts var sharedApply = { coverlessSearchGrid: "ehpeek-expand-coverless-search-grid", fitToViewport: "ehpeek-fit-to-viewport", galleryTagMenuItem: "ehpeek-layout-gallery-tag-menu-item", hideOriginalSearchAction: "ehpeek-hide-original-search-action", historyLabel: "ehpeek-prefix-read-history-label", liteSearchGrid: "ehpeek-layout-search-grid-lite", searchGrid: "ehpeek-layout-search-grid", searchResultColumns: "ehpeek-search-result-columns", stackSearchGridTags: "ehpeek-stack-search-grid-tags", tallSearchGridCover: "ehpeek-contain-tall-search-grid-cover" }, page = { footer: query("body > .dp"), html: tag("html", { apply: { fitToViewport: sharedApply.fitToViewport, galleryTouchLayout: "ehpeek-touch-gallery-page", galleryWideLayout: "ehpeek-gallery-wide-layout-root" } }), body: tag("body", { apply: { galleryTouchLayout: "ehpeek-touch-gallery-page", galleryWideLayout: "ehpeek-gallery-wide-layout-root", hidePreviewPageBars: "ehpeek-hide-original-preview-page-bars" } }) }, common = { descendants: query("*"), galleryLink: anchor('a[href*="/g/"]'), image: image("img"), interactive: query( "a[href], button, input, select, textarea, label, [onclick]" ), links: anchor("a[href]"), scripts: script("script") }, myTags = { tags: id("usertags_outer", { childs: { items: query(":scope > [id^='usertag_']", { childs: { color: input("input[id^='tagcolor_']"), preview: query("[id^='tagpreview_'][title]") } }) } }), options: option("#tagset_outer select option"), defaultColor: input("#tagcolor"), enabled: input("#tagset_enable") }, gallery = { actions: id("gd5", { apply: { expand: "ehpeek-expand-gallery-actions" }, childs: { items: query("a, button, input[type='button'], input[type='submit']", { apply: { layout: "ehpeek-layout-gallery-action" } }) } }), comments: id("cdiv", { apply: { touchScore: "ehpeek-enable-touch-comment-score" }, childs: { showAll: anchor("#chd > :first-child a[href*='hc=1']"), score: cls("c5"), scoreComment: cls("c1", { childs: { details: query(".c7[id^='cvotes_']") } }) } }), commentActions: id("postnewcomment"), favoriteDialog: { note: textarea("textarea[name='favnote']"), optionRow: query("div[style*='height']"), options: input("input[name='favcat']") }, commentsAnchor: anchor('a[name="comments"]'), imagePage: { image: image("img#img"), links: anchor("a[href]") }, info: { category: id("gdc", { childs: { appearance: query("[class*='ct']") } }), cover: id("gd1", { childs: { image: image("img", { apply: { fit: "ehpeek-fit-gallery-cover" } }), descendants: query("*") } }), details: id("gdd", { childs: { rows: row("tr", { childs: { cells: cell("td, th") } }) } }), favorite: id("fav", { childs: { link: id("favoritelink"), titled: query("[title]") } }), hostFallback: id("gleft"), original: id("gmid"), rating: { actions: area('map[name="rating"] area'), count: id("rating_count"), image: id("rating_image"), label: id("rating_label"), rated: query(".irb, .irg, .irr") }, tagMenu: id("tagmenu_act", { apply: { layout: "ehpeek-layout-gallery-tag-menu" }, childs: { actions: anchor("a") } }), newTag: id("tagmenu_new", { apply: { layout: "ehpeek-layout-new-tag-form" }, childs: { button: control("#newtagbutton"), field: input("#newtagfield"), form: form("form") } }), titleMain: id("gn"), titleSub: id("gj"), uploader: id("gdn", { childs: { link: anchor("a[href]") } }) }, preview: { description: cls("gpc"), imageLinks: anchor( "#gdt a[href], .gdtm a[href], .gdtl a[href], a[href*='/s/']" ), imageLinkHost: query("#gdt, .gdtm, .gdtl"), pageBarBottom: cls("ptb"), pageBarHost: cls("gtb"), pageBarTop: cls("ptt"), thumbs: id("gdt", { apply: { swipe: "ehpeek-enable-preview-swipe-input" }, childs: { images: image("img"), links: anchor("a[href]") } }) }, tagContainer: query("div.gt, div.gtl, div.gtw", { apply: { myTag: "ehpeek-color-my-tag" } }), tags: id("taglist", { childs: { links: anchor("a"), rows: row("tr", { childs: { namespace: query(".tc, td:first-child"), links: anchor("a") } }) } }) }, search = { controls: query("#toppane, .searchtext, .searchwarn, .searchnav, .ptt, .ptb"), displayMode: select("select[onchange*='inline_set=dm_']", { childs: { options: option("option") } }), favorites: { categories: query(".ido > .nosel", { apply: { hide: "ehpeek-hide-original-favorites-categories" }, childs: { items: query(":scope > .fp, :scope > .fps", { childs: { indicator: cls("i") } }) } }), input: input("input[name='f_search']"), selectedCategory: cls("fps") }, input: input("#f_search, input[name='f_search']", { apply: { expand: "ehpeek-expand-search-input" } }), navigation: cls("searchnav", { childs: { first: anchor("a[id$='first'][href]"), previous: anchor("a[id$='prev'][href]"), next: anchor("a[id$='next'][href]"), last: anchor("a[id$='last'][href]"), links: anchor("a[href]") } }), navigationLink: anchor( ".searchnav a[id$='first'][href], .searchnav a[id$='prev'][href], .searchnav a[id$='next'][href], .searchnav a[id$='last'][href]" ), panel: { box: id("searchbox", { apply: { reset: "ehpeek-reset-search-box-layout" }, childs: { advanced: id("advdiv", { apply: { expand: "ehpeek-expand-search-advanced-options" } }), categories: table("form > table", { apply: { layout: "ehpeek-layout-search-categories" } }), form: form("form", { apply: { stack: "ehpeek-stack-search-form" } }) } }), clear: control("input[name='f_clear'], button[name='f_clear']", { apply: { hide: sharedApply.hideOriginalSearchAction } }), clearFallback: control("input[type='button'], button[type='button']", { apply: { hide: sharedApply.hideOriginalSearchAction } }), fileSearch: id("fsdiv", { apply: { expand: "ehpeek-expand-file-search" } }), optionLinks: anchor("a"), submit: control("input[name='f_apply'], button[name='f_apply']", { apply: { hide: sharedApply.hideOriginalSearchAction } }), submitFallback: control("input[type='submit'], button[type='submit']", { apply: { hide: sharedApply.hideOriginalSearchAction } }) }, rangeBar: id("rangebar"), removeHistory: control("[data-ehpeek-remove-history]"), results: cls("itg", { apply: { compactFavorites: "ehpeek-compact-all-favorites-results", containFavorites: "ehpeek-contain-favorites-results", containSearch: "ehpeek-contain-search-results", columns: sharedApply.searchResultColumns, grid: sharedApply.searchGrid, swipe: "ehpeek-enable-search-swipe-input" }, childs: { body: query(":scope > tbody"), rows: row("tbody > tr", { apply: { coverless: sharedApply.coverlessSearchGrid }, childs: { cover: query(":scope > .gl1e"), content: query(":scope > .gl2e", { childs: { detail: cls("gl4e", { childs: { tags: query(":scope > *", { apply: { stack: sharedApply.stackSearchGridTags } }), title: query(":scope > .glink", { apply: { history: sharedApply.historyLabel } }) } }), metadata: cls("gl3e") } }) } }), galleryLinks: anchor('a[href*="/g/"]'), links: anchor("a[href]"), titles: cls("glink", { apply: { history: "ehpeek-prefix-read-history-label" } }) } }), resultText: cls("searchtext"), submit: control("input[name='f_apply'], button[name='f_apply']"), submitFallback: control("input[type='submit'], button[type='submit']") }, settings = { titleDefault: input("#tl_r"), titleJapanese: input("#tl_j") }, topBar = { galleryTitle: query("#gd2, h1"), navigation: id("nb", { apply: { hide: "ehpeek-hide-original-top-bar" }, childs: { links: anchor("a[href]", { apply: { layout: "ehpeek-layout-top-bar-menu-item" } }) } }) }, domClass = { common, gallery, myTags, page, search, settings, topBar }; // locales/en.json var en_default = { metadata: { description: "A touch-optimized E-H/ExH viewer" }, common: { actions: { apply: "Apply", close: "Close", confirm: "Confirm", current: "Current", default: "Default", submit: "Submit", zoomIn: "Zoom in", zoomOut: "Zoom out" }, status: { failed: "Failed", loading: "Loading..." } }, help: { title: "Help", sections: [ { title: "Reader", items: [ "**Tap** the center to show or hide the toolbar.", "**Swipe or scroll** in the reading direction; tap either side or use the **Arrow keys** to turn pages.", "In **Paged mode**, pull down or swipe sideways across the reading direction to open Scroll Preview.", "**Pinch** to zoom. With a mouse, **press and hold** an image or use **Ctrl/⌘ + wheel** to zoom; while zoomed, press and hold, double-click, or double-tap to exit." ] }, { title: "Browse", items: [ "Choose EhPeek or EhPeekLite from the **layout menu** to replace the original result layout.", "When **Enhance > Search Swipe** is enabled, **swipe** horizontally to change search result pages." ] }, { title: "Gallery", items: [ "When **Enhance > Preview Swipe** is enabled, **swipe** horizontally to change preview pages.", "The Page Bar in Gallery can be **dragged** to adjust the range." ] } ] }, reader: { readingOptions: "Reading options", fullscreen: "Fullscreen", exitFullscreen: "Exit fullscreen", adjustScrollViewport: "Adjust Scroll viewport size", resizeScrollViewport: "Resize Scroll viewport", fit: "Fit", applyGlobally: "Set Default", pagedMode: "Paged mode", singlePageMode: "Single-page layout", doublePageMode: "Double-page layout", pairFirstAndSecondPages: "Pair 1+2", pairSecondAndThirdPages: "Pair 2+3", scrollMode: "Scroll mode", rightTapPrevious: `Right side action: Previous page`, rightTapNext: `Right side action: Next page`, directionRtl: `Reading direction: Right to left`, directionLtr: `Reading direction: Left to right`, directionTtb: `Reading direction: Top to bottom`, download: "Download", downloadHelpLabel: "Can't download?", downloadHelp: 'You may enable the browser download API in your userscript manager. For example, enable "Use browser download API" in Violentmonkey.', openImage: "Open image", displayedImageShort: "Displayed", originalImageShort: "Original", downloadDisplayedImage: "Displayed image", downloadOriginalImage: "Original image", originalImageSource: "Original source provided by E-Hentai", originalImageUnavailable: "Original image unavailable", startReading: "Read", continueReading: "Continue", endPage: "End", end: "End of gallery. Tap to exit.", reloadPage: "Reload page" }, history: { actions: { clear: "Clear All", export: "Export", import: "Import", remove: "Delete" }, clearConfirm: "Clear all reading history?", empty: "No reading history", exported: "History exported", importFailed: "History import failed", imported: "Imported {count} history entries", limit: "(max {limit})", visitedLabel: "VISITED", range: "{start}–{end} of {total} history entries", removeConfirm: "Remove this gallery from Read History?" }, gallery: { pages: "Pages", notFavorited: "Not Favorited", rate: "Rate gallery", rateWithStars: "Rate gallery: {rating} stars", tagging: "Tagging", scrollPreview: "Scroll Preview", confirmScrollPreviewDirection: "Change Scroll Preview direction?", openScrollPreview: "Open full-screen Scroll Preview", scrollPreviewDirectionTtb: "Scroll Preview: top to bottom", scrollPreviewDirectionLtr: "Scroll Preview: left to right", scrollPreviewDirectionRtl: "Scroll Preview: right to left", favoriteTag: "Add My Tag", copyOriginalTag: "Copy Original Tag", editFavoriteNote: "Edit Note", discardFavoriteNote: "Discard note edits?", removeFavoriteTag: "Remove My Tag", tagCollection: "Collection", tagBehavior: "Behavior", markTag: "Mark", watchTag: "Watch", hideTag: "Hide" }, settings: { openSettings: "Settings", on: "On", off: "Off", discardChanges: "Discard unsaved settings changes?", general: "General", options: "Options", about: "About", licenses: "Licenses", readerLabel: "Reader", readerHelp: "Opens gallery images in EhPeek's reader", readerFullscreenLabel: "Open Reader in Fullscreen", readerFullscreenHelp: "Enters fullscreen when the Reader opens", exitReaderOnFullscreenExitLabel: "Close Reader on Fullscreen Exit", exitReaderOnFullscreenExitHelp: "Closes the Reader when fullscreen is exited", replacePreviewWithScrollLabel: "Embed Scroll Preview", replacePreviewWithScrollHelp: "Replaces the original gallery preview area with Scroll Preview", openGalleryInNewTabLabel: "Open Gallery in New Tab", openGalleryInNewTabHelp: "Opens gallery links in a new browser tab", uiControlsLabel: "UI Layout", uiScaleLabel: "UI Scale", portraitUiScaleLabel: "Portrait UI Scale", landscapeUiScaleLabel: "Landscape UI Scale", leftHandedControlsLabel: "Left-handed controls", columnsLabel: "Two Columns", enhance: "Enhance", enhanceSearchLabel: "Search Swipe", enhanceSearchHelp: "Swipe between search result pages", enhanceThumbsLabel: "Preview Swipe", enhanceThumbsHelp: "Swipe between preview pages; makes the page bar draggable", myTagsLabel: "My Tags Color", myTagsHelp: "Colors my tags in galleries", historyLabel: "History", readHistoryLabel: "Read History", readHistoryHelp: "Adds galleries to Read History and remembers reading progress", includeUnreadHistoryLabel: "Include Unread History", includeUnreadHistoryHelp: "Records opened but unread galleries in Read History", searchHistoryLabel: "Search History", searchHistoryHelp: "Keeps a history of search keywords", touchUiLabel: "Touch UI", touchUiHelp: "Uses touch-friendly navigation UI", fitToViewportLabel: "Fit Pages to Screen", fitToViewportHelp: "Fits pages to the browser width" }, search: { advancedOptions: "Advanced Options", categories: "Categories", fileSearch: "File Search" }, errors: { imageNotFound: "Image not found", downloadFailed: "Download failed", loadFailed: "Load failed", imageLoadFailed: "Image load failed", searchPageContentNotFound: "Cannot find search page content" } }; // locales/ja.json var ja_default = { metadata: { description: "タッチ操作向けの E-H/ExH リーダー" }, common: { actions: { apply: "適用", close: "閉じる", confirm: "確定", current: "現在位置", default: "デフォルト", submit: "送信", zoomIn: "拡大", zoomOut: "縮小" }, status: { failed: "失敗", loading: "読み込み中..." } }, help: { title: "ヘルプ", sections: [ { title: "リーダー", items: [ "中央を**タップ**すると、ツールバーの表示と非表示を切り替えられます。", "閲覧方向に**スワイプまたはスクロール**します。左右をタップするか、**矢印キー**でもページをめくれます。", "**ページモード**では、下へ引くか閲覧方向と交差する向きにスワイプすると、スクロールプレビューを開けます。", "**ピンチ**で拡大縮小できます。マウスでは画像を**長押し**するか、**Ctrl/⌘ + ホイール**を使用します。拡大中に長押し、ダブルクリック、またはダブルタップすると終了します。" ] }, { title: "ブラウズ", items: [ "**レイアウトメニュー**から EhPeek または EhPeekLite を選択すると、元の検索結果レイアウトを置き換えます。", "**拡張 > 検索スワイプ**を有効にすると、**横方向にスワイプ**して検索結果ページを切り替えられます。" ] }, { title: "ギャラリー", items: [ "**拡張 > プレビュースワイプ**を有効にすると、**横方向にスワイプ**してプレビューページを切り替えられます。", "ギャラリーのページバーを**ドラッグ**して範囲を調整できます。" ] } ] }, reader: { readingOptions: "閲覧オプション", fullscreen: "フルスクリーン", exitFullscreen: "フルスクリーンを終了", adjustScrollViewport: "スクロール表示領域のサイズを調整", resizeScrollViewport: "スクロール表示領域をリサイズ", fit: "フィット", applyGlobally: "デフォルトに設定", pagedMode: "ページモード", singlePageMode: "単ページレイアウト", doublePageMode: "見開きレイアウト", pairFirstAndSecondPages: "1+2ページを組み合わせる", pairSecondAndThirdPages: "2+3ページを組み合わせる", scrollMode: "スクロールモード", rightTapPrevious: `右側の操作: 前のページ`, rightTapNext: `右側の操作: 次のページ`, directionRtl: `閲覧方向: 右から左`, directionLtr: `閲覧方向: 左から右`, directionTtb: `閲覧方向: 上から下`, download: "ダウンロード", downloadHelpLabel: "ダウンロードできませんか?", downloadHelp: "ユーザースクリプトマネージャーでブラウザーのダウンロード API を有効にできます。たとえば Violentmonkey では「ブラウザーのダウンロード API を使用」を有効にしてください。", openImage: "画像を開く", displayedImageShort: "表示中", originalImageShort: "オリジナル", downloadDisplayedImage: "表示中の画像", downloadOriginalImage: "オリジナル画像", originalImageSource: "E-Hentai が提供するオリジナル画像", originalImageUnavailable: "オリジナル画像は利用できません", startReading: "読む", continueReading: "続きから読む", endPage: "終了", end: "ギャラリーの最後です。タップして終了します。", reloadPage: "ページを再読み込み" }, history: { actions: { clear: "すべて消去", export: "エクスポート", import: "インポート", remove: "削除" }, clearConfirm: "閲覧履歴をすべて消去しますか?", empty: "閲覧履歴はありません", exported: "履歴をエクスポートしました", importFailed: "履歴のインポートに失敗しました", imported: "{count} 件の履歴をインポートしました", limit: "(最大 {limit} 件)", visitedLabel: "閲覧済み", range: "全 {total} 件中 {start}–{end} 件", removeConfirm: "このギャラリーを閲覧履歴から削除しますか?" }, gallery: { pages: "ページ", notFavorited: "お気に入り未登録", rate: "ギャラリーを評価", rateWithStars: "ギャラリーを評価:{rating} 星", tagging: "タグ操作", scrollPreview: "スクロールプレビュー", confirmScrollPreviewDirection: "スクロールプレビューの方向を変更しますか?", openScrollPreview: "全画面スクロールプレビューを開く", scrollPreviewDirectionTtb: "スクロールプレビュー:上から下", scrollPreviewDirectionLtr: "スクロールプレビュー:左から右", scrollPreviewDirectionRtl: "スクロールプレビュー:右から左", favoriteTag: "マイタグを追加", copyOriginalTag: "元のタグをコピー", editFavoriteNote: "メモを編集", discardFavoriteNote: "メモの変更を破棄しますか?", removeFavoriteTag: "マイタグを削除", tagCollection: "コレクション", tagBehavior: "動作", markTag: "マーク", watchTag: "ウォッチ", hideTag: "非表示" }, settings: { openSettings: "設定", on: "オン", off: "オフ", discardChanges: "未適用の設定変更を破棄しますか?", general: "一般", options: "オプション", about: "情報", licenses: "ライセンス", readerLabel: "リーダー", readerHelp: "ギャラリー画像を EhPeek のリーダーで開きます", readerFullscreenLabel: "リーダーをフルスクリーンで開く", readerFullscreenHelp: "リーダーを開いたときにフルスクリーンにします", exitReaderOnFullscreenExitLabel: "フルスクリーン終了時にリーダーを閉じる", exitReaderOnFullscreenExitHelp: "フルスクリーンを終了するとリーダーを閉じます", replacePreviewWithScrollLabel: "スクロールプレビューを埋め込む", replacePreviewWithScrollHelp: "元のギャラリープレビュー領域をスクロールプレビューに置き換えます", openGalleryInNewTabLabel: "ギャラリーを新しいタブで開く", openGalleryInNewTabHelp: "ギャラリーリンクをブラウザーの新しいタブで開きます", uiControlsLabel: "UI レイアウト", uiScaleLabel: "UI スケール", portraitUiScaleLabel: "縦向き UI スケール", landscapeUiScaleLabel: "横向き UI スケール", leftHandedControlsLabel: "左手用コントロール", columnsLabel: "2 列", enhance: "拡張", enhanceSearchLabel: "検索スワイプ", enhanceSearchHelp: "スワイプして検索結果ページを切り替えます", enhanceThumbsLabel: "プレビュースワイプ", enhanceThumbsHelp: "スワイプしてプレビューページを切り替え、ページバーをドラッグ可能にします", myTagsLabel: "マイタグの色", myTagsHelp: "ギャラリー内のマイタグに色を付けます", historyLabel: "履歴", readHistoryLabel: "閲覧履歴", readHistoryHelp: "ギャラリーを閲覧履歴に追加し、閲覧進捗を保存します", includeUnreadHistoryLabel: "未読の履歴を含める", includeUnreadHistoryHelp: "開いたものの未読のギャラリーを閲覧履歴に記録します", searchHistoryLabel: "検索履歴", searchHistoryHelp: "検索キーワードの履歴を保存します", touchUiLabel: "タッチ UI", touchUiHelp: "タッチ操作に適したナビゲーション UI を使用します", fitToViewportLabel: "ページを画面幅に合わせる", fitToViewportHelp: "ページをブラウザーの幅に合わせます" }, search: { advancedOptions: "詳細オプション", categories: "カテゴリ", fileSearch: "ファイル検索" }, errors: { imageNotFound: "画像が見つかりません", downloadFailed: "ダウンロードに失敗しました", loadFailed: "読み込みに失敗しました", imageLoadFailed: "画像の読み込みに失敗しました", searchPageContentNotFound: "検索ページの内容が見つかりません" } }; // locales/zh-CN.json var zh_CN_default = { metadata: { description: "为触屏优化的 E-H/ExH 阅读器" }, common: { actions: { apply: "应用", close: "关闭", confirm: "确认", current: "当前位置", default: "默认", submit: "提交", zoomIn: "放大", zoomOut: "缩小" }, status: { failed: "失败", loading: "加载中..." } }, help: { title: "帮助", sections: [ { title: "阅读器", items: [ "**点击**中央区域可显示或隐藏工具栏。", "沿阅读方向**滑动或滚动**;点击两侧或使用**方向键**翻页。", "在**分页模式**下,下拉或沿阅读方向的垂直方向滑动可打开滚动预览。", "通过**双指捏合**缩放。使用鼠标时,可**长按**图像或使用 **Ctrl/⌘ + 滚轮**缩放;缩放后,长按、双击鼠标或双击屏幕可退出缩放。" ] }, { title: "浏览", items: [ "在**布局菜单**中选择 EhPeek 或 EhPeekLite,以替换原始结果布局。", "启用**增强 > 搜索滑动**后,可**水平滑动**切换搜索结果页。" ] }, { title: "画廊", items: [ "启用**增强 > 预览滑动**后,可**水平滑动**切换预览页。", "可以**拖动画廊中的页码条**来调整范围。" ] } ] }, reader: { readingOptions: "阅读选项", fullscreen: "全屏", exitFullscreen: "退出全屏", adjustScrollViewport: "调整滚动视口大小", resizeScrollViewport: "调整滚动视口", fit: "适应", applyGlobally: "设为默认", pagedMode: "分页模式", singlePageMode: "单页布局", doublePageMode: "双页布局", pairFirstAndSecondPages: "组合第 1+2 页", pairSecondAndThirdPages: "组合第 2+3 页", scrollMode: "滚动模式", rightTapPrevious: `右侧操作: 上一页`, rightTapNext: `右侧操作: 下一页`, directionRtl: `阅读方向: 从右向左`, directionLtr: `阅读方向: 从左向右`, directionTtb: `阅读方向: 从上向下`, download: "下载", downloadHelpLabel: "无法下载?", downloadHelp: "你可以在用户脚本管理器中启用浏览器下载 API。例如,在 Violentmonkey 中启用“使用浏览器下载 API”。", openImage: "打开图像", displayedImageShort: "当前显示", originalImageShort: "原图", downloadDisplayedImage: "当前显示的图像", downloadOriginalImage: "原始图像", originalImageSource: "由 E-Hentai 提供的原图来源", originalImageUnavailable: "原始图像不可用", startReading: "阅读", continueReading: "继续", endPage: "结束", end: "已到画廊末尾。点击退出。", reloadPage: "重新加载页面" }, history: { actions: { clear: "全部清除", export: "导出", import: "导入", remove: "删除" }, clearConfirm: "清除全部阅读历史?", empty: "没有阅读历史", exported: "历史记录已导出", importFailed: "历史记录导入失败", imported: "已导入 {count} 条历史记录", limit: "(最多 {limit} 条)", visitedLabel: "已访问", range: "第 {start}–{end} 条,共 {total} 条历史记录", removeConfirm: "从阅读历史中删除此画廊?" }, gallery: { pages: "页", notFavorited: "未收藏", rate: "为画廊评分", rateWithStars: "为画廊评分:{rating} 星", tagging: "标签操作", scrollPreview: "滚动预览", confirmScrollPreviewDirection: "更改滚动预览方向?", openScrollPreview: "打开全屏滚动预览", scrollPreviewDirectionTtb: "滚动预览:从上向下", scrollPreviewDirectionLtr: "滚动预览:从左向右", scrollPreviewDirectionRtl: "滚动预览:从右向左", favoriteTag: "添加我的标签", copyOriginalTag: "复制原始标签", editFavoriteNote: "编辑备注", discardFavoriteNote: "放弃备注修改?", removeFavoriteTag: "移除我的标签", tagCollection: "标签集", tagBehavior: "行为", markTag: "标记", watchTag: "关注", hideTag: "隐藏" }, settings: { openSettings: "设置", on: "开", off: "关", discardChanges: "放弃尚未应用的设置修改?", general: "常规", options: "选项", about: "关于", licenses: "许可证", readerLabel: "阅读器", readerHelp: "使用 EhPeek 阅读器打开画廊图像", readerFullscreenLabel: "以全屏方式打开阅读器", readerFullscreenHelp: "打开阅读器时进入全屏", exitReaderOnFullscreenExitLabel: "退出全屏时关闭阅读器", exitReaderOnFullscreenExitHelp: "退出全屏后关闭阅读器", replacePreviewWithScrollLabel: "嵌入滚动预览", replacePreviewWithScrollHelp: "使用滚动预览替换原始画廊预览区域", openGalleryInNewTabLabel: "在新标签页中打开画廊", openGalleryInNewTabHelp: "在新的浏览器标签页中打开画廊链接", uiControlsLabel: "界面布局", uiScaleLabel: "界面缩放", portraitUiScaleLabel: "竖屏界面缩放", landscapeUiScaleLabel: "横屏界面缩放", leftHandedControlsLabel: "左手控制", columnsLabel: "双栏", enhance: "增强", enhanceSearchLabel: "搜索滑动", enhanceSearchHelp: "通过滑动切换搜索结果页", enhanceThumbsLabel: "预览滑动", enhanceThumbsHelp: "通过滑动切换预览页,并使页码条可拖动", myTagsLabel: "我的标签颜色", myTagsHelp: "在画廊中为我的标签着色", historyLabel: "历史记录", readHistoryLabel: "阅读历史", readHistoryHelp: "将画廊加入阅读历史并记住阅读进度", includeUnreadHistoryLabel: "包括未读记录", includeUnreadHistoryHelp: "将打开但尚未阅读的画廊记录到阅读历史", searchHistoryLabel: "搜索历史", searchHistoryHelp: "保存搜索关键词历史", touchUiLabel: "触控界面", touchUiHelp: "使用适合触控操作的导航界面", fitToViewportLabel: "页面贴合屏幕", fitToViewportHelp: "使页面贴合浏览器宽度" }, search: { advancedOptions: "高级选项", categories: "分类", fileSearch: "文件搜索" }, errors: { imageNotFound: "未找到图像", downloadFailed: "下载失败", loadFailed: "加载失败", imageLoadFailed: "图像加载失败", searchPageContentNotFound: "无法找到搜索页面内容" } }; // src/i18n.ts var APP_LOCALES = ["en", "zh-CN", "ja"], APP_LOCALE_OPTIONS = [ { label: "English", value: "en" }, { label: "简体中文", value: "zh-CN" }, { label: "日本語", value: "ja" } ], APP_LOCALE_SETTING_KEY = "ehpeek:language", DEFAULT_APP_LOCALE = "en", localeTexts = { en: en_default, "zh-CN": zh_CN_default, ja: ja_default }, storedLocale = GM_getValue( APP_LOCALE_SETTING_KEY, DEFAULT_APP_LOCALE ), appLocale = APP_LOCALES.includes(storedLocale) ? storedLocale : DEFAULT_APP_LOCALE, i18n_default = localeTexts[appLocale]; // src/utils.ts function clamp(value, min, max) { return max < min ? min : Math.min(max, Math.max(min, value)); } function normalizeUrl(url, baseUrl = window.location.href) { try { return new URL(url, baseUrl).href; } catch { return ""; } } function normalizedAspectRatio(value, fallback) { return value && Number.isFinite(value) && value > 0 ? value : fallback; } function positiveNumber(value) { return value && Number.isFinite(value) && value > 0 ? value : null; } function stopEvent(event) { event.stopPropagation(); } function registerGlobalStyle(id2, css) { if (!css || document.getElementById(id2)) return; let style2 = document.createElement("style"); style2.id = id2, style2.textContent = css, (document.head ?? document.documentElement).append(style2); } // src/eh/dom/gallery.ts function extractMyTagsPageData(root = document, tagSet) { let source = DomNode.from(root).use(domClass.myTags), tags = source.tags.one(); if (!tags) throw new Error("The My Tags page could not be read."); let options = source.options.all().map((option2) => ({ label: option2.text() || option2.inputValue(), selected: option2.selected(), value: option2.inputValue() })), activeTagSet = tagSet ?? options.find((option2) => option2.selected)?.value ?? "1", defaultColor = source.defaultColor.one()?.inputValue().trim() ?? "", output = []; for (let item of tags.all(domClass.myTags.tags.items)) { let preview = item.one(domClass.myTags.tags.items.preview), name = normalizeTagName(preview?.attribute("title") ?? ""); if (!preview || !name) continue; let itemColor = item.one(domClass.myTags.tags.items.color)?.inputValue() ?? "", backgroundColor = normalizeTagColor(itemColor) || normalizeTagColor(defaultColor), id2 = item.attribute("id")?.match(/^usertag_(\d+)$/)?.[1] ?? ""; id2 && output.push({ name, backgroundColor, color: readableTagColor(backgroundColor), id: id2, tagSet: activeTagSet }); } return { appearances: output, enabled: source.enabled.one()?.checked() ?? !0, options }; } function normalizeTagName(value) { return value.trim().replace(/\s+/g, " ").toLowerCase(); } function normalizeTagColor(value) { let color = value.trim(); return /^#[\da-f]{6}$/i.test(color) ? color : ""; } function readableTagColor(backgroundColor) { let red = Number.parseInt(backgroundColor.slice(1, 3), 16) / 255, green = Number.parseInt(backgroundColor.slice(3, 5), 16) / 255, blue = Number.parseInt(backgroundColor.slice(5, 7), 16) / 255, linear = (channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4; return 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue) > 0.179 ? "#000000" : "#ffffff"; } function mutateGalleryMyTags(appearances) { let byName = new Map(appearances.map((appearance) => [appearance.name, appearance])), source = DomNode.from(document).use(domClass.gallery), apply = () => { for (let tag2 of source.tags.links.requery()) { let name = galleryTagNameFromUrl(tag2.attribute("href") ?? ""), appearance = name ? byName.get(normalizeTagName(name)) : void 0; if (!appearance?.backgroundColor) continue; let container = tag2.closest(domClass.gallery.tagContainer); container && (container.inplace(domClass.gallery.tagContainer.apply).styles({ "--ehpeek-my-tag-background": appearance.backgroundColor, "--ehpeek-my-tag-color": appearance.color }).apply("myTag"), tag2.inplace().setAttributes({ "data-ehpeek-my-tag-id": appearance.id, "data-ehpeek-my-tag-set": appearance.tagSet })); } }; return apply(), source.tags.inplace()?.observe(apply) ?? (() => { }); } function manageGalleryContinueReadingButtonMount() { let managedHost = createManagedElement("div").replaceClasses("box-border w-full px-sm pt-sm pb-sm"), viewerOptions = DomNode.from(document).use(domClass.gallery).actions.inplace(); return viewerOptions ? (viewerOptions.apply("expand").append(managedHost), managedHost) : (documentBody().append(managedHost), managedHost); } function manageGalleryPreview(root = document, baseUrl = window.location.href) { let source = DomNode.from(root).use(domClass.gallery.preview), currentUrl = new URL(baseUrl, window.location.href).href, currentIndex = previewPageIndex(currentUrl), pageDescriptionSource = source.description.one(), rangeText = pageDescriptionSource?.text() ?? "", rangeValues = rangeText.match(/([\d,]+)\s*-\s*([\d,]+)\D+([\d,]+)/)?.slice(1).map((value) => Number(value.replace(/,/g, ""))) ?? [], startImage = rangeValues[0], endImage = rangeValues[1], totalImages = rangeValues[2]; if (startImage === void 0 || endImage === void 0 || totalImages === void 0 || !Number.isSafeInteger(startImage) || !Number.isSafeInteger(endImage) || !Number.isSafeInteger(totalImages) || startImage <= 0 || endImage <= 0 || totalImages <= 0 || endImage < startImage || totalImages < endImage) throw new Error("Cannot read the gallery preview image range."); let currentPageSize = endImage - startImage + 1, inferredFullPageSize = endImage === totalImages && currentIndex > 0 ? (totalImages - currentPageSize) / currentIndex : currentPageSize; if (!Number.isInteger(inferredFullPageSize) || inferredFullPageSize <= 0) throw new Error("Cannot determine the gallery preview page size."); let pageSize = inferredFullPageSize, maxIndex = Math.max(currentIndex, Math.ceil(totalImages / pageSize) - 1), seen = /* @__PURE__ */ new Set(), previewItems = source.imageLinks.all().flatMap((link) => { let url = normalizeUrl(link.attribute("href") || "", currentUrl), imagePage = extractPageType(url); if (imagePage.type !== "image" || seen.has(url)) return []; seen.add(url); let image2 = link.one(domClass.common.image), size = image2?.imageSize(), backgroundStyle = (link.one("[style*='url(']") ?? link.closest("[style*='url(']"))?.attribute("style") ?? "", backgroundUrl = cssBackgroundUrl(backgroundStyle), imageSrc = image2?.attribute("src") || "", lazyImageSrc = image2?.attribute("data-src") || "", imageSource = imageSrc && !/blank\.gif(?:$|\?)/i.test(imageSrc) ? imageSrc : lazyImageSrc || imageSrc, thumbnail = backgroundUrl ? backgroundThumbnail(backgroundStyle, backgroundUrl, currentUrl, size) : imageThumbnail(imageSource, currentUrl, size); return [{ aspectRatio: thumbnail.height / thumbnail.width, pageNum: imagePage.pageNum, pageUrl: url, thumbnail }]; }).sort((left, right) => left.pageNum - right.pageNum), pages = previewItems.map((item) => ({ aspectRatio: item.aspectRatio, pageNum: item.pageNum, url: item.pageUrl })), data = { currentIndex, currentUrl, descriptionText: rangeText, dominantAspectRatio: dominantPreviewAspectRatio(previewItems), endImage, maxIndex, pageSize, pages, previewItems, startImage, totalImages }, thumbsSource = source.thumbs.one(), pageBarTopSource = source.pageBarTop.one(), pageBarBottomSource = source.pageBarBottom.one(), pageBarTopHostSource = pageBarTopSource?.closest( domClass.gallery.preview.pageBarHost ), pageBarBottomHostSource = pageBarBottomSource?.closest( domClass.gallery.preview.pageBarHost ), createPageBarMount = (position) => createManagedElement("div").replaceClasses( `w-max max-w-full mx-auto overflow-x-auto touch-pan-y [-webkit-overflow-scrolling:touch] [&[data-dragging=true]]:select-none ${position === "top" ? "mt-2px mb-0" : "mt-0 mb-10px"}` ), elems = { mount: root === document && thumbsSource ? createManagedElement("div").replaceClasses("contents") : null, originalPageBarBottom: pageBarBottomSource?.inplace() ?? null, originalPageBarBottomHost: pageBarBottomHostSource?.inplace() ?? null, originalPageBarTop: pageBarTopSource?.inplace() ?? null, originalPageBarTopHost: pageBarTopHostSource?.inplace() ?? null, originalPageDescription: pageDescriptionSource?.inplace() ?? null, pageBarBottom: pageBarBottomSource ? createPageBarMount("bottom") : null, pageBarDescription: pageDescriptionSource && pageBarTopSource ? createManagedElement("div") : null, pageBarTop: pageBarTopSource ? createPageBarMount("top") : null, thumbImages: source.thumbs.images.inplaceAll(), thumbItems: thumbsSource?.children().map( (item) => root === document ? item.inplace() : item.move() ) ?? [], thumbs: root === document ? source.thumbs.inplace() : null }; return elems.mount && elems.thumbs && elems.thumbs.before(elems.mount), { data, elems, handle: { /** Opens thumbnail image links in EhPeek Reader instead of original navigation. */ interceptPreviewImageOpen(onOpen) { let handleClick = (event) => { let link = event.target instanceof Element ? DomNode.from(event.target).closest(domClass.common.links) : null, href = link?.attribute("href") ?? ""; !link || extractPageType(href).type !== "image" || !link.one(domClass.common.image) && !link.closest(domClass.gallery.preview.imageLinkHost) || (event.preventDefault(), event.stopPropagation(), onOpen(normalizeUrl(href, currentUrl))); }; return elems.thumbs?.listen("click", handleClick) ?? (() => { }); }, /** Makes thumbnail dragging available to the horizontal preview-page gesture. */ ensurePreviewSwipeInput() { elems.thumbs?.apply("swipe"); for (let image2 of elems.thumbImages) image2.setAttributes({ draggable: "false" }); }, /** Installs a fetched preview page into the currently visible thumbnail host. */ replacePreviewThumbs(items) { elems.thumbs?.replaceChildren(...items); }, /** Marks preview loading while retaining the currently visible thumbnails. */ updatePreviewLoading(loading) { elems.thumbs?.attribute("aria-busy", String(loading)); }, /** Gives Scroll Preview its own layout box while retaining the original Preview DOM. */ installScrollPreviewMount() { elems.mount?.replaceClasses("ehpeek-scroll-preview-mount"), elems.originalPageBarTopHost?.hideOriginal(), elems.originalPageBarBottomHost?.hideOriginal(), elems.originalPageDescription?.hideOriginal(), elems.thumbs?.hideOriginal(); }, /** Replaces both original page bars with mounts owned by EhPeek pagination. */ installPreviewPageBars() { DomNode.from(document).use(domClass.page).body.inplace()?.apply("hidePreviewPageBars"), elems.originalPageBarTop && elems.pageBarTop && elems.originalPageBarTop.after(elems.pageBarTop), elems.originalPageBarBottom && elems.pageBarBottom && elems.originalPageBarBottom.after(elems.pageBarBottom), elems.originalPageDescription && elems.pageBarDescription && elems.pageBarTop && elems.pageBarTop.before(elems.pageBarDescription); }, /** Brings the requested EhPeek page bar into view after preview navigation. */ scrollPreviewPageBarIntoView(position) { (position === "top" ? elems.pageBarTop : elems.pageBarBottom)?.scrollIntoView({ behavior: "smooth", block: position === "top" ? "start" : "end" }); } } }; } function dominantPreviewAspectRatio(items) { let buckets = /* @__PURE__ */ new Map(), dominant = null; for (let item of items) { let key = Math.round(item.aspectRatio * 10), bucket = buckets.get(key) ?? { count: 0, total: 0 }; bucket.count += 1, bucket.total += item.aspectRatio, buckets.set(key, bucket), (!dominant || bucket.count > dominant.count) && (dominant = bucket); } if (!dominant) throw new Error("Cannot determine the gallery preview aspect ratio."); return dominant.total / dominant.count; } function cssBackgroundUrl(style2) { return style2.match(/url\(\s*(['"]?)(.*?)\1\s*\)/i)?.[2] ?? ""; } function backgroundThumbnail(style2, url, baseUrl, fallbackSize) { let declaration = document.createElement("div").style; return declaration.cssText = style2, { backgroundPosition: declaration.backgroundPosition || "0 0", backgroundRepeat: declaration.backgroundRepeat || "no-repeat", backgroundSize: declaration.backgroundSize || "auto", height: cssPixelSize(declaration.height) ?? validThumbnailSize(fallbackSize?.height), kind: "background", url: normalizeUrl(url, baseUrl), width: cssPixelSize(declaration.width) ?? validThumbnailSize(fallbackSize?.width) }; } function imageThumbnail(url, baseUrl, size) { return { backgroundPosition: "0 0", backgroundRepeat: "no-repeat", backgroundSize: "auto", height: validThumbnailSize(size?.height), kind: "image", url: url ? normalizeUrl(url, baseUrl) : "", width: validThumbnailSize(size?.width) }; } function cssPixelSize(value) { if (!value.endsWith("px")) return null; let parsed = Number.parseFloat(value); return Number.isFinite(parsed) && parsed > 0 ? parsed : null; } function validThumbnailSize(value) { return value && Number.isFinite(value) && value > 0 ? value : 100; } async function loadGalleryPreviewPage(previewIndex, pageUrl) { let url = previewUrlForIndex(previewIndex, pageUrl), response = await requestPage(url); return manageGalleryPreview(response.document, response.url); } function extractImageGalleryPage(root = document) { for (let link of DomNode.from(root).use(domClass.common).links.all()) { let page2 = extractPageType(normalizeUrl(link.attribute("href") || "")); if (page2.type === "gallery") return page2; } return null; } async function loadEhImagePage(page2) { let response = await requestPage(page2.url), imagePage = DomNode.from(response.document).use(domClass.gallery.imagePage), image2 = imagePage.image.one(), imageUrl = normalizeUrl( image2?.attribute("src") || image2?.attribute("data-src") || "", page2.url ); if (!imageUrl) throw new Error(i18n_default.errors.imageNotFound); let numberAttribute = (name) => { let value = Number(image2?.attribute(name)); return Number.isFinite(value) && value > 0 ? value : null; }; return { height: numberAttribute("height"), imageUrl, originalImageUrl: imagePage.links.all().map((link) => normalizeUrl(link.attribute("href") || "", page2.url)).find(isFullImageUrl) ?? null, width: numberAttribute("width") }; } // src/eh/dom/galleryInfo.ts var GALLERY_CATEGORY_FLAGS = { ct1: 1, ct2: 2, ct3: 4, ct4: 8, ct5: 16, ct6: 32, ct7: 64, ct8: 128, ct9: 256, cta: 512 }; function extractGalleryHistoryInfo() { let page2 = DomNode.from(document), source = page2.use(domClass.gallery.info), category = source.category.one(), historyCategory = page2.one( domClass.gallery.info.category, retainedOriginalPageNode ) ?? category, categoryClass = (historyCategory?.one( domClass.gallery.info.category.appearance, anyDomNode )?.attribute("class") ?? historyCategory?.attribute("class") ?? "").split(/\s+/).find((className2) => /^ct[1-9a]$/.test(className2)), readDetailRows = (details) => details?.all(domClass.gallery.info.details.rows, anyDomNode).map((detailRow) => detailRow.all( domClass.gallery.info.details.rows.cells, anyDomNode ).slice(1).map((detailCell) => detailCell.text()).filter(Boolean).join(" ")) ?? [], retainedDetails = page2.one( domClass.gallery.info.details, retainedOriginalPageNode ), retainedRows = readDetailRows(retainedDetails), rows = retainedRows.length > 0 ? retainedRows : source.details.rows.all().map((detailRow) => detailRow.all(domClass.gallery.info.details.rows.cells).slice(1).map((detailCell) => detailCell.text()).filter(Boolean).join(" ")), postedAt = page2.one(domClass.gallery.info.details, anyDomNode)?.all(domClass.gallery.info.details.rows.cells, anyDomNode).map((cell2) => parseGalleryPostedAt(cell2.text())).find((value) => value !== void 0), ratingMatch = (page2.all(domClass.common.scripts).map((pageScript) => pageScript.text()).find((script2) => script2.includes("display_rating")) ?? "").match(/\bdisplay_rating\s*=\s*(-?\d+(?:\.\d+)?)/), rating = Number(ratingMatch?.[1]), cover = source.cover.one(), coverUrl = source.cover.image.one()?.attribute("src") ?? ""; if (!coverUrl && cover) for (let node of [cover, ...cover.all(domClass.common.descendants)]) { let match = node.computedStyle().backgroundImage.match(/url\(["']?(.+?)["']?\)/); if (match?.[1]) { coverUrl = match[1]; break; } } return { category: historyCategory?.text() || void 0, categoryClass, coverUrl: coverUrl || void 0, language: rows[3] || void 0, postedAt, rating: ratingMatch && Number.isFinite(rating) ? rating : void 0, title: page2.one(domClass.gallery.info.titleMain, retainedOriginalPageNode)?.text() || source.titleMain.one()?.text() || void 0, titleSub: page2.one(domClass.gallery.info.titleSub, retainedOriginalPageNode)?.text() || source.titleSub.one()?.text() || void 0, uploader: page2.one(domClass.gallery.info.uploader, retainedOriginalPageNode)?.text() || source.uploader.one()?.text() || void 0 }; } function parseGalleryPostedAt(value) { let match = value?.match( /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?$/ ); if (!match) return; let [, year, month, day, hour, minute, second = "0"] = match; return Date.UTC( Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second) ); } function manageGalleryInfo(preview) { let mount = createAnchor("gallery-info"); if (!mount) return null; let page2 = DomNode.from(document), gallery2 = page2.use(domClass.gallery), source = gallery2.info, original = source.original.one(), host = original?.parent() ?? source.hostFallback.one()?.parent() ?? null; if (!original || !host) return null; let readMeta = () => { let rows = source.details.rows.all().map((detailRow) => { let cells = detailRow.all(domClass.gallery.info.details.rows.cells); return { label: cells[0]?.text() ?? "", value: cells.slice(1).map((cell2) => cell2.text()).filter(Boolean).join(" ") }; }); return { favorited: rows[6]?.value ? [rows[6].label, rows[6].value].filter(Boolean).join(" ") : void 0, fileSize: rows[4]?.value, language: rows[3]?.value, parent: rows[1]?.value, posted: rows[0]?.value }; }, readCategory = (node) => { let style2 = node?.computedStyle(); return { "background-color": style2?.backgroundColor ?? "", "background-image": style2?.backgroundImage ?? "", "border-color": style2?.borderColor ?? "", color: style2?.color ?? "" }; }, readCategoryUrl = (node) => { let categoryClass = node?.attribute("class")?.split(/\s+/).find((className2) => className2 in GALLERY_CATEGORY_FLAGS); if (!categoryClass) return null; let url = new URL("/", window.location.href); return url.searchParams.set( "f_cats", String(1023 - GALLERY_CATEGORY_FLAGS[categoryClass]) ), url.href; }, readCoverUrl = (cover2, source2) => { let direct = source2?.attribute("src") ?? ""; if (direct) return direct; for (let node of cover2 ? [cover2, ...cover2.all(domClass.common.descendants)] : []) { let match = node.computedStyle().backgroundImage.match(/url\(["']?(.+?)["']?\)/); if (match?.[1]) return match[1]; } return ""; }, readFavorite = (element, scripts2) => { let displayed = element?.one(domClass.gallery.info.favorite.link)?.text() || element?.one(domClass.gallery.info.favorite.titled)?.attribute("title")?.trim() || "", slot = displayed.match(/(?:^|\D)([0-9])(?:\D|$)/)?.[1], favorited = slot !== void 0 || /^favorited$/i.test(displayed), match = (scripts2.find( (item) => item.includes("popbase") && item.includes("addfav") ) ?? "").match( /popbase\s*=\s*base_url\s*\+\s*"gallerypopups\.php\?gid=(\d+)&t=([^"]+)&act="/ ); return { actionUrl: match ? `/gallerypopups.php?gid=${match[1]}&t=${match[2]}&act=addfav` : "", color: slot === void 0 ? null : `var(--color-site-favorite-${slot})`, favorited, label: favorited ? displayed : i18n_default.gallery.notFavorited }; }, readRating = (count, image2, labelNode, scripts2) => { let label = labelNode?.text() ?? "", match = (scripts2.find((item) => item.includes("display_rating")) ?? "").match(/\bdisplay_rating\s*=\s*(-?\d+(?:\.\d+)?)/), scriptValue = Number(match?.[1]), value = match && Number.isFinite(scriptValue) ? scriptValue : null; return label && value !== null ? { count: count?.text() ?? "", label, rated: image2?.matches(domClass.gallery.info.rating.rated) ?? !1, value } : null; }, readActions = () => gallery2.actions.items.all().filter((node) => { let href = node.attribute("href")?.trim() ?? ""; return node.hasAttribute("onclick") || !!(href && href !== "#" && !/^javascript:/i.test(href)); }).slice(0, 6), readTag = (tag2) => { let label = tag2.text() || tag2.attribute(externalDom.tagLabelAttribute)?.trim() || tag2.attribute("title")?.trim() || "", href = tag2.attribute("href") ?? "", name = galleryTagNameFromUrl(href); if (!label || !name || !href) return null; let container = tag2.closest(domClass.gallery.tagContainer) ?? tag2, tagStyle = tag2.computedStyle(), containerStyle = container.computedStyle(), myTagId = tag2.attribute("data-ehpeek-my-tag-id"), myTagSet = tag2.attribute("data-ehpeek-my-tag-set"); return { data: { appearance: { backgroundColor: containerStyle.backgroundColor, borderColor: containerStyle.borderColor, color: tagStyle.color }, label, myTag: myTagId && myTagSet ? { id: myTagId, tagSet: myTagSet } : null, name, url: href }, source: tag2 }; }, readTagGroups = () => { let rows = gallery2.tags.rows.all(); return rows.length > 0 ? rows.map((row2) => ({ namespace: row2.one(domClass.gallery.tags.rows.namespace)?.text().replace(/:$/, "") || "tag", tags: row2.all(domClass.gallery.tags.rows.links).map(readTag).filter((tag2) => tag2 !== null).slice(0, 30) })).filter((group) => group.tags.length > 0) : [{ namespace: "tag", tags: gallery2.tags.links.all().map(readTag).filter((tag2) => tag2 !== null).slice(0, 60) }].filter((group) => group.tags.length > 0); }, favoriteColor = (value) => { let slot = value.match(/^(?:fav)?([0-9])$/i)?.[1] ?? value.match(/^favorites?\s+([0-9])$/i)?.[1]; return slot === void 0 ? null : `var(--color-site-favorite-${slot})`; }, readFavoriteDialog = (doc, favorited) => { let dialog = DomNode.from(doc).use(domClass.gallery.favoriteDialog), options = dialog.options.all().map((favoriteInput) => { let row2 = favoriteInput.closest(domClass.gallery.favoriteDialog.optionRow), value = favoriteInput.inputValue(); return { color: favoriteColor(value), label: row2?.text().replace(/\s+/g, " ") || value, selected: favorited && favoriteInput.checked(), value }; }); return { note: dialog.note.one()?.inputValue() ?? "", options }; }, manageTagGroups = () => readTagGroups().map((group) => ({ namespace: group.namespace, tags: group.tags.map(({ data: tag2, source: source2 }) => ({ ...tag2, contentSource: source2.inplace() })) })), meta = readMeta(), category = source.category.one(), categoryStyle = source.category.appearance.one() ?? category, cover = source.cover.one(), coverSource = source.cover.image.one(), favorite = source.favorite.one(), ratingCount = source.rating.count.one(), ratingImage = source.rating.image.one(), ratingLabel = source.rating.label.one(), ratingActions = source.rating.actions.all(), uploader = source.uploader.link.one(), newTagButton = source.newTag.button.one(), newTagField = source.newTag.field.one(), newTagForm = source.newTag.form.one(), scripts = page2.all(domClass.common.scripts).map((pageScript) => pageScript.text()), actionSources = readActions(), tagContentSources = [], tagGroups = readTagGroups().map((group) => ({ namespace: group.namespace, tags: group.tags.map(({ data: tag2, source: source2 }) => { let contentSourceIndex = tagContentSources.push(source2) - 1; return { ...tag2, contentSourceIndex }; }) })), data = { category: category?.text() ?? "", categoryAppearance: readCategory(categoryStyle), categoryUrl: readCategoryUrl(categoryStyle), favorite: readFavorite(favorite, scripts), rating: readRating(ratingCount, ratingImage, ratingLabel, scripts), summary: [ meta.language, preview?.totalImages ? `${preview.totalImages} ${i18n_default.gallery.pages.toLowerCase()}` : void 0, meta.fileSize, meta.favorited, meta.posted ?? meta.parent ].filter((value) => !!value).slice(0, 6).map((value) => ({ value })), tagGroups, titleMain: source.titleMain.one()?.text() ?? "", titleSub: source.titleSub.one()?.text() ?? "", uploader: uploader?.text() ?? "", uploaderUrl: uploader?.attribute("href") ?? "" }, coverUrl = readCoverUrl(cover, coverSource), managedCover = coverUrl ? source.cover.image.clone()?.replaceClasses("").apply("fit") ?? createManagedElement("img", { fit: "ehpeek-fit-gallery-cover" }).apply("fit") : null; managedCover?.removeAttributes("id", "style", "width", "height").setAttributes({ alt: "", decoding: "async", loading: "eager", src: coverUrl }); let managedNewTag = newTagButton && newTagField && newTagForm ? source.newTag.inplace()?.apply("layout") ?? null : null, hostApply = { hide: "ehpeek-hide-original-gallery-info" }; managedNewTag?.setHidden(!1).removeStyles("display"); let elems = { actionItems: actionSources.map((item) => item.move(domClass.gallery.actions.items.apply).apply("layout")), cover: managedCover, host: host.inplace(hostApply).apply("hide"), mount, newTag: managedNewTag, ratingActions: ratingActions.map((action) => action.inplace()), tagContents: tagContentSources.map((source2) => source2.inplace()), tagList: gallery2.tags.inplace(), tagMenuAction: source.tagMenu.inplace() }, selectedTagSource = null, activateTagMenu = (source2) => { let stopNavigation = source2.listen("click", (event) => { event.preventDefault(); }, { once: !0 }); source2.click(), stopNavigation(); }; return { data, elems, handle: { /** Normalizes the original cover for GalleryInfoPanel's responsive layout. */ /** Hides original GalleryInfo children and installs the component mount. */ installGalleryInfoPanel() { elems.host.prepend(elems.mount); }, /** Loads the original favorite categories and note for EhPeek's favorite modal. */ async loadGalleryFavoriteDialog(actionUrl, favorited) { let response = await requestPage(actionUrl); return readFavoriteDialog(response.document, favorited); }, /** Submits a tag to the chosen My Tags collection and validates the response. */ async submitFavoriteTag(tag2, tagSet, mode) { let response = await addMyTag(tag2.name, tagSet, mode); return extractMyTagsPageData(response.document, tagSet); }, /** Keeps component tag groups synchronized with original-page tag updates. */ observeGalleryTagGroups(onChange) { return elems.tagList?.observe(() => { gallery2.tags.rows.requery(), gallery2.tags.links.requery(), onChange(manageTagGroups()); }) ?? (() => { }); }, /** Activates E-H's original rating area and lets its page script submit the vote. */ submitGalleryRating(value) { let rating = Math.round(value * 2); if (rating < 1 || rating > 10) throw new RangeError("Gallery rating must be between 0.5 and 5 stars."); let action = elems.ratingActions[rating - 1]; if (!action) throw new Error("Gallery rating action is unavailable."); action.click(); }, /** Removes the selected tag from its stored My Tags collection. */ async removeFavoriteTag(tag2) { if (!tag2.myTag) throw new Error("The tag is not in My Tags."); let response = await deleteMyTag(tag2.myTag.id, tag2.myTag.tagSet); return extractMyTagsPageData(response.document, tag2.myTag.tagSet); }, /** Opens E-H's original tag actions and only adapts their presentation. */ openGalleryTagMenu(tag2) { if (!elems.tagMenuAction) throw new Error("Gallery tag actions are unavailable."); activateTagMenu(tag2.contentSource), elems.newTag?.setHidden(!1).removeStyles("display"); let actions = elems.tagMenuAction.all(domClass.gallery.info.tagMenu.actions); if (actions.length === 0) throw activateTagMenu(tag2.contentSource), elems.newTag?.setHidden(!1).removeStyles("display"), new Error("Gallery tag actions could not be opened."); selectedTagSource = tag2.contentSource, elems.tagMenuAction.apply("layout"), actions.find((action) => action.readAttribute("onclick")?.includes("toggle_tagmenu"))?.hideOriginal(); }, /** Closes E-H's selected tag without replacing its action DOM. */ closeGalleryTagMenu() { selectedTagSource && activateTagMenu(selectedTagSource), elems.newTag?.setHidden(!1).removeStyles("display"), selectedTagSource = null; }, /** Updates the Gallery favorite state through the original site endpoint. */ updateGalleryFavorite } }; } function mutateGalleryTouchLayout(fitToViewport) { let page2 = DomNode.from(document).use(domClass.page), html = page2.html.inplace(), body = page2.body.inplace(); if (!html || !body) throw new Error("Gallery page layout is unavailable."); html.apply("galleryTouchLayout"), body.apply("galleryTouchLayout"), fitToViewport && html.apply("fitToViewport"); } function mutateGalleryWideLayout(info, preview, initiallyEnabled, replacesOriginalPreview) { let page2 = DomNode.from(document).use(domClass.page), source = DomNode.from(document).use(domClass.gallery), html = page2.html.inplace(), body = page2.body.inplace(), footer = page2.footer.inplace(), comments = source.comments.inplace(), commentsAnchor = source.commentsAnchor.inplace(), pageBarTopHost = source.preview.pageBarTop.one()?.parent()?.inplace() ?? null, pageBarBottomHost = source.preview.pageBarBottom.one()?.parent()?.inplace() ?? null, previewMount = preview.elems.mount, thumbs = preview.elems.thumbs; if (!html || !body || !comments || !previewMount || !thumbs) return null; let leftNodes = [info.elems.host, commentsAnchor, comments].filter((node) => node !== null), rightNodes = (replacesOriginalPreview ? [previewMount] : [pageBarTopHost, previewMount, thumbs, pageBarBottomHost]).filter((node) => node !== null), layout = null, positions = [], enabled = initiallyEnabled, update = () => { if (enabled && !layout) { if (layout = createAnchor("gallery-wide-layout")?.replaceClasses("ehpeek-touch-gallery-layout") ?? null, !layout) return; window.scrollTo(0, 0), html.apply("galleryWideLayout"), body.apply("galleryWideLayout"); let left = createManagedElement("div").replaceClasses("ehpeek-touch-gallery-layout-left"), right = createManagedElement("div").replaceClasses("ehpeek-touch-gallery-layout-right"); positions = [...leftNodes, ...rightNodes, footer].filter((node) => node !== null).map((node) => { let marker = createManagedElement("span").setHidden(!0); return node.before(marker), { marker, node }; }), info.elems.host.before(layout), layout.append(left, right, ...footer ? [footer] : []), left.append(...leftNodes), right.append(...rightNodes), window.dispatchEvent(new Event("resize")); return; } if (!enabled && layout) { for (let { marker, node } of positions) marker.after(node), marker.remove(); positions = [], layout.remove(), layout = null, html.removeClasses("ehpeek-gallery-wide-layout-root"), body.removeClasses("ehpeek-gallery-wide-layout-root"), window.dispatchEvent(new Event("resize")); } }; return update(), { updateEnabled(value) { enabled = value, update(); } }; } function manageGalleryCommentsTouch(onLoadError) { let gallery2 = DomNode.from(document).use(domClass.gallery), comments = gallery2.comments.inplace(); comments?.apply("touchScore"); let showAllSource = gallery2.comments.showAll.one(), showAll = showAllSource?.inplace() ?? null, showAllButton = showAllSource?.clone() ?? null, showAllUrl = showAll?.readAttribute("href"), loadingAll = !1; if (comments && showAll && showAllUrl) { showAllButton && gallery2.commentActions.inplace()?.append(showAllButton); let loadAll = (event) => { event.preventDefault(), !loadingAll && (loadingAll = !0, showAll.setAttributes({ "aria-busy": "true" }), showAllButton?.setAttributes({ "aria-busy": "true" }), requestPage(showAllUrl).then((response) => { let loaded = DomNode.from(response.document).use(domClass.gallery.comments).one(); if (!loaded) throw new Error("Cannot read all gallery comments"); comments.replaceChildren(...loaded.children().map((child) => child.move())), manageGalleryCommentsTouch(onLoadError); }).catch(onLoadError).finally(() => { loadingAll = !1, showAll.removeAttributes("aria-busy"), showAllButton?.removeAttributes("aria-busy"); })); }; showAll.listen("click", loadAll), showAllButton?.listen("click", loadAll); } let items = gallery2.comments.score.all().filter((trigger) => trigger.attribute("data-ehpeek-touch-comment-score") !== "true").map((trigger) => ({ trigger, details: trigger.closest(domClass.gallery.comments.scoreComment)?.one(domClass.gallery.comments.scoreComment.details) ?? null })).filter((item) => item.details !== null).map(({ trigger, details }) => ({ details: details.inplace(), detailsId: details.attribute("id") ?? "", expanded: !1, trigger: trigger.inplace() })), setExpanded = (item, expanded) => { item.expanded = expanded, item.trigger.attribute("aria-expanded", String(expanded)), item.details.attribute("aria-hidden", String(!expanded)); }; for (let item of items) { item.trigger.removeAttributes("onmouseover", "onmouseout", "onclick").setAttributes({ "data-ehpeek-touch-comment-score": "true", role: "button", tabindex: "0", "aria-controls": item.detailsId }), setExpanded(item, !1); let toggle = (event) => { event.preventDefault(), event.stopImmediatePropagation(); let shouldExpand = !item.expanded; for (let candidate of items) setExpanded(candidate, candidate === item && shouldExpand); }; item.trigger.listen("click", toggle), item.trigger.listen("keydown", (event) => { (event.key === "Enter" || event.key === " ") && toggle(event); }); } } // src/eh/dom/search.ts function listenGalleryLinksOpenInNewTab(host) { let handleClick = (event) => { let link = event.target instanceof Element ? DomNode.from(event.target).closest(domClass.search.results.galleryLinks) : null; link?.closest(domClass.search.results) && link.inplace().setAttributes({ target: "_blank", rel: "noopener noreferrer" }); }; return host.listen("click", handleClick, !0); } function createReadHistoryGridRow(item, titlePreference) { let info = item.info, metadataItems = [], appendMetadata = (value, className2) => { if (!value) return; let element = createManagedElement("div").replaceClasses(className2); element.setTextUnlessInput(value), metadataItems.push(element); }; if (info?.category && info.categoryClass) { let category = createManagedElement("div").replaceClasses(`cn ${info.categoryClass} ehpeek-search-meta-category`); category.setTextUnlessInput(info.category), metadataItems.push(category); } if (appendMetadata( info?.postedAt === void 0 ? void 0 : new Date(info.postedAt).toISOString().slice(0, 16).replace("T", " "), "ehpeek-search-meta-posted" ), info?.rating !== void 0) { let rounded = Math.round(info.rating * 2) / 2, rating = createManagedElement("div").replaceClasses("ir ehpeek-search-meta-rating").styles({ "background-position": `${-16 * (5 - Math.ceil(rounded))}px ${Number.isInteger(rounded) ? -1 : -21}px`, opacity: "1" }); metadataItems.push(rating); } appendMetadata(info?.uploader, "ehpeek-search-meta-uploader"), appendMetadata( item.totalPages ? `${item.totalPages} ${i18n_default.gallery.pages.toLowerCase()}` : void 0, "ehpeek-search-meta-pages" ); let historyLabel = item.currentPage > 0 ? `${item.currentPage} / ${item.totalPages ?? "?"}` : i18n_default.history.visitedLabel, removeButton = createManagedElement("button").setAttributes({ type: "button", "data-ehpeek-remove-history": "true" }).replaceClasses( "relative z-2 ui-hit-min-h-lg ui-py-xs ui-px-lg ui-rounded-md border border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] ehp-color-site-text font-inherit textsize-md font-700 text-center cursor-pointer [touch-action:manipulation] hover:bg-[var(--color-site-item-hover)]" ); removeButton.setTextUnlessInput(i18n_default.history.actions.remove); let historyActions = createManagedElement("div").replaceClasses("ehpeek-read-history-actions box-border flex items-center justify-end ui-gap-md ui-pr-sm ui-pb-xs").append(removeButton), titleText = titlePreference === "sub" ? info?.titleSub || info?.title : info?.title || info?.titleSub, galleryHref = new URL( `/g/${item.galleryId}/${item.token}/`, window.location.href ).href, row2 = createManagedElement("tr").setAttributes({ "data-ehpeek-read-history": item.currentPage > 0 ? "reading" : "visited" }), thumbnailCell = createManagedElement("td").replaceClasses("gl1e"), thumbnail = createManagedElement("div"), image2 = info?.coverUrl ? createManagedElement("img").setAttributes({ alt: titleText ?? "", loading: "lazy", src: info.coverUrl }) : null; image2 ? thumbnail.append( createManagedElement("a").attribute("href", galleryHref).append(image2) ) : thumbnailCell.setHidden(!0), thumbnailCell.append(thumbnail); let contentCell = createManagedElement("td").replaceClasses("gl2e"), galleryLink = createManagedElement("a").attribute("href", galleryHref), detail = createManagedElement("div").replaceClasses("gl4e h-full"), title = createManagedElement("div").replaceClasses(`glink ${sharedApply.historyLabel}`).setAttributes({ "data-ehpeek-history-label": historyLabel }); title.setTextUnlessInput(titleText ?? ""), title.setHidden(!titleText); let metadata = createManagedElement("div").replaceClasses("gl3e"); return metadata.append(...metadataItems), galleryLink.append(detail.append(title)), contentCell.append( createManagedElement("div").replaceClasses("h-full").append(metadata, galleryLink) ), row2.append(thumbnailCell, contentCell), { coverImage: image2, detail, galleryHref, galleryLink, metadata, originalGalleryLink: !1, row: row2, stackTags: !1, tags: [historyActions], title, titleText: titleText ?? "", withoutCover: !image2 }; } function manageReadHistoryPage(items, titlePreference, mode = "ehpeek-lite") { let page2 = DomNode.from(document), resultList = page2.use(domClass.search).results.one(), navigationTopMount = createAnchor("read-history-navigation-top"), navigationBottomMount = createAnchor("read-history-navigation-bottom"); if (!resultList || !navigationTopMount || !navigationBottomMount) return null; let grids = manageReadHistoryGrids({ items, mode, source: resultList, titlePreference }); grids.elems.resultList.before(navigationTopMount), grids.elems.resultList.after(navigationBottomMount); for (let control2 of page2.all(domClass.search.controls, anyDomNode)) control2.inplace().hideOriginal(); let handle = { /** Applies new-tab semantics to History gallery links at activation time. */ listenGalleryLinksOpenInNewTab: () => listenGalleryLinksOpenInNewTab(grids.elems.resultList), /** Replaces the visible History rows without navigating away from the current document. */ updateReadHistoryItems: grids.handle.updateItems, /** Reports explicit removal requests without exposing History rows. */ listenForReadHistoryRemoval: grids.handle.listenForItemRemoval, /** Keeps navigation anchored to the corresponding edge after an in-page page change. */ scrollReadHistoryPage(position) { (position === "bottom" ? navigationBottomMount : navigationTopMount).scrollIntoView({ behavior: "smooth", block: position === "bottom" ? "end" : "start" }); }, /** Switches the History result list between one and two result columns. */ updateResultColumns(enabled) { enabled ? grids.elems.resultList.addClasses(sharedApply.searchResultColumns) : grids.elems.resultList.removeClasses(sharedApply.searchResultColumns); } }; return { elems: { navigationBottomMount, navigationTopMount, resultList: grids.elems.resultList }, handle }; } function manageSearchResults() { let source = DomNode.from(document).use(domClass.search), resultSource = source.results.one(); if (!resultSource) return null; let resultHost = resultSource.parent()?.inplace() ?? null, elems = { resultList: resultSource.inplace(domClass.search.results.apply), searchInput: source.input.inplace() }; return { elems, handle: { /** Reads the live original link so swipe navigation matches the current page controls. */ readNavigationUrl(direction) { return (direction === "next" ? source.navigation.next.one() : source.navigation.previous.one())?.attribute("href") ?? null; }, /** Routes the original pagination controls through the active page owner. */ interceptSearchNavigation(onNavigate) { let handleClick = (event) => { let url = (event.target instanceof Element ? DomNode.from(event.target).closest(domClass.search.navigationLink) : null)?.attribute("href") ?? null; url && (event.preventDefault(), event.stopPropagation(), onNavigate(url)); }; return document.addEventListener("click", handleClick, !0), () => document.removeEventListener("click", handleClick, !0); }, /** Replaces the current result page without deciding browser-history behavior. */ async loadSearchPage(url, signal) { let response = await requestPage(url, { signal }); if (!replaceSearchPageContent(response.document)) throw new Error(i18n_default.errors.searchPageContentNotFound); }, /** Returns enhanced Search navigation to its input, or the page top when absent. */ scrollSearchPageToInput() { elems.searchInput ? elems.searchInput.scrollIntoView({ block: "start", behavior: "auto" }) : window.scrollTo({ top: 0, behavior: "auto" }); }, /** Exposes result loading state without removing the current result list. */ updateSearchLoading(busy) { busy ? elems.resultList.setAttributes({ "aria-busy": "true" }) : elems.resultList.removeAttributes("aria-busy"); }, /** Switches the EhPeek result list between one and two result columns. */ updateResultColumns(enabled) { enabled ? elems.resultList.apply("columns") : elems.resultList.removeClasses(sharedApply.searchResultColumns); }, /** Prevents result content from stealing a horizontal swipe gesture. */ ensureSearchSwipeInput() { elems.resultList.apply("swipe"); }, /** Applies new-tab semantics at activation time so replaced result pages need no rebinding. */ listenGalleryLinksOpenInNewTab: () => resultHost ? listenGalleryLinksOpenInNewTab(resultHost) : () => { }, /** Reports rows appended by third-party infinite-scroll integrations. */ listenResultRowsAdded(callback) { let observer = new MutationObserver((records) => { records.some((record) => Array.from(record.addedNodes).some((node) => node instanceof HTMLTableRowElement || node instanceof Element && node.querySelector("tr"))) && callback(); }); return observer.observe(elems.resultList.Component(), { childList: !0, subtree: !0 }), () => observer.disconnect(); } } }; } function manageSearchTextInput(inputSource) { let formSource = inputSource?.form() ?? null, submitSource = formSource?.one(domClass.search.submit) ?? inputSource?.parent()?.one(domClass.search.submitFallback) ?? null; if (!submitSource) return null; let elems = { form: formSource?.inplace() ?? null, input: inputSource.inplace(), submit: submitSource.inplace() }; return { data: { value: elems.input.inputValue() }, elems, handle: { /** Connects the original input to EhPeek's history and suggestion overlay. */ listenSearchHistoryOverlay(callbacks, overlay) { let update = () => callbacks.onInput( elems.input.inputValue(), document.activeElement === elems.input.Component() ), submitValue = () => callbacks.onSubmit(elems.input.inputValue()), positionChange = (event) => { let target = event.target; target instanceof Node && overlay()?.contains(target) || callbacks.onPositionChange(); }, outsidePointer = (event) => { let target = event.target; target instanceof Node && (elems.input.isNode(target) || overlay()?.contains(target)) || callbacks.onOutsidePointer(); }, disconnect = [ elems.input.listen("input", update), elems.input.listen("focus", callbacks.onFocus), elems.input.listen("pointerdown", callbacks.onFocus), elems.input.listen("keydown", callbacks.onKeyDown), elems.submit.listen("click", submitValue), ...elems.form ? [elems.form.listen("submit", submitValue)] : [] ]; return document.addEventListener("pointerdown", outsidePointer, !0), document.addEventListener("scroll", positionChange, !0), window.addEventListener("resize", callbacks.onPositionChange), () => { disconnect.forEach((cleanup) => cleanup()), document.removeEventListener("pointerdown", outsidePointer, !0), document.removeEventListener("scroll", positionChange, !0), window.removeEventListener("resize", callbacks.onPositionChange); }; }, /** Locates the overlay directly below the original search input. */ readSearchOverlayPosition() { let rect = elems.input.rect(); return { left: rect.left, top: rect.bottom, width: rect.width }; }, /** Commits a history or suggestion choice through the original input events. */ applySearchSelection(value) { elems.input.setInputValue(value), elems.input.dispatchInput(), elems.input.focus(), elems.input.Component().setSelectionRange(value.length, value.length); } } }; } function observeSearchBar(onManaged) { return DomNode.from(document).observe( domClass.search.panel.box, (searchBar) => { let input2 = searchBar.one(domClass.search.input); if (!input2) return; let source = manageSearchTextInput(input2); if (source) return onManaged(source); } ); } function manageReadHistoryGrids(options) { let resultList = createManagedElement("table").replaceClasses("itg"), body = createManagedElement("tbody"), visibleRows = []; resultList.append(body).addClasses( "overscroll-x-contain", "touch-pan-y", "[&[data-dragging=true]]:select-none" ), options.source.inplace().replaceWith(resultList); let updateItems = (items) => { visibleRows = items.map((item) => ({ item, row: createReadHistoryGridRow(item, options.titlePreference) })), body.replaceChildren(...visibleRows.map(({ row: row2 }) => row2.row)), manageEhPeekGrid( resultList, visibleRows.map(({ row: row2 }) => row2), options.mode ); }; return updateItems(options.items), { elems: { resultList }, handle: { updateItems, listenForItemRemoval(callback) { let itemForTarget = (target) => target instanceof Node ? visibleRows.find(({ row: row2 }) => row2.row.contains(target))?.item ?? null : null; return resultList.listen("click", (event) => { let item = (event.target instanceof Element ? DomNode.from(event.target).closest(domClass.search.removeHistory) : null) ? itemForTarget(event.target) : null; item && (event.preventDefault(), event.stopPropagation(), callback(item)); }); } } }; } function manageSearchGrids(mode) { let managedAttribute = "data-ehpeek-search-grid-managed", source = DomNode.from(document).use(domClass.search), resultList = source.results.one(); if (!resultList) return; let rows = source.results.rows.all().map(manageSearchGridRow).filter((row2) => row2 !== null); manageEhPeekGrid( resultList.inplace(), rows, mode ); function manageSearchGridRow(row2) { if (row2.hasAttribute(managedAttribute)) return null; let thumbnailCell = row2.one(domClass.search.results.rows.cover), contentCell = row2.one(domClass.search.results.rows.content), detail = contentCell?.one(domClass.search.results.rows.content.detail), metadata = contentCell?.one(domClass.search.results.rows.content.metadata); if (!thumbnailCell || !contentCell || !detail || !metadata) return null; let title = detail.one(domClass.search.results.rows.content.detail.title), parent = detail.parent(), galleryLink = parent?.matches(domClass.common.links) ? parent : null, tags = detail.children().filter((element) => !title?.sameNode(element)), coverImage = thumbnailCell.one(domClass.common.image), metadataItems = metadata.children(), category = metadataItems.find((item) => item.matches(':is(.cn, .cs, [class*="ct"])')), posted = metadataItems.find((item) => item.matches('[id^="posted_"]')), rating = metadataItems.find((item) => item.matches(".ir")), uploader = metadataItems.find((item) => item.one('a[href*="/uploader/"]') !== null), download = metadataItems.find((item) => item.matches(".gldown")), pages = download?.previous(), knownMetadata = [category, posted, rating, uploader, pages, download], extraMetadata = metadataItems.filter((item) => !knownMetadata.some((known) => known?.sameNode(item))), metadataFields = [ [category, "ehpeek-search-meta-category"], [pages, "ehpeek-search-meta-pages"], [posted, "ehpeek-search-meta-posted"], [rating, "ehpeek-search-meta-rating"], [uploader, "ehpeek-search-meta-uploader"], [download, "ehpeek-search-meta-download"] ]; for (let [field, className2] of metadataFields) field?.inplace().addClasses(className2); for (let field of extraMetadata) field.inplace().addClasses("ehpeek-search-meta-extra"); let managedRow = row2.inplace(); return managedRow.attribute(managedAttribute, "true"), { coverImage: coverImage?.inplace() ?? null, detail: detail.inplace(), galleryHref: galleryLink?.attribute("href") ?? null, galleryLink: galleryLink?.inplace() ?? null, metadata: metadata.inplace(), originalGalleryLink: !0, row: managedRow, stackTags: !0, tags: tags.map((item) => item.inplace()), title: title?.inplace() ?? null, titleText: title?.text() ?? "", withoutCover: !1 }; } } function manageEhPeekGrid(resultList, rows, mode = "ehpeek") { let liteTagPrefixes = { female: "f", male: "m", mixed: "x" }; resultList.addClasses(sharedApply.searchGrid), DomNode.from(resultList.Component()).parent()?.inplace().addClasses("[container-type:inline-size]"), mode === "ehpeek-lite" && resultList.addClasses(sharedApply.liteSearchGrid); for (let row2 of rows) row2.row.addClasses( ...row2.withoutCover ? [sharedApply.coverlessSearchGrid] : [] ), manageSearchGridCover(row2), manageEhPeekGridContent(row2); function manageSearchGridCover(source) { if (!source.coverImage) return; let update = () => { let size = source.coverImage?.imageSize(); !!(size && size.width > 0 && size.height / size.width > 4) ? source.row.addClasses(sharedApply.tallSearchGridCover) : source.row.removeClasses(sharedApply.tallSearchGridCover); }; update(), source.coverImage.listen("load", update, { once: !0 }); } function manageEhPeekGridContent(source) { let { detail, galleryLink, metadata, row: row2, tags, title } = source; if (galleryLink && title && source.galleryHref) { let titleLink = createManagedElement("a").attribute("href", source.galleryHref).replaceClasses("block min-w-0 ehp-color-site-text no-underline"); titleLink.append(title), galleryLink.before(detail), source.originalGalleryLink ? galleryLink.hideOriginal() : galleryLink.remove(), detail.replaceChildren(titleLink, metadata, ...tags), ensureEhPeekGridRowNavigation( row2, titleLink, source.galleryHref, source.titleText ); } else title && title.after(metadata); if (source.stackTags) for (let tag2 of tags) tag2.addClasses(sharedApply.stackSearchGridTags), mode === "ehpeek-lite" && markLiteSearchTags(tag2); } function markLiteSearchTags(container) { for (let tag2 of container.all( ':is(.gt, .gtl, .gtw)[title*=":"]' )) { let namespace = tag2.readAttribute("title")?.split(":", 1)[0]?.trim(), prefix = namespace ? liteTagPrefixes[namespace.toLowerCase()] : null; prefix && tag2.attribute("data-ehpeek-lite-prefix", prefix); } } function ensureEhPeekGridRowNavigation(row2, galleryLink, galleryHref, title) { row2.attribute("data-ehpeek-pressable", "true"); let overlay = createManagedElement("a", { cover: "ehpeek-cover-search-grid-row" }).attribute("href", galleryHref).attribute("aria-label", title || "Open gallery").replaceClasses("hidden search-panel-compact:block absolute inset-0 z-1").apply("cover"); row2.append(overlay).listen("click", (event) => { (event.target instanceof Element ? DomNode.from(event.target) : null)?.closest(domClass.common.interactive) || galleryLink.click(); }); } } function mutateSearchReadHistoryAppearance(readProgressForGallery) { let resultList = DomNode.from(document).use(domClass.search).results.one(); if (!resultList) return; let items = (resultList.one(domClass.search.results.body) ?? resultList).children(); for (let item of items) { let galleryLinks = item.all(domClass.search.results.galleryLinks), galleryLink = galleryLinks.find((link) => !!link.text()) ?? galleryLinks[0]; if (!galleryLink) continue; let identity = galleryIdentityFromUrl(galleryLink.attribute("href") ?? ""); if (!identity) continue; let progress = readProgressForGallery(identity.galleryId, identity.token); if (!progress) continue; (item.one(domClass.search.results.titles) ?? galleryLink).inplace(domClass.search.results.titles.apply).setAttributes({ "data-ehpeek-history-label": progress.pageNum > 0 ? `${progress.pageNum} / ${progress.totalPages ?? "?"}` : i18n_default.history.visitedLabel }).apply("history"), item.inplace().setAttributes({ "data-ehpeek-read-history": progress.pageNum > 0 ? "reading" : "visited" }); } } function mutateSearchGridModeSelect(selected, onEhPeekSelect, onOriginalSelect) { let selects = DomNode.from(document).use(domClass.search).displayMode.all(); for (let source of selects) { let select2 = source.inplace(); for (let { label, value } of [ { label: "EhPeek", value: "ehpeek" }, { label: "EhPeekLite", value: "ehpeek-lite" } ]) { let option2 = source.all(domClass.search.displayMode.options).find((item) => item.inputValue() === value)?.inplace() ?? null; option2 || (option2 = createManagedElement("option").attribute("value", value), option2.setTextUnlessInput(label), select2.append(option2)), option2.setSelected(selected === value); } source.attribute("data-ehpeek-grid-mode") !== "true" && (select2.attribute("data-ehpeek-grid-mode", "true"), select2.listen("change", (event) => { let value = select2.inputValue(); if (value !== "ehpeek" && value !== "ehpeek-lite") { onOriginalSelect(); return; } event.preventDefault(), event.stopImmediatePropagation(), onEhPeekSelect(value); }, !0)); } } function replaceSearchPageContent(doc) { let currentList = DomNode.from(document).use(domClass.search).results.one(), incomingList = DomNode.from(doc).use(domClass.search).results.one(); if (!currentList || !incomingList || !refreshSearchRangeBar(doc)) return !1; replaceSearchResultText(doc), replaceSearchNavigationBars(doc); let current = currentList.inplace(), importedList = incomingList.clone(); return current.replaceWith(importedList), !0; } function refreshSearchRangeBar(doc) { let current = DomNode.from(document).use(domClass.search).rangeBar.one(), incomingPage = DomNode.from(doc), incoming = incomingPage.use(domClass.search).rangeBar.one(); if (!current && !incoming) return !0; if (!current || !incoming) return !1; let script2 = incomingPage.all(domClass.common.scripts).map((item) => item.text()).find((item) => item.includes("build_rangebar()")), rangeUrl = script2?.match(/\brangeurl\s*=\s*["']([^"']*)["']/)?.[1], rangeMin = Number(script2?.match(/\brangemin\s*=\s*(-?\d+)/)?.[1]), rangeMax = Number(script2?.match(/\brangemax\s*=\s*(-?\d+)/)?.[1]), rangeSpan = Number(script2?.match(/\brangespan\s*=\s*(-?\d+)/)?.[1]); if (rangeUrl === void 0 || !Number.isFinite(rangeMin) || !Number.isFinite(rangeMax) || !Number.isFinite(rangeSpan)) return !1; let items = []; if (rangeSpan > 0) for (let index = 0; index < 99; index += rangeSpan) { let marker = createManagedElement("div"); if ((index === 98 && rangeMin === 99 || index >= rangeMin && index <= rangeMax) && marker.attribute("data-inrange", "1"), !rangeUrl) { items.push(marker); continue; } let href = index === 0 ? rangeUrl : `${rangeUrl}${rangeUrl.includes("?") ? "&" : "?"}range=${index}`; items.push(createManagedElement("a").attribute("href", href).append(marker)); } return current.inplace().replaceChildren(...items), !0; } function replaceSearchNavigationBars(doc) { let currentBars = DomNode.from(document).use(domClass.search).navigation.all(), incomingBars = DomNode.from(doc).use(domClass.search).navigation.all(), count = Math.min(currentBars.length, incomingBars.length); for (let index = 0; index < count; index += 1) { let currentSource = currentBars[index], incomingSource = incomingBars[index]; if (!currentSource || !incomingSource) continue; let current = currentSource.inplace(), incoming = incomingSource.clone(); current.replaceWith(incoming); } } function replaceSearchResultText(doc) { let current = DomNode.from(document).use(domClass.search).resultText.one(), incoming = DomNode.from(doc).use(domClass.search).resultText.one(); if (!current || !incoming) return; let currentElement = current.inplace(), incomingElement = incoming.clone(); currentElement.replaceWith(incomingElement); } function favoritesPageTouch(fitToViewport) { let page2 = DomNode.from(document), pageSource = page2.use(domClass.page), source = page2.use(domClass.search); fitToViewport && pageSource.html.inplace()?.apply("fitToViewport"); let categories = source.favorites.categories.one(), categorySelect = categories ? manageFavoritesCategories(categories) : null, searchHostApply = { expand: "ehpeek-expand-favorites-search" }; source.favorites.input.one()?.form()?.parent()?.inplace(searchHostApply).apply("expand"); let resultSource = source.results.one(); if (!resultSource) return categorySelect; let allSelected = categorySelect?.info.categories[0]?.selected === !0, resultList = resultSource.inplace(domClass.search.results.apply).apply("containFavorites"); return allSelected && resultList.apply("compactFavorites"), categorySelect; } function manageFavoritesCategories(container) { let nodes = container.all(domClass.search.favorites.categories.items); if (nodes.length === 0) return null; let parsed = nodes.map((node) => { let children2 = node.children(), countText = children2[0]?.text() ?? "0", label = children2[children2.length - 1]?.text() || node.text(), count = Number(countText.replace(/,/g, "")), indicatorStyle = node.one(domClass.search.favorites.categories.items.indicator)?.computedStyle() ?? null; return { appearance: indicatorStyle ? { backgroundImage: indicatorStyle.backgroundImage, backgroundPosition: indicatorStyle.backgroundPosition, backgroundSize: indicatorStyle.backgroundSize } : null, count: Number.isFinite(count) ? count : 0, label, selected: node.matches(domClass.search.favorites.selectedCategory), source: node }; }), all = parsed.find((category) => category.source.childElementCount() === 0), favorites = parsed.filter((category) => category !== all), total = favorites.reduce((sum, category) => sum + category.count, 0); container.inplace(domClass.search.favorites.categories.apply).apply("hide"); let categories = [ ...all ? [{ ...all, count: total }] : [], ...favorites ]; return { info: { categories: categories.map(({ appearance, count, label, selected }) => ({ appearance, count, label, selected })) }, items: categories.map(({ source }) => source.inplace()) }; } function searchResultsPageTouch(fitToViewport) { let page2 = DomNode.from(document), pageSource = page2.use(domClass.page), source = page2.use(domClass.search); fitToViewport && pageSource.html.inplace()?.apply("fitToViewport"); let resultSource = source.results.one(); resultSource && resultSource.inplace(domClass.search.results.apply).apply("containSearch"); } function manageTouchResultsPage(page2, fitToViewport) { let apply = () => page2.type === "favorites" ? favoritesPageTouch(fitToViewport) : ((page2.type === "search" || page2.type === "readHistory") && searchResultsPageTouch(fitToViewport), null), favoritesCategory = apply(), data = { favoritesCategory: favoritesCategory?.info ?? null }, elems = { favoriteCategoryItems: favoritesCategory?.items ?? [] }; return { data, elems, handle: { /** Activates E-H's original Favorites collection control. */ activateFavoriteCategory(index) { elems.favoriteCategoryItems[index]?.click(); }, /** Reapplies TouchUI layout after the result list is replaced in place. */ updateTouchResultsLayout() { let updated = apply(); updated && elems.favoriteCategoryItems.splice( 0, elems.favoriteCategoryItems.length, ...updated.items ); } } }; } // src/eh/dom/searchPanel.ts function manageSearchPanel() { let search2 = DomNode.from(document).use(domClass.search), source = search2.panel, searchInput = search2.input.one(), form2 = searchInput?.form() ?? null, standardSearchBox = source.box.one(), categories = source.box.categories.one(), advancedPanel = source.box.advanced.one(), optionLinks = advancedPanel?.previous() ?? null, fileSearch = source.fileSearch.one(), searchSubmit = form2?.one(domClass.search.submit) ?? searchInput?.parent()?.one(domClass.search.submitFallback) ?? null, clearButton = form2?.one(domClass.search.panel.clear) ?? searchInput?.parent()?.one(domClass.search.panel.clearFallback) ?? null; if (!searchInput || !form2 || !searchSubmit) return null; let mount = createAnchor("search-panel"), categoryToggleMount = categories && optionLinks ? createAnchor("search-category-toggle") : null, searchActionMount = createAnchor("search-action"), clearActionMount = clearButton ? createAnchor("search-clear-action") : null; if (!mount || !searchActionMount || clearButton && !clearActionMount) return null; let optionLinkItems = optionLinks?.all(domClass.search.panel.optionLinks) ?? [], advancedToggle = advancedPanel ? optionLinkItems[0] ?? null : null, fileSearchToggle = fileSearch ? optionLinkItems[advancedToggle ? 1 : 0] ?? null : null, advancedToggleMount = advancedToggle ? createAnchor("search-advanced-toggle") : null, fileSearchToggleMount = fileSearchToggle ? createAnchor("search-file-toggle") : null, searchControls = createManagedElement("div", { overlay: "ehpeek-overlay-search-actions" }).apply("overlay"); searchSubmit.inplace(domClass.search.panel.submit.apply).apply("hide"); let optionLinksApply = { wrap: "ehpeek-wrap-search-options" }, elems = { advancedPanel: source.box.advanced.inplace()?.apply("expand") ?? null, advancedToggle: advancedToggle?.inplace() ?? null, advancedToggleMount, categories: source.box.categories.inplace()?.apply("layout") ?? null, categoryToggleMount, clearActionMount, clearButton: clearButton?.inplace(domClass.search.panel.clear.apply).apply("hide") ?? null, fileSearch: source.fileSearch.inplace()?.apply("expand") ?? null, fileSearchToggle: fileSearchToggle?.inplace() ?? null, fileSearchToggleMount, form: form2.inplace(domClass.search.panel.box.form.apply), mount, optionLinks: optionLinks?.inplace(optionLinksApply).apply("wrap") ?? null, searchActionMount, searchBox: source.box.inplace()?.apply("reset") ?? searchControls, searchControls, searchInput: searchInput.inplace(domClass.search.input.apply).apply("expand") }; (standardSearchBox ? elems.searchBox : elems.form).before(elems.mount), elems.searchInput.replaceWith(elems.searchControls), elems.searchControls.append(elems.searchInput), elems.clearButton && elems.clearActionMount && (elems.clearButton.hideOriginal(), elems.searchControls.append(elems.clearActionMount)), elems.searchControls.append(elems.searchActionMount), elems.categories && elems.optionLinks && elems.categoryToggleMount && (elems.optionLinks.after(elems.categories), elems.optionLinks.prepend(elems.categoryToggleMount)), elems.optionLinks && elems.advancedToggle && elems.advancedToggleMount && (elems.advancedToggle.after(elems.advancedToggleMount), elems.advancedToggle.hideOriginal()), elems.optionLinks && elems.fileSearchToggle && elems.fileSearchToggleMount && (elems.fileSearchToggle.after(elems.fileSearchToggleMount), elems.fileSearchToggle.hideOriginal()); let formInsideSearchBox = source.box.form.one()?.sameNode(form2) ?? !1, formId = form2.attribute("id") || "ehpeek-search-form", data = { clearLabel: clearButton ? actionLabel(clearButton) : null, hasClear: elems.clearButton !== null && elems.clearActionMount !== null, searchLabel: actionLabel(searchSubmit) }; elems.searchActionMount.addClasses("contents"), elems.clearActionMount?.addClasses("contents"), elems.form.apply("stack"), elems.searchControls.setAttributes({ "data-ehpeek-has-clear": String(data.hasClear) }), elems.categories?.setAttributes({ "aria-hidden": "true" }); let handle = { /** Controls the original category table from EhPeek's category toggle. */ updateCategoryVisibility(open) { elems.categories?.setAttributes({ "aria-hidden": String(!open) }); }, /** Activates the live Search submit control in case another script replaced it. */ activateSearch() { elems.form.all( `.${domClass.search.panel.submit.apply.hide}[type="submit"]` )[0]?.click(); }, /** Clears only the Search text without invoking E-H's page-navigation reset. */ clearSearchText() { elems.searchInput.setInputValue(""), elems.searchInput.dispatchInput(), elems.searchInput.focus(); }, toggleAdvancedOptions() { elems.advancedToggle?.click(); }, toggleFileSearch() { elems.fileSearchToggle?.click(); } }; return formInsideSearchBox || (elems.form.setAttributes({ id: formId }), elems.searchInput.setAttributes({ form: formId }), elems.clearButton?.setAttributes({ form: formId })), { data, elems, handle }; } function actionLabel(element) { return element.attribute("value") ?? element.text(); } // src/eh/dom/settings.ts function extractGalleryTitlePreference() { let source = DomNode.from(document).use(domClass.settings), japaneseTitle = source.titleJapanese.one(), defaultTitle = source.titleDefault.one(); return japaneseTitle?.checked() ? "sub" : defaultTitle?.checked() ? "main" : null; } // src/eh/dom/topBar.ts function manageSettingsMenuMount() { let page2 = DomNode.from(document), source = page2.use(domClass.topBar), thumbnailContainer = page2.use(domClass.gallery).preview.thumbs.one(), titleContainer = source.galleryTitle.one(), topNav = source.navigation.one(), anchor2 = thumbnailContainer ?? titleContainer; if (topNav) { let item2 = createManagedElement("div"); return topNav.inplace().append(item2), item2; } if (!anchor2?.parent()) return null; let item = createManagedElement("div").replaceClasses("text-right"), managedAnchor = anchor2.inplace(); return thumbnailContainer ? managedAnchor.before(item) : managedAnchor.after(item), item; } function manageTopBar() { let mount = createAnchor("top-bar"); if (!mount) return null; let source = DomNode.from(document).use(domClass.topBar), original = source.navigation.one(), links = source.navigation.links.all(); if (!original || links.length === 0) return null; let data = { favoritesHref: new URL("/favorites.php", window.location.href).href, homeHref: links[0]?.attribute("href") ?? "/" }, elems = { mount }, originalNavigation = original.inplace(domClass.topBar.navigation.apply).apply("hide").setAttributes({ "aria-hidden": "true" }), navigationTargets = []; return originalNavigation.before(elems.mount), { data, elems, handle: { /** Reads the live original navigation only when TouchUI opens its menu. */ readNavigationItems() { let current = source.navigation.links.requery(); return navigationTargets = current.map((link) => link.inplace()), current.map((link, index) => ({ href: link.attribute("href") ?? "#", index, label: link.text(), target: link.attribute("target") })); }, /** Delegates activation to the retained original node, including third-party handlers. */ activateNavigationItem(index) { navigationTargets[index]?.click(); } } }; } // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/defaultAttributes.mjs var defaultAttributes = { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" }; // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/context.mjs var LucideContext = createContext({ size: 24, color: "currentColor", strokeWidth: 2, absoluteStrokeWidth: !1, class: "" }); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/shared/src/utils/hasA11yProp.mjs var hasA11yProp = (props) => { for (let prop in props) if (prop.startsWith("aria-") || prop === "role" || prop === "title") return !0; return !1; }; // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/shared/src/utils/mergeClasses.mjs var mergeClasses = (...classes) => classes.filter((className2, index, array) => !!className2 && className2.trim() !== "" && array.indexOf(className2) === index).join(" ").trim(); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/shared/src/utils/toKebabCase.mjs var toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/shared/src/utils/toCamelCase.mjs var toCamelCase = (string) => string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/shared/src/utils/toPascalCase.mjs var toPascalCase = (string) => { let camelCase = toCamelCase(string); return camelCase.charAt(0).toUpperCase() + camelCase.slice(1); }; // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/Icon.mjs var _tmpl$ = /* @__PURE__ */ template(""), Icon = (props) => { let [localProps, rest] = splitProps(props, ["color", "size", "strokeWidth", "children", "class", "name", "iconNode", "absoluteStrokeWidth"]), globalProps = useContext(LucideContext); return (() => { var _el$ = _tmpl$(); return spread(_el$, mergeProps(defaultAttributes, { get width() { return localProps.size ?? globalProps.size ?? defaultAttributes.width; }, get height() { return localProps.size ?? globalProps.size ?? defaultAttributes.height; }, get stroke() { return localProps.color ?? globalProps.color ?? defaultAttributes.stroke; }, get "stroke-width"() { return memo(() => (localProps.absoluteStrokeWidth ?? globalProps.absoluteStrokeWidth) === !0)() ? Number(localProps.strokeWidth ?? globalProps.strokeWidth ?? defaultAttributes["stroke-width"]) * 24 / Number(localProps.size ?? globalProps.size) : Number(localProps.strokeWidth ?? globalProps.strokeWidth ?? defaultAttributes["stroke-width"]); }, get class() { return mergeClasses("lucide", "lucide-icon", globalProps.class, ...localProps.name != null ? [`lucide-${toKebabCase(toPascalCase(localProps.name))}`, `lucide-${toKebabCase(localProps.name)}`] : [], localProps.class); }, get "aria-hidden"() { return !localProps.children && !hasA11yProp(rest) ? "true" : void 0; } }, rest), !0, !0), insert(_el$, createComponent(For, { get each() { return localProps.iconNode; }, children: ([elementName, attrs]) => createComponent(Dynamic, mergeProps({ component: elementName }, attrs)) })), _el$; })(); }; // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/arrow-down.mjs var iconNode = [["path", { d: "M12 5v14", key: "s699le" }], ["path", { d: "m19 12-7 7-7-7", key: "1idqje" }]], ArrowDown = (props) => createComponent(Icon, mergeProps(props, { iconNode, name: "arrow-down" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/arrow-left.mjs var iconNode2 = [["path", { d: "m12 19-7-7 7-7", key: "1l729n" }], ["path", { d: "M19 12H5", key: "x3x0zl" }]], ArrowLeft = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode2, name: "arrow-left" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/arrow-right.mjs var iconNode3 = [["path", { d: "M5 12h14", key: "1ays0h" }], ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]], ArrowRight = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode3, name: "arrow-right" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/arrow-up.mjs var iconNode4 = [["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }], ["path", { d: "M12 19V5", key: "x0mq9r" }]], ArrowUp = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode4, name: "arrow-up" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/book-open.mjs var iconNode5 = [["path", { d: "M12 5v16", key: "1f6ucr" }], ["path", { d: "M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z", key: "1fyvmf" }]], BookOpen = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode5, name: "book-open" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/check.mjs var iconNode6 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]], Check = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode6, name: "check" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/chevron-left.mjs var iconNode7 = [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]], ChevronLeft = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode7, name: "chevron-left" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/chevron-right.mjs var iconNode8 = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]], ChevronRight = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode8, name: "chevron-right" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/columns-2.mjs var iconNode9 = [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }], ["path", { d: "M12 3v18", key: "108xh3" }]], Columns2 = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode9, name: "columns-2" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/copy.mjs var iconNode10 = [["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }], ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]], Copy = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode10, name: "copy" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/download.mjs var iconNode11 = [["path", { d: "M12 15V3", key: "m9g1x1" }], ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }], ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]], Download = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode11, name: "download" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/ellipsis-vertical.mjs var iconNode12 = [["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }], ["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }], ["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }]], EllipsisVertical = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode12, name: "ellipsis-vertical" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/external-link.mjs var iconNode13 = [["path", { d: "M15 3h6v6", key: "1q9fwt" }], ["path", { d: "M10 14 21 3", key: "gplh6r" }], ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]], ExternalLink = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode13, name: "external-link" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/file.mjs var iconNode14 = [["path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z", key: "1oefj6" }], ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5", key: "wfsgrz" }]], File = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode14, name: "file" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/grid-2x2.mjs var iconNode15 = [["path", { d: "M12 3v18", key: "108xh3" }], ["path", { d: "M3 12h18", key: "1i2n21" }], ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", key: "h1oib" }]], Grid2x2 = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode15, name: "grid-2x2" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/heart.mjs var iconNode16 = [["path", { d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5", key: "mvr1a0" }]], Heart = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode16, name: "heart" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/history.mjs var iconNode17 = [["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "1357e3" }], ["path", { d: "M3 3v5h5", key: "1xhq8a" }], ["path", { d: "M12 7v5l4 2", key: "1fdv2h" }]], History = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode17, name: "history" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/house.mjs var iconNode18 = [["path", { d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8", key: "5wwlr5" }], ["path", { d: "M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z", key: "r6nss1" }]], House = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode18, name: "house" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/hand.mjs var iconNode19 = [["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2", key: "1fvzgz" }], ["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2", key: "1kc0my" }], ["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8", key: "10h0bg" }], ["path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15", key: "1s1gnw" }]], Hand = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode19, name: "hand" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/info.mjs var iconNode20 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], ["path", { d: "M12 16v-4", key: "1dtifu" }], ["path", { d: "M12 8h.01", key: "e9boi3" }]], Info = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode20, name: "info" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/locate-fixed.mjs var iconNode21 = [["line", { x1: "2", x2: "5", y1: "12", y2: "12", key: "bvdh0s" }], ["line", { x1: "19", x2: "22", y1: "12", y2: "12", key: "1tbv5k" }], ["line", { x1: "12", x2: "12", y1: "2", y2: "5", key: "11lu5j" }], ["line", { x1: "12", x2: "12", y1: "19", y2: "22", key: "x3vr5v" }], ["circle", { cx: "12", cy: "12", r: "7", key: "fim9np" }], ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]], LocateFixed = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode21, name: "locate-fixed" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/maximize.mjs var iconNode22 = [["path", { d: "M8 3H5a2 2 0 0 0-2 2v3", key: "1dcmit" }], ["path", { d: "M21 8V5a2 2 0 0 0-2-2h-3", key: "1e4gt3" }], ["path", { d: "M3 16v3a2 2 0 0 0 2 2h3", key: "wsl5sc" }], ["path", { d: "M16 21h3a2 2 0 0 0 2-2v-3", key: "18trek" }]], Maximize = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode22, name: "maximize" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/minimize.mjs var iconNode23 = [["path", { d: "M8 3v3a2 2 0 0 1-2 2H3", key: "hohbtr" }], ["path", { d: "M21 8h-3a2 2 0 0 1-2-2V3", key: "5jw1f3" }], ["path", { d: "M3 16h3a2 2 0 0 1 2 2v3", key: "198tvr" }], ["path", { d: "M16 21v-3a2 2 0 0 1 2-2h3", key: "ph8mxp" }]], Minimize = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode23, name: "minimize" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/move-horizontal.mjs var iconNode24 = [["path", { d: "m18 8 4 4-4 4", key: "1ak13k" }], ["path", { d: "M2 12h20", key: "9i4pu4" }], ["path", { d: "m6 8-4 4 4 4", key: "15zrgr" }]], MoveHorizontal = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode24, name: "move-horizontal" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/move-vertical.mjs var iconNode25 = [["path", { d: "M12 2v20", key: "t6zp3m" }], ["path", { d: "m8 18 4 4 4-4", key: "bh5tu3" }], ["path", { d: "m8 6 4-4 4 4", key: "ybng9g" }]], MoveVertical = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode25, name: "move-vertical" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/palette.mjs var iconNode26 = [["path", { d: "M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z", key: "e79jfc" }], ["circle", { cx: "13.5", cy: "6.5", r: ".5", fill: "currentColor", key: "1okk4w" }], ["circle", { cx: "17.5", cy: "10.5", r: ".5", fill: "currentColor", key: "f64h9f" }], ["circle", { cx: "6.5", cy: "12.5", r: ".5", fill: "currentColor", key: "qy21gx" }], ["circle", { cx: "8.5", cy: "7.5", r: ".5", fill: "currentColor", key: "fotxhn" }]], Palette = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode26, name: "palette" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/pencil.mjs var iconNode27 = [["path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z", key: "1a8usu" }], ["path", { d: "m15 5 4 4", key: "1mk7zo" }]], Pencil = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode27, name: "pencil" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/play.mjs var iconNode28 = [["path", { d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z", key: "10ikf1" }]], Play = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode28, name: "play" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/refresh-cw.mjs var iconNode29 = [["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }], ["path", { d: "M21 3v5h-5", key: "1q7to0" }], ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }], ["path", { d: "M8 16H3v5", key: "1cv678" }]], RefreshCw = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode29, name: "refresh-cw" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/rows-3.mjs var iconNode30 = [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }], ["path", { d: "M21 9H3", key: "1338ky" }], ["path", { d: "M21 15H3", key: "9uk58r" }]], Rows3 = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode30, name: "rows-3" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/scan-line.mjs var iconNode31 = [["path", { d: "M3 7V5a2 2 0 0 1 2-2h2", key: "aa7l1z" }], ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2", key: "4qcy5o" }], ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2", key: "6vwrx8" }], ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2", key: "ioqczr" }], ["path", { d: "M7 12h10", key: "b7w52i" }]], ScanLine = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode31, name: "scan-line" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/search.mjs var iconNode32 = [["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }], ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]], Search = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode32, name: "search" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/settings.mjs var iconNode33 = [["path", { d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915", key: "1i5ecw" }], ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]], Settings = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode33, name: "settings" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/sparkles.mjs var iconNode34 = [["path", { d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z", key: "1s2grr" }], ["path", { d: "M20 2v4", key: "1rf3ol" }], ["path", { d: "M22 4h-4", key: "gwowj6" }], ["circle", { cx: "4", cy: "20", r: "2", key: "6kqj1y" }]], Sparkles = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode34, name: "sparkles" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/star.mjs var iconNode35 = [["path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z", key: "r04s7s" }]], Star = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode35, name: "star" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/x.mjs var iconNode36 = [["path", { d: "M18 6 6 18", key: "1bl5f8" }], ["path", { d: "m6 6 12 12", key: "d8bk6v" }]], X = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode36, name: "x" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/zoom-in.mjs var iconNode37 = [["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }], ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65", key: "13gj7c" }], ["line", { x1: "11", x2: "11", y1: "8", y2: "14", key: "1vmskp" }], ["line", { x1: "8", x2: "14", y1: "11", y2: "11", key: "durymu" }]], ZoomIn = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode37, name: "zoom-in" })); // node_modules/.pnpm/lucide-solid@1.26.0_solid-js@1.9.14/node_modules/lucide-solid/dist/esm/icons/zoom-out.mjs var iconNode38 = [["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }], ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65", key: "13gj7c" }], ["line", { x1: "8", x2: "14", y1: "11", y2: "11", key: "durymu" }]], ZoomOut = (props) => createComponent(Icon, mergeProps(props, { iconNode: iconNode38, name: "zoom-out" })); // src/components/Widgets/Icon.tsx var _tmpl$2 = /* @__PURE__ */ template('