"use strict"; // Explicit CCRD pilot bridge. No URL text import, automatic synthesis or wildcard // postMessage. Parent identity and exact iframe Window are both mandatory. (() => { if (!embeddedReader) return; const PARENT = "https://vomebook-search.hf.space"; let channel = "", imported = null, replacement = null, modal = false; const title = document.createElement("p"); title.className = "note embed-source"; document.querySelector("main").prepend(title); const confirmation = document.createElement("div"); confirmation.className = "actions"; confirmation.hidden = true; const replace = document.createElement("button"), keep = document.createElement("button"); replace.type = keep.type = "button"; replace.id = "embed-replace"; keep.id = "embed-keep"; replace.textContent = "停止原朗读并替换文字"; keep.textContent = "保留原朗读"; confirmation.append(replace, keep); title.after(confirmation); function send(type, extra = {}) { if (channel) window.parent.postMessage({ type, channel, ...extra }, PARENT); } function validText(text, max, allowEmpty = false) { if (typeof text !== "string" || (!allowEmpty && !text.trim())) return false; let count = 0; for (const _ of text) if (++count > max) return false; return true; } function focusable() { return Array.from(document.querySelectorAll("button, a[href], input, select, textarea, audio[controls], [tabindex]:not([tabindex='-1'])")) .filter(node => !node.disabled && !node.hidden && node.getClientRects().length); } function load(data) { replacement = null; confirmation.hidden = true; stop({ message: "已载入 CCRD 正文;确认文字后点击合成才向语音 API 提交。" }); imported = { text: data.text, context_before: data.context_before, context_after: data.context_after }; bookmark = null; $("#remember").checked = false; $("#speech-text").value = data.text; $("#reader-view").hidden = true; $("#reader-source").replaceChildren(); $("#position").textContent = ""; $("#metrics").textContent = ""; $("#reading-progress").value = 0; title.textContent = "CCRD · " + data.title; controls(); send("ccrd-reader-loaded", { characters: Array.from(data.text).length }); } replace.addEventListener("click", () => { if (replacement) load(replacement); }); keep.addEventListener("click", () => { replacement = null; confirmation.hidden = true; send("ccrd-reader-rejected"); }); window.HitokotoEmbed = { context(plan, text) { if (!imported || text !== imported.text || !plan.length) return; plan[0].context_before = imported.context_before; plan[plan.length - 1].context_after = imported.context_after; }, }; window.addEventListener("message", (event) => { const data = event.data; if (event.origin !== PARENT || event.source !== window.parent || !data || typeof data.channel !== "string" || !/^[a-f0-9]{48}$/.test(data.channel)) return; if (data.type === "ccrd-reader-init" && data.version === 1) { if (channel && channel !== data.channel) return; channel = data.channel; modal = data.modal === true; send("ccrd-reader-ready", { version: 1, max_characters: ReaderPlan.maxText }); return; } if (!channel || data.channel !== channel) return; if (data.type === "ccrd-reader-import") { if (!validText(data.text, ReaderPlan.maxText) || !validText(data.title, 200, true) || !validText(data.context_before, 32, true) || !validText(data.context_after, 32, true)) { send("ccrd-reader-rejected"); return; } const payload = { text: data.text, title: data.title, context_before: data.context_before, context_after: data.context_after }; if (session) { // Native confirm() is restricted in cross-origin iframes in some // browsers. Keep this explicit user decision inside the visible UI. replacement = payload; confirmation.hidden = false; send("ccrd-reader-pending"); return; } load(payload); } else if (data.type === "ccrd-reader-theme" && typeof data.dark === "boolean") { document.documentElement.style.colorScheme = data.dark ? "dark" : "light"; } else if (data.type === "ccrd-reader-layout" && typeof data.modal === "boolean") { modal = data.modal; } else if (data.type === "ccrd-reader-focus" && modal && typeof data.last === "boolean") { const nodes = focusable(); (data.last ? nodes[nodes.length - 1] : nodes[0])?.focus(); } else if (data.type === "ccrd-reader-stop") { replacement = null; confirmation.hidden = true; stop({ message: "已停止,当前页面内可从进度继续" }); send("ccrd-reader-stopped"); } }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && event.target.tagName !== "SELECT") { event.preventDefault(); send("ccrd-reader-collapse"); } if (modal && event.key === "Tab") { const nodes = focusable(); if ((!event.shiftKey && event.target === nodes[nodes.length - 1]) || (event.shiftKey && event.target === nodes[0])) { event.preventDefault(); send("ccrd-reader-focus-out", { reverse: event.shiftKey }); } } }); $("#speech-text").value = ""; $("#remember").checked = false; title.textContent = "CCRD 朗读试点:请从搜索站载入选文或预览正文。仅在当前页面保留进度,不自动保存正文。"; })();