Download static/reader-runtime.js from VoiceOfML/Search: direct link, hf CLI and curl.
- Browser
- Download file 6.62 kB
-
https://huggingface.co/spaces/VoiceOfML/Search/resolve/main/static/reader-runtime.js
- Command line
-
hf download hf://spaces/VoiceOfML/Search/static/reader-runtime.js
-
curl -L -o reader-runtime.js https://huggingface.co/spaces/VoiceOfML/Search/resolve/main/static/reader-runtime.js
6.62 kB
| (function (root) { | |
| "use strict"; | |
| const FEATURE_MATRIX = root.VoiceOfMLReader.features; | |
| function createEventBus() { | |
| const listeners = new Map(); | |
| let disposed = false; | |
| function on(type, listener) { | |
| if (disposed) return () => {}; | |
| const set = listeners.get(type) || new Set(); | |
| set.add(listener); | |
| listeners.set(type, set); | |
| return () => { | |
| set.delete(listener); | |
| if (!set.size) listeners.delete(type); | |
| }; | |
| } | |
| function emit(type, detail) { | |
| if (disposed) return; | |
| for (const listener of [...(listeners.get(type) || []), ...(listeners.get("*") || [])]) | |
| listener(detail, type); | |
| } | |
| function dispose() { | |
| disposed = true; | |
| listeners.clear(); | |
| } | |
| return Object.freeze({ | |
| on, | |
| emit, | |
| dispose, | |
| get disposed() { | |
| return disposed; | |
| } | |
| }); | |
| } | |
| function createReaderRuntime(options = {}) { | |
| const events = options.events || createEventBus(); | |
| const state = { | |
| lifecycle: { phase: "startup", stage: "startup", disposed: false, errorCode: "" }, | |
| source: { id: "", url: "", contentUrl: "", downloadUrl: "", extension: "", metadata: null }, | |
| document: { | |
| title: "", | |
| zoom: 1, | |
| page: 1, | |
| pageCount: 0, | |
| restoredEntry: null, | |
| restorationReady: false | |
| }, | |
| navigation: { tocEntries: [], currentChapterIndex: -1 }, | |
| search: { query: "", results: [], index: -1 }, | |
| panel: { open: false, selected: "toc", showingAllBookmarks: false, editingBookmark: null }, | |
| formats: { active: null, pdf: {}, foliate: {}, html: {}, media: {} }, | |
| capability: null, | |
| ...(options.initialState || {}) | |
| }; | |
| const generations = new Map(); | |
| const disposables = new Set(); | |
| const timers = new Set(); | |
| const frames = new Set(); | |
| function snapshot() { | |
| return Object.freeze({ | |
| lifecycle: Object.freeze({ ...state.lifecycle }), | |
| source: Object.freeze({ ...state.source }), | |
| document: Object.freeze({ ...state.document }), | |
| navigation: Object.freeze({ ...state.navigation }), | |
| search: Object.freeze({ ...state.search }), | |
| panel: Object.freeze({ ...state.panel }), | |
| formats: Object.freeze( | |
| Object.fromEntries( | |
| Object.entries(state.formats).map(([name, value]) => [ | |
| name, | |
| Object.freeze({ ...value }) | |
| ]) | |
| ) | |
| ), | |
| capability: state.capability | |
| }); | |
| } | |
| function update(domain, patch) { | |
| if (state.lifecycle.disposed && domain !== "lifecycle") return false; | |
| Object.assign(state[domain], patch); | |
| const current = snapshot(); | |
| events.emit(`state:${domain}`, current); | |
| events.emit("state", current); | |
| return true; | |
| } | |
| function updateFormat(format, patch) { | |
| if (state.lifecycle.disposed || !state.formats[format]) return false; | |
| Object.assign(state.formats[format], patch); | |
| events.emit(`format:${format}`, snapshot()); | |
| events.emit("state", snapshot()); | |
| return true; | |
| } | |
| function setPhase(phase) { | |
| if (state.lifecycle.disposed && phase !== "disposed") return false; | |
| state.lifecycle.phase = phase; | |
| events.emit("phase", { phase, state: snapshot() }); | |
| return true; | |
| } | |
| function setStage(stage) { | |
| if (!setPhase(stage)) return false; | |
| state.lifecycle.stage = stage; | |
| events.emit("stage", { stage, state: snapshot() }); | |
| return true; | |
| } | |
| function fail(code) { | |
| state.lifecycle.errorCode = code; | |
| setPhase("failed"); | |
| events.emit("error", { code, state: snapshot() }); | |
| } | |
| function nextGeneration(name) { | |
| const value = (generations.get(name) || 0) + 1; | |
| generations.set(name, value); | |
| events.emit("generation", { name, value }); | |
| return value; | |
| } | |
| function currentGeneration(name) { | |
| return generations.get(name) || 0; | |
| } | |
| function isCurrent(name, value) { | |
| return !state.lifecycle.disposed && generations.get(name) === value; | |
| } | |
| function track(disposable) { | |
| if (disposable) disposables.add(disposable); | |
| return disposable; | |
| } | |
| function untrack(disposable) { | |
| disposables.delete(disposable); | |
| } | |
| function schedule(callback, delay = 0) { | |
| const handle = setTimeout(() => { | |
| timers.delete(handle); | |
| callback(); | |
| }, delay); | |
| timers.add(handle); | |
| return handle; | |
| } | |
| function cancel(handle) { | |
| if (!timers.has(handle)) return; | |
| clearTimeout(handle); | |
| timers.delete(handle); | |
| } | |
| function frame(callback) { | |
| const handle = requestAnimationFrame(() => { | |
| frames.delete(handle); | |
| callback(); | |
| }); | |
| frames.add(handle); | |
| return handle; | |
| } | |
| function cancelFrame(handle) { | |
| if (!frames.has(handle)) return; | |
| cancelAnimationFrame(handle); | |
| frames.delete(handle); | |
| } | |
| function negotiate(capability) { | |
| const mode = capability?.mode || "unsupported"; | |
| const value = Object.freeze({ | |
| ...capability, | |
| features: Object.freeze({ | |
| ...(capability?.features || FEATURE_MATRIX[mode] || FEATURE_MATRIX.unsupported) | |
| }) | |
| }); | |
| state.capability = value; | |
| events.emit("capability", value); | |
| return value; | |
| } | |
| function dispose() { | |
| if (state.lifecycle.disposed) return; | |
| state.lifecycle.disposed = true; | |
| state.lifecycle.phase = "disposed"; | |
| for (const name of generations.keys()) nextGeneration(name); | |
| for (const disposable of [...disposables].reverse()) { | |
| try { | |
| if (typeof disposable === "function") disposable(); | |
| else disposable.dispose?.(); | |
| } catch (_) {} | |
| } | |
| for (const handle of timers) clearTimeout(handle); | |
| for (const handle of frames) cancelAnimationFrame(handle); | |
| timers.clear(); | |
| frames.clear(); | |
| disposables.clear(); | |
| events.emit("phase", { phase: "disposed", state: snapshot() }); | |
| events.emit("dispose", snapshot()); | |
| events.dispose(); | |
| } | |
| return Object.freeze({ | |
| state, | |
| events, | |
| snapshot, | |
| update, | |
| updateFormat, | |
| setPhase, | |
| setStage, | |
| fail, | |
| nextGeneration, | |
| currentGeneration, | |
| isCurrent, | |
| track, | |
| untrack, | |
| schedule, | |
| cancel, | |
| frame, | |
| cancelFrame, | |
| negotiate, | |
| dispose | |
| }); | |
| } | |
| root.VoiceOfMLReaderRuntime = Object.freeze({ | |
| FEATURE_MATRIX, | |
| createEventBus, | |
| createReaderRuntime | |
| }); | |
| })(typeof self !== "undefined" ? self : globalThis); | |