Files
2026-09-21 13:41:40 +09:00

517 lines
30 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
"use strict";
// This fixed program runs only in Qt's isolated ApplicationWorld. It adds
// no DOM nodes, IDs, bridges, document scripts, or host capabilities.
if (globalThis.__docviewReader) return;
const MAX_INDEX = 2000;
let headings = [];
let options = { fixed: false, rtl: false, publisherStyle: true };
let originalTypography = null;
let focusedLink = null;
let linkOutline = null;
let restoredRange = null;
let layoutAnchor = null, resourceRestoreFrame = 0;
let userScrollPending = false, userScrollFrame = 0;
let navigationTargets = [], navigationRevision = "";
let resourceIssues = Object.create(null);
// Only aggregate directive classes. Never copy blockedURI, sourceFile,
// originalPolicy or sample into the application or its saved state.
document.addEventListener("securitypolicyviolation", event => {
if (!event.isTrusted || event.disposition !== "enforce") return;
const directive = String(event.effectiveDirective || "").split("-")[0];
const category = ({img:"image", style:"style", font:"font", script:"script", frame:"frame",
child:"frame", connect:"connect", form:"form", base:"base", object:"object", media:"media"})[directive] || "other";
const key = "csp." + category;
resourceIssues[key] = Math.min(999999, (resourceIssues[key] || 0) + 1);
});
function text(node, limit = 512) {
return String(node ? node.textContent || "" : "").replace(/\s+/g, " ").trim().slice(0, limit);
}
function visible(element) {
if (!element || !element.isConnected || element.hidden || element.closest("[hidden],[aria-hidden='true']")) return false;
const style = getComputedStyle(element);
return style.display !== "none" && style.visibility !== "hidden" && style.visibility !== "collapse" && element.getClientRects().length > 0;
}
function axis() {
const style = getComputedStyle(document.body || document.documentElement);
const vertical = /^(vertical|sideways)/.test(style.writingMode);
return { vertical, reverse: vertical && /-rl$/.test(style.writingMode), writingMode: style.writingMode };
}
function progression() {
const root = document.scrollingElement || document.documentElement;
const a = axis();
const total = a.vertical ? root.scrollWidth - innerWidth : root.scrollHeight - innerHeight;
const position = a.vertical ? (a.reverse ? -scrollX : scrollX) : scrollY;
return total > 0 ? Math.max(0, Math.min(1, position / total)) : 0;
}
function setProgression(value) {
const root = document.scrollingElement || document.documentElement;
const a = axis();
const p = Math.max(0, Math.min(1, Number(value) || 0));
const total = a.vertical ? root.scrollWidth - innerWidth : root.scrollHeight - innerHeight;
if (a.vertical) scrollTo({ left: Math.max(0, total) * p * (a.reverse ? -1 : 1), top: 0, behavior: "instant" });
else scrollTo({ left: 0, top: Math.max(0, total) * p, behavior: "instant" });
}
function hrefFor(reference, base = document.baseURI) {
try {
const url = new URL(reference, base);
if (url.protocol !== "doc:" || url.host !== location.host || url.username || url.password) return "";
return url.pathname.replace(/^\//, "") + url.search + url.hash;
} catch (_) { return ""; }
}
function headingIndex() {
headings = [];
const nodes = document.querySelectorAll("h1,h2,h3,h4,h5,h6,[role='heading']");
let truncated = false;
for (const node of nodes) {
if (!visible(node)) continue;
const name = node.localName.toLowerCase();
const nativeLevel = /^h[1-6]$/.test(name) ? Number(name[1]) : 0;
const ariaLevel = Number(node.getAttribute("aria-level"));
const level = nativeLevel || (Number.isInteger(ariaLevel) && ariaLevel > 0 && ariaLevel <= 6 ? ariaLevel : 0);
if (!level) continue;
if (headings.length === MAX_INDEX) { truncated = true; break; }
headings.push({ element: node, level, title: text(node) || "見出し(レベル" + level + ")" });
}
return { truncated, rows: headings.map((h, index) => ({ index, title: h.title, level: h.level, depth: h.level - 1,
href: h.element.id ? hrefFor("#" + encodeURIComponent(h.element.id), location.href) : "", source: "html-heading", precision: "exact" })) };
}
function index() {
const result = headingIndex();
const outline = [];
const navs = document.querySelectorAll("[role='doc-toc'],nav");
for (const nav of navs) {
// XHTML keeps epub:type in its XML namespace; HTML retains the
// prefixed attribute name. CSS attribute selectors do not match both.
const epubType = nav.getAttributeNS("http://www.idpf.org/2007/ops", "type") || nav.getAttribute("epub:type") || "";
if (nav.getAttribute("role") !== "doc-toc" && !epubType.split(/\s+/).includes("toc")) continue;
for (const link of nav.querySelectorAll("a[href]")) {
if (!visible(link) || outline.length >= MAX_INDEX) continue;
let depth = 0;
for (let parent = link.parentElement; parent && parent !== nav; parent = parent.parentElement)
if (parent.localName.toLowerCase() === "ol" || parent.localName.toLowerCase() === "ul") ++depth;
const href = hrefFor(link.getAttribute("href"));
outline.push({ title: text(link) || "目次項目", href, depth: Math.max(0, depth - 1), missing: !href, source: "html-toc" });
}
}
return { title: String(document.title || "").slice(0, 4096), headings: result.rows, outline,
truncated: result.truncated, scriptCount: document.scripts.length,
fixedViewport: viewport(), writingMode: axis().writingMode };
}
function viewport() {
const element = document.querySelector("meta[name='viewport']");
const value = element ? element.getAttribute("content") || "" : "";
const w = /(?:^|[,;\s])width\s*=\s*([0-9.]+)/i.exec(value);
const h = /(?:^|[,;\s])height\s*=\s*([0-9.]+)/i.exec(value);
let width = w ? Number(w[1]) : 0, height = h ? Number(h[1]) : 0;
if ((!width || !height) && document.documentElement.localName === "svg") {
const box = document.documentElement.viewBox && document.documentElement.viewBox.baseVal;
if (box) { width = box.width; height = box.height; }
}
return width > 0 && height > 0 && width <= 100000 && height <= 100000 ? { width, height } : {};
}
function cfiFor(node, offset = 0) {
const steps = [];
let target = node;
if (!target) return "";
if (target.nodeType === Node.TEXT_NODE) {
let elements = 0, previousText = 0;
for (const sibling of target.parentNode.childNodes) {
if (sibling === target) break;
if (sibling.nodeType === Node.ELEMENT_NODE) { ++elements; previousText = 0; }
if (sibling.nodeType === Node.TEXT_NODE) previousText += sibling.length;
}
steps.unshift(elements * 2 + 1);
offset += previousText;
target = target.parentElement;
}
while (target && target !== document.documentElement && steps.length < 128) {
let order = 1;
for (let sibling = target.previousElementSibling; sibling; sibling = sibling.previousElementSibling) ++order;
steps.unshift(order * 2);
target = target.parentElement;
}
return target === document.documentElement ? "epubcfi(/" + steps.join("/") + ":" + Math.max(0, offset) + ")" : "";
}
function resolveCfi(cfi) {
if (typeof cfi !== "string" || cfi.length > 4096 || !/^epubcfi\(.*\)$/.test(cfi)) return null;
let value = cfi.slice(8, -1);
if (value.includes("!")) value = value.slice(value.lastIndexOf("!") + 1);
value = value.replace(/\[(?:\^.|[^\]])*\]/g, "");
if (!/^\/(?:[1-9][0-9]*\/)*[1-9][0-9]*(?::[0-9]+)?$/.test(value)) return null;
const parts = value.split(":");
const steps = parts[0].slice(1).split("/").map(Number);
if (steps.length > 128) return null;
let node = document.documentElement;
let offset = Number(parts[1] || 0);
for (let i = 0; i < steps.length; ++i) {
const step = steps[i];
if (step % 2 === 0) node = node && node.children && node.children[step / 2 - 1];
else {
if (!node || i !== steps.length - 1) return null;
const slot = (step - 1) / 2;
let elements = 0, found = null;
for (const child of node.childNodes) {
if (child.nodeType === Node.ELEMENT_NODE) ++elements;
else if (child.nodeType === Node.TEXT_NODE && elements === slot) {
if (offset <= child.length) { found = child; break; }
offset -= child.length;
}
}
node = found;
}
if (!node) return null;
}
const range = document.createRange();
if (node.nodeType === Node.TEXT_NODE) range.setStart(node, Math.min(offset, node.length));
else range.setStart(node, Math.min(offset, node.childNodes.length));
range.collapse(true);
return range;
}
function visibleRange() {
const a = axis();
const x = Math.max(1, Math.min(innerWidth - 1, a.vertical && a.reverse ? innerWidth - 20 : 20));
const y = Math.max(1, Math.min(innerHeight - 1, 20));
let range = document.caretRangeFromPoint ? document.caretRangeFromPoint(x, y) : null;
if (range && range.startContainer.nodeType === Node.TEXT_NODE) return range;
const element = document.elementFromPoint(x, y) || document.body || document.documentElement;
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
let node, inspected = 0;
while ((node = walker.nextNode()) && ++inspected < 1000) {
if (!node.textContent.trim() || !visible(node.parentElement)) continue;
range = document.createRange(); range.setStart(node, 0); range.collapse(true); return range;
}
range = document.createRange(); range.selectNodeContents(element); range.collapse(true); return range;
}
function locate() {
// A font can change layout before its loadingdone event, and a location
// poll can run before the image's animation-frame restoration. Retain
// the pre-layout anchor until that restoration has used it.
if (layoutAnchor && layoutMoved(layoutAnchor)) resourcesSettled();
// Reflow can put a different character at the viewport sampling point.
// Keep the restored logical character until the reader actually scrolls.
const keep = restoredRange && document.documentElement.contains(restoredRange.range.startContainer) &&
Math.abs(scrollX - restoredRange.x) < 1 && Math.abs(scrollY - restoredRange.y) < 1;
if (!keep) restoredRange = null;
const range = keep ? restoredRange.range : visibleRange();
const node = range.startContainer;
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
const rect = range.getBoundingClientRect();
const result = { url: location.href, cfi: cfiFor(node, range.startOffset), fragment: element && element.id || "",
progression: progression(), textQuote: node.nodeType === Node.TEXT_NODE ? node.textContent.slice(range.startOffset, range.startOffset + 160) : text(element, 160),
offsetX: rect.left / Math.max(1, innerWidth), offsetY: rect.top / Math.max(1, innerHeight), writingMode: axis().writingMode,
currentOutline: currentNavigation(range), navigationRevision };
if (!resourceRestoreFrame) layoutAnchor = { range: range.cloneRange(), saved: result,
x:scrollX, y:scrollY, documentX:rect.left + scrollX, documentY:rect.top + scrollY,
width:innerWidth, height:innerHeight };
return result;
}
function layoutMoved(anchor) {
if (!anchor.range.startContainer.isConnected || anchor.width !== innerWidth || anchor.height !== innerHeight) return false;
const rect = anchor.range.getBoundingClientRect();
return Math.abs(rect.left + scrollX - anchor.documentX) > 1 || Math.abs(rect.top + scrollY - anchor.documentY) > 1;
}
function cancelResourceRestore() {
if (resourceRestoreFrame) cancelAnimationFrame(resourceRestoreFrame);
resourceRestoreFrame = 0;
layoutAnchor = null;
}
function resourcesSettled() {
if (resourceRestoreFrame || !layoutAnchor || options.fixed) return;
const anchor = layoutAnchor;
resourceRestoreFrame = requestAnimationFrame(() => {
resourceRestoreFrame = 0;
// Retain the character and its viewport fraction across a decoded
// image or font swap. Never insert helper nodes into the source DOM.
// Clear the old snapshot before locate(), otherwise it would see
// the same completed layout change and schedule another frame.
const current = layoutAnchor === anchor;
if (current) layoutAnchor = null;
if (current && anchor.range.startContainer.isConnected)
positionRange(anchor.range, anchor.saved);
userScrollPending = false;
locate();
});
}
document.addEventListener("load", event => {
if (event.isTrusted && event.target instanceof HTMLImageElement) resourcesSettled();
}, true);
document.addEventListener("error", event => {
if (event.isTrusted && event.target instanceof HTMLImageElement) resourcesSettled();
}, true);
if (document.fonts) {
document.fonts.addEventListener("loadingdone", resourcesSettled);
document.fonts.addEventListener("loadingerror", resourcesSettled);
}
for (const type of ["wheel", "touchstart", "touchmove", "pointerdown", "pointermove", "keydown"])
document.addEventListener(type, event => {
if (!event.isTrusted || (type === "pointermove" && !event.buttons)) return;
cancelResourceRestore();
// Clicking without scrolling must not leave a 400 ms gap without
// any anchor. A following input scroll still takes precedence.
if (document.body) locate();
userScrollPending = true;
// Input intent applies to this frame, not to an unrelated native
// anchor adjustment long after a click that did not scroll.
if (userScrollFrame) cancelAnimationFrame(userScrollFrame);
userScrollFrame = requestAnimationFrame(() => { userScrollFrame = 0; userScrollPending = false; });
}, {capture:true, passive:true});
document.addEventListener("scroll", () => {
if (layoutAnchor && Math.abs(scrollX - layoutAnchor.x) < 1 && Math.abs(scrollY - layoutAnchor.y) < 1) return;
if (!userScrollPending && layoutAnchor && layoutMoved(layoutAnchor)) {
// Native overflow-anchor can move the scroll offset as resources
// reflow. This is not a new reader navigation.
resourcesSettled();
return;
}
userScrollPending = false;
cancelResourceRestore();
restoredRange = null;
locate();
}, {passive:true});
window.addEventListener("hashchange", () => { cancelResourceRestore(); locate(); });
function configureNavigation(args) {
navigationTargets = []; navigationRevision = "";
if (!args || typeof args.revision !== "string" || !/^[0-9]{1,19}$/.test(args.revision) ||
!Array.isArray(args.targets) || args.targets.length > 20000) return false;
const targets = [];
for (const item of args.targets) {
if (!item || !Number.isInteger(item.row) || item.row < 0 || item.row >= 20000) return false;
let element;
if (Number.isInteger(item.heading) && item.heading >= 0 && item.heading < headings.length)
element = headings[item.heading].element;
else if (typeof item.fragment === "string" && item.fragment.length <= 2048)
element = item.fragment ? document.getElementById(item.fragment) : document.body || document.documentElement;
if (!visible(element)) continue;
const range = document.createRange(); range.selectNodeContents(element); range.collapse(true);
targets.push({ row:item.row, element, range });
}
targets.sort((a, b) => a.range.compareBoundaryPoints(Range.START_TO_START, b.range) || a.row - b.row);
navigationTargets = targets; navigationRevision = args.revision;
return true;
}
function currentNavigation(position) {
// DOM order is stable across zoom/reflow. Avoid measuring every target's
// layout at each 400 ms location poll, even for a 20,000-item EPUB nav.
let low = 0, high = navigationTargets.length;
while (low < high) {
const mid = (low + high) >>> 1;
if (navigationTargets[mid].range.compareBoundaryPoints(Range.START_TO_START, position) <= 0) low = mid + 1;
else high = mid;
}
for (let i = low - 1; i >= 0; --i)
if (visible(navigationTargets[i].element)) return navigationTargets[i].row;
return -1;
}
function positionRange(range, saved) {
const node = range.startContainer;
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
if (!element || !visible(element)) return false;
element.scrollIntoView({ block: "start", inline: "start", behavior: "instant" });
const rect = range.getBoundingClientRect();
const a = axis();
const offset = a.vertical ? Number(saved.offsetX) : Number(saved.offsetY);
const fraction = Number.isFinite(offset) && Math.abs(offset) <= 2 ? offset : 0;
if (a.vertical) scrollBy({ left: rect.left - fraction * innerWidth, behavior: "instant" });
else scrollBy({ top: rect.top - fraction * innerHeight, behavior: "instant" });
restoredRange = { range: range.cloneRange(), x: scrollX, y: scrollY };
return true;
}
function restore(saved) {
if (!saved || typeof saved !== "object") return { restored: false, approximate: true };
let range = resolveCfi(saved.cfi);
if (range && positionRange(range, saved)) return { restored: true, approximate: false };
const fragment = typeof saved.fragment === "string" ? saved.fragment.slice(0, 1024) : "";
const element = fragment ? document.getElementById(fragment) : null;
if (element && visible(element)) {
restoredRange = null;
element.scrollIntoView({ block: "start", inline: "start", behavior: "instant" });
// A failed character-level CFI resolved only to its containing ID.
return { restored: true, approximate: Boolean(saved.cfi) };
}
const quote = typeof saved.textQuote === "string" ? saved.textQuote.slice(0, 160) : "";
if (quote) {
const walker = document.createTreeWalker(document.body || document.documentElement, NodeFilter.SHOW_TEXT);
let node, inspected = 0;
while ((node = walker.nextNode()) && ++inspected < 100000) {
const offset = node.textContent.indexOf(quote);
if (offset >= 0 && visible(node.parentElement)) {
range = document.createRange(); range.setStart(node, offset); range.collapse(true);
if (positionRange(range, saved)) return { restored: true, approximate: true };
}
}
}
setProgression(saved.progression);
return { restored: true, approximate: true };
}
function heading(direction) {
if (!headings.length) headingIndex();
const a = axis();
const candidates = direction > 0 ? headings : headings.slice().reverse();
const seen = new Set();
for (const h of candidates) {
const rect = h.element.getBoundingClientRect();
const distance = a.vertical ? (a.reverse ? innerWidth - rect.right : rect.left) : rect.top;
const position = Math.round(distance);
if (seen.has(position)) continue;
seen.add(position);
if ((direction > 0 && distance > 3) || (direction < 0 && distance < -3)) {
const x = scrollX, y = scrollY;
h.element.scrollIntoView({ block: "start", inline: "start", behavior: "instant" });
// Several final headings can clamp to the same destination.
// Check the current layout's actual result, so a repeated
// destination cannot masquerade as movement or block a boundary.
const after = h.element.getBoundingClientRect();
if (Math.abs(scrollX - x) < 1 && Math.abs(scrollY - y) < 1 &&
Math.abs(after.left - rect.left) < 1 && Math.abs(after.top - rect.top) < 1) continue;
return { moved: true, title: h.title };
}
}
return { moved: false, boundary: direction, empty: !headings.length };
}
function applyOptions(next) {
options = Object.assign({}, options, next || {});
if (!options.fixed && options.publisherStyle === false) {
const size = Number(options.fontSize), height = Number(options.lineHeight);
if (!originalTypography) {
const body = document.body || document.documentElement;
const base = parseFloat(getComputedStyle(body).fontSize) || 16;
const nodes = [document.documentElement, body];
// Inline priorities override authored fixed-pixel declarations.
// Relative heading/ruby sizes survive; DOM nodes and CFI paths do not change.
for (const node of body.querySelectorAll("*")) {
if (node.namespaceURI !== "http://www.w3.org/1999/xhtml") continue;
if (Array.from(node.childNodes).some(child => child.nodeType === Node.TEXT_NODE && child.textContent.trim())) nodes.push(node);
}
originalTypography = Array.from(new Set(nodes)).map(node => ({ node,
hadStyle: node.hasAttribute("style"),
font: node.style.getPropertyValue("font-size"), fontPriority: node.style.getPropertyPriority("font-size"),
line: node.style.getPropertyValue("line-height"), linePriority: node.style.getPropertyPriority("line-height"),
ratio: node === document.documentElement || node === body ? 1 : (parseFloat(getComputedStyle(node).fontSize) || base) / base }));
}
for (const entry of originalTypography) {
if (size >= 12 && size <= 40) entry.node.style.setProperty("font-size", size * entry.ratio + "px", "important");
if (height >= 1 && height <= 2.5) entry.node.style.setProperty("line-height", String(height), "important");
}
} else if (originalTypography) {
for (const entry of originalTypography) {
if (entry.font) entry.node.style.setProperty("font-size", entry.font, entry.fontPriority);
else entry.node.style.removeProperty("font-size");
if (entry.line) entry.node.style.setProperty("line-height", entry.line, entry.linePriority);
else entry.node.style.removeProperty("line-height");
if (!entry.hadStyle && !entry.node.getAttribute("style")) entry.node.removeAttribute("style");
}
originalTypography = null;
}
return { fixed: !!options.fixed, fixedViewport: viewport() };
}
function restoreLinkOutline() {
if (!focusedLink || !linkOutline) return;
for (const entry of linkOutline.properties) {
if (entry.value) focusedLink.style.setProperty(entry.name, entry.value, entry.priority);
else focusedLink.style.removeProperty(entry.name);
}
if (!linkOutline.hadStyle && !focusedLink.getAttribute("style")) focusedLink.removeAttribute("style");
linkOutline = null;
}
function clearLinkSelection() {
restoreLinkOutline();
focusedLink = null;
// selectedLink also supports a natively focused anchor. Clear both
// routes so Enter cannot follow a link after selection is cancelled.
const active = document.activeElement;
if (active && active.localName === "a") active.blur();
return { cleared: true };
}
function focusLink(direction) {
const links = Array.from(document.querySelectorAll("a[href]")).filter(visible).slice(0, 20000);
if (!links.length) return { empty: true };
const current = links.indexOf(focusedLink || document.activeElement);
const index = current < 0 ? (direction > 0 ? 0 : links.length - 1) : (current + direction + links.length) % links.length;
restoreLinkOutline();
focusedLink = links[index];
linkOutline = { hadStyle: focusedLink.hasAttribute("style"),
properties: ["outline-color", "outline-style", "outline-width", "outline-offset"].map(name => ({
name, value: focusedLink.style.getPropertyValue(name), priority: focusedLink.style.getPropertyPriority(name)
})) };
focusedLink.style.outline = "2px solid #008577";
focusedLink.style.outlineOffset = "3px";
focusedLink.focus({ preventScroll: false });
return { focused: true, title: text(focusedLink), index, count: links.length };
}
function selectedLink() {
const element = focusedLink && focusedLink.isConnected ? focusedLink : document.activeElement;
if (!element || element.localName !== "a" || !element.hasAttribute("href")) return { empty: true };
try {
const url = new URL(element.getAttribute("href"), document.baseURI);
if (!["doc:", "http:", "https:"].includes(url.protocol) || url.username || url.password) return { blocked: true };
if (url.protocol === "doc:" && url.host !== location.host) return { blocked: true };
return { url: url.href.slice(0, 8192), title: text(element) };
} catch (_) { return { blocked: true }; }
}
function executeCommand(id, args) {
args = args && typeof args === "object" ? args : {};
const a = axis();
const step = Math.max(1, Math.min(100, Number(args.count) || 1));
switch (id) {
case "resource.issues": {
const result = resourceIssues;
resourceIssues = Object.create(null);
return result;
}
case "index": return index();
case "location": return locate();
case "navigation.configure": return configureNavigation(args);
case "location.restore": return restore(args);
case "layout": return applyOptions(args);
case "link.next": return focusLink(1);
case "link.previous": return focusLink(-1);
case "link.clear": return clearLinkSelection();
case "link.activate": return selectedLink();
case "scroll.down": scrollBy({ top: 48 * step, behavior: "instant" }); break;
case "scroll.up": scrollBy({ top: -48 * step, behavior: "instant" }); break;
case "scroll.left": scrollBy({ left: -48 * step, behavior: "instant" }); break;
case "scroll.right": scrollBy({ left: 48 * step, behavior: "instant" }); break;
case "scroll.half_down": case "scroll.half_up": case "scroll.page_down": case "scroll.page_up": {
const forward = id.endsWith("down") ? 1 : -1;
const size = id.includes("half") ? 0.5 : 0.9;
scrollBy(a.vertical ? { left: innerWidth * size * forward * (a.reverse ? -1 : 1), behavior: "instant" }
: { top: innerHeight * size * forward, behavior: "instant" }); break;
}
case "nav.document_start": setProgression(0); break;
case "nav.document_end": setProgression(1); break;
case "nav.heading_next": return heading(1);
case "nav.heading_previous": return heading(-1);
case "heading.first": if (!headings.length) headingIndex(); if (headings.length) headings[0].element.scrollIntoView({block:"start",inline:"start"}); return {moved:!!headings.length};
case "heading.last": if (!headings.length) headingIndex(); if (headings.length) headings[headings.length-1].element.scrollIntoView({block:"start",inline:"start"}); return {moved:!!headings.length};
case "heading.index": {
const index = Number(args.index);
if (Number.isInteger(index) && index >= 0 && index < headings.length) headings[index].element.scrollIntoView({ block: "start", inline: "start", behavior: "instant" });
break;
}
default: return { handled: false };
}
return { handled: true };
}
function command(id, args) {
const passive = ["location", "index", "navigation.configure", "resource.issues"].includes(id);
if (!passive) { cancelResourceRestore(); userScrollPending = false; }
try { return executeCommand(id, args); }
finally { if (!passive && document.body) locate(); }
}
function block(event) { event.preventDefault(); event.stopImmediatePropagation(); }
document.addEventListener("submit", block, true);
document.addEventListener("reset", block, true);
document.addEventListener("beforeinput", block, true);
document.addEventListener("drop", block, true);
document.addEventListener("click", event => {
if (event.target.closest && event.target.closest("button,input,select,textarea,[contenteditable]")) block(event);
}, true);
document.addEventListener("keydown", event => {
if (event.target.closest && event.target.closest("button,input,select,textarea,[contenteditable]") && !(event.ctrlKey && /^(a|c)$/i.test(event.key))) block(event);
}, true);
document.addEventListener("play", event => { if (event.target.pause) event.target.pause(); }, true);
globalThis.__docviewReader = Object.freeze({ command, index, location: locate, restore });
})();