import { SidebarOverlay } from "./overlay.js";
import { t } from "../core/i18n.js";
// Global helper for code block copying in Markdown view
window.moonCopyCode = function(button) {
try {
const container = button.parentElement;
if (!container) return;
const codeEl = container.querySelector("code");
if (!codeEl) return;
const text = codeEl.innerText;
navigator.clipboard.writeText(text).then(() => {
button.innerText = t("Copied!");
button.style.backgroundColor = "#4CAF50";
button.style.borderColor = "#45a049";
button.style.color = "#ffffff";
setTimeout(() => {
button.innerText = t("Copy");
button.style.backgroundColor = "#333333";
button.style.borderColor = "#555555";
button.style.color = "#aaaaaa";
}, 1500);
}).catch(() => {
button.innerText = t("Error");
setTimeout(() => button.innerText = t("Copy"), 1500);
});
} catch (e) {
button.innerText = t("Error");
setTimeout(() => button.innerText = t("Copy"), 1500);
}
};
function parseInlineMarkdown(html) {
if (!html) return "";
html = html.replace(/`([^`]+)`/g, '$1');
html = html.replace(/\*\*\*(.*?)\*\*\*/g, '$1');
html = html.replace(/\*\*(.*?)\*\*/g, '$1');
html = html.replace(/__(.*?)__/g, '$1');
html = html.replace(/\*(.*?)\*/g, '$1');
html = html.replace(/_(.*?)_/g, '$1');
html = html.replace(/~~(.*?)~~/g, '$1');
html = html.replace(/\[([^\]]+)\]\(((?:https?:\/\/|\/)[^\s)]+)\)/g, '$1');
html = html.replace(/\$(?!\s)([^\$]+?)(?$1');
return html;
}
function parseMarkdown(text) {
if (!text) return "";
let rawLines = text.replace(/&/g, "&").replace(/"); inUl = false; } }
function closeOl() { if (inOl) { htmlLines.push(""); inOl = false; } }
function closeTable() {
if (inTable) {
let tableHtml = `
| ${parseInlineMarkdown(tableHeaders[i])} | `; } tableHtml += `
|---|
| ${parseInlineMarkdown(row[i] || '')} | `; } tableHtml += `
${rCode}${quote}`; } htmlLines.push(quote); } continue; } if (line.startsWith("### ")) { closeUl(); closeOl(); htmlLines.push(`
${parseInlineMarkdown(line)}
`); } } closeUl(); closeOl(); closeTable(); return htmlLines.join("\n"); } export function createTextReader(textData, onSwitchMedia = () => {}, onDestroy = () => {}, onUserClose = () => {}) { let rawText = typeof textData === "object" ? (textData.text || "") : String(textData); let pid = typeof textData === "object" ? textData.pid : ""; let isMarkdownMode = true; const overlay = new SidebarOverlay({ onDestroy, onUserClose }); const readerBox = document.createElement("div"); Object.assign(readerBox.style, { background: "color-mix(in srgb, var(--card, var(--secondary, var(--p-surface-800, #181818))) 94%, var(--background, #000) 6%)", backdropFilter: "blur(16px)", border: "1px solid var(--border, var(--border-color, rgba(255, 255, 255, 0.15)))", borderRadius: "12px", padding: "20px 24px", width: "85%", maxWidth: "840px", height: "80vh", boxShadow: "0 12px 40px rgba(0,0,0,0.5)", display: "flex", flexDirection: "column", gap: "14px", zIndex: "20", color: "var(--foreground, var(--fg-color, #eee))", pointerEvents: "auto" }); const formatTitle = (id) => { if (!id) return t("Text Output"); const shortId = id.length > 10 ? `${id.slice(0, 8)}…` : id; return `${t("Text Output #")}${shortId}`; }; const headerRow = document.createElement("div"); Object.assign(headerRow.style, { display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: "1px solid var(--border, var(--border-color, rgba(255, 255, 255, 0.12)))", paddingBottom: "12px", flexWrap: "wrap", gap: "10px" }); const titleInfo = document.createElement("div"); Object.assign(titleInfo.style, { display: "flex", alignItems: "center", gap: "10px", minWidth: "0", flex: "1 1 auto", overflow: "hidden" }); const title = document.createElement("span"); title.textContent = formatTitle(pid); title.title = pid ? `${t("Text Output #")}${pid}` : t("Text Output"); Object.assign(title.style, { fontSize: "14px", fontWeight: "bold", color: "#f8fafc", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", flexShrink: "1" }); const statsBadge = document.createElement("span"); Object.assign(statsBadge.style, { fontSize: "10px", color: "var(--muted-foreground, #94a3b8)", background: "var(--secondary, rgba(255,255,255,0.06))", border: "1px solid var(--border, transparent)", padding: "3px 8px", borderRadius: "4px", fontFamily: "monospace", whiteSpace: "nowrap", flexShrink: "0" }); titleInfo.append(title, statsBadge); const actionsRow = document.createElement("div"); Object.assign(actionsRow.style, { display: "flex", gap: "8px", alignItems: "center", flexShrink: "0" }); const modeBtn = document.createElement("button"); Object.assign(modeBtn.style, { background: "var(--primary, var(--p-primary-color, #a855f7))", border: "1px solid var(--primary, var(--p-primary-color, #a855f7))", color: "#ffffff", borderRadius: "6px", padding: "5px 12px", fontSize: "11px", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "6px", whiteSpace: "nowrap", minWidth: "96px", height: "28px", boxSizing: "border-box" }); modeBtn.innerHTML = `Markdown`; const copyBtn = document.createElement("button"); Object.assign(copyBtn.style, { background: "var(--secondary, var(--p-surface-800, #1e293b))", border: "1px solid var(--border, var(--border-color, #475569))", color: "var(--foreground, #e2e8f0)", borderRadius: "6px", padding: "5px 10px", fontSize: "11px", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "6px", whiteSpace: "nowrap", height: "28px", boxSizing: "border-box" }); copyBtn.innerHTML = `${t("Copy")}`; const downloadBtn = document.createElement("button"); Object.assign(downloadBtn.style, { background: "var(--secondary, var(--p-surface-800, #1e293b))", border: "1px solid var(--border, var(--border-color, #475569))", color: "var(--foreground, #e2e8f0)", borderRadius: "6px", padding: "5px 10px", fontSize: "11px", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "6px", whiteSpace: "nowrap", height: "28px", boxSizing: "border-box" }); downloadBtn.innerHTML = `.txt`; actionsRow.append(modeBtn, copyBtn, downloadBtn); headerRow.append(titleInfo, actionsRow); const textArea = document.createElement("div"); Object.assign(textArea.style, { flex: "1", overflowY: "auto", fontSize: "13px", lineHeight: "1.6", color: "var(--foreground, var(--fg-color, #e2e8f0))", wordBreak: "break-word", userSelect: "text", "-webkit-user-select": "text", background: "color-mix(in srgb, var(--background, var(--comfy-input-bg, #000)) 75%, transparent)", padding: "16px 20px", borderRadius: "6px", border: "1px solid var(--border, var(--border-color, rgba(255, 255, 255, 0.1)))", scrollbarWidth: "thin" }); const updateView = () => { const wc = rawText.trim() ? rawText.trim().split(/\s+/).length : 0; statsBadge.textContent = t("{wc} words • {chars} chars", { wc, chars: rawText.length }); if (isMarkdownMode) { modeBtn.innerHTML = `Markdown`; modeBtn.style.background = "var(--primary, var(--p-primary-color, #a855f7))"; modeBtn.style.borderColor = "var(--primary, var(--p-primary-color, #a855f7))"; modeBtn.style.color = "#ffffff"; textArea.style.fontFamily = "sans-serif"; textArea.style.whiteSpace = "normal"; textArea.innerHTML = parseMarkdown(rawText); } else { modeBtn.innerHTML = `${t("Raw Text")}`; modeBtn.style.background = "var(--secondary, #334155)"; modeBtn.style.borderColor = "var(--border, #64748b)"; modeBtn.style.color = "var(--muted-foreground, #f8fafc)"; textArea.style.fontFamily = "monospace"; textArea.style.whiteSpace = "pre-wrap"; textArea.textContent = rawText; } }; modeBtn.onclick = () => { isMarkdownMode = !isMarkdownMode; updateView(); }; copyBtn.onclick = () => { navigator.clipboard.writeText(rawText); copyBtn.innerHTML = `${t("Copied!")}`; setTimeout(() => { copyBtn.innerHTML = `${t("Copy")}`; }, 1800); }; downloadBtn.onclick = () => { const blob = new Blob([rawText], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `output_${pid || "text"}.txt`; a.click(); URL.revokeObjectURL(url); }; updateView(); readerBox.append(headerRow, textArea); overlay.container.appendChild(readerBox); return { isText: true, loadTarget(targetData) { if (typeof targetData !== "object" || !targetData.text) { overlay.destroy(); onSwitchMedia(targetData); return; } rawText = targetData.text || ""; pid = targetData.pid || ""; title.textContent = formatTitle(pid); title.title = pid ? `${t("Text Output #")}${pid}` : t("Text Output"); updateView(); }, destroy: () => overlay.destroy() }; }