Convert AI chat to Word documents — powered by Pandoc WASM
// ==UserScript==
// @name Give Me Doc
// @name:zh-CN Give Me Doc — AI 对话导出 Word
// @namespace https://github.com/Qalxry/GiveMeDoc
// @version 1.0.0
// @description Convert AI chat to Word documents — powered by Pandoc WASM
// @description:zh-CN 将 AI 对话导出为 Word 文档 — 由 Pandoc WASM 驱动
// @author GiveMeDoc Contributors
// @homepageURL https://github.com/Qalxry/GiveMeDoc
// @supportURL https://github.com/Qalxry/GiveMeDoc/issues
// @icon https://raw.githubusercontent.com/Qalxry/GiveMeDoc/main/public/icons/logo.svg
// @match https://chat.deepseek.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @connect pandoc.org
// @connect cdn.jsdelivr.net
// @noframes
// @run-at document-idle
// @license AGPL-3.0
// ==/UserScript==
(function() {
"use strict";
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
const proxyMarker = Symbol("Comlink.proxy");
const createEndpoint = Symbol("Comlink.endpoint");
const releaseProxy = Symbol("Comlink.releaseProxy");
const finalizer = Symbol("Comlink.finalizer");
const throwMarker = Symbol("Comlink.thrown");
const isObject = (val) => typeof val === "object" && val !== null || typeof val === "function";
const proxyTransferHandler = {
canHandle: (val) => isObject(val) && val[proxyMarker],
serialize(obj) {
const { port1, port2 } = new MessageChannel();
expose(obj, port1);
return [port2, [port2]];
},
deserialize(port) {
port.start();
return wrap(port);
}
};
const throwTransferHandler = {
canHandle: (value) => isObject(value) && throwMarker in value,
serialize({ value }) {
let serialized;
if (value instanceof Error) {
serialized = {
isError: true,
value: {
message: value.message,
name: value.name,
stack: value.stack
}
};
} else {
serialized = { isError: false, value };
}
return [serialized, []];
},
deserialize(serialized) {
if (serialized.isError) {
throw Object.assign(new Error(serialized.value.message), serialized.value);
}
throw serialized.value;
}
};
const transferHandlers = /* @__PURE__ */ new Map([
["proxy", proxyTransferHandler],
["throw", throwTransferHandler]
]);
function isAllowedOrigin(allowedOrigins, origin) {
for (const allowedOrigin of allowedOrigins) {
if (origin === allowedOrigin || allowedOrigin === "*") {
return true;
}
if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
return true;
}
}
return false;
}
function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
ep.addEventListener("message", function callback(ev) {
if (!ev || !ev.data) {
return;
}
if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
return;
}
const { id, type, path } = Object.assign({ path: [] }, ev.data);
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
let returnValue;
try {
const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);
const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);
switch (type) {
case "GET":
{
returnValue = rawValue;
}
break;
case "SET":
{
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
returnValue = true;
}
break;
case "APPLY":
{
returnValue = rawValue.apply(parent, argumentList);
}
break;
case "CONSTRUCT":
{
const value = new rawValue(...argumentList);
returnValue = proxy(value);
}
break;
case "ENDPOINT":
{
const { port1, port2 } = new MessageChannel();
expose(obj, port2);
returnValue = transfer(port1, [port1]);
}
break;
case "RELEASE":
{
returnValue = void 0;
}
break;
default:
return;
}
} catch (value) {
returnValue = { value, [throwMarker]: 0 };
}
Promise.resolve(returnValue).catch((value) => {
return { value, [throwMarker]: 0 };
}).then((returnValue2) => {
const [wireValue, transferables] = toWireValue(returnValue2);
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
if (type === "RELEASE") {
ep.removeEventListener("message", callback);
closeEndPoint(ep);
if (finalizer in obj && typeof obj[finalizer] === "function") {
obj[finalizer]();
}
}
}).catch((error) => {
const [wireValue, transferables] = toWireValue({
value: new TypeError("Unserializable return value"),
[throwMarker]: 0
});
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
});
});
if (ep.start) {
ep.start();
}
}
function isMessagePort(endpoint) {
return endpoint.constructor.name === "MessagePort";
}
function closeEndPoint(endpoint) {
if (isMessagePort(endpoint))
endpoint.close();
}
function wrap(ep, target) {
const pendingListeners = /* @__PURE__ */ new Map();
ep.addEventListener("message", function handleMessage(ev) {
const { data } = ev;
if (!data || !data.id) {
return;
}
const resolver = pendingListeners.get(data.id);
if (!resolver) {
return;
}
try {
resolver(data);
} finally {
pendingListeners.delete(data.id);
}
});
return createProxy(ep, pendingListeners, [], target);
}
function throwIfProxyReleased(isReleased) {
if (isReleased) {
throw new Error("Proxy has been released and is not useable");
}
}
function releaseEndpoint(ep) {
return requestResponseMessage(ep, /* @__PURE__ */ new Map(), {
type: "RELEASE"
}).then(() => {
closeEndPoint(ep);
});
}
const proxyCounter = /* @__PURE__ */ new WeakMap();
const proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry((ep) => {
const newCount = (proxyCounter.get(ep) || 0) - 1;
proxyCounter.set(ep, newCount);
if (newCount === 0) {
releaseEndpoint(ep);
}
});
function registerProxy(proxy2, ep) {
const newCount = (proxyCounter.get(ep) || 0) + 1;
proxyCounter.set(ep, newCount);
if (proxyFinalizers) {
proxyFinalizers.register(proxy2, ep, proxy2);
}
}
function unregisterProxy(proxy2) {
if (proxyFinalizers) {
proxyFinalizers.unregister(proxy2);
}
}
function createProxy(ep, pendingListeners, path = [], target = function() {
}) {
let isProxyReleased = false;
const proxy2 = new Proxy(target, {
get(_target, prop) {
throwIfProxyReleased(isProxyReleased);
if (prop === releaseProxy) {
return () => {
unregisterProxy(proxy2);
releaseEndpoint(ep);
pendingListeners.clear();
isProxyReleased = true;
};
}
if (prop === "then") {
if (path.length === 0) {
return { then: () => proxy2 };
}
const r = requestResponseMessage(ep, pendingListeners, {
type: "GET",
path: path.map((p) => p.toString())
}).then(fromWireValue);
return r.then.bind(r);
}
return createProxy(ep, pendingListeners, [...path, prop]);
},
set(_target, prop, rawValue) {
throwIfProxyReleased(isProxyReleased);
const [value, transferables] = toWireValue(rawValue);
return requestResponseMessage(ep, pendingListeners, {
type: "SET",
path: [...path, prop].map((p) => p.toString()),
value
}, transferables).then(fromWireValue);
},
apply(_target, _thisArg, rawArgumentList) {
throwIfProxyReleased(isProxyReleased);
const last = path[path.length - 1];
if (last === createEndpoint) {
return requestResponseMessage(ep, pendingListeners, {
type: "ENDPOINT"
}).then(fromWireValue);
}
if (last === "bind") {
return createProxy(ep, pendingListeners, path.slice(0, -1));
}
const [argumentList, transferables] = processArguments(rawArgumentList);
return requestResponseMessage(ep, pendingListeners, {
type: "APPLY",
path: path.map((p) => p.toString()),
argumentList
}, transferables).then(fromWireValue);
},
construct(_target, rawArgumentList) {
throwIfProxyReleased(isProxyReleased);
const [argumentList, transferables] = processArguments(rawArgumentList);
return requestResponseMessage(ep, pendingListeners, {
type: "CONSTRUCT",
path: path.map((p) => p.toString()),
argumentList
}, transferables).then(fromWireValue);
}
});
registerProxy(proxy2, ep);
return proxy2;
}
function myFlat(arr) {
return Array.prototype.concat.apply([], arr);
}
function processArguments(argumentList) {
const processed = argumentList.map(toWireValue);
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
}
const transferCache = /* @__PURE__ */ new WeakMap();
function transfer(obj, transfers) {
transferCache.set(obj, transfers);
return obj;
}
function proxy(obj) {
return Object.assign(obj, { [proxyMarker]: true });
}
function toWireValue(value) {
for (const [name, handler] of transferHandlers) {
if (handler.canHandle(value)) {
const [serializedValue, transferables] = handler.serialize(value);
return [
{
type: "HANDLER",
name,
value: serializedValue
},
transferables
];
}
}
return [
{
type: "RAW",
value
},
transferCache.get(value) || []
];
}
function fromWireValue(value) {
switch (value.type) {
case "HANDLER":
return transferHandlers.get(value.name).deserialize(value.value);
case "RAW":
return value.value;
}
}
function requestResponseMessage(ep, pendingListeners, msg, transfers) {
return new Promise((resolve) => {
const id = generateUUID();
pendingListeners.set(id, resolve);
if (ep.start) {
ep.start();
}
ep.postMessage(Object.assign({ id }, msg), transfers);
});
}
function generateUUID() {
return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");
}
let worker = null;
let pandoc = null;
let _ready = false;
let _version = "";
async function initPandoc(wasmSource, workerUrlOrInstance) {
if (_ready) return;
worker = workerUrlOrInstance instanceof Worker ? workerUrlOrInstance : new Worker(workerUrlOrInstance, { type: "module" });
pandoc = wrap(worker);
await pandoc.init(wasmSource);
_version = await pandoc.getVersion();
if (_version.startsWith('"') && _version.endsWith('"')) {
_version = _version.slice(1, -1);
}
_ready = true;
}
function isPandocReady() {
return _ready;
}
async function getPandocVersion() {
if (!_ready || !pandoc) return "";
return _version;
}
function formatMarkdown(raw) {
let s = raw;
const codeSlots = [];
function stash(m) {
codeSlots.push(m);
return `\0CODE${codeSlots.length - 1}\0`;
}
s = s.replace(/^(`{3,}|~{3,}).*\n[\s\S]*?\n\1\s*$/gm, stash);
s = s.replace(/`[^`]+`/g, stash);
s = s.replace(/\\\((.+?)\\\)/gs, (_, inner) => `$${inner}$`);
s = s.replace(/\\\[(.+?)\\\]/gs, (_, inner) => `$$${inner}$$`);
s = s.replace(
/(?<!\$)\$(?!\$)([^\S\n]*)([^\$\n]+?)([^\S\n]*)\$(?!\$)/g,
(match, pre, inner, post) => {
const trimmed = inner.trim();
if (trimmed === inner && !pre && !post) return match;
return `$${trimmed}$`;
}
);
s = s.replace(/\[citation:\d+\]/g, "");
s = s.replace(/\n{3,}/g, "\n\n");
s = s.replace(/^([^\S\n]*)[•*+]\s/gm, "$1- ");
s = s.replace(/\x00CODE(\d+)\x00/g, (_, idx) => codeSlots[Number(idx)]);
return s;
}
function assembleDocument(messages, config2, sessionTitle) {
const now = /* @__PURE__ */ new Date();
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
let doc = config2.documentPrefix.replace(/\{title\}/g, sessionTitle).replace(/\{output_date\}/g, dateStr);
for (const msg of messages) {
const template = msg.role === "user" ? config2.userMessageTemplate : config2.assistantMessageTemplate;
doc += renderTemplate(template, msg, config2);
}
return doc;
}
function renderTemplate(template, msg, config2) {
let result = template;
result = result.replace(/\{content\}/g, () => msg.content);
if (!config2.includeThinking || !msg.thinkingContent) {
result = result.replace(/^>?\s*\{thinking_content\}\s*\n?/gm, "");
result = result.replace(/^>\s*$/gm, "");
result = result.replace(/\n{3,}/g, "\n\n");
} else {
const thinkLines = msg.thinkingContent.split("\n");
result = result.replace(
/^(>\s*)\{thinking_content\}/gm,
(_, prefix) => thinkLines.map((l) => `${prefix}${l}`).join("\n")
);
result = result.replace(/\{thinking_content\}/g, () => msg.thinkingContent);
}
return result;
}
const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
async function exportToDocx(messages, config2, sessionTitle, referenceDocx) {
if (!pandoc) throw new Error("Pandoc Worker not initialized");
const raw = assembleDocument(messages, config2, sessionTitle);
console.debug("[GiveMeDoc] Assembled Markdown before formatting:", "\n" + raw);
const formatted = formatMarkdown(raw);
console.debug("[GiveMeDoc] Formatted Markdown for export:", "\n" + formatted);
const buffer = await pandoc.convert(formatted, referenceDocx, config2.lineBreaks);
const safeName = sessionTitle.replace(/[<>:"/\\|?*]/g, "_").slice(0, 100) || "export";
return {
blob: new Blob([buffer], { type: DOCX_MIME }),
filename: `${safeName}.docx`
};
}
async function exportRawToDocx(markdown, filename, referenceDocx, lineBreaks) {
if (!pandoc) throw new Error("Pandoc Worker not initialized");
console.debug("[GiveMeDoc] Raw Markdown before formatting:", "\n" + markdown);
const formatted = formatMarkdown(markdown);
console.debug("[GiveMeDoc] Formatted raw Markdown for export:", "\n" + formatted);
const buffer = await pandoc.convert(formatted, referenceDocx, lineBreaks);
const safeName = filename.replace(/[<>:"/\\|?*]/g, "_").slice(0, 100) || "export";
return {
blob: new Blob([buffer], { type: DOCX_MIME }),
filename: `${safeName}.docx`
};
}
function downloadBlob(blob2, filename) {
const url = URL.createObjectURL(blob2);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
const S = 'xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
const ICON_LOGO = `<svg ${S}><rect x="1" y="1" width="22" height="22" rx="6.5" ry="6.5" fill="#6750a4"/> <path d="M7.5 4.5 H14.5 L17.5 7.5 V18.5 Q17.5 19.5 16.5 19.5 H7.5 Q6.5 19.5 6.5 18.5 V5.5 Q6.5 4.5 7.5 4.5 Z" fill="white" fill-opacity="0.9"/> <path d="M14.5 4.5 V6.5 Q14.5 7.5 15.5 7.5 H17.5 L14.5 4.5 Z" fill="#a193c6" fill-opacity="1.0"/> <line x1="9.3" y1="8.4" x2="12.7" y2="8.4" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.375" stroke-linecap="round"/> <line x1="9.3" y1="10.8" x2="14.8" y2="10.8" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.375" stroke-linecap="round"/> <g stroke="#6750a4" stroke-width="1.375" stroke-linecap="round" stroke-linejoin="round"> <line x1="12" y1="13" x2="12" y2="17.25" opacity="0.6"/> <line x1="12" y1="17.25" x2="10.25" y2="15.5" opacity="0.6"/> <line x1="12" y1="17.25" x2="13.75" y2="15.5" opacity="0.6"/> </g></svg>`;
const ICON_LOGO_FG = `<svg ${S}><path d="M7 2 H14 L19 7 V20 Q19 22 17 22 H7 Q5 22 5 20 V4 Q5 2 7 2 Z" fill="white" fill-opacity="0.9"/> <path d="M14 2 V5 Q14 7 16 7 H19 L14 2 Z" fill="#a193c6" fill-opacity="1.0"/> <line x1="8.5" y1="8" x2="13.5" y2="8" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.8" stroke-linecap="round"/> <line x1="8.5" y1="11" x2="15.5" y2="11" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.8" stroke-linecap="round"/> <g stroke="#6750a4" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <line x1="12" y1="14" x2="12" y2="19" opacity="0.6"/> <line x1="12" y1="19" x2="9.5" y2="16.5" opacity="0.6"/> <line x1="12" y1="19" x2="14.5" y2="16.5" opacity="0.6"/> </g></svg>`;
const ICON_FILE_TEXT = `<svg ${S}><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"/> <path d="M14 2v5a1 1 0 0 0 1 1h5"/> <path d="M10 9H8"/> <path d="M16 13H8"/> <path d="M16 17H8"/></svg>`;
const ICON_FILE_TYPE = `<svg ${S}><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"/> <path d="M14 2v5a1 1 0 0 0 1 1h5"/> <path d="M11 18h2"/> <path d="M12 12v6"/> <path d="M9 13v-.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v.5"/></svg>`;
const ICON_DOWNLOAD = `<svg ${S}><path d="M12 15V3"/> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> <path d="m7 10 5 5 5-5"/></svg>`;
const ICON_UPLOAD = `<svg ${S}><path d="M12 3v12"/> <path d="m17 8-5-5-5 5"/> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>`;
const ICON_TRASH = `<svg ${S}><path d="M10 11v6"/> <path d="M14 11v6"/> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/> <path d="M3 6h18"/> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
const ICON_CHEVRON_LEFT = `<svg ${S}><path d="m15 18-6-6 6-6"/></svg>`;
const ICON_CHEVRON_RIGHT = `<svg ${S}><path d="m9 18 6-6-6-6"/></svg>`;
const ICON_CHECK = `<svg ${S}><path d="M20 6 9 17l-5-5"/></svg>`;
const ICON_X = `<svg ${S}><path d="M18 6 6 18"/> <path d="m6 6 12 12"/></svg>`;
const ICON_SETTINGS = `<svg ${S}><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"/> <circle cx="12" cy="12" r="3"/></svg>`;
const ICON_INFO = `<svg ${S}><circle cx="12" cy="12" r="10"/> <path d="M12 16v-4"/> <path d="M12 8h.01"/></svg>`;
const ICON_EXTERNAL_LINK = `<svg ${S}><path d="M15 3h6v6"/> <path d="M10 14 21 3"/> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>`;
const ICON_REFRESH = `<svg ${S}><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/> <path d="M21 3v5h-5"/> <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/> <path d="M8 16H3v5"/></svg>`;
const ICON_GITHUB = `<svg ${S}><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/> <path d="M9 18c-4.51 2-5-2-7-2"/></svg>`;
const ICON_BUG = `<svg ${S}><path d="M12 20v-9"/> <path d="M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z"/> <path d="M14.12 3.88 16 2"/> <path d="M21 21a4 4 0 0 0-3.81-4"/> <path d="M21 5a4 4 0 0 1-3.55 3.97"/> <path d="M22 13h-4"/> <path d="M3 21a4 4 0 0 1 3.81-4"/> <path d="M3 5a4 4 0 0 0 3.55 3.97"/> <path d="M6 13H2"/> <path d="m8 2 1.88 1.88"/> <path d="M9 7.13V6a3 3 0 1 1 6 0v1.13"/></svg>`;
const ICON_USER = `<svg ${S}><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/> <circle cx="12" cy="7" r="4"/></svg>`;
const ICON_BOT = `<svg ${S}><path d="M12 8V4H8"/> <rect width="16" height="12" x="4" y="8" rx="2"/> <path d="M2 14h2"/> <path d="M20 14h2"/> <path d="M15 13v2"/> <path d="M9 13v2"/></svg>`;
const ICON_CIRCLE_CHECK = `<svg ${S}><circle cx="12" cy="12" r="10"/> <path d="m9 12 2 2 4-4"/></svg>`;
const ICON_TRIANGLE_ALERT = `<svg ${S}><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/> <path d="M12 9v4"/> <path d="M12 17h.01"/></svg>`;
const ICON_CIRCLE_X = `<svg ${S}><circle cx="12" cy="12" r="10"/> <path d="m15 9-6 6"/> <path d="m9 9 6 6"/></svg>`;
const ICON_TEXT_CURSOR_INPUT = `<svg ${S}><path d="M12 20h-1a2 2 0 0 1-2-2 2 2 0 0 1-2 2H6"/> <path d="M13 8h7a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-7"/> <path d="M5 16H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1"/> <path d="M6 4h1a2 2 0 0 1 2 2 2 2 0 0 1 2-2h1"/> <path d="M9 6v12"/></svg>`;
const ICON_MESSAGES_SQUARE = `<svg ${S}><path d="M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/> <path d="M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1"/></svg>`;
function iconSize(svg, size) {
return svg.replace('width="20"', `width="${size}"`).replace('height="20"', `height="${size}"`);
}
function iconStroke(svg, strokeWidth) {
return svg.replace('stroke-width="2"', `stroke-width="${strokeWidth}"`);
}
function getToken() {
try {
const raw = localStorage.getItem("userToken");
if (!raw) return null;
const parsed = JSON.parse(raw);
return parsed?.value ?? null;
} catch {
return null;
}
}
const API_BASE = "https://chat.deepseek.com/api/v0";
const COMMON_HEADERS = {
"Accept": "*/*",
"x-client-platform": "web",
"x-client-version": "1.7.0",
"x-client-locale": "zh_CN",
"x-app-version": "20241129.1"
};
async function fetchSessionFromAPI(sessionId, token) {
const url = `${API_BASE}/chat/history_messages?chat_session_id=${encodeURIComponent(sessionId)}`;
const res = await fetch(url, {
headers: { ...COMMON_HEADERS, authorization: `Bearer ${token}` },
credentials: "include"
});
if (!res.ok) throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);
const json = await res.json();
if (json.code !== 0) throw new Error(`DeepSeek biz error: code=${json.code}`);
const { chat_session, chat_messages } = json.data.biz_data;
return buildSession(chat_session, chat_messages);
}
function openDeepSeekDB() {
return new Promise((resolve, reject) => {
const req = indexedDB.open("deepseek-chat");
req.onerror = () => reject(new Error("Cannot open deepseek-chat IndexedDB"));
req.onsuccess = () => resolve(req.result);
});
}
function idbGet(db, storeName, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readonly");
const store = tx.objectStore(storeName);
const req = store.get(key);
req.onerror = () => reject(req.error);
req.onsuccess = () => resolve(req.result);
});
}
async function fetchSessionFromIDB(sessionId) {
try {
const db = await openDeepSeekDB();
const record = await idbGet(db, "history-message", sessionId);
db.close();
if (!record?.data?.chat_session || !record.data.chat_messages?.length) return null;
return buildSession(record.data.chat_session, record.data.chat_messages);
} catch {
return null;
}
}
async function getSession(sessionId) {
const idbSession = await fetchSessionFromIDB(sessionId);
if (idbSession && idbSession.messages.size > 0) {
return idbSession;
}
const token = getToken();
if (!token) throw new Error("No DeepSeek auth token found");
return fetchSessionFromAPI(sessionId, token);
}
function buildSession(rawSession, rawMessages) {
const messages = /* @__PURE__ */ new Map();
for (const rm of rawMessages) {
const id = String(rm.message_id);
const parentId = rm.parent_id != null ? String(rm.parent_id) : null;
let content = "";
let thinkingContent = "";
for (const frag of rm.fragments) {
if (frag.type === "REQUEST" || frag.type === "RESPONSE") {
content += frag.content;
} else if (frag.type === "THINK") {
thinkingContent += frag.content;
}
}
messages.set(id, {
id,
parentId,
role: rm.role === "USER" ? "user" : "assistant",
content,
thinkingContent,
timestamp: rm.inserted_at,
status: rm.status === "FINISHED" ? "finished" : rm.status === "INCOMPLETE" ? "incomplete" : "error",
childrenIds: []
});
}
for (const msg of messages.values()) {
if (msg.parentId && messages.has(msg.parentId)) {
const parent = messages.get(msg.parentId);
if (!parent.childrenIds.includes(msg.id)) {
parent.childrenIds.push(msg.id);
}
}
}
return {
id: rawSession.id,
title: rawSession.title,
updatedAt: rawSession.updated_at,
messages,
currentMessageId: String(rawSession.current_message_id)
};
}
function getActiveChain(session2) {
const chain2 = [];
let id = session2.currentMessageId;
while (id) {
const msg = session2.messages.get(id);
if (!msg) break;
chain2.unshift(msg);
id = msg.parentId;
}
return chain2;
}
function switchBranch(session2, nodeId, targetChildId) {
const messages = session2.messages;
const prefix = [];
if (nodeId !== null) {
let id = nodeId;
while (id) {
const msg = messages.get(id);
if (!msg) break;
prefix.unshift(msg);
id = msg.parentId;
}
}
const suffix = [];
let current = targetChildId;
while (current) {
const msg = messages.get(current);
if (!msg) break;
suffix.push(msg);
current = msg.childrenIds.length > 0 ? msg.childrenIds[0] : null;
}
return [...prefix, ...suffix];
}
function hasBranch(session2, nodeId) {
if (nodeId === null) return getRootIds(session2).length > 1;
const msg = session2.messages.get(nodeId);
return !!msg && msg.childrenIds.length > 1;
}
function getChildIndex(session2, parentId, childId) {
if (parentId === null) return getRootIds(session2).indexOf(childId);
const parent = session2.messages.get(parentId);
if (!parent) return 0;
return parent.childrenIds.indexOf(childId);
}
function getRootIds(session2) {
const ids = [];
for (const msg of session2.messages.values()) {
if (msg.parentId === null) ids.push(msg.id);
}
return ids;
}
function getCurrentSessionId() {
const m = window.location.pathname.match(/\/a\/chat\/s\/([a-f0-9-]+)/i);
return m ? m[1] : null;
}
const FILE_TYPE_SVG = iconSize(ICON_FILE_TYPE, 16);
function injectSingleExportButtons(onClick) {
const MARKER_ATTR = "data-gmd-injected";
function processToolbar(toolbar) {
if (toolbar.hasAttribute(MARKER_ATTR)) return;
toolbar.setAttribute(MARKER_ATTR, "1");
const copyBtn = toolbar.querySelector("div.ds-icon-button");
if (!copyBtn) return;
const exportBtn2 = document.createElement("div");
exportBtn2.className = "ds-icon-button ds-icon-button--m ds-icon-button--sizing-container";
exportBtn2.setAttribute("tabindex", "0");
exportBtn2.setAttribute("role", "button");
exportBtn2.setAttribute("aria-disabled", "false");
exportBtn2.title = "导出为 Word";
exportBtn2.innerHTML = `
<div class="ds-icon-button__hover-bg"></div>
<div class="ds-icon">${FILE_TYPE_SVG}</div>
<div class="ds-focus-ring"></div>
`;
exportBtn2.addEventListener("click", async (e) => {
e.stopPropagation();
copyBtn.click();
await new Promise((r) => setTimeout(r, 300));
try {
const md = await navigator.clipboard.readText();
if (md) {
const title = document.title.replace(/ - DeepSeek$/, "").trim() || "单条消息导出";
onClick(md, title);
}
} catch (err) {
console.error("[GiveMeDoc] Clipboard read failed:", err);
}
});
toolbar.appendChild(exportBtn2);
}
function scanAll() {
const toolbars = document.querySelectorAll(
"div.ds-scroll-area div.ds-flex div.ds-flex"
);
toolbars.forEach(processToolbar);
}
scanAll();
const observer = new MutationObserver(() => scanAll());
observer.observe(document.body, { childList: true, subtree: true });
}
function injectSharePanelButton(onClick) {
const MARKER_ATTR = "data-gmd-share-injected";
function tryInject() {
const allPrimary = document.querySelectorAll(
".ds-atom-button.ds-basic-button.ds-basic-button--primary"
);
let shareBtn = null;
for (const btn of allPrimary) {
if (btn.innerText.includes("创建分享链接")) {
shareBtn = btn;
break;
}
}
if (!shareBtn) return;
const bottomBar = shareBtn.parentElement;
if (!bottomBar || bottomBar.hasAttribute(MARKER_ATTR)) return;
bottomBar.setAttribute(MARKER_ATTR, "1");
const exportBtn2 = document.createElement("button");
exportBtn2.className = "ds-atom-button ds-basic-button ds-basic-button--outlined";
exportBtn2.style = "padding: 6px 14px; font-size: 14px; line-height: 22px; min-width: 72px;";
exportBtn2.innerHTML = `
<div class="ds-icon ds-atom-button__icon" style="font-size: 14px; width: 14px; height: 14px; margin-right: 6px;"><div class="ds-icon" style="font-size: 14px; width: 14px; height: 14px;">${FILE_TYPE_SVG}</div></div>
<span>导出为 Word</span>
`;
exportBtn2.addEventListener("click", (e) => {
e.stopPropagation();
const checkboxes = document.querySelectorAll(".ds-checkbox");
const indices = [];
for (let i = 0; i < checkboxes.length - 1; i++) {
if (checkboxes[i].classList.contains("ds-checkbox--active")) {
indices.push(i);
}
}
onClick(indices);
});
bottomBar.insertBefore(exportBtn2, shareBtn);
}
const observer = new MutationObserver(() => tryInject());
observer.observe(document.body, { childList: true, subtree: true });
tryInject();
}
function el(tag, className, attrs) {
const e = document.createElement(tag);
if (className) e.className = className;
if (attrs) {
for (const [k, v] of Object.entries(attrs)) {
e.setAttribute(k, v);
}
}
return e;
}
function text(content) {
return document.createTextNode(content);
}
function html(tag, className, innerHTML) {
const e = el(tag, className);
e.innerHTML = innerHTML;
return e;
}
function append(parent, ...children) {
for (const c of children) {
if (typeof c === "string") {
parent.appendChild(text(c));
} else {
parent.appendChild(c);
}
}
return parent;
}
function createButton(opts) {
const variant = opts.variant || "filled";
const btn = el("button", `gmd-btn gmd-btn--${variant}${opts.className ? " " + opts.className : ""}`);
btn.type = "button";
if (opts.disabled) btn.disabled = true;
if (opts.icon) {
const iconSpan = html("span", "gmd-btn__icon", opts.icon);
btn.appendChild(iconSpan);
}
const labelSpan = el("span", "gmd-btn__label");
labelSpan.textContent = opts.label;
btn.appendChild(labelSpan);
if (opts.onClick) {
btn.addEventListener("click", opts.onClick);
}
return btn;
}
function createIconButton(opts) {
const variant = opts.variant || "standard";
const btn = el("button", `gmd-icon-btn gmd-icon-btn--${variant}`);
btn.type = "button";
btn.innerHTML = opts.icon;
if (opts.title) btn.title = opts.title;
if (opts.onClick) btn.addEventListener("click", opts.onClick);
return btn;
}
function createCheckbox(opts) {
const wrapper = el("label", "gmd-checkbox");
const input = el("input", "gmd-checkbox__input", { type: "checkbox" });
if (opts.checked) input.checked = true;
const box = el("span", "gmd-checkbox__box");
box.innerHTML = iconStroke(ICON_CHECK, 3).replace("<svg ", '<svg class="gmd-checkbox__check" ');
wrapper.appendChild(input);
wrapper.appendChild(box);
if (opts.label) {
const lbl = el("span", "gmd-checkbox__label");
lbl.textContent = opts.label;
wrapper.appendChild(lbl);
}
if (opts.onChange) {
input.addEventListener("change", () => opts.onChange(input.checked));
}
return wrapper;
}
function setCheckboxState(wrapper, checked) {
const input = wrapper.querySelector("input");
if (input) input.checked = checked;
}
function createSelect(opts) {
const select = el("select", "gmd-select");
if (opts.placeholder) {
const ph = el("option", void 0, { value: "", disabled: "true", selected: "true" });
ph.textContent = opts.placeholder;
select.appendChild(ph);
}
for (const o of opts.options) {
const option = el("option", void 0, { value: o.value });
option.textContent = o.label;
if (opts.value === o.value) option.selected = true;
select.appendChild(option);
}
if (opts.onChange) {
select.addEventListener("change", () => opts.onChange(select.value));
}
return select;
}
function createSegmentedControl(opts) {
const root2 = el("div", "gmd-segmented");
const buttons = [];
for (const seg of opts.segments) {
const btn = el("button", "gmd-segmented__btn");
btn.type = "button";
btn.dataset.segmentId = seg.id;
if (seg.icon) {
const iconSpan = html("span", "gmd-segmented__icon", seg.icon);
btn.appendChild(iconSpan);
}
const labelSpan = el("span", "gmd-segmented__label");
labelSpan.textContent = seg.label;
btn.appendChild(labelSpan);
if (seg.id === opts.activeId) btn.classList.add("gmd-segmented__btn--active");
btn.addEventListener("click", () => {
for (const b of buttons) b.classList.remove("gmd-segmented__btn--active");
btn.classList.add("gmd-segmented__btn--active");
opts.onChange?.(seg.id);
});
buttons.push(btn);
root2.appendChild(btn);
}
return root2;
}
function createInput(opts) {
const wrapper = el("div", "gmd-input");
if (opts.label) {
const lbl = el("label", "gmd-input__label");
lbl.textContent = opts.label;
wrapper.appendChild(lbl);
}
const input = el("input", "gmd-input__field", { type: "text" });
if (opts.value) input.value = opts.value;
if (opts.placeholder) input.placeholder = opts.placeholder;
if (opts.onChange) {
input.addEventListener("input", () => opts.onChange(input.value));
}
wrapper.appendChild(input);
return wrapper;
}
function createTextarea(opts) {
const wrapper = el("div", "gmd-textarea");
if (opts.label) {
const lbl = el("label", "gmd-textarea__label");
lbl.textContent = opts.label;
wrapper.appendChild(lbl);
}
const textarea = el("textarea", "gmd-textarea__input");
if (opts.value) textarea.value = opts.value;
if (opts.placeholder) textarea.placeholder = opts.placeholder;
textarea.rows = opts.rows ?? 4;
if (opts.wrap) {
textarea.wrap = opts.wrap;
if (opts.wrap === "off") textarea.classList.add("gmd-textarea__input--nowrap");
}
if (opts.onChange) {
textarea.addEventListener("input", () => opts.onChange(textarea.value));
}
wrapper.appendChild(textarea);
return wrapper;
}
function createTabs(opts) {
const root2 = el("div", "gmd-tabs");
const bar = el("div", "gmd-tabs__bar");
bar.setAttribute("role", "tablist");
const panelContainer = el("div", "gmd-tabs__panels");
const indicator = el("div", "gmd-tabs__indicator");
const tabEls = /* @__PURE__ */ new Map();
const panelEls = /* @__PURE__ */ new Map();
for (const tab of opts.tabs) {
const tabEl = el("button", "gmd-tabs__tab");
tabEl.setAttribute("role", "tab");
tabEl.setAttribute("data-tab-id", tab.id);
tabEl.type = "button";
if (tab.icon) {
const iconSpan = el("span", "gmd-tabs__tab-icon");
iconSpan.innerHTML = tab.icon;
tabEl.appendChild(iconSpan);
}
const labelSpan = el("span", "gmd-tabs__tab-label");
labelSpan.textContent = tab.label;
tabEl.appendChild(labelSpan);
bar.appendChild(tabEl);
tabEls.set(tab.id, tabEl);
const panel = el("div", "gmd-tabs__panel");
panel.setAttribute("role", "tabpanel");
panel.setAttribute("data-panel-id", tab.id);
panelEls.set(tab.id, panel);
panelContainer.appendChild(panel);
}
bar.appendChild(indicator);
root2.appendChild(bar);
root2.appendChild(panelContainer);
let activeId = opts.activeId ?? opts.tabs[0]?.id ?? "";
function setActive(tabId) {
activeId = tabId;
for (const [id, tabEl] of tabEls) {
const isActive = id === tabId;
tabEl.classList.toggle("gmd-tabs__tab--active", isActive);
tabEl.setAttribute("aria-selected", String(isActive));
}
const activeTabEl = tabEls.get(tabId);
if (activeTabEl) {
const barRect = bar.getBoundingClientRect();
const tabRect = activeTabEl.getBoundingClientRect();
indicator.style.left = `${tabRect.left - barRect.left}px`;
indicator.style.width = `${tabRect.width}px`;
}
for (const [id, panel] of panelEls) {
const isActive = id === tabId;
panel.classList.toggle("gmd-tabs__panel--active", isActive);
panel.hidden = !isActive;
if (isActive && panel.children.length === 0) {
const tab = opts.tabs.find((t) => t.id === id);
if (tab) {
panel.appendChild(tab.render());
}
}
}
opts.onTabChange?.(tabId);
}
for (const [id, tabEl] of tabEls) {
tabEl.addEventListener("click", () => setActive(id));
}
requestAnimationFrame(() => setActive(activeId));
return { root: root2, setActive };
}
let container = null;
function ensureContainer() {
if (container && document.body.contains(container)) return container;
container = el("div", "gmd-toast-container");
document.body.appendChild(container);
return container;
}
function showToast(opts) {
const c = ensureContainer();
const level = opts.level ?? "info";
const duration = opts.duration ?? 4e3;
const toast = el("div", `gmd-toast gmd-toast--${level}`);
const iconMap = {
info: ICON_INFO,
success: ICON_CIRCLE_CHECK,
warning: ICON_TRIANGLE_ALERT,
error: ICON_CIRCLE_X
};
const iconEl = el("span", "gmd-toast__icon");
iconEl.innerHTML = iconMap[level];
toast.appendChild(iconEl);
const msgEl = el("span", "gmd-toast__message");
msgEl.textContent = opts.message;
toast.appendChild(msgEl);
if (opts.action) {
const actionBtn = el("button", "gmd-toast__action");
actionBtn.textContent = opts.action.label;
actionBtn.addEventListener("click", () => {
opts.action.onClick();
dismiss();
});
toast.appendChild(actionBtn);
}
const closeBtn = el("button", "gmd-toast__close");
closeBtn.innerHTML = iconSize(ICON_X, 16);
closeBtn.addEventListener("click", dismiss);
toast.appendChild(closeBtn);
c.appendChild(toast);
requestAnimationFrame(() => toast.classList.add("gmd-toast--visible"));
let timer = null;
function dismiss() {
if (timer) clearTimeout(timer);
toast.classList.remove("gmd-toast--visible");
toast.addEventListener("transitionend", () => toast.remove(), { once: true });
setTimeout(() => toast.remove(), 400);
}
if (duration > 0) {
timer = setTimeout(dismiss, duration);
}
return dismiss;
}
function debounce$1(fn, ms) {
let timer = null;
return (...args) => {
if (timer !== null) clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
timer = null;
}, ms);
};
}
let session = null;
let chain = [];
let selectedIds = /* @__PURE__ */ new Set();
let templates$1 = [];
let selectedTemplateId = "";
let spinStartTime = 0;
const MIN_SPIN_MS = 600;
let mode = "session";
let freetextMd = "";
let freetextFilename = "export";
let listEl;
let selectAllCb;
let templateSelect;
let exportBtn;
let countLabel;
let refreshBtn;
let root;
let sessionContainer;
let freetextContainer;
let freetextFilenameInput;
let freetextTextarea;
let segmentedEl;
const persistFreetextMd = debounce$1((cb, v) => {
cb.onConfigChange({ freetextMd: v });
}, 400);
const persistFreetextFilename = debounce$1((cb, v) => {
cb.onConfigChange({ freetextFilename: v });
}, 400);
function renderExportTab(cb) {
root = el("div", "gmd-export");
const modeSwitch = el("div", "gmd-export__mode-switch");
const segmented = createSegmentedControl({
segments: [
{ id: "session", label: "当前会话", icon: ICON_MESSAGES_SQUARE },
{ id: "freetext", label: "自由输入", icon: ICON_TEXT_CURSOR_INPUT }
],
activeId: mode,
onChange: (id) => {
mode = id;
cb.onConfigChange({ exportMode: mode });
applyModeSwitch();
}
});
segmentedEl = segmented;
modeSwitch.appendChild(segmented);
root.appendChild(modeSwitch);
sessionContainer = el("div", "gmd-export__session");
const toolbar = el("div", "gmd-export__toolbar");
selectAllCb = createCheckbox({
label: "全选",
onChange: (checked) => {
for (const msg of chain) {
if (checked) selectedIds.add(msg.id);
else selectedIds.delete(msg.id);
}
syncList();
}
});
countLabel = el("span", "gmd-export__count");
countLabel.textContent = "0 条消息";
refreshBtn = createIconButton({
icon: ICON_REFRESH,
title: "刷新会话",
variant: "standard",
onClick: () => {
refreshBtn.classList.add("gmd-icon-btn--spinning");
spinStartTime = Date.now();
loadSession(cb);
}
});
append(toolbar, selectAllCb, countLabel, refreshBtn);
sessionContainer.appendChild(toolbar);
listEl = el("div", "gmd-export__list");
sessionContainer.appendChild(listEl);
root.appendChild(sessionContainer);
freetextContainer = el("div", "gmd-export__freetext");
freetextContainer.style.display = "none";
freetextFilenameInput = createInput({
label: "导出文件名",
value: freetextFilename,
placeholder: "export",
onChange: (v) => {
freetextFilename = v;
persistFreetextFilename(cb, v);
syncExportBtnState();
}
});
freetextContainer.appendChild(freetextFilenameInput);
freetextTextarea = createTextarea({
label: "Markdown 内容",
value: freetextMd,
placeholder: "在此粘贴或输入 Markdown 文本…",
rows: 12,
onChange: (v) => {
freetextMd = v;
persistFreetextMd(cb, v);
syncExportBtnState();
}
});
freetextTextarea.classList.add("gmd-export__freetext-editor");
freetextContainer.appendChild(freetextTextarea);
root.appendChild(freetextContainer);
const bottomBar = el("div", "gmd-export__bottom");
templateSelect = createSelect({
options: [],
placeholder: "选择模板…",
onChange: (v) => {
selectedTemplateId = v;
}
});
exportBtn = createButton({
label: "导出",
icon: ICON_DOWNLOAD,
variant: "filled",
onClick: () => doExport(cb)
});
append(bottomBar, templateSelect, exportBtn);
root.appendChild(bottomBar);
loadSession(cb);
loadTemplates(cb);
loadFreetextState(cb);
return root;
}
async function loadSession(cb) {
try {
session = await cb.getSession();
if (!session) {
listEl.innerHTML = `
<div class="gmd-export__empty">
<div class="gmd-export__empty-icon">${ICON_MESSAGES_SQUARE}</div>
<div class="gmd-export__empty-text">未检测到消息,请打开一个对话。<br/>或者复制内容后粘贴到“自由输入”中导出。</div>
</div>
`;
return;
}
chain = getActiveChain(session);
selectedIds = new Set(chain.map((m) => m.id));
renderList();
} catch (err) {
showToast({ message: `加载会话失败: ${err.message}`, level: "error" });
} finally {
const elapsed = Date.now() - spinStartTime;
const remaining = Math.max(0, MIN_SPIN_MS - elapsed);
setTimeout(() => refreshBtn?.classList.remove("gmd-icon-btn--spinning"), remaining);
}
}
async function loadTemplates(cb) {
try {
templates$1 = await cb.getTemplateList();
const config2 = await cb.getConfig();
selectedTemplateId = config2.selectedTemplateId;
rebuildTemplateSelect();
} catch (err) {
showToast({ message: `加载模板失败: ${err.message}`, level: "error" });
}
}
async function loadFreetextState(cb) {
try {
const config2 = await cb.getConfig();
if (config2.exportMode && config2.exportMode !== mode) {
mode = config2.exportMode;
const btns = segmentedEl.querySelectorAll(".gmd-segmented__btn");
btns.forEach((btn) => {
const el2 = btn;
if (el2.dataset.segmentId === mode) {
el2.classList.add("gmd-segmented__btn--active");
} else {
el2.classList.remove("gmd-segmented__btn--active");
}
});
applyModeSwitch();
}
if (config2.freetextMd) {
freetextMd = config2.freetextMd;
const ta = freetextTextarea.querySelector("textarea");
if (ta) ta.value = freetextMd;
}
if (config2.freetextFilename) {
freetextFilename = config2.freetextFilename;
const inp = freetextFilenameInput.querySelector("input");
if (inp) inp.value = freetextFilename;
}
syncExportBtnState();
} catch (_) {
}
}
function rebuildTemplateSelect() {
templateSelect.innerHTML = "";
for (const t of templates$1) {
const opt = document.createElement("option");
opt.value = t.id;
opt.textContent = t.name + (t.isBuiltin ? "" : " (自定义)");
if (t.id === selectedTemplateId) opt.selected = true;
templateSelect.appendChild(opt);
}
}
function renderList() {
listEl.innerHTML = "";
for (const msg of chain) {
listEl.appendChild(createMessageRow(msg));
}
syncCount();
}
function createMessageRow(msg) {
const row = el("div", `gmd-export__row gmd-export__row--${msg.role}`);
const cb = createCheckbox({
checked: selectedIds.has(msg.id),
onChange: (checked) => {
if (checked) selectedIds.add(msg.id);
else selectedIds.delete(msg.id);
syncCount();
}
});
row.appendChild(cb);
const icon = html("span", "gmd-export__role-icon", msg.role === "user" ? ICON_USER : ICON_BOT);
row.appendChild(icon);
const summary = el("span", "gmd-export__summary");
const plain = msg.content.replace(/[#*`>\-\[\]()]/g, "").trim();
summary.textContent = plain.length > 80 ? plain.slice(0, 80) + "…" : plain;
summary.title = plain.slice(0, 300);
row.appendChild(summary);
if (session && hasBranch(session, msg.parentId)) {
const branchCtrl = createBranchSwitcher(msg);
row.appendChild(branchCtrl);
}
return row;
}
function createBranchSwitcher(msg) {
if (!session) return el("span", "");
const parentId = msg.parentId;
const childrenIds = parentId === null ? getRootIds(session) : session.messages.get(parentId).childrenIds;
const idx = getChildIndex(session, parentId, msg.id);
const total = childrenIds.length;
const ctrl = el("span", "gmd-export__branch");
const prevBtn = createIconButton({
icon: ICON_CHEVRON_LEFT,
title: "上一分支",
variant: "standard",
onClick: () => navigateBranch(parentId, idx - 1)
});
if (idx <= 0) prevBtn.disabled = true;
const label = el("span", "gmd-export__branch-label");
label.textContent = `${idx + 1}/${total}`;
const nextBtn = createIconButton({
icon: ICON_CHEVRON_RIGHT,
title: "下一分支",
variant: "standard",
onClick: () => navigateBranch(parentId, idx + 1)
});
if (idx >= total - 1) nextBtn.disabled = true;
append(ctrl, prevBtn, label, nextBtn);
return ctrl;
}
function navigateBranch(parentId, targetIdx) {
if (!session) return;
const childrenIds = parentId === null ? getRootIds(session) : session.messages.get(parentId)?.childrenIds;
if (!childrenIds || targetIdx < 0 || targetIdx >= childrenIds.length) return;
const targetChildId = childrenIds[targetIdx];
const prevSelectedIds = new Set(selectedIds);
const prevChainIds = new Set(chain.map((m) => m.id));
chain = switchBranch(session, parentId, targetChildId);
selectedIds = new Set(
chain.filter((m) => prevChainIds.has(m.id) ? prevSelectedIds.has(m.id) : true).map((m) => m.id)
);
renderList();
}
function applyModeSwitch() {
if (mode === "session") {
sessionContainer.style.display = "";
freetextContainer.style.display = "none";
} else {
sessionContainer.style.display = "none";
freetextContainer.style.display = "";
}
syncExportBtnState();
}
function syncExportBtnState() {
if (mode === "freetext") {
exportBtn.disabled = !freetextMd.trim();
} else {
exportBtn.disabled = selectedIds.size === 0;
}
}
function syncList() {
const rows = listEl.querySelectorAll(".gmd-export__row");
rows.forEach((row, i) => {
const cb = row.querySelector(".gmd-checkbox");
if (cb && chain[i]) {
setCheckboxState(cb, selectedIds.has(chain[i].id));
}
});
syncCount();
}
function syncCount() {
const total = chain.length;
const selected = selectedIds.size;
countLabel.textContent = `${selected}/${total} 条消息`;
const allChecked = total > 0 && selected === total;
setCheckboxState(selectAllCb, allChecked);
syncExportBtnState();
}
async function doExport(cb) {
if (mode === "freetext") {
if (!freetextMd.trim()) {
showToast({ message: "请输入 Markdown 文本", level: "warning" });
return;
}
exportBtn.disabled = true;
const origLabel2 = exportBtn.querySelector(".gmd-btn__label");
const prevText2 = origLabel2.textContent;
origLabel2.textContent = "导出中…";
try {
await cb.onExportRaw(freetextMd, selectedTemplateId, freetextFilename || "export");
showToast({ message: "导出成功", level: "success" });
} catch (err) {
showToast({ message: `导出失败: ${err.message}`, level: "error" });
} finally {
origLabel2.textContent = prevText2;
exportBtn.disabled = false;
}
return;
}
if (selectedIds.size === 0) {
showToast({ message: "请至少选择一条消息", level: "warning" });
return;
}
exportBtn.disabled = true;
const origLabel = exportBtn.querySelector(".gmd-btn__label");
const prevText = origLabel.textContent;
origLabel.textContent = "导出中…";
try {
await cb.onExport(Array.from(selectedIds), selectedTemplateId);
showToast({ message: "导出成功", level: "success" });
} catch (err) {
showToast({ message: `导出失败: ${err.message}`, level: "error" });
} finally {
origLabel.textContent = prevText;
exportBtn.disabled = false;
}
}
function refreshExportTab(cb) {
loadSession(cb);
}
function refreshExportTemplates(cb) {
if (!root) return;
loadTemplates(cb);
}
function setupUrlWatcher(cb) {
let lastSessionId = getCurrentSessionId();
function checkUrlChange() {
const newId = getCurrentSessionId();
if (newId !== lastSessionId) {
lastSessionId = newId;
refreshExportTab(cb);
}
}
const origPushState = history.pushState.bind(history);
const origReplaceState = history.replaceState.bind(history);
history.pushState = function(...args) {
origPushState(...args);
checkUrlChange();
};
history.replaceState = function(...args) {
origReplaceState(...args);
checkUrlChange();
};
window.addEventListener("popstate", checkUrlChange);
}
function createSwitch(opts) {
const wrapper = el("label", "gmd-switch");
const input = el("input", "gmd-switch__input", { type: "checkbox" });
if (opts.checked) input.checked = true;
const track = el("span", "gmd-switch__track");
const thumb = el("span", "gmd-switch__thumb");
track.appendChild(thumb);
const thumbIcon = el("span", "gmd-switch__icon");
thumbIcon.innerHTML = iconStroke(iconSize(ICON_CHECK, 14), 3);
thumb.appendChild(thumbIcon);
wrapper.appendChild(input);
wrapper.appendChild(track);
if (opts.label) {
const lbl = el("span", "gmd-switch__label");
lbl.textContent = opts.label;
wrapper.appendChild(lbl);
}
function sync() {
track.classList.toggle("gmd-switch__track--checked", input.checked);
}
sync();
input.addEventListener("change", () => {
sync();
opts.onChange?.(input.checked);
});
return wrapper;
}
function setSwitchState(wrapper, checked) {
const input = wrapper.querySelector("input");
if (input) {
input.checked = checked;
const track = wrapper.querySelector(".gmd-switch__track");
track?.classList.toggle("gmd-switch__track--checked", checked);
}
}
function debounce(fn, ms) {
let timer = null;
return (...args) => {
if (timer !== null) clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
timer = null;
}, ms);
};
}
let config;
let templates = [];
let templateListEl;
let fabSwitch;
let thinkingSwitch;
let singleExportSwitch;
let autoRefreshSwitch;
let lineBreaksSelect;
let prefixTextarea;
let userTplTextarea;
let assistantTplTextarea;
let editorSegmented;
let activeEditorId = "prefix";
let cdnSection = null;
let cdnTextarea;
function renderSettingsTab(cb) {
const root2 = el("div", "gmd-settings");
const tplSection = createSection("模板管理");
templateListEl = el("div", "gmd-settings__tpl-list");
tplSection.appendChild(templateListEl);
const uploadBtn = createButton({
label: "上传模板",
icon: ICON_UPLOAD,
variant: "tonal",
onClick: () => handleUpload(cb)
});
tplSection.appendChild(uploadBtn);
root2.appendChild(tplSection);
const displaySection = createSection("显示设置");
fabSwitch = createSwitch({
label: "显示悬浮球",
checked: true,
onChange: (checked) => {
config.showFab = checked;
cb.onConfigChange({ showFab: checked });
cb.onFabToggle?.(checked);
}
});
displaySection.appendChild(fabSwitch);
autoRefreshSwitch = createSwitch({
label: "打开面板时自动刷新",
checked: true,
onChange: (checked) => {
config.autoRefreshOnOpen = checked;
cb.onConfigChange({ autoRefreshOnOpen: checked });
}
});
displaySection.appendChild(autoRefreshSwitch);
root2.appendChild(displaySection);
const exportSettingsSection = createSection("导出设置");
thinkingSwitch = createSwitch({
label: "导出时包含思考内容",
checked: false,
onChange: (checked) => {
config.includeThinking = checked;
cb.onConfigChange({ includeThinking: checked });
}
});
exportSettingsSection.appendChild(thinkingSwitch);
singleExportSwitch = createSwitch({
label: "单条导出时使用模板",
checked: false,
onChange: (checked) => {
config.singleExportWithTemplate = checked;
cb.onConfigChange({ singleExportWithTemplate: checked });
}
});
exportSettingsSection.appendChild(singleExportSwitch);
const lineBreaksLabel = el("label", "gmd-settings__select-label");
lineBreaksLabel.textContent = "换行模式";
exportSettingsSection.appendChild(lineBreaksLabel);
lineBreaksSelect = createSelect({
options: [
{ value: "hard", label: "强制换行(推荐)(每行保留,类似 Shift+Enter)" },
{ value: "default", label: "Pandoc 默认(标准 Markdown,换行合并)" },
{ value: "east_asian", label: "东亚优化(中日韩文字间换行忽略)" }
],
value: "hard",
onChange: (value) => {
config.lineBreaks = value;
cb.onConfigChange({ lineBreaks: config.lineBreaks });
}
});
exportSettingsSection.appendChild(lineBreaksSelect);
root2.appendChild(exportSettingsSection);
const editorSection = createSection("模板文本编辑");
editorSegmented = createSegmentedControl({
segments: [
{ id: "prefix", label: "文档前缀" },
{ id: "user", label: "用户消息" },
{ id: "assistant", label: "助手消息" }
],
activeId: activeEditorId,
onChange: (id) => {
activeEditorId = id;
applyEditorSwitch();
}
});
editorSection.appendChild(editorSegmented);
prefixTextarea = createTextarea({
label: "文档前缀(document prefix)",
rows: 6,
onChange: debounce((v) => {
config.documentPrefix = v;
cb.onConfigChange({ documentPrefix: v });
}, 400)
});
editorSection.appendChild(prefixTextarea);
userTplTextarea = createTextarea({
label: "用户消息模板(user message)",
rows: 6,
onChange: debounce((v) => {
config.userMessageTemplate = v;
cb.onConfigChange({ userMessageTemplate: v });
}, 400)
});
userTplTextarea.style.display = "none";
editorSection.appendChild(userTplTextarea);
assistantTplTextarea = createTextarea({
label: "助手消息模板(assistant message)",
rows: 6,
onChange: debounce((v) => {
config.assistantMessageTemplate = v;
cb.onConfigChange({ assistantMessageTemplate: v });
}, 400)
});
assistantTplTextarea.style.display = "none";
editorSection.appendChild(assistantTplTextarea);
const placeholderHint = el("p", "gmd-settings__hint");
placeholderHint.textContent = "可用占位符: {title}, {output_date}, {content}, {thinking_content}";
editorSection.appendChild(placeholderHint);
root2.appendChild(editorSection);
{
cdnSection = createSection("Pandoc WASM CDN");
cdnTextarea = createTextarea({
label: "CDN URL 列表(每行一个,按顺序尝试)",
rows: 3,
wrap: "off",
onChange: debounce((v) => {
const urls = v.split("\n").map((l) => l.trim()).filter(Boolean);
config.cdnUrls = urls;
cb.onConfigChange({ cdnUrls: urls });
}, 400)
});
cdnSection.appendChild(cdnTextarea);
root2.appendChild(cdnSection);
}
const dangerSection = createSection("危险区域");
const resetBtn = createButton({
label: "重置设置",
icon: ICON_REFRESH,
variant: "tonal",
onClick: async () => {
try {
config = await cb.onResetConfig();
setSwitchState(fabSwitch, config.showFab);
setSwitchState(autoRefreshSwitch, config.autoRefreshOnOpen);
setSwitchState(thinkingSwitch, config.includeThinking);
setSwitchState(singleExportSwitch, config.singleExportWithTemplate);
lineBreaksSelect.value = config.lineBreaks;
setTextareaValue(prefixTextarea, config.documentPrefix);
setTextareaValue(userTplTextarea, config.userMessageTemplate);
setTextareaValue(assistantTplTextarea, config.assistantMessageTemplate);
if (cdnSection && true) {
setTextareaValue(cdnTextarea, config.cdnUrls.join("\n"));
}
cb.onFabToggle?.(config.showFab);
showToast({ message: "设置已重置为默认值", level: "success" });
} catch (err) {
showToast({ message: `重置失败: ${err.message}`, level: "error" });
}
}
});
dangerSection.appendChild(resetBtn);
const clearCacheBtn = createButton({
label: "清除缓存",
icon: ICON_TRASH,
variant: "tonal",
onClick: async () => {
try {
await cb.onClearCache();
showToast({ message: "缓存已清除,下次加载将重新下载", level: "success" });
} catch (err) {
showToast({ message: `清除缓存失败: ${err.message}`, level: "error" });
}
}
});
dangerSection.appendChild(clearCacheBtn);
root2.appendChild(dangerSection);
loadData(cb);
return root2;
}
function renderTemplateList(cb) {
templateListEl.innerHTML = "";
for (const tpl of templates) {
const isSelected = tpl.id === config.selectedTemplateId;
const row = el("div", "gmd-settings__tpl-row");
if (isSelected) row.classList.add("gmd-settings__tpl-row--selected");
row.style.cursor = isSelected ? "default" : "pointer";
row.addEventListener("click", async (e) => {
if (e.target.closest(".gmd-icon-btn")) return;
if (tpl.id === config.selectedTemplateId) return;
config.selectedTemplateId = tpl.id;
await cb.onConfigChange({ selectedTemplateId: tpl.id });
renderTemplateList(cb);
showToast({ message: `已将 "${tpl.name}" 设为默认模板`, level: "success" });
});
const nameEl = el("span", "gmd-settings__tpl-name");
nameEl.textContent = tpl.name;
row.appendChild(nameEl);
if (tpl.description) {
const descEl = el("span", "gmd-settings__tpl-desc");
descEl.textContent = tpl.description;
row.appendChild(descEl);
}
if (!tpl.isBuiltin) {
const delBtn = createIconButton({
icon: ICON_TRASH,
title: "删除",
variant: "standard",
onClick: async () => {
await cb.onTemplateDelete(tpl.id);
templates = templates.filter((t) => t.id !== tpl.id);
if (config.selectedTemplateId === tpl.id) {
const fallback = templates.find((t) => t.isBuiltin) ?? templates[0];
if (fallback) {
config.selectedTemplateId = fallback.id;
await cb.onConfigChange({ selectedTemplateId: fallback.id });
}
}
renderTemplateList(cb);
showToast({ message: `已删除 "${tpl.name}"`, level: "info" });
}
});
row.appendChild(delBtn);
}
const badge = el("span", "gmd-settings__tpl-badge");
badge.textContent = tpl.isBuiltin ? "内置" : "自定义";
row.appendChild(badge);
templateListEl.appendChild(row);
}
}
function handleUpload(cb) {
const input = document.createElement("input");
input.type = "file";
input.accept = ".docx";
input.addEventListener("change", async () => {
const file = input.files?.[0];
if (!file) return;
try {
const buffer = await file.arrayBuffer();
const name = file.name.replace(/\.docx$/i, "");
await cb.onTemplateUpload(name, buffer);
templates = await cb.getTemplateList();
renderTemplateList(cb);
showToast({ message: `已上传模板 "${name}"`, level: "success" });
} catch (err) {
showToast({ message: `上传失败: ${err.message}`, level: "error" });
}
});
input.click();
}
async function loadData(cb) {
try {
config = await cb.getConfig();
templates = await cb.getTemplateList();
setSwitchState(fabSwitch, config.showFab);
setSwitchState(autoRefreshSwitch, config.autoRefreshOnOpen);
setSwitchState(thinkingSwitch, config.includeThinking);
setSwitchState(singleExportSwitch, config.singleExportWithTemplate);
lineBreaksSelect.value = config.lineBreaks;
setTextareaValue(prefixTextarea, config.documentPrefix);
setTextareaValue(userTplTextarea, config.userMessageTemplate);
setTextareaValue(assistantTplTextarea, config.assistantMessageTemplate);
if (cdnSection && true) {
setTextareaValue(cdnTextarea, config.cdnUrls.join("\n"));
}
renderTemplateList(cb);
} catch (err) {
showToast({ message: `加载设置失败: ${err.message}`, level: "error" });
}
}
function createSection(title) {
const section = el("div", "gmd-settings__section");
const heading = el("h3", "gmd-settings__section-title");
heading.textContent = title;
section.appendChild(heading);
return section;
}
function setTextareaValue(wrapper, value) {
const ta = wrapper.querySelector("textarea");
if (ta) ta.value = value;
}
function applyEditorSwitch() {
prefixTextarea.style.display = activeEditorId === "prefix" ? "" : "none";
userTplTextarea.style.display = activeEditorId === "user" ? "" : "none";
assistantTplTextarea.style.display = activeEditorId === "assistant" ? "" : "none";
}
function renderAboutTab(cb) {
const root2 = el("div", "gmd-about");
const statusCard = el("div", "gmd-about__card");
const statusTitle = el("h3", "gmd-about__card-title");
statusTitle.textContent = "系统状态";
statusCard.appendChild(statusTitle);
statusCard.appendChild(
createInfoRow("运行环境", "Tampermonkey 油猴脚本")
);
const pandocStatusRow = createInfoRow("Pandoc 引擎", "检测中…");
statusCard.appendChild(pandocStatusRow);
const pandocVersionRow = createInfoRow("Pandoc 版本", "—");
statusCard.appendChild(pandocVersionRow);
root2.appendChild(statusCard);
updatePandocStatus(cb, pandocStatusRow, pandocVersionRow);
const infoCard = el("div", "gmd-about__card");
const infoTitle = el("h3", "gmd-about__card-title");
infoTitle.textContent = "项目信息";
infoCard.appendChild(infoTitle);
infoCard.appendChild(createInfoRow("名称", "Give Me Doc"));
infoCard.appendChild(createInfoRow("版本", "1.0.0"));
infoCard.appendChild(
createInfoRow("描述", "将 AI 对话导出为 Word 文档 — 由 Pandoc WASM 驱动")
);
infoCard.appendChild(createInfoRow("许可证", "AGPL-3.0"));
root2.appendChild(infoCard);
const linksCard = el("div", "gmd-about__card");
const linksTitle = el("h3", "gmd-about__card-title");
linksTitle.textContent = "链接";
linksCard.appendChild(linksTitle);
linksCard.appendChild(
createLinkRow(ICON_GITHUB, "GitHub 仓库", "https://github.com/Qalxry/GiveMeDoc")
);
linksCard.appendChild(
createLinkRow(ICON_BUG, "报告问题", "https://github.com/Qalxry/GiveMeDoc/issues/new")
);
linksCard.appendChild(
createLinkRow(ICON_EXTERNAL_LINK, "Pandoc 官网", "https://pandoc.org")
);
root2.appendChild(linksCard);
return root2;
}
function createInfoRow(label, value) {
const row = el("div", "gmd-about__row");
const labelEl = el("span", "gmd-about__label");
labelEl.textContent = label;
row.appendChild(labelEl);
const valueEl = el("span", "gmd-about__value");
valueEl.textContent = value;
row.appendChild(valueEl);
return row;
}
function createLinkRow(icon, text2, href) {
const row = el("a", "gmd-about__link");
row.href = href;
row.target = "_blank";
row.rel = "noopener noreferrer";
const iconEl = html("span", "gmd-about__link-icon", icon);
row.appendChild(iconEl);
const labelEl = el("span", "gmd-about__link-label");
labelEl.textContent = text2;
row.appendChild(labelEl);
const arrow = html("span", "gmd-about__link-arrow", ICON_EXTERNAL_LINK);
row.appendChild(arrow);
return row;
}
async function updatePandocStatus(cb, statusRow, versionRow) {
const statusVal = statusRow.querySelector(".gmd-about__value");
const versionVal = versionRow.querySelector(".gmd-about__value");
if (cb.isPandocReady()) {
if (statusVal) {
statusVal.textContent = "已就绪";
statusVal.classList.add("gmd-about__value--ok");
}
if (versionVal) {
const v = await cb.getPandocVersion();
versionVal.textContent = v || "未知";
}
} else {
if (statusVal) {
statusVal.textContent = "未加载";
statusVal.classList.add("gmd-about__value--warn");
}
if (versionVal) {
versionVal.textContent = "—";
}
}
}
const LS_KEY = "gmd-fab-pos";
const DEFAULT_PCT = 50;
const MIN_PCT = 5;
const MAX_PCT = 95;
const DRAG_THRESHOLD = 4;
let fabEl = null;
let callbacks = null;
function createFab(cb) {
if (fabEl) return;
callbacks = cb;
fabEl = el("button", "gmd-fab");
fabEl.type = "button";
fabEl.title = "Give Me Doc";
fabEl.setAttribute("aria-label", "Toggle Give Me Doc panel");
fabEl.innerHTML = ICON_LOGO_FG;
const savedPct = loadPosition();
applyPosition(savedPct);
let startY = 0;
let dragging = false;
let didDrag = false;
function onPointerDown(e) {
if (e.button !== 0) return;
e.preventDefault();
fabEl.setPointerCapture(e.pointerId);
startY = e.clientY;
loadPosition();
dragging = true;
didDrag = false;
fabEl.classList.add("gmd-fab--dragging");
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", onPointerUp);
}
function onPointerMove(e) {
if (!dragging) return;
const dy = Math.abs(e.clientY - startY);
if (dy >= DRAG_THRESHOLD) didDrag = true;
const pct = e.clientY / window.innerHeight * 100;
const clamped = clampPct(pct);
applyPosition(clamped);
}
function onPointerUp(e) {
if (!dragging) return;
dragging = false;
fabEl.classList.remove("gmd-fab--dragging");
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
if (didDrag) {
const pct = e.clientY / window.innerHeight * 100;
const clamped = clampPct(pct);
savePosition(clamped);
applyPosition(clamped);
} else {
handleClick();
}
}
fabEl.addEventListener("pointerdown", onPointerDown);
if (isPanelVisible()) {
fabEl.classList.add("gmd-fab--hidden");
}
document.body.appendChild(fabEl);
}
function hideFab() {
fabEl?.classList.add("gmd-fab--hidden");
}
function showFab() {
fabEl?.classList.remove("gmd-fab--hidden");
}
function destroyFab() {
if (fabEl) {
fabEl.remove();
fabEl = null;
callbacks = null;
}
}
function isFabMounted() {
return fabEl !== null;
}
function handleClick() {
if (!callbacks) return;
hideFab();
showPanel(callbacks);
}
function clampPct(pct) {
return Math.max(MIN_PCT, Math.min(MAX_PCT, pct));
}
function applyPosition(pct) {
if (!fabEl) return;
fabEl.style.top = `${pct}%`;
}
function loadPosition() {
try {
const raw = localStorage.getItem(LS_KEY);
if (raw !== null) {
const n = Number(raw);
if (!Number.isNaN(n)) return clampPct(n);
}
} catch {
}
return DEFAULT_PCT;
}
function savePosition(pct) {
try {
localStorage.setItem(LS_KEY, String(pct));
} catch {
}
}
let panelEl = null;
let isVisible = false;
let clickOutsideHandler = null;
function createPanel(cb, opts) {
if (panelEl) return;
const container2 = document.body;
panelEl = el("div", "gmd-panel");
panelEl.setAttribute("data-gmd-panel", "");
const header = el("div", "gmd-panel__header");
const titleWrap = el("div", "gmd-panel__title-wrap");
const logoIcon = html("span", "gmd-panel__logo", ICON_LOGO);
const titleText = el("span", "gmd-panel__title");
titleText.textContent = "Give Me Doc";
append(titleWrap, logoIcon, titleText);
{
const closeBtn = createIconButton({
icon: ICON_X,
title: "关闭面板",
variant: "standard",
onClick: () => hidePanel()
});
append(header, titleWrap, closeBtn);
panelEl.appendChild(header);
}
const tabs = [
{
id: "export",
label: "导出",
icon: ICON_FILE_TEXT,
render: () => renderExportTab(cb)
},
{
id: "settings",
label: "设置",
icon: ICON_SETTINGS,
render: () => renderSettingsTab(cb)
},
{
id: "about",
label: "关于",
icon: ICON_INFO,
render: () => renderAboutTab(cb)
}
];
const { root: tabsRoot } = createTabs({
tabs,
activeId: "export",
onTabChange: (tabId) => {
if (tabId === "export") refreshExportTemplates(cb);
}
});
panelEl.appendChild(tabsRoot);
container2.appendChild(panelEl);
isVisible = true;
{
enableDrag(header, panelEl);
clickOutsideHandler = (e) => {
if (!isVisible || !panelEl) return;
const target = e.target;
if (panelEl.contains(target)) return;
if (target.closest?.(".gmd-toast-container")) return;
if (target.closest?.("[data-gmd-trigger]")) return;
if (target.closest?.(".gmd-fab")) return;
hidePanel();
};
document.addEventListener("mousedown", clickOutsideHandler);
}
}
async function showPanel(cb, opts) {
if (!panelEl) {
createPanel(cb);
} else {
panelEl.classList.remove("gmd-panel--hidden");
isVisible = true;
const cfg = await cb.getConfig();
if (cfg.autoRefreshOnOpen) refreshExportTab(cb);
}
}
function hidePanel() {
if (panelEl) {
panelEl.classList.add("gmd-panel--hidden");
isVisible = false;
if (isFabMounted()) showFab();
}
}
function togglePanel(cb) {
if (isVisible) hidePanel();
else showPanel(cb);
}
function isPanelVisible() {
return isVisible;
}
function enableDrag(handle, target) {
let startY = 0;
let origY = 0;
let dragging = false;
handle.style.cursor = "grab";
function onMouseDown(e) {
if (e.button !== 0) return;
if (e.target.closest("button, input, a")) return;
dragging = true;
startY = e.clientY;
const rect = target.getBoundingClientRect();
origY = rect.top;
handle.style.cursor = "grabbing";
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
e.preventDefault();
}
function onMouseMove(e) {
if (!dragging) return;
const dy = e.clientY - startY;
const h = target.offsetHeight;
const vh = window.innerHeight;
const newTop = Math.max(0, Math.min(vh - h, origY + dy));
target.style.top = `${newTop}px`;
target.style.bottom = "auto";
}
function onMouseUp() {
dragging = false;
handle.style.cursor = "grab";
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
}
handle.addEventListener("mousedown", onMouseDown);
}
const DEFAULT_CONFIG = {
locale: "zh-CN",
includeThinking: false,
singleExportWithTemplate: false,
showFab: true,
autoRefreshOnOpen: true,
selectedTemplateId: "builtin-minimal",
lineBreaks: "hard",
documentPrefix: `---
title: {title}
date: {output_date}
---
`,
userMessageTemplate: `***
**用户:**
{content}
`,
assistantMessageTemplate: `***
**助手:**
> {thinking_content}
{content}
`,
cdnUrls: [
"https://pandoc.org/app/pandoc.wasm?sha1=2ab8055eb0803168da93d4b784fe40aa06551dfa"
],
exportMode: "session",
freetextMd: "",
freetextFilename: "export"
};
const BUILTIN_TEMPLATES = {
"builtin-minimal": {
name: "简单样式",
description: "宋体+Times,无额外格式",
data: "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"
},
"builtin-academic": {
name: "学术文档风格",
description: "适合学术论文、技术报告",
data: "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"
},
"builtin-manual": {
name: "企业手册风格",
description: "适合企业文档、操作手册",
data: "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"
},
"builtin-gw": {
name: "党政公文风格",
description: "参考GB/T 9704公文风格",
data: "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"
},
"builtin-modern": {
name: "现代简约风格",
description: "华文细黑,适合现代报告",
data: "UEsDBBQAAAAIAEKnW1w01cGcgAEAACkHAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbLWVy26DMBBF9/0KxIZFBU66qKoqJIs+lm2kph/gmCGxih+yhzz+vgMkqKrSkDRhgwQzc+6xjWA02agiWIHz0ug0GiaDKAAtTCb1Io0+Z6/xQxR45DrjhdGQRlvw0WR8M5ptLfiAhrVPwyWifWTMiyUo7hNjQVMlN05xpFu3YJaLL74AdjcY3DNhNILGGCtGOB49Q87LAoOXDT2uRUIHhQ+Dp6axykpDbm0hBUeqs5XOfqXEu4SEJusev5TW31JDyA4mVJW/A3Zz77QzTmYQTLnDN66oi2VGTJ2xnlF/cpxyQNPkuRRAjFLRSAKVUAZZbAkJDiW0zkezhXFwfvh+j6rpsxNLj0ZdvOAGc2L42riMlqqqSX9xdEWjXAHe09utimRP7lRoEVdXaCuKS93pkVPyjM+Lfxx9l0iLPkHCoDYIPRxIi+6U0KWag6Op60u06E4JD4jU18NG7MndCrgt+jiJhtsZj/TBh+Y6vFiixnRGrmH+0du+/4DvRVj9pxt/A1BLAwQUAAAACABCp1tcd7o5LPYAAADgAgAACwAAAF9yZWxzLy5yZWxzrZLLTgMxDEX3fEWUTVYdT3kJoWa6QUjdIVQ+wEo8DzF5KHGh/XsCAkFRGbroMs718ZHlxXLrRvFCKQ/BazWvaiXIm2AH32n1tL6f3SiRGb3FMXjSakdZLZuzxSONyKUn90PMokB81rJnjrcA2fTkMFchki8/bUgOuTxTBxHNM3YE53V9DeknQzZ7TLGyWqaVnUux3kU6hh3adjB0F8zGkecDI34lChlTR6zla0gW7Ge5KlgJh20uT2lDWyZvyc5iKv2JB8rfSsXmoZQzYIxTRhfHG/29e3DEaJERTEg07fOemBK6OuWKzCZzcP8IfWS+lGDvMJs3UEsDBBQAAAAIAEKnW1wZ2fZsuwMAAOcVAAARAAAAd29yZC9kb2N1bWVudC54bWzlmF1zmzgUhu/zKxhufBUD/rYndiZNttvObGc6jdteyyAMG4SYI9lu+utXQhIuIXYl7+SqN2Ch8573OUII5JvbH6Tw9hhYTstlL+qHPQ+XMU3ycrvsfV2/v571PMZRmaCClnjZe8asd7u6ujksEhrvCC65JzKUbHFY+hnn1SIIWJxhglifVrgUfSkFgrhowjY4UEgqoDFmTBiQIhiE4SQgKC99nYbYpKFpmsf4QQOYJDwzSeDSJIALxMVIsCyvmMlGl/4OyoVOdU3yGCijKb+OKVmoLPpkFPtzij0pTNwhCi1yy0EzCmRTWQLocGJ4qzy+IINQ8R005R2qC3K0b/2D6vRXYiJtaPIsz1V9+Az16ZE/F9g7LPaoWPrrnBfYD1Y3QRNQH+q5t2AVivHSrwAzDHvsr67q+CsZzWsNKOV5k8fdhjv6GImr1d2OZxQcjJTA1eYBcZdqZLirxT0lIg+XLp0iN4yD7rMtU0tcKT5gJBesqG21ofSJIHh65Ai4CM2TpT+IfPGrREQYZ0p1HSn6s2TawYvaaEeTv8rkaBE4QA8soAdd6IED9MACeuAEPbSAHnahhw7QQwvooRP0yAJ61IUeOUCPLKBHTtBjC+hxF3rsAD22gB47QU8soCdd6IkD9MQCeuIEPbWAnnahpw7QUwvoqRP0zAJ61oWeOUDPLKBnTtBzC+h5F3ruAD23gJ7bQb/PgfHPCNAWUJU5vM1qodco+65vtXfiu2iNf7i8QKXEk5q+1/z07jME/ZfjcTaN92o0aIAW5DcMG/HJTKSJBtVxp5IbRc3lhPU/i8ieKwxFXj55UE8A+JgMQ/98eR+Mxq62Jrxzrxvzt67y/JSklJeU49c9Xh0BI/mCUwxia4jbI5G+7NZP19B8BdlN9YLGT65zXWrUZHd9stZoU+B7VMmtnsu2Qsq8WOlOmPJNoU96pmyKrrVayETXd3GZi5khbMSHsmiIXXTYD5v+f8SqJa6mciX5QkVn1HRp/Fbehk0X9SvF35AnSquu6PsXl6nhe+ESHIN4rI6m9U7s5TAwta5yTomhkFu7ZgfVitvfFfm2oVWqJk7fo5P3q7XL+Dc2lwucukyYeoheu2vBscI/oc7gOAeORb81VGQx8G/NMPj9oATNE3wa5iNB2wtWj1rmaZ3zVh6neZlL5RoDcXBtCy+3vcjyT66S4Zhrs+3jT7W0R9E8rPc9mfg9mQ31Z3i1/YRAvgloJa6PRqEMUUvHsS3numjNwnpfDfk2M03FbPwC84dacPyLdvUfUEsDBBQAAAAIAEKnW1xbfIeyOgEAAF4FAAAcAAAAd29yZC9fcmVscy9kb2N1bWVudC54bWwucmVsc63Uy07DMBAF0D1fEWWTFXVSoFSoSTeA1AUbKB/gJJOHGo8jewrt3zP0mYrKYuGlr+U7x1GS2XyjuuALjG01plEyiqMAsNBli3UafS5fb6dRYEliKTuNkEZbsNE8u5m9QyeJz9im7W3AJWjTsCHqn4SwRQNK2pHuAXmn0kZJ4qWpRS+LlaxBjON4IsywI8wuOoNFmYZmUSZhsNz28J9uXVVtAc+6WCtAujJC4FrlYPhqXCpNDZSGp2jEbaG4jhj7RFjadmDPgv3aNf7O63gg4usOAYfERbj3SfiG/OOPYhC6IA8+IZVGWsq8gzPjFLkQE58I4rMDwG65DxOX4dHvg9CEmoav5SlyIaY+EYVWv1sDwzFxfhyxT0PDTaZrcXVGHGphI1XPrwWbjntvuuSxLxsCg3IHFBe/xewHUEsDBBQAAAAIAEKnW1ywyJ9EkwEAAE4IAAASAAAAd29yZC9udW1iZXJpbmcueG1sxZa7boMwFIb3PgViYWpszDVRSLZIqaqqQ/oADjgJki8IG5K8fW0CtMmAWgayYHzO72N/sn4dL9cXRq2alDIXPHHcGXQswlOR5fyYOF+7zWvsWFJhnmEqOEmcK5HOevWyPC94xfak1DpLl+BycU7sk1LFAgCZngjDciYKwnXuIEqGlZ6WR3AWZVaUIiVS6pWMAgRhCBjOub3SNfFeqhKn6qNi1t1smyX2fA4bEZd5prM1pontRjDNUEZsYDKsoip/JzWhu2tBOk0TpSZ6U9Ga6lSuh8RuK1Zsw1Sn31eUEtVrd+TSp6w++pZ2MUoOrbj4LM2g9LnbsdPoDWz9XwhpttRi8CPLuaExVRLbj6HRnTA/mgto5jd1Uxs0Wz8yuNMzRGiIwtWX+n8MND2G6/uDHHM0gsObngO54RAHCv0RHP4TOOJ4iMPzwhEcwfQc+pyDLodjbB5Oz+F7gz7Xxx7BEU3PEcBBnwfBGJ/HT+CIBn0eor/5HNy11pbCar6mz7oQwsduvO07runCt5rcrAW/HgKrb1BLAwQUAAAACABCp1tckDSK+fkHAAAsWwAADwAAAHdvcmQvc3R5bGVzLnhtbO2c227bNhiA7/sUgi6Wq8627DhOVrdIXAQpkLVZnXTXtETbXGTRI6m46QNsF8MGDAP2GHuBYm+zA/YWoyhK1oGyfJDlQ+yLxPx/kqLJjz9/nvTi1ceRrT1AQhF22ke1L6tHGnRMbCFn0D66u7183jrSKAOOBWzswPbRI6RHr14+ezE5o+zRhlTj6R16Rtr6kLHxWaVCzSEcAfolHkOH6/qYjADjQTKo4H4fmfA1Nt0RdFjFqFabFQJtwPiz6RCNqS5zm8yT2wQTa0ywCSnlhR3Zfn4jgBz9JS+ehc3XsA9cm1EvSG6IDMqQ+HeJHUa1yRmgJkK3/FGwrY+Qg8nVuUORzjUQUHZOEVAqh94XpcakLCK+QBbSK94T6SeufAB2WzcagaRDkzIbOINABp3nd91oSSKiHs+3rQPyvHvuJazIH1ZJ/txxMiQePAYmEs8BfQZ5A/L6k7mMZS7RdJVUlfKG483Y9THgWti/xuY9tLqMK9p6VfeFd29uCMIEsceprAtH6ApZFnQi8ZwhsuC3Q+jcUWhN5d9cikaXAhO7Dv9unDRl9XmP53L2OObPHAMCBgSMhzKtV9a2XvNCIuIbnu9bLztbMOKAEQzqWYpFpt/7j6xEausewvFb+JEF0at+TE98jRxIE/IxGMALAsH9BeTMwoR2giw86XD2CLYr8baweWZtvd4UP9YLvHdtLgAuw/q0qWqG0PdE5mG2yLG4sI8IZdciGz8T2A8qj6DBkIXRvzODYvUwG0abPqODtPW/f/7ln99//PfzD/99/jXsAGnxFNWYRjy1l6gLlAib2MYklIlPquvUfIkbCBxumhIdQDR3DiAhEhfYerzljZuCwlNoQuOXHXAy3zkqZHhT3UdTeYk6Q0D0TUA05SKFzCy+ioYo2o2eBFCmSxkeCYuYtDqXXoXehJGToAm1NtWrcAspFUonwlFcs73WK4vLg/UqFrYOHvEmSZuzQJ5P1wYZUkESNV01owTT9SQICnm5RcyGKVp8ad7At5QdipNTPC+tCDBGq1U4MF6UTQHT69BlwTkOnObInKPeLBimrttjSp5ChQqpCGwFELVOW9RordkabRKuJaCqVxVQVdc+xp27bIhJCjIp3noXSclNFnGrekg7RlTh49trwNLmSAj3jJMDKIWbmR5lhPvMahcp0GqL+0pB0mV8pWmDmdDhTa9nDFv1atyJVrVspDYVhjyQ9dJNUm8cn7e8dccvBuyrZxr/rKHaM2t8PX7pujt10DC1ZrRhjMbqSzMymezYXmg/5jgqGItb90M9G2Ehf0yv/UWVC9GWSrhbw0gzNooMgTPwNoP0RiOCmwwcJtRqsK4g8HbQaimopEKrrWC/XBRsqAT6041MuJux0cWoljQtmpxhl3k5Xz/YsUKXAtuqU/CGYpCtG+vBz8jEz1gHfm5sA28TRMqpebCy3NwgkbWdITKgLzbSttZDZD2TyPp+ElmPOXrxPbiSiTR2hkhDsUwZHI0omshGJpGNbSOy6EmIEbOWZa1qqtCsl4VmEsY4qvOgOePUTtFoHmeiebz3aFa3Bc1GuVYzCShaAE3VjHlNaDYz0WzuO5rxpZtNonm8WTS3DMmTTCRP9h5JY1uQbO7gok8ZcLYy4Wwd4CwLzpONwbmNSJ5mInl6QLIsJFsHJMU+jI3Ne/UBbE+TfQJ73iOxBWGZqMSSfMz4Ombe9qDcn2lFmZShGLwJSp/6js0lxszBDCopDJTzXQWIYhjLdpdQlGcnT5rz7xs+scMntZOlETSHwDvDAIlXzIxrSvKmVXglwKuD9BkmP9L04oAmovklDe9YqfHKLh4DPRvOKNqt0CfL4ktVnUNooj0kr2hK8vMOvUhiGyr7OIvYKW6sZ4df3giKJzK+Xy/WR5BkNloUnuoCEwsScTuO4XEQybuf6Fco/eTNkTTJV0iW5v+OqV32usLyqbktZ3i0fHrRQZdPjnhvtODVqhl8WDaDSqopenYH2vbXgEwbhrfssappZc1PQi8xoQ/rNiuDoPIycqjEi1OJUyfOQt2QMJGwou/xRM/pBymTGdq32kx7VgkTMNP//3Buo4GjfIIfpRItaBFHvbgNQw7y7gDfQjJSmTip1oR+kdF3mlYvb5ahuLKSdx+q1typsXPTTttcMM0AKZehw625rfO2Cnf4O2CsxCSQ5zGy0IXf9U4XS93HnTHobCc5tYLOssywOsLBzQJKKLVZWMV0pZAz56nQ1F7XAZ2i0Xkz4m2YhY5QLo1OScNWHkSpM3kHiIp/ucDAJekZuRQv6O1skcV5grSU8XYA32ZAKwObUK/NACiqmtPuHADacoBiy5IzAIq5u0l6wlfmaFNvOMGOcmkz06mWZV+xzB8g6QGGRsoyB8rsIqc9/AwIOLwU24BGAIiKIo3/x09//fnbLjV7F5qeXXjrjnowXYdSq0n1SpW4Xz0ptcf0HvYhgY6pGLGDjaZplD2oyMydEh6EhMVWHKk7hoSaBI3ZKlV9xXXEsybpUw6hZrdq9rhZ79RyEZVr5Susi9y+68iDIOmZ7LuOFuhUlRfeehPKRXbj69tzYSP/isbUvTAKu3CvOhxyWprX4f/tJJ39FS4TbfxG/nwX8tcI1Jz+qtyvLGxZdzc2Agp659CsKbEcSbOPFoVjbc4Zo41vDOQewoghNM/LYnJfFrBrRzHWz1MXu8SEHWwp3mslVJrQbSVA9Wre/qOxISO0qdnSAmtywTf68n9QSwMEFAAAAAgAQqdbXBntmHy8AgAAcQYAABEAAAB3b3JkL3NldHRpbmdzLnhtbJ1VTW/bMAy991cYvuSy1E7aZoNRp8DaZT00WzG3u8synQjRhyHJ9txfP8q26nQdumKniO89UiRFOpdXvwQPGtCGKZnOFqfxLABJVcHkLp09Pmzmn2aBsUQWhCsJ6awDM7tan1y2iQFrUWUCjCBNotKw1jIxdA+CmLlgVCujSjunSiSqLBmF8SccPXQa7q2tkiganU5VBRK5UmlBLJp6Fw0uN4rWAqSNlnG8ijRwYjFfs2eV8dHE/0ZDcu+DNG8V0Qjude0ifke5rdLFs8d70nMOlVYUjMHOCu4TZNKHMfw9cQbqjuWa6O4oyBqf7UkpEbRJBZpiC9JwEcdh5AgQORRZZyyIjZLW9CBmo8rMEgvoYyrg3A1GSDkQzKlNdpoIQbRHeh9jOw73RMKmT2nDuAWN2oZg8nEcnw+yUikrlYV7fWyhjhVpOF+8FI1wn2r0p2+hvin7oAk9bFUDQ+IFlKTm9oHkmVWVv/3jciy20KTFSr5qVtwqzZ6wYMKzilAEvfhs9Vr8E7Rl9A0pMxUn3RTzZvL9gmvWPffhhd6H/Yea7glWid0cr7/GK7TiXtX34VqJSuMAjW9BGmwSNAzae0ZtraGHcd0Lsz4JAtfMwbgUidsE19Hh5IYgEEPkayJyzUiwdbsSOUWuD5+Z9HwOOH5wzGR17sn5fCCMIJxvsABPxAPuenADZX/mW6J3U9xRof+KtppUP9hub3uLSXvHhJeYOs/qatBJXIIjqpbF90b3gzSV3CYWVwZczXdkeleQ88ds7D3XmVsr2JKqGp4+3y3SkLsMFm4XLFoF0YfeyHfLkVv23HLgeoNQt3qoHg8TtvTYke7MY2cTdu6x8wm78NjFhK08tnLYvsOtxwU+4DfEHx1eKs5VC8XtxL+CxnEFyvAVs07k09idDhxnxmZQ4YRa9bzuH8aF9X8W699QSwMEFAAAAAgAQqdbXIUcVM6cAAAAxwAAABQAAAB3b3JkL3dlYlNldHRpbmdzLnhtbF2OOw7CMBBE+5zCck9sKBCK8hFN6CKkwAFMsiSWbG/ktRKOz0JBQTnz9EZTNi/vxAqRLIZK7nMtBYQBRxumSt5v7e4kmzorA+lig0cPKTEhwVaggttKzikthVI0zOAN5bhAYPrE6E3iGCe1YRyXiAMQseydOmh9VN7YIOtMiO+4cQ63a3cR6leN2GHqzQpn6tlz0FoHH16qvzv1G1BLAwQUAAAACABCp1tce9xIOpsBAAC+CQAAEgAAAHdvcmQvZm9udFRhYmxlLnhtbO2VXU+DMBSG7/crSE28cxSGk+HY4kd26YXOeF1YGU1oS9oy3L/3wJgOtogx8cqRNClv356ePDmnnc7feWZtqNJMihA5Q4wsKmK5YmIdotfl4spH89lgWgaJFEZb4BY6UCFKjckD29ZxSjnRQ5lTAWuJVJwY+FVrWyYJi+mjjAtOhbFdjMe2ohkxcJJOWa5RE638SbRSqlWuZEy1htR4tovHCRNoNri8mNw2KVplIAinIXrZ8khmzWJjyImQmjrg2ZAsRBhi4Gvc+pBlt7bEKVGamq8tXUNCOMu2+3VSGNl15MzE6d6wIYqRKKNdk2ZrsBQ6wnDIZy614hwpbq04B8qo7YnrOH5bacVpTp/aO2ynGS4Zp9p6oqX1LDkRfTBdPMYjAOrBcGHm9cE8ov13MEc9MA+VkzAbwfk1zAdZKEZVhbMP5A3Am9RAK5DeGWQL5BtcANX9pL/HWKFz6uEDQh+g+ufmPqxHwiNIt68Wq2beNXXV3O65FjsQMwYU+yAu6mp0a5xniF2Id5Br70t9D/XnNRDdf41wP9OzD1BLAwQUAAAACABCp1tcPZUKtBkGAAD6HQAAFQAAAHdvcmQvdGhlbWUvdGhlbWUxLnhtbO1ZTW/bNhi+71cQureybCt1gjpF7Njt1qYNErdDj7RES2woUSDpJL4N7XHAgGHdsMMK7LbDsK1AC+zS/ZpsHbYO6F8YRckSZVONk7bbijUHR6Se5/3mS9K+fOU4IuAQMY5p3LWciw0LoNijPo6DrnV7NLzQsa5sfnAZbogQRQhIdMw3YNcKhUg2bJt7chryizRBsXw3oSyCQg5ZYPsMHkkpEbGbjcaaHUEcWyCGEepatyYT7CEwSkVam3PhAyI/YsHTCY+wfU9p1BkK6x846T8+433CwCEkXUvq8enRCB0LCxDIhXzRtRrqzwL25mW7YBFRQ9aIQ/U3J+YM/6CpiCwYF0xn2F6/tF1qaGYaloGDwaA/cEqJCgE9T3rrLIHbw47TK6RqqOxxWXq/4TbaCwRNQ2uJsN7r9dz1KqFVEtpLhE5jrb3VrBLaJcFd9qG31e+vVQluSVhbIgwvra+1FwgKFRIcHyzB08yWKSowE0quGfEdie8UtVDCbK3SMgGxqKu7CN6jbCgBKstQ4BiIWYIm0JO4PiR4zLDSADcQ1F7lcx5fnkvVAe4xnIiu9VEC5QIpMS+f/fDy2RNwcv/pyf2fTx48OLn/k4l2DcaBTnvx3ed/PfoE/Pnk2xcPv6whcJ3w24+f/vrLFzVIoSOff/X496ePn3/92R/fPzThtxgc6/gRjhAHN9ER2KNR6pxBBRqzM1JGIcQ6ZSsOOIxhSjLBByKswG/OIIEmYA9VA3mHycZgRF6d3qsYvR+yqcAm5PUwqiB3KCU9ysyOXVfqtFhM46BGP5vqwD0ID43q+wupHkwTWdvYKLQfooqpu0RmHwYoRgKk7+gBQibeXYwr8d3BHqOcTgS4i0EPYnNgRngszKxrOJIJmhltlKmvRGjnDuhRYlSwjQ6rULlMIDEKRaQSzatwKmBkthpGRIfegCI0Gro/Y14l8FzIpAeIUDDwEedG0i02q5h8XfaUmgrYIbOoCmUCHxihNyClOnSbHvRDGCVmu3Ec6uAP+YGsWAh2qTDbQatrJh3LhMC4PvN3MBJnXPG3cRCaiyV9M2Xzvl7p0BGOX9WuI9mt4Vto17I7Pv/m0TvWqLdkLIxrY7E91wIXm3KfMh+/Gz15G07jXZTW/fuW/L4lv2/Jr1jlKzfisvfa+qFaCYxqT9gTTMi+mBF0g6uuzaXd/lBOqoEiFSf6JJSPc30VYMCgegaMio+xCPdDmEg9jlIR8Fx2wEFCubxJWLXC1cUUS/fVnFvcJiUcih3qZ/OtyjWzEKRGAddVtVIRq6prXXpddU6GXFGf49boc1+tz9ZiKtcGgOn3Bs5aMzeTe5AgP41+LmGenbeYKaehpyqEPjLNaz46rbcTU/eMdryhWDeWY20vLy4SV0fgqGutu03XAh5MutZEHpnkY5RIgTztKJAEcdfyRObk6Utzwen1mvpyGm6tzxUlCeNiG/Iwo6lXxRcqcelC022n4t6MD6b2sqIdrY7zr9phL2YYTSbIEzUz5TB/R6cCsf3QPwJjMmV7UFrezqrMx1zuBM35gMkyb+cFWF3G+TJZ/NomXz6QJCHMy76jV0CGV8+FEWqk2WfXGH9OX1pv0Bf3/+xLWr7ydNry1Q1Kbu8MgrROuxZlIqSyHyUh9oZMHgiUMmkYkGtDtSySfv+cGosOtRaWCckaXhCKPRwAhmXXEyFDaFfknp4izZl3yHx55JLyjlMYzJPs/xgdIjJKF/FaGgILhEVbyWOhgIuJs01rbBwM/8uHmvY5d6JSVfssG2Jb3wS0vWH9da1YZV/WFDZr3G669ZvR4gacyIsGSD9kI8fMI+URdkT3ZBWA8gAgS/JCJ1+KxeRYWt3R/Utl/VNHpE5d3t/o6VKLeKsu4qcoPH/EXUPA3VPibS8vWFu7sajR0k9VdHxPKt+WV6IpyWZ4IkfZwy7LfB5TfzZ/JjxrEXk05n2exHtoArB/PE/vQlzzX4LKTX4vU5IGoGC2VmDmhHJvKdjNFdgFZX47LNjq1meSQDTdGSHLdtE3i4CR+DUjt4oH5sgZa3nlyK2SsXNEThyfErk8YLapDNGxYLA//3lLVnMuSVXw5t9QSwMEFAAAAAgAQqdbXHMYauJ0AQAAbwQAABIAAAB3b3JkL2Zvb3Rub3Rlcy54bWydk8tuwjAQRfd8ReQ9OLRSVUUkbFDXVaEfYJlJsRR7rPGQtH9fOyQUEK0oGz809555OFksP22TtUDBoCvFfJaLDJzGrXEfpXjfvEyfxbKaLLqiRmSHDCGLDheKrhQ7Zl9IGfQOrAoz9OBirEayiuOVPmSHtPWEGkKIQNvIhzx/klYZJwaMvQWDdW00rFDvLTgeIbwbIXQvhKBRHDsPO+PDSMNS7MkVA2pqjSYMWPNUoy0OlGEbHe1fjtY2o66b5zew09BGh7qlsy2p7pfxeqPvIEQX7+nYXufvYJw//eoQFNXJl5R1BX95KIVGx8bt+5dYg1ekGEnEsNmWIu89Pi2UlqviTFYL2Qtkr5U/Wa5mDJdZpvOLNOH/6MR5POH4157k1/zVpHirmlK8DPoNfLIY2IOQqkmWpf21Px3OV81vUAPF3xQSIZnk0XVSUlSl8FD9sHB60yJ4peMYPEEAakFUPYKrm7RjFVnqYXZmvDKik0uovgFQSwMEFAAAAAgAQqdbXPQQwRvFAAAAPQEAAB0AAAB3b3JkL19yZWxzL2Zvb3Rub3Rlcy54bWwucmVsc43PsWrDMBAG4D1PIbRoquW2UEqwnKUJZMhS0gc4pLMtIt0JSQ3O21dLSwMdOh4///dzw26NQVwxF89k1GPXK4Fk2Xmajfo4Hx5elSgVyEFgQqNuWNRu3AzvGKC2Tll8KqIhVIxcak1brYtdMELpOCG1ZOIcobYzzzqBvcCM+qnvX3T+bcjxzhRHZ2Q+uudeivMt4X9wniZv8Y3tZ0Sqf2zopUk5eLo0FPKM9YfFFWIK2FmO39mJXZvdrxUzQZB6HPTd1+MXUEsDBBQAAAAIAEKnW1xuJSPA6AAAAIICAAARAAAAd29yZC9jb21tZW50cy54bWyd0bFuwyAQBuC9T2F5YXJwOlQVCskS9QnaB0AYx0jAoTts2rcvUUylDq0sTwgd98H9nC6f3jWLQbIQJDseetaYoGGw4SbZx/tb98oaSioMykEwkn0ZYpfz0ykLDd6bkKgpQiCRZTulFAXnpCfjFR0gmlBqI6BXqWzxxjPgEBG0ISoXeMef+/6Fe2VDuzJ+CwPjaLW5gp7vL6hImiqCexE0TqWSBE02UtVAtjMGsVKdtxqBYExdSUA8lHWpHct/HYt39Vw+9hvse2i1Q22ZbECV/4g3Wr1DKF1pxp/xctxh/P7666PY8vM3UEsDBBQAAAAIAEKnW1wBZE5GZAEAANQCAAAQAAAAZG9jUHJvcHMvYXBwLnhtbJ1Sy07DMBC89yui3IlLeapyXSEQ4gAIqSmcLXuTWDi2ZRsEf89u04YgOJHT7szO7GYSvv7obfEOMRnvVuVxNS8LcMpr49pVua1vjy7LtZjxp+gDxGwgFShwaVV2OYclY0l10MtUIe2QaXzsZcY2tsw3jVFw49VbDy6zxXx+zuAjg9Ogj8JoWA6Oy/f8X1PtFd2XnuvPgH5iVhT8xUedxOUJZ0NF2KaTETRqRSNtAs6+AaLvUB2tca/pupOuBX0Y+03Q+L1xkMTxgrOhIuwqhOchSySqOT6cTbC97DVtQ+1vZIbDhp/g3skaJTPJHoyKPvkmF/QuBTlXg/E4QhI8LkqVcdeLyd0mSIVXnVEEfzIkqaEPllY+UsS20j73nI0ojWA6G1Bv0eRPgUun7c7BZ2lr04M4R+HY7OJW0sI1fpgx7hH4ea44vTibHrmjn7BrowwdfkXOJt1AtpQ94VTMsBj/J/EFUEsDBBQAAAAIAEKnW1x6+mrNMgEAAFcCAAARAAAAZG9jUHJvcHMvY29yZS54bWylkk9LwzAYh+9+itJLT23ajjkJbYYoOykIVhzeQvJuC2v+kGR2+/amne0Ud/MSePN78vDmTarlUbbRJ1gntKqTIsuTCBTTXKhtnbw1q/QuiZynitNWK6iTE7hkSW4qZjDTFl6sNmC9ABcFkXKYmTreeW8wQo7tQFKXBUKFcKOtpD6UdosMZXu6BVTm+S2S4CmnnqJemJrJGH8rOZuU5mDbQcAZghYkKO9QkRXownqw0l09MCQ/SCn8ycBVdAwn+ujEBHZdl3WzAQ39F2j9/PQ6XDUVqh8Vg5hUnGEvfAuk6dcKTXWfMAvUa0vuD36n7RCOW/1g93DqtOUO9fDQ9TkGHoU+8LnrMXmfPTw2q5iUebFIizItF00+x2WJ5/lHb/51/iKU4Yk34h/GUUAq9OcvkC9QSwMEFAAAAAgAQqdbXKvUWuWYAAAA8gAAABMAAABkb2NQcm9wcy9jdXN0b20ueG1snc49C8IwFIXhvb8iZG9THURK0y7i7FDdQ3r7Ac29ITct9t8bEXR3PLzwcOr26RaxQeCZUMtDUUoBaKmfcdTy3l3zsxQcDfZmIQQtd2DZNll9C+QhxBlYJAFZyylGXynFdgJnuEgZUxkoOBPTDKOiYZgtXMiuDjCqY1melF05ksv9l5Mfr9riv2RP9v2OH93uk9fU6ne2yV5QSwECFAMUAAAACABCp1tcNNXBnIABAAApBwAAEwAAAAAAAAAAAAAAgAEAAAAAW0NvbnRlbnRfVHlwZXNdLnhtbFBLAQIUAxQAAAAIAEKnW1x3ujks9gAAAOACAAALAAAAAAAAAAAAAACAAbEBAABfcmVscy8ucmVsc1BLAQIUAxQAAAAIAEKnW1wZ2fZsuwMAAOcVAAARAAAAAAAAAAAAAACAAdACAAB3b3JkL2RvY3VtZW50LnhtbFBLAQIUAxQAAAAIAEKnW1xbfIeyOgEAAF4FAAAcAAAAAAAAAAAAAACAAboGAAB3b3JkL19yZWxzL2RvY3VtZW50LnhtbC5yZWxzUEsBAhQDFAAAAAgAQqdbXLDIn0STAQAATggAABIAAAAAAAAAAAAAAIABLggAAHdvcmQvbnVtYmVyaW5nLnhtbFBLAQIUAxQAAAAIAEKnW1yQNIr5+QcAACxbAAAPAAAAAAAAAAAAAACAAfEJAAB3b3JkL3N0eWxlcy54bWxQSwECFAMUAAAACABCp1tcGe2YfLwCAABxBgAAEQAAAAAAAAAAAAAAgAEXEgAAd29yZC9zZXR0aW5ncy54bWxQSwECFAMUAAAACABCp1tchRxUzpwAAADHAAAAFAAAAAAAAAAAAAAAgAECFQAAd29yZC93ZWJTZXR0aW5ncy54bWxQSwECFAMUAAAACABCp1tce9xIOpsBAAC+CQAAEgAAAAAAAAAAAAAAgAHQFQAAd29yZC9mb250VGFibGUueG1sUEsBAhQDFAAAAAgAQqdbXD2VCrQZBgAA+h0AABUAAAAAAAAAAAAAAIABmxcAAHdvcmQvdGhlbWUvdGhlbWUxLnhtbFBLAQIUAxQAAAAIAEKnW1xzGGridAEAAG8EAAASAAAAAAAAAAAAAACAAecdAAB3b3JkL2Zvb3Rub3Rlcy54bWxQSwECFAMUAAAACABCp1tc9BDBG8UAAAA9AQAAHQAAAAAAAAAAAAAAgAGLHwAAd29yZC9fcmVscy9mb290bm90ZXMueG1sLnJlbHNQSwECFAMUAAAACABCp1tcbiUjwOgAAACCAgAAEQAAAAAAAAAAAAAAgAGLIAAAd29yZC9jb21tZW50cy54bWxQSwECFAMUAAAACABCp1tcAWRORmQBAADUAgAAEAAAAAAAAAAAAAAAgAGiIQAAZG9jUHJvcHMvYXBwLnhtbFBLAQIUAxQAAAAIAEKnW1x6+mrNMgEAAFcCAAARAAAAAAAAAAAAAACAATQjAABkb2NQcm9wcy9jb3JlLnhtbFBLAQIUAxQAAAAIAEKnW1yr1FrlmAAAAPIAAAATAAAAAAAAAAAAAACAAZUkAABkb2NQcm9wcy9jdXN0b20ueG1sUEsFBgAAAAAQABAADAQAAF4lAAAAAA=="
}
};
function b64ToArrayBuffer(b64) {
const bin = atob(b64);
const buf = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
return buf.buffer;
}
function arrayBufferToB64(ab) {
const bytes = new Uint8Array(ab);
let s = "";
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
return btoa(s);
}
async function loadConfig(storage) {
const saved = await storage.get("config");
return { ...DEFAULT_CONFIG, ...saved };
}
async function saveConfigPartial(storage, partial) {
const current = await loadConfig(storage);
await storage.set("config", { ...current, ...partial });
}
const builtinTemplates = Object.entries(BUILTIN_TEMPLATES).map(
([id, tpl]) => ({ id, name: tpl.name, isBuiltin: true, description: tpl.description })
);
async function getTemplateList(storage) {
const custom = await storage.get("custom-templates") ?? [];
return [...builtinTemplates, ...custom];
}
async function uploadTemplate(storage, name, data) {
const id = `custom-${Date.now()}`;
const meta = { id, name, isBuiltin: false };
const list = await storage.get("custom-templates") ?? [];
list.push(meta);
await storage.set("custom-templates", list);
await storage.setBlob(`tpl-blob-${id}`, data);
}
async function deleteTemplate(storage, id) {
const list = await storage.get("custom-templates") ?? [];
await storage.set("custom-templates", list.filter((t) => t.id !== id));
await storage.remove(`tpl-blob-${id}`);
}
async function getTemplateBlob(storage, id) {
if (id.startsWith("builtin-")) {
const tpl = BUILTIN_TEMPLATES[id];
if (!tpl) return void 0;
return b64ToArrayBuffer(tpl.data);
}
return await storage.getBlob(`tpl-blob-${id}`) ?? void 0;
}
function createCallbacks(storage, clearCacheFn, fabToggleFn, overrides) {
const base = {
async onExport(selectedIds2, templateId) {
const sessionId = getCurrentSessionId();
if (!sessionId) throw new Error("未检测到会话 ID");
const session2 = await getSession(sessionId);
const messages = [];
for (const id of selectedIds2) {
const msg = session2.messages.get(id);
if (msg) messages.push(msg);
}
const depthOf = (m) => {
let d = 0;
let cur = m.parentId;
while (cur) {
const p = session2.messages.get(cur);
if (!p) break;
d++;
cur = p.parentId;
}
return d;
};
messages.sort((a, b) => depthOf(a) - depthOf(b));
if (messages.length === 0) throw new Error("没有选中任何消息");
const config2 = await loadConfig(storage);
const refDocx = await getTemplateBlob(storage, templateId);
const { blob: blob2, filename } = await exportToDocx(messages, config2, session2.title, refDocx);
downloadBlob(blob2, filename);
},
async onExportRaw(markdown, templateId, filename) {
if (!isPandocReady()) throw new Error("Pandoc 尚未就绪");
const config2 = await loadConfig(storage);
const refDocx = await getTemplateBlob(storage, templateId);
const { blob: blob2, filename: outName } = await exportRawToDocx(markdown, filename, refDocx, config2.lineBreaks);
downloadBlob(blob2, outName);
},
async onTemplateUpload(name, data) {
await uploadTemplate(storage, name, data);
},
async onTemplateDelete(id) {
await deleteTemplate(storage, id);
},
async onConfigChange(partial) {
await saveConfigPartial(storage, partial);
},
async onResetConfig() {
const fresh = { ...DEFAULT_CONFIG };
await storage.set("config", fresh);
return fresh;
},
async onClearCache() {
if (clearCacheFn) {
await clearCacheFn();
}
},
async getConfig() {
return loadConfig(storage);
},
async getTemplateList() {
return getTemplateList(storage);
},
async getSession() {
const id = getCurrentSessionId();
if (!id) return null;
return getSession(id);
},
async getPandocVersion() {
return getPandocVersion();
},
isPandocReady() {
return isPandocReady();
},
onFabToggle: fabToggleFn
};
return base;
}
function createSingleExportHandler(storage) {
return async (md, title) => {
if (!isPandocReady()) {
showToast({ message: "Pandoc 尚未就绪,请稍候…", level: "warning" });
return;
}
try {
const config2 = await loadConfig(storage);
const refDocx = await getTemplateBlob(storage, config2.selectedTemplateId);
const effectiveConfig = config2.singleExportWithTemplate ? config2 : { ...config2, documentPrefix: "", userMessageTemplate: "{content}\n", assistantMessageTemplate: "{content}\n" };
const msg = {
id: "0",
parentId: null,
role: "assistant",
content: md,
thinkingContent: "",
timestamp: Date.now(),
status: "finished",
childrenIds: []
};
const { blob: blob2, filename } = await exportToDocx([msg], effectiveConfig, title, refDocx);
downloadBlob(blob2, filename);
showToast({ message: "导出成功", level: "success" });
} catch (err) {
showToast({ message: `导出失败: ${err.message}`, level: "error" });
}
};
}
function createShareExportHandler(storage) {
return async (selectedIndices) => {
if (!isPandocReady()) {
showToast({ message: "Pandoc 尚未就绪,请稍候…", level: "warning" });
return;
}
try {
const sessionId = getCurrentSessionId();
if (!sessionId) throw new Error("未检测到会话 ID");
const session2 = await getSession(sessionId);
const chain2 = getActiveChain(session2);
const messages = selectedIndices.map((i) => chain2[i]).filter(Boolean);
if (messages.length === 0) throw new Error("没有选中任何消息");
const config2 = await loadConfig(storage);
const refDocx = await getTemplateBlob(storage, config2.selectedTemplateId);
const { blob: blob2, filename } = await exportToDocx(messages, config2, session2.title, refDocx);
downloadBlob(blob2, filename);
showToast({ message: "导出成功", level: "success" });
} catch (err) {
showToast({ message: `导出失败: ${err.message}`, level: "error" });
}
};
}
const gmStorage = {
async get(key) {
const v = GM_getValue(key, null);
return v;
},
async set(key, value) {
GM_setValue(key, value);
},
async remove(key) {
GM_deleteValue(key);
},
async getBlob(key) {
const b64 = GM_getValue(key, null);
if (!b64) return null;
return b64ToArrayBuffer(b64);
},
async setBlob(key, value) {
GM_setValue(key, arrayBufferToB64(value));
}
};
const jsContent = '(function() {\n "use strict";\n /**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n const proxyMarker = Symbol("Comlink.proxy");\n const createEndpoint = Symbol("Comlink.endpoint");\n const releaseProxy = Symbol("Comlink.releaseProxy");\n const finalizer = Symbol("Comlink.finalizer");\n const throwMarker = Symbol("Comlink.thrown");\n const isObject = (val) => typeof val === "object" && val !== null || typeof val === "function";\n const proxyTransferHandler = {\n canHandle: (val) => isObject(val) && val[proxyMarker],\n serialize(obj) {\n const { port1, port2 } = new MessageChannel();\n expose(obj, port1);\n return [port2, [port2]];\n },\n deserialize(port) {\n port.start();\n return wrap(port);\n }\n };\n const throwTransferHandler = {\n canHandle: (value) => isObject(value) && throwMarker in value,\n serialize({ value }) {\n let serialized;\n if (value instanceof Error) {\n serialized = {\n isError: true,\n value: {\n message: value.message,\n name: value.name,\n stack: value.stack\n }\n };\n } else {\n serialized = { isError: false, value };\n }\n return [serialized, []];\n },\n deserialize(serialized) {\n if (serialized.isError) {\n throw Object.assign(new Error(serialized.value.message), serialized.value);\n }\n throw serialized.value;\n }\n };\n const transferHandlers = /* @__PURE__ */ new Map([\n ["proxy", proxyTransferHandler],\n ["throw", throwTransferHandler]\n ]);\n function isAllowedOrigin(allowedOrigins, origin) {\n for (const allowedOrigin of allowedOrigins) {\n if (origin === allowedOrigin || allowedOrigin === "*") {\n return true;\n }\n if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {\n return true;\n }\n }\n return false;\n }\n function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {\n ep.addEventListener("message", function callback(ev) {\n if (!ev || !ev.data) {\n return;\n }\n if (!isAllowedOrigin(allowedOrigins, ev.origin)) {\n console.warn(`Invalid origin \'${ev.origin}\' for comlink proxy`);\n return;\n }\n const { id, type, path } = Object.assign({ path: [] }, ev.data);\n const argumentList = (ev.data.argumentList || []).map(fromWireValue);\n let returnValue;\n try {\n const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);\n const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);\n switch (type) {\n case "GET":\n {\n returnValue = rawValue;\n }\n break;\n case "SET":\n {\n parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);\n returnValue = true;\n }\n break;\n case "APPLY":\n {\n returnValue = rawValue.apply(parent, argumentList);\n }\n break;\n case "CONSTRUCT":\n {\n const value = new rawValue(...argumentList);\n returnValue = proxy(value);\n }\n break;\n case "ENDPOINT":\n {\n const { port1, port2 } = new MessageChannel();\n expose(obj, port2);\n returnValue = transfer(port1, [port1]);\n }\n break;\n case "RELEASE":\n {\n returnValue = void 0;\n }\n break;\n default:\n return;\n }\n } catch (value) {\n returnValue = { value, [throwMarker]: 0 };\n }\n Promise.resolve(returnValue).catch((value) => {\n return { value, [throwMarker]: 0 };\n }).then((returnValue2) => {\n const [wireValue, transferables] = toWireValue(returnValue2);\n ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);\n if (type === "RELEASE") {\n ep.removeEventListener("message", callback);\n closeEndPoint(ep);\n if (finalizer in obj && typeof obj[finalizer] === "function") {\n obj[finalizer]();\n }\n }\n }).catch((error) => {\n const [wireValue, transferables] = toWireValue({\n value: new TypeError("Unserializable return value"),\n [throwMarker]: 0\n });\n ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);\n });\n });\n if (ep.start) {\n ep.start();\n }\n }\n function isMessagePort(endpoint) {\n return endpoint.constructor.name === "MessagePort";\n }\n function closeEndPoint(endpoint) {\n if (isMessagePort(endpoint))\n endpoint.close();\n }\n function wrap(ep, target) {\n const pendingListeners = /* @__PURE__ */ new Map();\n ep.addEventListener("message", function handleMessage(ev) {\n const { data } = ev;\n if (!data || !data.id) {\n return;\n }\n const resolver = pendingListeners.get(data.id);\n if (!resolver) {\n return;\n }\n try {\n resolver(data);\n } finally {\n pendingListeners.delete(data.id);\n }\n });\n return createProxy(ep, pendingListeners, [], target);\n }\n function throwIfProxyReleased(isReleased) {\n if (isReleased) {\n throw new Error("Proxy has been released and is not useable");\n }\n }\n function releaseEndpoint(ep) {\n return requestResponseMessage(ep, /* @__PURE__ */ new Map(), {\n type: "RELEASE"\n }).then(() => {\n closeEndPoint(ep);\n });\n }\n const proxyCounter = /* @__PURE__ */ new WeakMap();\n const proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry((ep) => {\n const newCount = (proxyCounter.get(ep) || 0) - 1;\n proxyCounter.set(ep, newCount);\n if (newCount === 0) {\n releaseEndpoint(ep);\n }\n });\n function registerProxy(proxy2, ep) {\n const newCount = (proxyCounter.get(ep) || 0) + 1;\n proxyCounter.set(ep, newCount);\n if (proxyFinalizers) {\n proxyFinalizers.register(proxy2, ep, proxy2);\n }\n }\n function unregisterProxy(proxy2) {\n if (proxyFinalizers) {\n proxyFinalizers.unregister(proxy2);\n }\n }\n function createProxy(ep, pendingListeners, path = [], target = function() {\n }) {\n let isProxyReleased = false;\n const proxy2 = new Proxy(target, {\n get(_target, prop) {\n throwIfProxyReleased(isProxyReleased);\n if (prop === releaseProxy) {\n return () => {\n unregisterProxy(proxy2);\n releaseEndpoint(ep);\n pendingListeners.clear();\n isProxyReleased = true;\n };\n }\n if (prop === "then") {\n if (path.length === 0) {\n return { then: () => proxy2 };\n }\n const r = requestResponseMessage(ep, pendingListeners, {\n type: "GET",\n path: path.map((p) => p.toString())\n }).then(fromWireValue);\n return r.then.bind(r);\n }\n return createProxy(ep, pendingListeners, [...path, prop]);\n },\n set(_target, prop, rawValue) {\n throwIfProxyReleased(isProxyReleased);\n const [value, transferables] = toWireValue(rawValue);\n return requestResponseMessage(ep, pendingListeners, {\n type: "SET",\n path: [...path, prop].map((p) => p.toString()),\n value\n }, transferables).then(fromWireValue);\n },\n apply(_target, _thisArg, rawArgumentList) {\n throwIfProxyReleased(isProxyReleased);\n const last = path[path.length - 1];\n if (last === createEndpoint) {\n return requestResponseMessage(ep, pendingListeners, {\n type: "ENDPOINT"\n }).then(fromWireValue);\n }\n if (last === "bind") {\n return createProxy(ep, pendingListeners, path.slice(0, -1));\n }\n const [argumentList, transferables] = processArguments(rawArgumentList);\n return requestResponseMessage(ep, pendingListeners, {\n type: "APPLY",\n path: path.map((p) => p.toString()),\n argumentList\n }, transferables).then(fromWireValue);\n },\n construct(_target, rawArgumentList) {\n throwIfProxyReleased(isProxyReleased);\n const [argumentList, transferables] = processArguments(rawArgumentList);\n return requestResponseMessage(ep, pendingListeners, {\n type: "CONSTRUCT",\n path: path.map((p) => p.toString()),\n argumentList\n }, transferables).then(fromWireValue);\n }\n });\n registerProxy(proxy2, ep);\n return proxy2;\n }\n function myFlat(arr) {\n return Array.prototype.concat.apply([], arr);\n }\n function processArguments(argumentList) {\n const processed = argumentList.map(toWireValue);\n return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];\n }\n const transferCache = /* @__PURE__ */ new WeakMap();\n function transfer(obj, transfers) {\n transferCache.set(obj, transfers);\n return obj;\n }\n function proxy(obj) {\n return Object.assign(obj, { [proxyMarker]: true });\n }\n function toWireValue(value) {\n for (const [name, handler] of transferHandlers) {\n if (handler.canHandle(value)) {\n const [serializedValue, transferables] = handler.serialize(value);\n return [\n {\n type: "HANDLER",\n name,\n value: serializedValue\n },\n transferables\n ];\n }\n }\n return [\n {\n type: "RAW",\n value\n },\n transferCache.get(value) || []\n ];\n }\n function fromWireValue(value) {\n switch (value.type) {\n case "HANDLER":\n return transferHandlers.get(value.name).deserialize(value.value);\n case "RAW":\n return value.value;\n }\n }\n function requestResponseMessage(ep, pendingListeners, msg, transfers) {\n return new Promise((resolve) => {\n const id = generateUUID();\n pendingListeners.set(id, resolve);\n if (ep.start) {\n ep.start();\n }\n ep.postMessage(Object.assign({ id }, msg), transfers);\n });\n }\n function generateUUID() {\n return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");\n }\n const CLOCKID_REALTIME = 0;\n const CLOCKID_MONOTONIC = 1;\n const ERRNO_SUCCESS = 0;\n const ERRNO_BADF = 8;\n const ERRNO_EXIST = 20;\n const ERRNO_INVAL = 28;\n const ERRNO_ISDIR = 31;\n const ERRNO_NAMETOOLONG = 37;\n const ERRNO_NOENT = 44;\n const ERRNO_NOSYS = 52;\n const ERRNO_NOTDIR = 54;\n const ERRNO_NOTEMPTY = 55;\n const ERRNO_NOTSUP = 58;\n const ERRNO_PERM = 63;\n const ERRNO_NOTCAPABLE = 76;\n const RIGHTS_FD_WRITE = 1 << 6;\n class Iovec {\n static read_bytes(view, ptr) {\n const iovec = new Iovec();\n iovec.buf = view.getUint32(ptr, true);\n iovec.buf_len = view.getUint32(ptr + 4, true);\n return iovec;\n }\n static read_bytes_array(view, ptr, len) {\n const iovecs = [];\n for (let i = 0; i < len; i++) {\n iovecs.push(Iovec.read_bytes(view, ptr + 8 * i));\n }\n return iovecs;\n }\n }\n class Ciovec {\n static read_bytes(view, ptr) {\n const iovec = new Ciovec();\n iovec.buf = view.getUint32(ptr, true);\n iovec.buf_len = view.getUint32(ptr + 4, true);\n return iovec;\n }\n static read_bytes_array(view, ptr, len) {\n const iovecs = [];\n for (let i = 0; i < len; i++) {\n iovecs.push(Ciovec.read_bytes(view, ptr + 8 * i));\n }\n return iovecs;\n }\n }\n const WHENCE_SET = 0;\n const WHENCE_CUR = 1;\n const WHENCE_END = 2;\n const FILETYPE_CHARACTER_DEVICE = 2;\n const FILETYPE_DIRECTORY = 3;\n const FILETYPE_REGULAR_FILE = 4;\n class Dirent {\n head_length() {\n return 24;\n }\n name_length() {\n return this.dir_name.byteLength;\n }\n write_head_bytes(view, ptr) {\n view.setBigUint64(ptr, this.d_next, true);\n view.setBigUint64(ptr + 8, this.d_ino, true);\n view.setUint32(ptr + 16, this.dir_name.length, true);\n view.setUint8(ptr + 20, this.d_type);\n }\n write_name_bytes(view8, ptr, buf_len) {\n view8.set(this.dir_name.slice(0, Math.min(this.dir_name.byteLength, buf_len)), ptr);\n }\n constructor(next_cookie, name, type) {\n this.d_ino = 0n;\n const encoded_name = new TextEncoder().encode(name);\n this.d_next = next_cookie;\n this.d_namlen = encoded_name.byteLength;\n this.d_type = type;\n this.dir_name = encoded_name;\n }\n }\n const FDFLAGS_APPEND = 1 << 0;\n class Fdstat {\n write_bytes(view, ptr) {\n view.setUint8(ptr, this.fs_filetype);\n view.setUint16(ptr + 2, this.fs_flags, true);\n view.setBigUint64(ptr + 8, this.fs_rights_base, true);\n view.setBigUint64(ptr + 16, this.fs_rights_inherited, true);\n }\n constructor(filetype, flags) {\n this.fs_rights_base = 0n;\n this.fs_rights_inherited = 0n;\n this.fs_filetype = filetype;\n this.fs_flags = flags;\n }\n }\n const OFLAGS_CREAT = 1 << 0;\n const OFLAGS_DIRECTORY = 1 << 1;\n const OFLAGS_EXCL = 1 << 2;\n const OFLAGS_TRUNC = 1 << 3;\n class Filestat {\n write_bytes(view, ptr) {\n view.setBigUint64(ptr, this.dev, true);\n view.setBigUint64(ptr + 8, this.ino, true);\n view.setUint8(ptr + 16, this.filetype);\n view.setBigUint64(ptr + 24, this.nlink, true);\n view.setBigUint64(ptr + 32, this.size, true);\n view.setBigUint64(ptr + 38, this.atim, true);\n view.setBigUint64(ptr + 46, this.mtim, true);\n view.setBigUint64(ptr + 52, this.ctim, true);\n }\n constructor(filetype, size) {\n this.dev = 0n;\n this.ino = 0n;\n this.nlink = 0n;\n this.atim = 0n;\n this.mtim = 0n;\n this.ctim = 0n;\n this.filetype = filetype;\n this.size = size;\n }\n }\n const PREOPENTYPE_DIR = 0;\n class PrestatDir {\n write_bytes(view, ptr) {\n view.setUint32(ptr, this.pr_name.byteLength, true);\n }\n constructor(name) {\n this.pr_name = new TextEncoder().encode(name);\n }\n }\n class Prestat {\n static dir(name) {\n const prestat = new Prestat();\n prestat.tag = PREOPENTYPE_DIR;\n prestat.inner = new PrestatDir(name);\n return prestat;\n }\n write_bytes(view, ptr) {\n view.setUint32(ptr, this.tag, true);\n this.inner.write_bytes(view, ptr + 4);\n }\n }\n let Debug = class Debug {\n enable(enabled) {\n this.log = createLogger(enabled === void 0 ? true : enabled, this.prefix);\n }\n get enabled() {\n return this.isEnabled;\n }\n constructor(isEnabled) {\n this.isEnabled = isEnabled;\n this.prefix = "wasi:";\n this.enable(isEnabled);\n }\n };\n function createLogger(enabled, prefix) {\n if (enabled) {\n const a = console.log.bind(console, "%c%s", "color: #265BA0", prefix);\n return a;\n } else {\n return () => {\n };\n }\n }\n const debug = new Debug(false);\n class WASIProcExit extends Error {\n constructor(code) {\n super("exit with exit code " + code);\n this.code = code;\n }\n }\n let WASI = class WASI {\n start(instance2) {\n this.inst = instance2;\n try {\n instance2.exports._start();\n return 0;\n } catch (e) {\n if (e instanceof WASIProcExit) {\n return e.code;\n } else {\n throw e;\n }\n }\n }\n initialize(instance2) {\n this.inst = instance2;\n if (instance2.exports._initialize) {\n instance2.exports._initialize();\n }\n }\n constructor(args, env, fds, options = {}) {\n this.args = [];\n this.env = [];\n this.fds = [];\n debug.enable(options.debug);\n this.args = args;\n this.env = env;\n this.fds = fds;\n const self = this;\n this.wasiImport = { args_sizes_get(argc, argv_buf_size) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n buffer.setUint32(argc, self.args.length, true);\n let buf_size = 0;\n for (const arg of self.args) {\n buf_size += arg.length + 1;\n }\n buffer.setUint32(argv_buf_size, buf_size, true);\n debug.log(buffer.getUint32(argc, true), buffer.getUint32(argv_buf_size, true));\n return 0;\n }, args_get(argv, argv_buf) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n const orig_argv_buf = argv_buf;\n for (let i = 0; i < self.args.length; i++) {\n buffer.setUint32(argv, argv_buf, true);\n argv += 4;\n const arg = new TextEncoder().encode(self.args[i]);\n buffer8.set(arg, argv_buf);\n buffer.setUint8(argv_buf + arg.length, 0);\n argv_buf += arg.length + 1;\n }\n if (debug.enabled) {\n debug.log(new TextDecoder("utf-8").decode(buffer8.slice(orig_argv_buf, argv_buf)));\n }\n return 0;\n }, environ_sizes_get(environ_count, environ_size) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n buffer.setUint32(environ_count, self.env.length, true);\n let buf_size = 0;\n for (const environ of self.env) {\n buf_size += environ.length + 1;\n }\n buffer.setUint32(environ_size, buf_size, true);\n debug.log(buffer.getUint32(environ_count, true), buffer.getUint32(environ_size, true));\n return 0;\n }, environ_get(environ, environ_buf) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n const orig_environ_buf = environ_buf;\n for (let i = 0; i < self.env.length; i++) {\n buffer.setUint32(environ, environ_buf, true);\n environ += 4;\n const e = new TextEncoder().encode(self.env[i]);\n buffer8.set(e, environ_buf);\n buffer.setUint8(environ_buf + e.length, 0);\n environ_buf += e.length + 1;\n }\n if (debug.enabled) {\n debug.log(new TextDecoder("utf-8").decode(buffer8.slice(orig_environ_buf, environ_buf)));\n }\n return 0;\n }, clock_res_get(id, res_ptr) {\n let resolutionValue;\n switch (id) {\n case CLOCKID_MONOTONIC: {\n resolutionValue = 5000n;\n break;\n }\n case CLOCKID_REALTIME: {\n resolutionValue = 1000000n;\n break;\n }\n default:\n return ERRNO_NOSYS;\n }\n const view = new DataView(self.inst.exports.memory.buffer);\n view.setBigUint64(res_ptr, resolutionValue, true);\n return ERRNO_SUCCESS;\n }, clock_time_get(id, precision, time) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n if (id === CLOCKID_REALTIME) {\n buffer.setBigUint64(time, BigInt((/* @__PURE__ */ new Date()).getTime()) * 1000000n, true);\n } else if (id == CLOCKID_MONOTONIC) {\n let monotonic_time;\n try {\n monotonic_time = BigInt(Math.round(performance.now() * 1e6));\n } catch (e) {\n monotonic_time = 0n;\n }\n buffer.setBigUint64(time, monotonic_time, true);\n } else {\n buffer.setBigUint64(time, 0n, true);\n }\n return 0;\n }, fd_advise(fd, offset, len, advice) {\n if (self.fds[fd] != void 0) {\n return ERRNO_SUCCESS;\n } else {\n return ERRNO_BADF;\n }\n }, fd_allocate(fd, offset, len) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_allocate(offset, len);\n } else {\n return ERRNO_BADF;\n }\n }, fd_close(fd) {\n if (self.fds[fd] != void 0) {\n const ret = self.fds[fd].fd_close();\n self.fds[fd] = void 0;\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, fd_datasync(fd) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_sync();\n } else {\n return ERRNO_BADF;\n }\n }, fd_fdstat_get(fd, fdstat_ptr) {\n if (self.fds[fd] != void 0) {\n const { ret, fdstat } = self.fds[fd].fd_fdstat_get();\n if (fdstat != null) {\n fdstat.write_bytes(new DataView(self.inst.exports.memory.buffer), fdstat_ptr);\n }\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, fd_fdstat_set_flags(fd, flags) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_fdstat_set_flags(flags);\n } else {\n return ERRNO_BADF;\n }\n }, fd_fdstat_set_rights(fd, fs_rights_base, fs_rights_inheriting) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_fdstat_set_rights(fs_rights_base, fs_rights_inheriting);\n } else {\n return ERRNO_BADF;\n }\n }, fd_filestat_get(fd, filestat_ptr) {\n if (self.fds[fd] != void 0) {\n const { ret, filestat } = self.fds[fd].fd_filestat_get();\n if (filestat != null) {\n filestat.write_bytes(new DataView(self.inst.exports.memory.buffer), filestat_ptr);\n }\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, fd_filestat_set_size(fd, size) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_filestat_set_size(size);\n } else {\n return ERRNO_BADF;\n }\n }, fd_filestat_set_times(fd, atim, mtim, fst_flags) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_filestat_set_times(atim, mtim, fst_flags);\n } else {\n return ERRNO_BADF;\n }\n }, fd_pread(fd, iovs_ptr, iovs_len, offset, nread_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n let nread = 0;\n for (const iovec of iovecs) {\n const { ret, data } = self.fds[fd].fd_pread(iovec.buf_len, offset);\n if (ret != ERRNO_SUCCESS) {\n buffer.setUint32(nread_ptr, nread, true);\n return ret;\n }\n buffer8.set(data, iovec.buf);\n nread += data.length;\n offset += BigInt(data.length);\n if (data.length != iovec.buf_len) {\n break;\n }\n }\n buffer.setUint32(nread_ptr, nread, true);\n return ERRNO_SUCCESS;\n } else {\n return ERRNO_BADF;\n }\n }, fd_prestat_get(fd, buf_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const { ret, prestat } = self.fds[fd].fd_prestat_get();\n if (prestat != null) {\n prestat.write_bytes(buffer, buf_ptr);\n }\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, fd_prestat_dir_name(fd, path_ptr, path_len) {\n if (self.fds[fd] != void 0) {\n const { ret, prestat } = self.fds[fd].fd_prestat_get();\n if (prestat == null) {\n return ret;\n }\n const prestat_dir_name = prestat.inner.pr_name;\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n buffer8.set(prestat_dir_name.slice(0, path_len), path_ptr);\n return prestat_dir_name.byteLength > path_len ? ERRNO_NAMETOOLONG : ERRNO_SUCCESS;\n } else {\n return ERRNO_BADF;\n }\n }, fd_pwrite(fd, iovs_ptr, iovs_len, offset, nwritten_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n let nwritten = 0;\n for (const iovec of iovecs) {\n const data = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len);\n const { ret, nwritten: nwritten_part } = self.fds[fd].fd_pwrite(data, offset);\n if (ret != ERRNO_SUCCESS) {\n buffer.setUint32(nwritten_ptr, nwritten, true);\n return ret;\n }\n nwritten += nwritten_part;\n offset += BigInt(nwritten_part);\n if (nwritten_part != data.byteLength) {\n break;\n }\n }\n buffer.setUint32(nwritten_ptr, nwritten, true);\n return ERRNO_SUCCESS;\n } else {\n return ERRNO_BADF;\n }\n }, fd_read(fd, iovs_ptr, iovs_len, nread_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n let nread = 0;\n for (const iovec of iovecs) {\n const { ret, data } = self.fds[fd].fd_read(iovec.buf_len);\n if (ret != ERRNO_SUCCESS) {\n buffer.setUint32(nread_ptr, nread, true);\n return ret;\n }\n buffer8.set(data, iovec.buf);\n nread += data.length;\n if (data.length != iovec.buf_len) {\n break;\n }\n }\n buffer.setUint32(nread_ptr, nread, true);\n return ERRNO_SUCCESS;\n } else {\n return ERRNO_BADF;\n }\n }, fd_readdir(fd, buf, buf_len, cookie, bufused_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n let bufused = 0;\n while (true) {\n const { ret, dirent } = self.fds[fd].fd_readdir_single(cookie);\n if (ret != 0) {\n buffer.setUint32(bufused_ptr, bufused, true);\n return ret;\n }\n if (dirent == null) {\n break;\n }\n if (buf_len - bufused < dirent.head_length()) {\n bufused = buf_len;\n break;\n }\n const head_bytes = new ArrayBuffer(dirent.head_length());\n dirent.write_head_bytes(new DataView(head_bytes), 0);\n buffer8.set(new Uint8Array(head_bytes).slice(0, Math.min(head_bytes.byteLength, buf_len - bufused)), buf);\n buf += dirent.head_length();\n bufused += dirent.head_length();\n if (buf_len - bufused < dirent.name_length()) {\n bufused = buf_len;\n break;\n }\n dirent.write_name_bytes(buffer8, buf, buf_len - bufused);\n buf += dirent.name_length();\n bufused += dirent.name_length();\n cookie = dirent.d_next;\n }\n buffer.setUint32(bufused_ptr, bufused, true);\n return 0;\n } else {\n return ERRNO_BADF;\n }\n }, fd_renumber(fd, to) {\n if (self.fds[fd] != void 0 && self.fds[to] != void 0) {\n const ret = self.fds[to].fd_close();\n if (ret != 0) {\n return ret;\n }\n self.fds[to] = self.fds[fd];\n self.fds[fd] = void 0;\n return 0;\n } else {\n return ERRNO_BADF;\n }\n }, fd_seek(fd, offset, whence, offset_out_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const { ret, offset: offset_out } = self.fds[fd].fd_seek(offset, whence);\n buffer.setBigInt64(offset_out_ptr, offset_out, true);\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, fd_sync(fd) {\n if (self.fds[fd] != void 0) {\n return self.fds[fd].fd_sync();\n } else {\n return ERRNO_BADF;\n }\n }, fd_tell(fd, offset_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const { ret, offset } = self.fds[fd].fd_tell();\n buffer.setBigUint64(offset_ptr, offset, true);\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n let nwritten = 0;\n for (const iovec of iovecs) {\n const data = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len);\n const { ret, nwritten: nwritten_part } = self.fds[fd].fd_write(data);\n if (ret != ERRNO_SUCCESS) {\n buffer.setUint32(nwritten_ptr, nwritten, true);\n return ret;\n }\n nwritten += nwritten_part;\n if (nwritten_part != data.byteLength) {\n break;\n }\n }\n buffer.setUint32(nwritten_ptr, nwritten, true);\n return ERRNO_SUCCESS;\n } else {\n return ERRNO_BADF;\n }\n }, path_create_directory(fd, path_ptr, path_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n return self.fds[fd].path_create_directory(path);\n } else {\n return ERRNO_BADF;\n }\n }, path_filestat_get(fd, flags, path_ptr, path_len, filestat_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n const { ret, filestat } = self.fds[fd].path_filestat_get(flags, path);\n if (filestat != null) {\n filestat.write_bytes(buffer, filestat_ptr);\n }\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, path_filestat_set_times(fd, flags, path_ptr, path_len, atim, mtim, fst_flags) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n return self.fds[fd].path_filestat_set_times(flags, path, atim, mtim, fst_flags);\n } else {\n return ERRNO_BADF;\n }\n }, path_link(old_fd, old_flags, old_path_ptr, old_path_len, new_fd, new_path_ptr, new_path_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[old_fd] != void 0 && self.fds[new_fd] != void 0) {\n const old_path = new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr, old_path_ptr + old_path_len));\n const new_path = new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr, new_path_ptr + new_path_len));\n const { ret, inode_obj } = self.fds[old_fd].path_lookup(old_path, old_flags);\n if (inode_obj == null) {\n return ret;\n }\n return self.fds[new_fd].path_link(new_path, inode_obj, false);\n } else {\n return ERRNO_BADF;\n }\n }, path_open(fd, dirflags, path_ptr, path_len, oflags, fs_rights_base, fs_rights_inheriting, fd_flags, opened_fd_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n debug.log(path);\n const { ret, fd_obj } = self.fds[fd].path_open(dirflags, path, oflags, fs_rights_base, fs_rights_inheriting, fd_flags);\n if (ret != 0) {\n return ret;\n }\n self.fds.push(fd_obj);\n const opened_fd = self.fds.length - 1;\n buffer.setUint32(opened_fd_ptr, opened_fd, true);\n return 0;\n } else {\n return ERRNO_BADF;\n }\n }, path_readlink(fd, path_ptr, path_len, buf_ptr, buf_len, nread_ptr) {\n const buffer = new DataView(self.inst.exports.memory.buffer);\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n debug.log(path);\n const { ret, data } = self.fds[fd].path_readlink(path);\n if (data != null) {\n const data_buf = new TextEncoder().encode(data);\n if (data_buf.length > buf_len) {\n buffer.setUint32(nread_ptr, 0, true);\n return ERRNO_BADF;\n }\n buffer8.set(data_buf, buf_ptr);\n buffer.setUint32(nread_ptr, data_buf.length, true);\n }\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, path_remove_directory(fd, path_ptr, path_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n return self.fds[fd].path_remove_directory(path);\n } else {\n return ERRNO_BADF;\n }\n }, path_rename(fd, old_path_ptr, old_path_len, new_fd, new_path_ptr, new_path_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0 && self.fds[new_fd] != void 0) {\n const old_path = new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr, old_path_ptr + old_path_len));\n const new_path = new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr, new_path_ptr + new_path_len));\n let { ret, inode_obj } = self.fds[fd].path_unlink(old_path);\n if (inode_obj == null) {\n return ret;\n }\n ret = self.fds[new_fd].path_link(new_path, inode_obj, true);\n if (ret != ERRNO_SUCCESS) {\n if (self.fds[fd].path_link(old_path, inode_obj, true) != ERRNO_SUCCESS) {\n throw "path_link should always return success when relinking an inode back to the original place";\n }\n }\n return ret;\n } else {\n return ERRNO_BADF;\n }\n }, path_symlink(old_path_ptr, old_path_len, fd, new_path_ptr, new_path_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr, old_path_ptr + old_path_len));\n new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr, new_path_ptr + new_path_len));\n return ERRNO_NOTSUP;\n } else {\n return ERRNO_BADF;\n }\n }, path_unlink_file(fd, path_ptr, path_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n if (self.fds[fd] != void 0) {\n const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n return self.fds[fd].path_unlink_file(path);\n } else {\n return ERRNO_BADF;\n }\n }, poll_oneoff(in_, out, nsubscriptions) {\n throw "async io not supported";\n }, proc_exit(exit_code) {\n throw new WASIProcExit(exit_code);\n }, proc_raise(sig) {\n throw "raised signal " + sig;\n }, sched_yield() {\n }, random_get(buf, buf_len) {\n const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n for (let i = 0; i < buf_len; i++) {\n buffer8[buf + i] = Math.random() * 256 | 0;\n }\n }, sock_recv(fd, ri_data, ri_flags) {\n throw "sockets not supported";\n }, sock_send(fd, si_data, si_flags) {\n throw "sockets not supported";\n }, sock_shutdown(fd, how) {\n throw "sockets not supported";\n }, sock_accept(fd, flags) {\n throw "sockets not supported";\n } };\n }\n };\n class Fd {\n fd_allocate(offset, len) {\n return ERRNO_NOTSUP;\n }\n fd_close() {\n return 0;\n }\n fd_fdstat_get() {\n return { ret: ERRNO_NOTSUP, fdstat: null };\n }\n fd_fdstat_set_flags(flags) {\n return ERRNO_NOTSUP;\n }\n fd_fdstat_set_rights(fs_rights_base, fs_rights_inheriting) {\n return ERRNO_NOTSUP;\n }\n fd_filestat_get() {\n return { ret: ERRNO_NOTSUP, filestat: null };\n }\n fd_filestat_set_size(size) {\n return ERRNO_NOTSUP;\n }\n fd_filestat_set_times(atim, mtim, fst_flags) {\n return ERRNO_NOTSUP;\n }\n fd_pread(size, offset) {\n return { ret: ERRNO_NOTSUP, data: new Uint8Array() };\n }\n fd_prestat_get() {\n return { ret: ERRNO_NOTSUP, prestat: null };\n }\n fd_pwrite(data, offset) {\n return { ret: ERRNO_NOTSUP, nwritten: 0 };\n }\n fd_read(size) {\n return { ret: ERRNO_NOTSUP, data: new Uint8Array() };\n }\n fd_readdir_single(cookie) {\n return { ret: ERRNO_NOTSUP, dirent: null };\n }\n fd_seek(offset, whence) {\n return { ret: ERRNO_NOTSUP, offset: 0n };\n }\n fd_sync() {\n return 0;\n }\n fd_tell() {\n return { ret: ERRNO_NOTSUP, offset: 0n };\n }\n fd_write(data) {\n return { ret: ERRNO_NOTSUP, nwritten: 0 };\n }\n path_create_directory(path) {\n return ERRNO_NOTSUP;\n }\n path_filestat_get(flags, path) {\n return { ret: ERRNO_NOTSUP, filestat: null };\n }\n path_filestat_set_times(flags, path, atim, mtim, fst_flags) {\n return ERRNO_NOTSUP;\n }\n path_link(path, inode, allow_dir) {\n return ERRNO_NOTSUP;\n }\n path_unlink(path) {\n return { ret: ERRNO_NOTSUP, inode_obj: null };\n }\n path_lookup(path, dirflags) {\n return { ret: ERRNO_NOTSUP, inode_obj: null };\n }\n path_open(dirflags, path, oflags, fs_rights_base, fs_rights_inheriting, fd_flags) {\n return { ret: ERRNO_NOTDIR, fd_obj: null };\n }\n path_readlink(path) {\n return { ret: ERRNO_NOTSUP, data: null };\n }\n path_remove_directory(path) {\n return ERRNO_NOTSUP;\n }\n path_rename(old_path, new_fd, new_path) {\n return ERRNO_NOTSUP;\n }\n path_unlink_file(path) {\n return ERRNO_NOTSUP;\n }\n }\n class Inode {\n }\n class OpenFile extends Fd {\n fd_allocate(offset, len) {\n if (this.file.size > offset + len) ;\n else {\n const new_data = new Uint8Array(Number(offset + len));\n new_data.set(this.file.data, 0);\n this.file.data = new_data;\n }\n return ERRNO_SUCCESS;\n }\n fd_fdstat_get() {\n return { ret: 0, fdstat: new Fdstat(FILETYPE_REGULAR_FILE, 0) };\n }\n fd_filestat_set_size(size) {\n if (this.file.size > size) {\n this.file.data = new Uint8Array(this.file.data.buffer.slice(0, Number(size)));\n } else {\n const new_data = new Uint8Array(Number(size));\n new_data.set(this.file.data, 0);\n this.file.data = new_data;\n }\n return ERRNO_SUCCESS;\n }\n fd_read(size) {\n const slice = this.file.data.slice(Number(this.file_pos), Number(this.file_pos + BigInt(size)));\n this.file_pos += BigInt(slice.length);\n return { ret: 0, data: slice };\n }\n fd_pread(size, offset) {\n const slice = this.file.data.slice(Number(offset), Number(offset + BigInt(size)));\n return { ret: 0, data: slice };\n }\n fd_seek(offset, whence) {\n let calculated_offset;\n switch (whence) {\n case WHENCE_SET:\n calculated_offset = offset;\n break;\n case WHENCE_CUR:\n calculated_offset = this.file_pos + offset;\n break;\n case WHENCE_END:\n calculated_offset = BigInt(this.file.data.byteLength) + offset;\n break;\n default:\n return { ret: ERRNO_INVAL, offset: 0n };\n }\n if (calculated_offset < 0) {\n return { ret: ERRNO_INVAL, offset: 0n };\n }\n this.file_pos = calculated_offset;\n return { ret: 0, offset: this.file_pos };\n }\n fd_tell() {\n return { ret: 0, offset: this.file_pos };\n }\n fd_write(data) {\n if (this.file.readonly) return { ret: ERRNO_BADF, nwritten: 0 };\n if (this.file_pos + BigInt(data.byteLength) > this.file.size) {\n const old = this.file.data;\n this.file.data = new Uint8Array(Number(this.file_pos + BigInt(data.byteLength)));\n this.file.data.set(old);\n }\n this.file.data.set(data, Number(this.file_pos));\n this.file_pos += BigInt(data.byteLength);\n return { ret: 0, nwritten: data.byteLength };\n }\n fd_pwrite(data, offset) {\n if (this.file.readonly) return { ret: ERRNO_BADF, nwritten: 0 };\n if (offset + BigInt(data.byteLength) > this.file.size) {\n const old = this.file.data;\n this.file.data = new Uint8Array(Number(offset + BigInt(data.byteLength)));\n this.file.data.set(old);\n }\n this.file.data.set(data, Number(offset));\n return { ret: 0, nwritten: data.byteLength };\n }\n fd_filestat_get() {\n return { ret: 0, filestat: this.file.stat() };\n }\n constructor(file) {\n super();\n this.file_pos = 0n;\n this.file = file;\n }\n }\n class OpenDirectory extends Fd {\n fd_seek(offset, whence) {\n return { ret: ERRNO_BADF, offset: 0n };\n }\n fd_tell() {\n return { ret: ERRNO_BADF, offset: 0n };\n }\n fd_allocate(offset, len) {\n return ERRNO_BADF;\n }\n fd_fdstat_get() {\n return { ret: 0, fdstat: new Fdstat(FILETYPE_DIRECTORY, 0) };\n }\n fd_readdir_single(cookie) {\n if (debug.enabled) {\n debug.log("readdir_single", cookie);\n debug.log(cookie, this.dir.contents.keys());\n }\n if (cookie == 0n) {\n return { ret: ERRNO_SUCCESS, dirent: new Dirent(1n, ".", FILETYPE_DIRECTORY) };\n } else if (cookie == 1n) {\n return { ret: ERRNO_SUCCESS, dirent: new Dirent(2n, "..", FILETYPE_DIRECTORY) };\n }\n if (cookie >= BigInt(this.dir.contents.size) + 2n) {\n return { ret: 0, dirent: null };\n }\n const [name, entry] = Array.from(this.dir.contents.entries())[Number(cookie - 2n)];\n return { ret: 0, dirent: new Dirent(cookie + 1n, name, entry.stat().filetype) };\n }\n path_filestat_get(flags, path_str) {\n const { ret: path_err, path } = Path.from(path_str);\n if (path == null) {\n return { ret: path_err, filestat: null };\n }\n const { ret, entry } = this.dir.get_entry_for_path(path);\n if (entry == null) {\n return { ret, filestat: null };\n }\n return { ret: 0, filestat: entry.stat() };\n }\n path_lookup(path_str, dirflags) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return { ret: path_ret, inode_obj: null };\n }\n const { ret, entry } = this.dir.get_entry_for_path(path);\n if (entry == null) {\n return { ret, inode_obj: null };\n }\n return { ret: ERRNO_SUCCESS, inode_obj: entry };\n }\n path_open(dirflags, path_str, oflags, fs_rights_base, fs_rights_inheriting, fd_flags) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return { ret: path_ret, fd_obj: null };\n }\n let { ret, entry } = this.dir.get_entry_for_path(path);\n if (entry == null) {\n if (ret != ERRNO_NOENT) {\n return { ret, fd_obj: null };\n }\n if ((oflags & OFLAGS_CREAT) == OFLAGS_CREAT) {\n const { ret: ret2, entry: new_entry } = this.dir.create_entry_for_path(path_str, (oflags & OFLAGS_DIRECTORY) == OFLAGS_DIRECTORY);\n if (new_entry == null) {\n return { ret: ret2, fd_obj: null };\n }\n entry = new_entry;\n } else {\n return { ret: ERRNO_NOENT, fd_obj: null };\n }\n } else if ((oflags & OFLAGS_EXCL) == OFLAGS_EXCL) {\n return { ret: ERRNO_EXIST, fd_obj: null };\n }\n if ((oflags & OFLAGS_DIRECTORY) == OFLAGS_DIRECTORY && entry.stat().filetype !== FILETYPE_DIRECTORY) {\n return { ret: ERRNO_NOTDIR, fd_obj: null };\n }\n return entry.path_open(oflags, fs_rights_base, fd_flags);\n }\n path_create_directory(path) {\n return this.path_open(0, path, OFLAGS_CREAT | OFLAGS_DIRECTORY, 0n, 0n, 0).ret;\n }\n path_link(path_str, inode, allow_dir) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return path_ret;\n }\n if (path.is_dir) {\n return ERRNO_NOENT;\n }\n const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, true);\n if (parent_entry == null || filename == null) {\n return parent_ret;\n }\n if (entry != null) {\n const source_is_dir = inode.stat().filetype == FILETYPE_DIRECTORY;\n const target_is_dir = entry.stat().filetype == FILETYPE_DIRECTORY;\n if (source_is_dir && target_is_dir) {\n if (allow_dir && entry instanceof Directory) {\n if (entry.contents.size == 0) ;\n else {\n return ERRNO_NOTEMPTY;\n }\n } else {\n return ERRNO_EXIST;\n }\n } else if (source_is_dir && !target_is_dir) {\n return ERRNO_NOTDIR;\n } else if (!source_is_dir && target_is_dir) {\n return ERRNO_ISDIR;\n } else if (inode.stat().filetype == FILETYPE_REGULAR_FILE && entry.stat().filetype == FILETYPE_REGULAR_FILE) ;\n else {\n return ERRNO_EXIST;\n }\n }\n if (!allow_dir && inode.stat().filetype == FILETYPE_DIRECTORY) {\n return ERRNO_PERM;\n }\n parent_entry.contents.set(filename, inode);\n return ERRNO_SUCCESS;\n }\n path_unlink(path_str) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return { ret: path_ret, inode_obj: null };\n }\n const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, true);\n if (parent_entry == null || filename == null) {\n return { ret: parent_ret, inode_obj: null };\n }\n if (entry == null) {\n return { ret: ERRNO_NOENT, inode_obj: null };\n }\n parent_entry.contents.delete(filename);\n return { ret: ERRNO_SUCCESS, inode_obj: entry };\n }\n path_unlink_file(path_str) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return path_ret;\n }\n const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, false);\n if (parent_entry == null || filename == null || entry == null) {\n return parent_ret;\n }\n if (entry.stat().filetype === FILETYPE_DIRECTORY) {\n return ERRNO_ISDIR;\n }\n parent_entry.contents.delete(filename);\n return ERRNO_SUCCESS;\n }\n path_remove_directory(path_str) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return path_ret;\n }\n const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, false);\n if (parent_entry == null || filename == null || entry == null) {\n return parent_ret;\n }\n if (!(entry instanceof Directory) || entry.stat().filetype !== FILETYPE_DIRECTORY) {\n return ERRNO_NOTDIR;\n }\n if (entry.contents.size !== 0) {\n return ERRNO_NOTEMPTY;\n }\n if (!parent_entry.contents.delete(filename)) {\n return ERRNO_NOENT;\n }\n return ERRNO_SUCCESS;\n }\n fd_filestat_get() {\n return { ret: 0, filestat: this.dir.stat() };\n }\n fd_filestat_set_size(size) {\n return ERRNO_BADF;\n }\n fd_read(size) {\n return { ret: ERRNO_BADF, data: new Uint8Array() };\n }\n fd_pread(size, offset) {\n return { ret: ERRNO_BADF, data: new Uint8Array() };\n }\n fd_write(data) {\n return { ret: ERRNO_BADF, nwritten: 0 };\n }\n fd_pwrite(data, offset) {\n return { ret: ERRNO_BADF, nwritten: 0 };\n }\n constructor(dir) {\n super();\n this.dir = dir;\n }\n }\n class PreopenDirectory extends OpenDirectory {\n fd_prestat_get() {\n return { ret: 0, prestat: Prestat.dir(this.prestat_name) };\n }\n constructor(name, contents) {\n super(new Directory(contents));\n this.prestat_name = name;\n }\n }\n class File extends Inode {\n path_open(oflags, fs_rights_base, fd_flags) {\n if (this.readonly && (fs_rights_base & BigInt(RIGHTS_FD_WRITE)) == BigInt(RIGHTS_FD_WRITE)) {\n return { ret: ERRNO_PERM, fd_obj: null };\n }\n if ((oflags & OFLAGS_TRUNC) == OFLAGS_TRUNC) {\n if (this.readonly) return { ret: ERRNO_PERM, fd_obj: null };\n this.data = new Uint8Array([]);\n }\n const file = new OpenFile(this);\n if (fd_flags & FDFLAGS_APPEND) file.fd_seek(0n, WHENCE_END);\n return { ret: ERRNO_SUCCESS, fd_obj: file };\n }\n get size() {\n return BigInt(this.data.byteLength);\n }\n stat() {\n return new Filestat(FILETYPE_REGULAR_FILE, this.size);\n }\n constructor(data, options) {\n super();\n this.data = new Uint8Array(data);\n this.readonly = !!options?.readonly;\n }\n }\n let Path = class Path2 {\n static from(path) {\n const self = new Path2();\n self.is_dir = path.endsWith("/");\n if (path.startsWith("/")) {\n return { ret: ERRNO_NOTCAPABLE, path: null };\n }\n if (path.includes("\\0")) {\n return { ret: ERRNO_INVAL, path: null };\n }\n for (const component of path.split("/")) {\n if (component === "" || component === ".") {\n continue;\n }\n if (component === "..") {\n if (self.parts.pop() == void 0) {\n return { ret: ERRNO_NOTCAPABLE, path: null };\n }\n continue;\n }\n self.parts.push(component);\n }\n return { ret: ERRNO_SUCCESS, path: self };\n }\n to_path_string() {\n let s = this.parts.join("/");\n if (this.is_dir) {\n s += "/";\n }\n return s;\n }\n constructor() {\n this.parts = [];\n this.is_dir = false;\n }\n };\n class Directory extends Inode {\n path_open(oflags, fs_rights_base, fd_flags) {\n return { ret: ERRNO_SUCCESS, fd_obj: new OpenDirectory(this) };\n }\n stat() {\n return new Filestat(FILETYPE_DIRECTORY, 0n);\n }\n get_entry_for_path(path) {\n let entry = this;\n for (const component of path.parts) {\n if (!(entry instanceof Directory)) {\n return { ret: ERRNO_NOTDIR, entry: null };\n }\n const child = entry.contents.get(component);\n if (child !== void 0) {\n entry = child;\n } else {\n debug.log(component);\n return { ret: ERRNO_NOENT, entry: null };\n }\n }\n if (path.is_dir) {\n if (entry.stat().filetype != FILETYPE_DIRECTORY) {\n return { ret: ERRNO_NOTDIR, entry: null };\n }\n }\n return { ret: ERRNO_SUCCESS, entry };\n }\n get_parent_dir_and_entry_for_path(path, allow_undefined) {\n const filename = path.parts.pop();\n if (filename === void 0) {\n return { ret: ERRNO_INVAL, parent_entry: null, filename: null, entry: null };\n }\n const { ret: entry_ret, entry: parent_entry } = this.get_entry_for_path(path);\n if (parent_entry == null) {\n return { ret: entry_ret, parent_entry: null, filename: null, entry: null };\n }\n if (!(parent_entry instanceof Directory)) {\n return { ret: ERRNO_NOTDIR, parent_entry: null, filename: null, entry: null };\n }\n const entry = parent_entry.contents.get(filename);\n if (entry === void 0) {\n if (!allow_undefined) {\n return { ret: ERRNO_NOENT, parent_entry: null, filename: null, entry: null };\n } else {\n return { ret: ERRNO_SUCCESS, parent_entry, filename, entry: null };\n }\n }\n if (path.is_dir) {\n if (entry.stat().filetype != FILETYPE_DIRECTORY) {\n return { ret: ERRNO_NOTDIR, parent_entry: null, filename: null, entry: null };\n }\n }\n return { ret: ERRNO_SUCCESS, parent_entry, filename, entry };\n }\n create_entry_for_path(path_str, is_dir) {\n const { ret: path_ret, path } = Path.from(path_str);\n if (path == null) {\n return { ret: path_ret, entry: null };\n }\n let { ret: parent_ret, parent_entry, filename, entry } = this.get_parent_dir_and_entry_for_path(path, true);\n if (parent_entry == null || filename == null) {\n return { ret: parent_ret, entry: null };\n }\n if (entry != null) {\n return { ret: ERRNO_EXIST, entry: null };\n }\n debug.log("create", path);\n let new_child;\n if (!is_dir) {\n new_child = new File(new ArrayBuffer(0));\n } else {\n new_child = new Directory(/* @__PURE__ */ new Map());\n }\n parent_entry.contents.set(filename, new_child);\n entry = new_child;\n return { ret: ERRNO_SUCCESS, entry };\n }\n constructor(contents) {\n super();\n if (contents instanceof Array) {\n this.contents = new Map(contents);\n } else {\n this.contents = contents;\n }\n }\n }\n class ConsoleStdout extends Fd {\n fd_filestat_get() {\n const filestat = new Filestat(FILETYPE_CHARACTER_DEVICE, BigInt(0));\n return { ret: 0, filestat };\n }\n fd_fdstat_get() {\n const fdstat = new Fdstat(FILETYPE_CHARACTER_DEVICE, 0);\n fdstat.fs_rights_base = BigInt(RIGHTS_FD_WRITE);\n return { ret: 0, fdstat };\n }\n fd_write(data) {\n this.write(data);\n return { ret: 0, nwritten: data.byteLength };\n }\n static lineBuffered(write) {\n const dec = new TextDecoder("utf-8", { fatal: false });\n let line_buf = "";\n return new ConsoleStdout((buffer) => {\n line_buf += dec.decode(buffer, { stream: true });\n const lines = line_buf.split("\\n");\n for (const [i, line] of lines.entries()) {\n if (i < lines.length - 1) {\n write(line);\n } else {\n line_buf = line;\n }\n }\n });\n }\n constructor(write) {\n super();\n this.write = write;\n }\n }\n let instance = null;\n let fileSystem = /* @__PURE__ */ new Map();\n function mem() {\n return new DataView(instance.exports.memory.buffer);\n }\n function memU8() {\n return new Uint8Array(instance.exports.memory.buffer);\n }\n function exp() {\n return instance.exports;\n }\n function addFileSync(name, data, readonly) {\n const f = new File(data, { readonly });\n fileSystem.set(name, f);\n }\n const api = {\n async init(wasmBytes) {\n const args = ["pandoc.wasm", "+RTS", "-H64m", "-RTS"];\n const env = [];\n fileSystem = /* @__PURE__ */ new Map();\n const fds = [\n new OpenFile(new File(new Uint8Array(), { readonly: true })),\n // fd 0 stdin\n ConsoleStdout.lineBuffered((m) => console.log(`[pandoc] ${m}`)),\n // fd 1\n ConsoleStdout.lineBuffered((m) => console.warn(`[pandoc] ${m}`)),\n // fd 2\n new PreopenDirectory("/", fileSystem)\n // fd 3\n ];\n const wasi = new WASI(args, env, fds, { debug: false });\n const { instance: inst } = await WebAssembly.instantiate(wasmBytes, {\n wasi_snapshot_preview1: wasi.wasiImport\n });\n instance = inst;\n wasi.initialize(instance);\n exp().__wasm_call_ctors();\n const argc_ptr = exp().malloc(4);\n mem().setUint32(argc_ptr, args.length, true);\n const argv = exp().malloc(4 * (args.length + 1));\n for (let i = 0; i < args.length; i++) {\n const arg = exp().malloc(args[i].length + 1);\n new TextEncoder().encodeInto(\n args[i],\n new Uint8Array(instance.exports.memory.buffer, arg, args[i].length)\n );\n mem().setUint8(arg + args[i].length, 0);\n mem().setUint32(argv + 4 * i, arg, true);\n }\n mem().setUint32(argv + 4 * args.length, 0, true);\n const argv_ptr = exp().malloc(4);\n mem().setUint32(argv_ptr, argv, true);\n exp().hs_init_with_rtsopts(argc_ptr, argv_ptr);\n },\n async convert(markdown, referenceDocx, lineBreaks) {\n if (!instance) throw new Error("Pandoc not initialized");\n let fromFormat = "markdown+lists_without_preceding_blankline";\n if (lineBreaks === "hard") {\n fromFormat += "+hard_line_breaks";\n } else if (lineBreaks === "east_asian") {\n fromFormat += "+east_asian_line_breaks";\n }\n const options = {\n from: fromFormat,\n to: "docx",\n "output-file": "output.docx",\n standalone: true\n };\n if (referenceDocx) {\n options["reference-doc"] = "reference.docx";\n }\n const optsStr = JSON.stringify(options);\n const optsBytes = new TextEncoder().encode(optsStr);\n const optsPtr = exp().malloc(optsBytes.length);\n memU8().set(optsBytes, optsPtr);\n fileSystem.clear();\n addFileSync("stdin", new TextEncoder().encode(markdown), true);\n addFileSync("stdout", new Uint8Array(), false);\n addFileSync("stderr", new Uint8Array(), false);\n addFileSync("warnings", new Uint8Array(), false);\n addFileSync("output.docx", new Uint8Array(), false);\n if (referenceDocx) {\n addFileSync("reference.docx", new Uint8Array(referenceDocx), true);\n }\n exp().convert(optsPtr, optsBytes.length);\n const outFile = fileSystem.get("output.docx");\n if (!outFile || !outFile.data || outFile.data.byteLength === 0) {\n const stderrFile = fileSystem.get("stderr");\n const errText = stderrFile ? new TextDecoder().decode(stderrFile.data) : "Unknown error";\n throw new Error(`Pandoc conversion failed: ${errText}`);\n }\n return outFile.data.slice().buffer;\n },\n async getVersion() {\n if (!instance) throw new Error("Pandoc not initialized");\n const queryObj = { query: "version" };\n const qStr = JSON.stringify(queryObj);\n const qBytes = new TextEncoder().encode(qStr);\n const qPtr = exp().malloc(qBytes.length);\n memU8().set(qBytes, qPtr);\n fileSystem.clear();\n addFileSync("stdin", new Uint8Array(), true);\n addFileSync("stdout", new Uint8Array(), false);\n addFileSync("stderr", new Uint8Array(), false);\n addFileSync("warnings", new Uint8Array(), false);\n exp().query(qPtr, qBytes.length);\n const outFile = fileSystem.get("stdout");\n if (!outFile) return "unknown";\n return new TextDecoder().decode(outFile.data).trim();\n }\n };\n expose(api);\n})();\n';
const blob = typeof self !== "undefined" && self.Blob && new Blob([jsContent], { type: "text/javascript;charset=utf-8" });
function WorkerWrapper(options) {
let objURL;
try {
objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob);
if (!objURL) throw "";
const worker2 = new Worker(objURL, {
name: options?.name
});
worker2.addEventListener("error", () => {
(self.URL || self.webkitURL).revokeObjectURL(objURL);
});
return worker2;
} catch (e) {
return new Worker(
"data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent),
{
name: options?.name
}
);
} finally {
objURL && (self.URL || self.webkitURL).revokeObjectURL(objURL);
}
}
const css = `/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — CSS Entry Point
*
* Import order matters: tokens → primitives → composites → page‑level.
* Vite will bundle all @import into one CSS string.
* ═══════════════════════════════════════════════════════════════════════ */
/* ── M3E Design tokens & reset ──────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — M3E Design Tokens & Base Reset
*
* Material 3 Expressive inspired CSS custom properties.
* All components reference these tokens; override them to re‑theme.
* ═══════════════════════════════════════════════════════════════════════ */
:root {
/* ── Surface / Background ────────────────────────────────────────── */
--gmd-surface: #fffbff;
--gmd-surface-container: #f3edf7;
--gmd-surface-container-low: #f7f2fa;
--gmd-surface-container-high: #ece6f0;
--gmd-surface-variant: #e7e0ec;
--gmd-on-surface: #1d1b20;
--gmd-on-surface-variant: #49454f;
--gmd-outline: #79747e;
--gmd-outline-variant: #cac4d0;
/* ── Primary ─────────────────────────────────────────────────────── */
--gmd-primary: #6750a4;
--gmd-on-primary: #ffffff;
--gmd-primary-container: #eaddff;
--gmd-on-primary-container: #21005d;
/* ── Secondary ───────────────────────────────────────────────────── */
--gmd-secondary: #625b71;
--gmd-on-secondary: #ffffff;
--gmd-secondary-container:#e8def8;
--gmd-on-secondary-container:#1d192b;
/* ── Tertiary ────────────────────────────────────────────────────── */
--gmd-tertiary: #7d5260;
--gmd-on-tertiary: #ffffff;
--gmd-tertiary-container: #ffd8e4;
/* ── Error ───────────────────────────────────────────────────────── */
--gmd-error: #b3261e;
--gmd-on-error: #ffffff;
--gmd-error-container: #f9dedc;
--gmd-on-error-container: #410e0b;
/* ── Success (custom) ────────────────────────────────────────────── */
--gmd-success: #1b6e2d;
--gmd-on-success: #ffffff;
--gmd-success-container: #a8f5a2;
--gmd-on-success-container: #002109;
/* ── Warning (custom) ────────────────────────────────────────────── */
--gmd-warning: #7c5800;
--gmd-on-warning: #ffffff;
--gmd-warning-container: #ffdea6;
--gmd-on-warning-container: #271900;
/* ── Typography ──────────────────────────────────────────────────── */
--gmd-font-family: 'Segoe UI', Roboto, 'Noto Sans SC', 'Helvetica Neue', Arial, sans-serif;
--gmd-font-size-xs: 11px;
--gmd-font-size-sm: 12px;
--gmd-font-size-md: 14px;
--gmd-font-size-lg: 16px;
--gmd-font-size-title: 18px;
--gmd-font-weight-regular: 400;
--gmd-font-weight-medium: 500;
--gmd-font-weight-bold: 600;
--gmd-line-height: 1.5;
/* ── Shape ───────────────────────────────────────────────────────── */
--gmd-radius-xs: 4px;
--gmd-radius-sm: 8px;
--gmd-radius-md: 12px;
--gmd-radius-lg: 16px;
--gmd-radius-xl: 28px;
--gmd-radius-full: 9999px;
/* ── Elevation (shadow) ──────────────────────────────────────────── */
--gmd-elevation-1: 0 1px 3px 1px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.3);
--gmd-elevation-2: 0 2px 6px 2px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.3);
--gmd-elevation-3: 0 4px 8px 3px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.3);
/* ── Transition ──────────────────────────────────────────────────── */
--gmd-duration-short: 150ms;
--gmd-duration-medium: 250ms;
--gmd-duration-long: 400ms;
--gmd-easing-standard: cubic-bezier(0.2, 0, 0, 1);
--gmd-easing-decelerate: cubic-bezier(0, 0, 0, 1);
--gmd-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);
/* ── Spacing ─────────────────────────────────────────────────────── */
--gmd-space-xs: 4px;
--gmd-space-sm: 8px;
--gmd-space-md: 12px;
--gmd-space-lg: 16px;
--gmd-space-xl: 24px;
--gmd-space-xxl: 32px;
}
/* ── Minimal reset scoped to .gmd-panel ─────────────────────────────── */
.gmd-panel *,
.gmd-panel *::before,
.gmd-panel *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ── Dark mode token overrides ──────────────────────────────────────── */
@media (prefers-color-scheme: dark) {
:root {
/* ── Surface / Background ──────────────────────────────────────── */
--gmd-surface: #141218;
--gmd-surface-container: #211f26;
--gmd-surface-container-low: #1d1b20;
--gmd-surface-container-high: #2b2930;
--gmd-surface-variant: #49454f;
--gmd-on-surface: #e6e0e9;
--gmd-on-surface-variant: #cac4d0;
--gmd-outline: #938f99;
--gmd-outline-variant: #49454f;
/* ── Primary ───────────────────────────────────────────────────── */
--gmd-primary: #d0bcff;
--gmd-on-primary: #381e72;
--gmd-primary-container: #4f378b;
--gmd-on-primary-container: #eaddff;
/* ── Secondary ─────────────────────────────────────────────────── */
--gmd-secondary: #ccc2dc;
--gmd-on-secondary: #332d41;
--gmd-secondary-container:#4a4458;
--gmd-on-secondary-container:#e8def8;
/* ── Tertiary ──────────────────────────────────────────────────── */
--gmd-tertiary: #efb8c8;
--gmd-on-tertiary: #492532;
--gmd-tertiary-container: #633b48;
/* ── Error ─────────────────────────────────────────────────────── */
--gmd-error: #f2b8b5;
--gmd-on-error: #601410;
--gmd-error-container: #8c1d18;
--gmd-on-error-container: #f9dedc;
/* ── Success (custom) ──────────────────────────────────────────── */
--gmd-success: #7ddb7f;
--gmd-on-success: #003910;
--gmd-success-container: #0a5218;
--gmd-on-success-container: #a8f5a2;
/* ── Warning (custom) ──────────────────────────────────────────── */
--gmd-warning: #f5c34a;
--gmd-on-warning: #3f2e00;
--gmd-warning-container: #5a4100;
--gmd-on-warning-container: #ffdea6;
/* ── Elevation (shadow — reduced in dark mode) ─────────────────── */
--gmd-elevation-1: 0 1px 3px 1px rgba(0,0,0,.30), 0 1px 2px rgba(0,0,0,.50);
--gmd-elevation-2: 0 2px 6px 2px rgba(0,0,0,.30), 0 1px 2px rgba(0,0,0,.50);
--gmd-elevation-3: 0 4px 8px 3px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.50);
}
}
.gmd-panel {
font-family: var(--gmd-font-family);
font-size: var(--gmd-font-size-md);
line-height: var(--gmd-line-height);
color: var(--gmd-on-surface);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ── M3E Primitive components ───────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — M3E Button & Icon‑Button
*
* .gmd-btn — regular button (filled / tonal / outlined / text / elevated)
* .gmd-icon-btn — icon‑only button (standard / filled / tonal / outlined)
* ═══════════════════════════════════════════════════════════════════════ */
/* ── Common button reset ────────────────────────────────────────────── */
.gmd-btn,
.gmd-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--gmd-space-sm);
border: none;
cursor: pointer;
font-family: var(--gmd-font-family);
font-weight: var(--gmd-font-weight-medium);
transition:
background var(--gmd-duration-short) var(--gmd-easing-standard),
box-shadow var(--gmd-duration-short) var(--gmd-easing-standard),
opacity var(--gmd-duration-short);
-webkit-tap-highlight-color: transparent;
}
.gmd-btn:disabled,
.gmd-icon-btn:disabled {
opacity: 0.38;
cursor: default;
pointer-events: none;
}
/* ════════════════════════════════════════════════════════════════════════
Regular Button .gmd-btn
════════════════════════════════════════════════════════════════════════ */
.gmd-btn {
height: 40px;
padding: 0 var(--gmd-space-xl);
border-radius: var(--gmd-radius-full);
font-size: var(--gmd-font-size-md);
line-height: 40px;
white-space: nowrap;
position: relative;
overflow: hidden;
}
.gmd-btn__icon {
display: inline-flex;
flex-shrink: 0;
}
.gmd-btn__icon svg {
width: 18px;
height: 18px;
}
.gmd-btn__label {
white-space: nowrap;
}
/* ── Filled ─────────────────────────────────────────────────────────── */
.gmd-btn--filled {
background: var(--gmd-primary);
color: var(--gmd-on-primary);
}
.gmd-btn--filled:hover {
box-shadow: var(--gmd-elevation-1);
filter: brightness(1.08);
}
.gmd-btn--filled:active {
filter: brightness(0.92);
}
/* ── Tonal ──────────────────────────────────────────────────────────── */
.gmd-btn--tonal {
background: var(--gmd-secondary-container);
color: var(--gmd-on-secondary-container);
}
.gmd-btn--tonal:hover {
box-shadow: var(--gmd-elevation-1);
filter: brightness(0.96);
}
/* ── Outlined ───────────────────────────────────────────────────────── */
.gmd-btn--outlined {
background: transparent;
color: var(--gmd-primary);
border: 1px solid var(--gmd-outline);
}
.gmd-btn--outlined:hover {
background: color-mix(in srgb, var(--gmd-primary) 8%, transparent);
}
/* ── Text ───────────────────────────────────────────────────────────── */
.gmd-btn--text {
background: transparent;
color: var(--gmd-primary);
padding: 0 var(--gmd-space-md);
}
.gmd-btn--text:hover {
background: color-mix(in srgb, var(--gmd-primary) 8%, transparent);
}
/* ── Elevated ───────────────────────────────────────────────────────── */
.gmd-btn--elevated {
background: var(--gmd-surface-container-low);
color: var(--gmd-primary);
box-shadow: var(--gmd-elevation-1);
}
.gmd-btn--elevated:hover {
box-shadow: var(--gmd-elevation-2);
}
/* ════════════════════════════════════════════════════════════════════════
Icon Button .gmd-icon-btn
════════════════════════════════════════════════════════════════════════ */
.gmd-icon-btn {
width: 40px;
height: 40px;
border-radius: var(--gmd-radius-full);
padding: 0;
font-size: 0;
background: transparent;
color: var(--gmd-on-surface-variant);
flex-shrink: 0;
}
.gmd-icon-btn svg {
width: 20px;
height: 20px;
}
/* ── Standard ───────────────────────────────────────────────────────── */
.gmd-icon-btn--standard:hover {
background: color-mix(in srgb, var(--gmd-on-surface-variant) 8%, transparent);
}
/* ── Filled ─────────────────────────────────────────────────────────── */
.gmd-icon-btn--filled {
background: var(--gmd-primary);
color: var(--gmd-on-primary);
}
.gmd-icon-btn--filled:hover {
filter: brightness(1.08);
}
/* ── Tonal ──────────────────────────────────────────────────────────── */
.gmd-icon-btn--tonal {
background: var(--gmd-secondary-container);
color: var(--gmd-on-secondary-container);
}
.gmd-icon-btn--tonal:hover {
filter: brightness(0.96);
}
/* ── Outlined ───────────────────────────────────────────────────────── */
.gmd-icon-btn--outlined {
border: 1px solid var(--gmd-outline);
color: var(--gmd-on-surface-variant);
}
.gmd-icon-btn--outlined:hover {
background: color-mix(in srgb, var(--gmd-on-surface-variant) 8%, transparent);
}
/* ── Spin animation for icon buttons ────────────────────────────────── */
@keyframes gmd-icon-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.gmd-icon-btn--spinning svg {
animation: gmd-icon-spin 0.6s linear infinite;
}
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — M3E Form Controls
*
* .gmd-checkbox — checkbox with animated check mark
* .gmd-select — native <select> with M3E outline style
* .gmd-textarea — labelled textarea
* .gmd-switch — on/off toggle
* ═══════════════════════════════════════════════════════════════════════ */
/* ════════════════════════════════════════════════════════════════════════
Checkbox .gmd-checkbox
════════════════════════════════════════════════════════════════════════ */
.gmd-checkbox {
display: inline-flex;
align-items: center;
gap: var(--gmd-space-sm);
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.gmd-checkbox__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.gmd-checkbox__box {
width: 20px;
height: 20px;
border: 2px solid var(--gmd-outline);
border-radius: var(--gmd-radius-xs);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition:
background var(--gmd-duration-short) var(--gmd-easing-standard),
border-color var(--gmd-duration-short) var(--gmd-easing-standard);
}
.gmd-checkbox__check {
width: 14px;
height: 14px;
color: var(--gmd-on-primary);
opacity: 0;
transform: scale(0.5);
transition:
opacity var(--gmd-duration-short) var(--gmd-easing-standard),
transform var(--gmd-duration-short) var(--gmd-easing-standard);
}
/* Checked state */
.gmd-checkbox__input:checked ~ .gmd-checkbox__box {
background: var(--gmd-primary);
border-color: var(--gmd-primary);
}
.gmd-checkbox__input:checked ~ .gmd-checkbox__box .gmd-checkbox__check {
opacity: 1;
transform: scale(1);
}
.gmd-checkbox__label {
font-size: var(--gmd-font-size-md);
color: var(--gmd-on-surface);
}
/* Hover ring */
.gmd-checkbox:hover .gmd-checkbox__box {
box-shadow: 0 0 0 4px color-mix(in srgb, var(--gmd-primary) 12%, transparent);
}
/* ════════════════════════════════════════════════════════════════════════
Select .gmd-select
════════════════════════════════════════════════════════════════════════ */
.gmd-select {
appearance: none;
-webkit-appearance: none;
height: 40px;
padding: 0 36px 0 var(--gmd-space-lg);
border: 1px solid var(--gmd-outline);
border-radius: var(--gmd-radius-xs);
background: var(--gmd-surface);
color: var(--gmd-on-surface);
font-family: var(--gmd-font-family);
font-size: var(--gmd-font-size-md);
cursor: pointer;
transition: border-color var(--gmd-duration-short) var(--gmd-easing-standard);
/* Dropdown arrow */
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2349454f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
}
.gmd-select:focus {
outline: none;
border-color: var(--gmd-primary);
box-shadow: 0 0 0 1px var(--gmd-primary);
}
/* ════════════════════════════════════════════════════════════════════════
Textarea .gmd-textarea
════════════════════════════════════════════════════════════════════════ */
.gmd-textarea {
display: flex;
flex-direction: column;
gap: var(--gmd-space-xs);
}
.gmd-textarea__label {
font-size: var(--gmd-font-size-sm);
font-weight: var(--gmd-font-weight-medium);
color: var(--gmd-on-surface-variant);
}
.gmd-textarea__input {
width: 100%;
padding: var(--gmd-space-md);
border: 1px solid var(--gmd-outline);
border-radius: var(--gmd-radius-sm);
background: var(--gmd-surface);
color: var(--gmd-on-surface);
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
font-size: var(--gmd-font-size-sm);
line-height: 1.6;
resize: vertical;
transition: border-color var(--gmd-duration-short) var(--gmd-easing-standard);
}
.gmd-textarea__input:focus {
outline: none;
border-color: var(--gmd-primary);
box-shadow: 0 0 0 1px var(--gmd-primary);
}
.gmd-textarea__input::placeholder {
color: var(--gmd-outline);
}
.gmd-textarea__input--nowrap {
white-space: pre;
overflow-x: auto;
resize: horizontal;
}
/* ════════════════════════════════════════════════════════════════════════
Switch .gmd-switch
════════════════════════════════════════════════════════════════════════ */
.gmd-switch {
display: inline-flex;
align-items: center;
gap: var(--gmd-space-md);
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.gmd-switch__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.gmd-switch__track {
position: relative;
width: 52px;
height: 32px;
border-radius: var(--gmd-radius-full);
background: var(--gmd-surface-variant);
border: 2px solid var(--gmd-outline);
transition:
background var(--gmd-duration-medium) var(--gmd-easing-standard),
border-color var(--gmd-duration-medium) var(--gmd-easing-standard);
}
.gmd-switch__thumb {
position: absolute;
top: 50%;
left: 6px;
width: 16px;
height: 16px;
border-radius: var(--gmd-radius-full);
background: var(--gmd-outline);
transform: translateY(-50%);
transition:
left var(--gmd-duration-medium) var(--gmd-easing-standard),
width var(--gmd-duration-medium) var(--gmd-easing-standard),
height var(--gmd-duration-medium) var(--gmd-easing-standard),
background var(--gmd-duration-medium) var(--gmd-easing-standard);
display: flex;
align-items: center;
justify-content: center;
}
.gmd-switch__icon {
display: flex;
color: var(--gmd-surface);
opacity: 0;
transform: scale(0);
transition:
opacity var(--gmd-duration-short) var(--gmd-easing-standard),
transform var(--gmd-duration-short) var(--gmd-easing-standard);
}
/* Checked state */
.gmd-switch__track--checked {
background: var(--gmd-primary);
border-color: var(--gmd-primary);
}
.gmd-switch__track--checked .gmd-switch__thumb {
left: calc(100% - 3px - 24px);
width: 24px;
height: 24px;
background: var(--gmd-on-primary);
}
.gmd-switch__track--checked .gmd-switch__icon {
opacity: 1;
transform: scale(1);
color: var(--gmd-primary);
}
.gmd-switch__label {
font-size: var(--gmd-font-size-md);
color: var(--gmd-on-surface);
}
/* Hover ring */
.gmd-switch:hover .gmd-switch__thumb {
box-shadow: 0 0 0 4px color-mix(in srgb, var(--gmd-on-surface) 8%, transparent);
}
.gmd-switch:hover .gmd-switch__track--checked .gmd-switch__thumb {
box-shadow: 0 0 0 4px color-mix(in srgb, var(--gmd-primary) 12%, transparent);
}
/* ════════════════════════════════════════════════════════════════════════
Segmented Control .gmd-segmented
════════════════════════════════════════════════════════════════════════ */
.gmd-segmented {
display: inline-flex;
border: 1px solid var(--gmd-outline);
border-radius: var(--gmd-radius-full);
overflow: hidden;
background: var(--gmd-surface);
}
.gmd-segmented__btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--gmd-space-md);
padding: var(--gmd-space-xs) var(--gmd-space-lg);
height: 36px;
border: none;
background: transparent;
color: var(--gmd-on-surface);
font-family: var(--gmd-font-family);
font-size: var(--gmd-font-size-sm);
font-weight: var(--gmd-font-weight-medium);
cursor: pointer;
transition:
background var(--gmd-duration-short) var(--gmd-easing-standard),
color var(--gmd-duration-short) var(--gmd-easing-standard);
position: relative;
}
/* Vertical divider between segments */
.gmd-segmented__btn + .gmd-segmented__btn {
border-left: 1px solid var(--gmd-outline);
}
.gmd-segmented__btn:hover:not(.gmd-segmented__btn--active) {
background: color-mix(in srgb, var(--gmd-on-surface) 8%, transparent);
}
.gmd-segmented__btn--active {
background: var(--gmd-secondary-container);
color: var(--gmd-on-secondary-container);
}
.gmd-segmented__icon {
display: inline-flex;
align-items: center;
}
.gmd-segmented__icon svg {
width: 16px;
height: 16px;
}
.gmd-segmented__label {
white-space: nowrap;
}
/* ════════════════════════════════════════════════════════════════════════
Input .gmd-input
════════════════════════════════════════════════════════════════════════ */
.gmd-input {
display: flex;
flex-direction: column;
gap: var(--gmd-space-xs);
}
.gmd-input__label {
font-size: var(--gmd-font-size-sm);
font-weight: var(--gmd-font-weight-medium);
color: var(--gmd-on-surface-variant);
}
.gmd-input__field {
height: 40px;
padding: 0 var(--gmd-space-lg);
border: 1px solid var(--gmd-outline);
border-radius: var(--gmd-radius-sm);
background: var(--gmd-surface);
color: var(--gmd-on-surface);
font-family: var(--gmd-font-family);
font-size: var(--gmd-font-size-md);
transition: border-color var(--gmd-duration-short) var(--gmd-easing-standard);
}
.gmd-input__field:focus {
outline: none;
border-color: var(--gmd-primary);
box-shadow: 0 0 0 1px var(--gmd-primary);
}
.gmd-input__field::placeholder {
color: var(--gmd-outline);
}
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — M3E Tabs
*
* .gmd-tabs — root container
* .gmd-tabs__bar — tab button strip
* .gmd-tabs__tab — individual tab button
* .gmd-tabs__indicator— animated underline
* .gmd-tabs__panels — panel container
* .gmd-tabs__panel — individual tab content
* ═══════════════════════════════════════════════════════════════════════ */
.gmd-tabs {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
/* ── Tab Bar ────────────────────────────────────────────────────────── */
.gmd-tabs__bar {
display: flex;
position: relative;
border-bottom: 1px solid var(--gmd-outline-variant);
background: var(--gmd-surface);
flex-shrink: 0;
}
.gmd-tabs__tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: var(--gmd-space-sm);
padding: var(--gmd-space-md) var(--gmd-space-lg);
height: 48px;
border: none;
background: transparent;
font-family: var(--gmd-font-family);
font-size: var(--gmd-font-size-md);
font-weight: var(--gmd-font-weight-medium);
color: var(--gmd-on-surface-variant);
cursor: pointer;
transition:
color var(--gmd-duration-short) var(--gmd-easing-standard),
background var(--gmd-duration-short) var(--gmd-easing-standard);
position: relative;
white-space: nowrap;
}
.gmd-tabs__tab:hover {
background: color-mix(in srgb, var(--gmd-primary) 8%, transparent);
}
.gmd-tabs__tab--active {
color: var(--gmd-primary);
}
.gmd-tabs__tab-icon {
display: inline-flex;
}
.gmd-tabs__tab-icon svg {
width: 18px;
height: 18px;
}
.gmd-tabs__tab-label {
white-space: nowrap;
}
/* ── Indicator (sliding underline) ──────────────────────────────────── */
.gmd-tabs__indicator {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: var(--gmd-primary);
border-radius: 3px 3px 0 0;
transition:
left var(--gmd-duration-medium) var(--gmd-easing-standard),
width var(--gmd-duration-medium) var(--gmd-easing-standard);
}
/* ── Panels ─────────────────────────────────────────────────────────── */
.gmd-tabs__panels {
flex: 1;
min-height: 0;
overflow: hidden;
position: relative;
}
.gmd-tabs__panel {
display: none;
height: 100%;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--gmd-outline-variant) transparent;
}
.gmd-tabs__panel--active {
display: block;
}
/* Webkit scrollbar */
.gmd-tabs__panel::-webkit-scrollbar {
width: 6px;
}
.gmd-tabs__panel::-webkit-scrollbar-track {
background: transparent;
}
.gmd-tabs__panel::-webkit-scrollbar-thumb {
background: var(--gmd-outline-variant);
border-radius: 3px;
}
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — M3E Toast / Snack‑bar
*
* .gmd-toast-container — fixed stack at bottom‑center
* .gmd-toast — individual notification chip
* ═══════════════════════════════════════════════════════════════════════ */
/* ── Container ──────────────────────────────────────────────────────── */
.gmd-toast-container {
position: fixed;
bottom: var(--gmd-space-xl);
left: 50%;
transform: translateX(-50%);
z-index: 100001;
display: flex;
flex-direction: column-reverse;
gap: var(--gmd-space-sm);
pointer-events: none;
}
/* ── Toast ──────────────────────────────────────────────────────────── */
.gmd-toast {
display: flex;
align-items: center;
gap: var(--gmd-space-md);
min-width: 280px;
max-width: 480px;
padding: var(--gmd-space-md) var(--gmd-space-lg);
border-radius: var(--gmd-radius-sm);
box-shadow: var(--gmd-elevation-3);
pointer-events: auto;
opacity: 0;
transform: translateY(16px) scale(0.95);
transition:
opacity var(--gmd-duration-medium) var(--gmd-easing-decelerate),
transform var(--gmd-duration-medium) var(--gmd-easing-decelerate);
}
.gmd-toast--visible {
opacity: 1;
transform: translateY(0) scale(1);
}
/* ── Level variants ─────────────────────────────────────────────────── */
.gmd-toast--info {
background: var(--gmd-on-surface);
color: var(--gmd-surface);
}
.gmd-toast--success {
background: var(--gmd-success-container);
color: var(--gmd-on-success-container);
}
.gmd-toast--warning {
background: var(--gmd-warning-container);
color: var(--gmd-on-warning-container);
}
.gmd-toast--error {
background: var(--gmd-error-container);
color: var(--gmd-on-error-container);
}
/* ── Inner elements ─────────────────────────────────────────────────── */
.gmd-toast__icon {
display: inline-flex;
flex-shrink: 0;
}
.gmd-toast__icon svg {
width: 20px;
height: 20px;
}
.gmd-toast__message {
flex: 1;
font-size: var(--gmd-font-size-md);
line-height: var(--gmd-line-height);
}
.gmd-toast__action {
border: none;
background: transparent;
color: inherit;
font-family: var(--gmd-font-family);
font-size: var(--gmd-font-size-md);
font-weight: var(--gmd-font-weight-bold);
cursor: pointer;
padding: var(--gmd-space-xs) var(--gmd-space-sm);
border-radius: var(--gmd-radius-xs);
white-space: nowrap;
opacity: 0.9;
}
.gmd-toast__action:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.12);
}
.gmd-toast__close {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: inherit;
cursor: pointer;
padding: var(--gmd-space-xs);
border-radius: var(--gmd-radius-full);
opacity: 0.7;
flex-shrink: 0;
}
.gmd-toast__close:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.12);
}
/* ── FAB ────────────────────────────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — Floating Action Button (FAB)
*
* .gmd-fab — fixed right-edge FAB for opening the panel.
* Half-hidden by default, reveals on hover/focus. Supports vertical drag.
* ═══════════════════════════════════════════════════════════════════════ */
.gmd-fab {
position: fixed;
right: -24px;
top: 50%;
transform: translateY(-50%);
z-index: 99999;
width: 48px;
height: 48px;
border: none;
border-radius: var(--gmd-radius-xl);
background: var(--gmd-primary);
color: var(--gmd-on-primary);
cursor: pointer;
opacity: 0.4;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--gmd-elevation-2);
font-family: var(--gmd-font-family);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition:
right var(--gmd-duration-medium) var(--gmd-easing-standard),
opacity var(--gmd-duration-medium) var(--gmd-easing-standard),
box-shadow var(--gmd-duration-short) var(--gmd-easing-standard);
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
.gmd-fab:hover,
.gmd-fab:focus-visible {
right: 12px;
opacity: 1;
box-shadow: var(--gmd-elevation-3);
}
.gmd-fab:active {
box-shadow: var(--gmd-elevation-1);
}
.gmd-fab svg {
width: 22px;
height: 22px;
pointer-events: none;
}
/* Invisible proximity hit area — extends 32px to the left of the FAB
so the hover triggers before the cursor reaches the visible button. */
.gmd-fab::before {
content: '';
position: absolute;
top: -10px;
right: -50px;
bottom: -10px;
left: -10px;
border-radius: var(--gmd-radius-xl); /* match FAB shape */
transition:
background-color var(--gmd-duration-medium) var(--gmd-easing-standard),
opacity var(--gmd-duration-medium) var(--gmd-easing-standard);
}
/* Dragging state — disable transitions to follow cursor smoothly */
.gmd-fab--dragging {
transition: none;
right: 12px;
opacity: 1;
}
.gmd-fab--dragging::before {
background-color: var(--gmd-primary);
opacity: 0.3;
}
/* Slide-out animation when panel opens */
.gmd-fab--hidden {
right: -60px;
opacity: 0;
pointer-events: none;
}
/* ── Panel shell ────────────────────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — Panel Shell
*
* .gmd-panel — floating side panel container
* .gmd-panel__header — drag handle + title + close btn
* ═══════════════════════════════════════════════════════════════════════ */
.gmd-panel {
position: fixed;
top: var(--gmd-space-xl);
right: var(--gmd-space-xl);
width: 420px;
max-width: calc(100vw - var(--gmd-space-xl) * 2);
height: calc(100vh - 48px);
max-height: 780px;
z-index: 100000;
display: flex;
flex-direction: column;
background: var(--gmd-surface);
border-radius: var(--gmd-radius-lg);
box-shadow: var(--gmd-elevation-3);
overflow: hidden;
transition:
opacity var(--gmd-duration-medium) var(--gmd-easing-standard),
transform var(--gmd-duration-medium) var(--gmd-easing-standard);
}
.gmd-panel--hidden {
opacity: 0;
pointer-events: none;
transform: translateX(24px);
}
/* ── Embedded mode (popup) ──────────────────────────────────────────── */
.gmd-panel--embedded {
position: static;
width: 100%;
height: 100%;
max-width: none;
max-height: none;
border-radius: 0;
box-shadow: none;
z-index: auto;
}
/* ── Header ─────────────────────────────────────────────────────────── */
.gmd-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--gmd-space-md) var(--gmd-space-lg);
background: var(--gmd-surface-container-low);
border-bottom: 1px solid var(--gmd-outline-variant);
flex-shrink: 0;
user-select: none;
}
.gmd-panel__title-wrap {
display: flex;
align-items: center;
gap: var(--gmd-space-sm);
}
.gmd-panel__logo {
display: inline-flex;
color: var(--gmd-primary);
}
.gmd-panel__logo svg {
width: 22px;
height: 22px;
}
.gmd-panel__title {
font-size: var(--gmd-font-size-title);
font-weight: var(--gmd-font-weight-bold);
color: var(--gmd-on-surface);
}
/* ── Tab pages ──────────────────────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — Export Tab
*
* .gmd-export — tab root
* .gmd-export__mode-switch — segmented control bar (session / freetext)
* .gmd-export__session — session mode container (toolbar + list)
* .gmd-export__toolbar — top bar (select‑all + count + refresh)
* .gmd-export__list — scrollable message list
* .gmd-export__row — single message row
* .gmd-export__freetext — freetext mode container (filename + editor)
* .gmd-export__bottom — action bar (template select + export btn)
* ═══════════════════════════════════════════════════════════════════════ */
.gmd-export {
display: flex;
flex-direction: column;
height: 100%;
}
/* ── Mode switch ────────────────────────────────────────────────────── */
.gmd-export__mode-switch {
display: flex;
justify-content: center;
padding: var(--gmd-space-lg) var(--gmd-space-lg) 0;
flex-shrink: 0;
}
.gmd-export__mode-switch .gmd-segmented {
width: 100%;
}
.gmd-export__mode-switch .gmd-segmented__btn {
flex: 1;
}
/* ── Session mode container ─────────────────────────────────────────── */
.gmd-export__session {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
/* ── Toolbar ────────────────────────────────────────────────────────── */
.gmd-export__toolbar {
display: flex;
align-items: center;
gap: var(--gmd-space-md);
padding: var(--gmd-space-lg) var(--gmd-space-lg) 0 var(--gmd-space-lg);
flex-shrink: 0;
}
.gmd-export__count {
margin-left: auto;
font-size: var(--gmd-font-size-sm);
color: var(--gmd-on-surface-variant);
}
/* ── Message list ───────────────────────────────────────────────────── */
.gmd-export__list {
flex: 1;
overflow-y: auto;
padding: 0 0 var(--gmd-space-sm) 0;
scrollbar-width: thin;
scrollbar-color: var(--gmd-outline-variant) transparent;
}
.gmd-export__row {
display: flex;
align-items: center;
gap: var(--gmd-space-sm);
padding: var(--gmd-space-sm) var(--gmd-space-lg);
transition: background var(--gmd-duration-short) var(--gmd-easing-standard);
}
.gmd-export__row:hover {
background: var(--gmd-surface-container-low);
}
/* ── Role icon ──────────────────────────────────────────────────────── */
.gmd-export__role-icon {
display: inline-flex;
flex-shrink: 0;
color: var(--gmd-on-surface-variant);
}
.gmd-export__row--user .gmd-export__role-icon {
color: var(--gmd-primary);
}
.gmd-export__row--assistant .gmd-export__role-icon {
color: var(--gmd-tertiary);
}
.gmd-export__role-icon svg {
width: 18px;
height: 18px;
}
/* ── Summary text ───────────────────────────────────────────────────── */
.gmd-export__summary {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--gmd-font-size-sm);
color: var(--gmd-on-surface);
}
/* ── Branch switcher ────────────────────────────────────────────────── */
.gmd-export__branch {
display: inline-flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
}
.gmd-export__branch .gmd-icon-btn {
width: 28px;
height: 28px;
}
.gmd-export__branch .gmd-icon-btn svg {
width: 16px;
height: 16px;
}
.gmd-export__branch-label {
font-size: var(--gmd-font-size-xs);
color: var(--gmd-on-surface-variant);
min-width: 28px;
text-align: center;
}
/* ── Bottom action bar ──────────────────────────────────────────────── */
.gmd-export__bottom {
display: flex;
align-items: center;
gap: var(--gmd-space-md);
padding: var(--gmd-space-md) var(--gmd-space-lg);
border-top: 1px solid var(--gmd-outline-variant);
background: var(--gmd-surface-container-low);
flex-shrink: 0;
}
.gmd-export__bottom .gmd-select {
flex: 1;
min-width: 0;
}
.gmd-export__bottom .gmd-btn {
flex-shrink: 0;
}
/* ── Freetext mode ──────────────────────────────────────────────────── */
.gmd-export__freetext {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--gmd-space-md);
padding: var(--gmd-space-lg) var(--gmd-space-lg);
min-height: 0;
overflow: hidden;
}
.gmd-export__freetext .gmd-input {
flex-shrink: 0;
}
/* Make the editor textarea fill remaining vertical space */
.gmd-export__freetext-editor {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.gmd-export__freetext-editor .gmd-textarea__input {
flex: 1;
resize: none;
}
/* Empty session placeholder */
.gmd-export__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--gmd-on-surface-variant);
padding: var(--gmd-space-lg);
text-align: center;
}
.gmd-export__empty-icon svg {
width: 64px;
height: 64px;
color: var(--gmd-on-surface-variant);
margin-bottom: var(--gmd-space-md);
}
.gmd-export__empty-text {
font-size: 14px;
color: var(--gmd-on-surface-variant);
}
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — Settings Tab
*
* .gmd-settings — tab root
* .gmd-settings__section — grouped section with title
* .gmd-settings__tpl-* — template management list
* .gmd-settings__hint — help text
* ═══════════════════════════════════════════════════════════════════════ */
.gmd-settings {
padding: var(--gmd-space-lg);
display: flex;
flex-direction: column;
gap: var(--gmd-space-xl);
}
/* ── Section ────────────────────────────────────────────────────────── */
.gmd-settings__section {
display: flex;
flex-direction: column;
gap: var(--gmd-space-md);
}
.gmd-settings__section-title {
font-size: var(--gmd-font-size-md);
font-weight: var(--gmd-font-weight-bold);
color: var(--gmd-primary);
padding-bottom: var(--gmd-space-xs);
border-bottom: 1px solid var(--gmd-outline-variant);
}
/* ── Template list ──────────────────────────────────────────────────── */
.gmd-settings__tpl-list {
display: flex;
flex-direction: column;
gap: var(--gmd-space-xs);
}
.gmd-settings__tpl-row {
display: flex;
align-items: center;
gap: var(--gmd-space-sm);
padding: var(--gmd-space-sm) var(--gmd-space-md);
border-radius: var(--gmd-radius-sm);
background: var(--gmd-surface-container-low);
transition: background var(--gmd-duration-short) var(--gmd-easing-standard);
cursor: pointer;
}
.gmd-settings__tpl-row:hover {
background: var(--gmd-surface-container);
}
.gmd-settings__tpl-row--selected {
background: var(--gmd-primary-container);
color: var(--gmd-on-primary-container);
}
.gmd-settings__tpl-row--selected:hover {
background: var(--gmd-primary-container);
}
.gmd-settings__tpl-row--selected .gmd-settings__tpl-name {
color: var(--gmd-on-primary-container);
font-weight: var(--gmd-font-weight-bold);
}
.gmd-settings__tpl-name {
font-size: var(--gmd-font-size-md);
font-weight: var(--gmd-font-weight-medium);
color: var(--gmd-on-surface);
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.gmd-settings__tpl-desc {
font-size: var(--gmd-font-size-xs);
color: var(--gmd-on-surface-variant);
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 180px;
}
.gmd-settings__tpl-badge {
font-size: var(--gmd-font-size-xs);
padding: 2px var(--gmd-space-sm);
border-radius: var(--gmd-radius-full);
background: var(--gmd-surface-variant);
color: var(--gmd-on-surface-variant);
white-space: nowrap;
flex-shrink: 0;
}
/* ── Icon buttons in template rows (smaller) ────────────────────────── */
.gmd-settings__tpl-row .gmd-icon-btn {
width: 32px;
height: 32px;
}
.gmd-settings__tpl-row .gmd-icon-btn svg {
width: 16px;
height: 16px;
}
/* ── Hint text ──────────────────────────────────────────────────────── */
.gmd-settings__hint {
font-size: var(--gmd-font-size-xs);
color: var(--gmd-on-surface-variant);
padding: var(--gmd-space-xs) 0;
font-style: italic;
}
/* ── Template editor segmented control ──────────────────────────────── */
.gmd-settings__section .gmd-segmented {
width: 100%;
}
.gmd-settings__section .gmd-segmented__btn {
flex: 1;
}
/* ── Select label ───────────────────────────────────────────────────── */
.gmd-settings__select-label {
font-size: var(--gmd-font-size-sm);
color: var(--gmd-on-surface-variant);
margin-bottom: calc(-1 * var(--gmd-space-xs));
}
/* ═══════════════════════════════════════════════════════════════════════
* Give Me Doc — About Tab
*
* .gmd-about — tab root
* .gmd-about__card — information card
* .gmd-about__row — label → value key‑pair
* .gmd-about__link — clickable external link row
* ═══════════════════════════════════════════════════════════════════════ */
.gmd-about {
padding: var(--gmd-space-lg);
display: flex;
flex-direction: column;
gap: var(--gmd-space-lg);
}
/* ── Card ───────────────────────────────────────────────────────────── */
.gmd-about__card {
background: var(--gmd-surface-container-low);
border-radius: var(--gmd-radius-md);
padding: var(--gmd-space-lg);
display: flex;
flex-direction: column;
gap: var(--gmd-space-md);
}
.gmd-about__card-title {
font-size: var(--gmd-font-size-md);
font-weight: var(--gmd-font-weight-bold);
color: var(--gmd-on-surface);
padding-bottom: var(--gmd-space-xs);
border-bottom: 1px solid var(--gmd-outline-variant);
}
/* ── Info row ───────────────────────────────────────────────────────── */
.gmd-about__row {
display: flex;
align-items: center;
gap: var(--gmd-space-md);
}
.gmd-about__label {
font-size: var(--gmd-font-size-sm);
color: var(--gmd-on-surface-variant);
flex-shrink: 0;
min-width: 72px;
}
.gmd-about__value {
font-size: var(--gmd-font-size-md);
color: var(--gmd-on-surface);
word-break: break-word;
}
.gmd-about__value--ok {
color: var(--gmd-success);
font-weight: var(--gmd-font-weight-medium);
}
.gmd-about__value--warn {
color: var(--gmd-warning);
font-weight: var(--gmd-font-weight-medium);
}
/* ── Link row ───────────────────────────────────────────────────────── */
.gmd-about__link {
display: flex;
align-items: center;
gap: var(--gmd-space-md);
padding: var(--gmd-space-sm) var(--gmd-space-md);
border-radius: var(--gmd-radius-sm);
text-decoration: none;
color: var(--gmd-on-surface);
transition: background var(--gmd-duration-short) var(--gmd-easing-standard);
}
.gmd-about__link:hover {
background: var(--gmd-surface-container);
}
.gmd-about__link-icon {
display: inline-flex;
color: var(--gmd-primary);
flex-shrink: 0;
}
.gmd-about__link-icon svg {
width: 20px;
height: 20px;
}
.gmd-about__link-label {
flex: 1;
font-size: var(--gmd-font-size-md);
}
.gmd-about__link-arrow {
display: inline-flex;
color: var(--gmd-on-surface-variant);
opacity: 0.5;
flex-shrink: 0;
}
.gmd-about__link-arrow svg {
width: 16px;
height: 16px;
}
`;
const WASM_DB_NAME = "gmd-wasm-cache";
const WASM_DB_VERSION = 1;
const WASM_STORE = "wasm";
function openWasmDb() {
return new Promise((resolve, reject) => {
const req = indexedDB.open(WASM_DB_NAME, WASM_DB_VERSION);
req.onupgradeneeded = () => {
req.result.createObjectStore(WASM_STORE);
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
async function getCachedWasm(url) {
try {
const db = await openWasmDb();
return new Promise((resolve, reject) => {
const tx = db.transaction(WASM_STORE, "readonly");
const req = tx.objectStore(WASM_STORE).get(url);
req.onsuccess = () => {
db.close();
resolve(req.result ?? null);
};
req.onerror = () => {
db.close();
reject(req.error);
};
});
} catch {
return null;
}
}
async function setCachedWasm(url, bytes) {
try {
const db = await openWasmDb();
await new Promise((resolve, reject) => {
const tx = db.transaction(WASM_STORE, "readwrite");
const req = tx.objectStore(WASM_STORE).put(bytes, url);
req.onsuccess = () => {
db.close();
resolve();
};
req.onerror = () => {
db.close();
reject(req.error);
};
});
} catch (err) {
console.warn("[GiveMeDoc] Failed to cache WASM:", err);
}
}
async function clearWasmCache() {
const db = await openWasmDb();
await new Promise((resolve, reject) => {
const tx = db.transaction(WASM_STORE, "readwrite");
const req = tx.objectStore(WASM_STORE).clear();
req.onsuccess = () => {
db.close();
resolve();
};
req.onerror = () => {
db.close();
reject(req.error);
};
});
}
async function loadPandocWasm() {
const config2 = await loadConfig(gmStorage);
const urls = config2.cdnUrls;
for (const url of urls) {
try {
const cached = await getCachedWasm(url);
if (cached) {
await initPandoc(cached, new WorkerWrapper());
return;
}
showToast({ message: "正在下载 Pandoc WASM…", level: "info", duration: 2e3 });
const wasmBytes = await fetchWasm(url);
await setCachedWasm(url, wasmBytes);
await initPandoc(wasmBytes, new WorkerWrapper());
showToast({ message: `Pandoc 就绪 (${await getPandocVersion()})`, level: "success" });
return;
} catch (err) {
console.warn(`[GiveMeDoc] Failed to load from ${url}:`, err);
}
}
showToast({ message: "Pandoc WASM 加载失败,请检查 CDN 配置", level: "error", duration: 0 });
}
function fetchWasm(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
responseType: "arraybuffer",
onload: (resp) => {
if (resp.status >= 200 && resp.status < 300) {
resolve(resp.response);
} else {
reject(new Error(`HTTP ${resp.status}`));
}
},
onerror: (err) => reject(err)
});
});
}
(function main() {
GM_addStyle(css);
const callbacks2 = createCallbacks(gmStorage, clearWasmCache, (show) => {
if (show) {
if (!isFabMounted()) createFab(callbacks2);
} else {
destroyFab();
}
});
GM_registerMenuCommand("📄 Give Me Doc 面板", () => togglePanel(callbacks2));
loadConfig(gmStorage).then((cfg) => {
if (cfg.showFab) createFab(callbacks2);
});
injectSingleExportButtons(createSingleExportHandler(gmStorage));
injectSharePanelButton(createShareExportHandler(gmStorage));
setupUrlWatcher(callbacks2);
loadPandocWasm().catch((err) => {
console.error("[GiveMeDoc] Pandoc init failed:", err);
});
console.log("[GiveMeDoc] Userscript loaded ✓");
})();
})();