/** * Iframe Builder - Builds HTML content for the viewer iframe */ import { escapeHtml } from "../utils/helpers.js"; import { detectContentType, renderContent, getViewStyles, getViewScripts, getView, viewUsesBaseStyles } from "../views/view_loader.js"; import { getPrismTheme, getPrismScriptTags } from "../views/code_scripts.js"; import { loadCssFile, getCssBasePath } from "../utils/css.js"; import { getFullTheme, themeToCssVars } from "../utils/theme.js"; const LIST_SEPARATOR = "\n---LIST_SEPARATOR---\n"; // Cached CSS content let cachedBaseCss = null; let cachedListCss = null; /** * Initialize CSS by loading from files */ async function initCss() { if (cachedBaseCss === null) { const basePath = getCssBasePath(); cachedBaseCss = await loadCssFile(`${basePath}/iframe-base.css`); cachedListCss = await loadCssFile(`${basePath}/iframe-list.css`); } } // Start loading CSS immediately initCss(); /** * Get base CSS with theme variables * @param {object} theme - Basic theme tokens (bg, fg, border, accent) * @returns {string} CSS styles */ function getBaseStyles(theme) { // Get full theme for comprehensive CSS variables const fullTheme = getFullTheme(); const cssVars = themeToCssVars(fullTheme); if (cachedBaseCss) { return cssVars + cachedBaseCss; } // Fallback inline styles if CSS not loaded yet return cssVars + ` * { box-sizing: border-box; } html, body { margin: 0; padding: 4px; background: var(--theme-bg); color: var(--theme-fg); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.6; overflow-x: hidden; word-wrap: break-word; } a { color: var(--theme-accent); } pre { background: rgba(0,0,0,0.3); padding: 12px; border-radius: 6px; overflow-x: hidden; white-space: pre-wrap; word-wrap: break-word; font-family: "Fira Code", "Consolas", "Monaco", monospace; font-size: 13px; } code { background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 4px; font-family: "Fira Code", "Consolas", "Monaco", monospace; } pre code { background: transparent; padding: 0; } blockquote { border-left: 4px solid var(--theme-accent); margin: 12px 0; padding: 8px 16px; background: rgba(0,0,0,0.15); } img { max-width: 100%; height: auto; border-radius: 4px; } table { border-collapse: collapse; width: 100%; margin: 12px 0; border: 1px solid var(--theme-border); } th, td { border: 1px solid var(--theme-border); padding: 8px; text-align: left; } th { background: rgba(0,0,0,0.2); } hr { border: none; border-top: 1px solid var(--theme-border); margin: 16px 0; } h1, h2, h3, h4, h5, h6 { margin-top: 16px; margin-bottom: 8px; color: var(--theme-fg); } h1:first-child, h2:first-child, h3:first-child, h4:first-child, h5:first-child, h6:first-child { margin-top: 0; } ul, ol { padding-left: 24px; margin: 8px 0; } li { margin: 2px 0; } `; } /** * Get list CSS * @returns {string} CSS styles */ function getListStyles() { if (cachedListCss) { return cachedListCss; } // Fallback inline styles if CSS not loaded yet return ` .list-item { background: rgba(0,0,0,0.2); border: 1px solid var(--theme-border); border-radius: 6px; padding: 12px; margin-bottom: 8px; position: relative; } .list-item:last-child { margin-bottom: 0; } .list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .list-header-left { display: flex; align-items: center; gap: 8px; } .list-checkbox { width: 16px; height: 16px; cursor: pointer; accent-color: var(--theme-accent); } .list-index { display: inline-block; background: var(--theme-accent); color: #fff; font-size: 11px; font-weight: bold; padding: 2px 6px; border-radius: 4px; } .list-item.excluded { opacity: 0.5; } .list-item.excluded .list-index { background: #666; } .copy-btn { background: transparent; border: 1px solid var(--theme-border); border-radius: 4px; color: var(--theme-fg); cursor: pointer; padding: 4px 8px; font-size: 11px; display: flex; align-items: center; gap: 4px; transition: all 0.2s; } .copy-btn:hover { background: var(--theme-accent); color: #fff; border-color: var(--theme-accent); } .copy-btn.copied { background: #22c55e; border-color: #22c55e; color: #fff; } .list-content { white-space: pre-wrap; word-wrap: break-word; } `; } /** * Build list content HTML with checkboxes and copy buttons * @param {string} content - Content with LIST_SEPARATOR * @param {object} theme - Theme tokens * @param {number[]} excluded - Excluded item indices * @returns {string} HTML content */ function buildListContent(content, theme, excluded, nodeId) { const items = content.split(LIST_SEPARATOR); const itemsData = JSON.stringify(items); let bodyContent = items.map((item, idx) => { const itemType = detectContentType(item); let itemHtml = ""; if (itemType === "html") { itemHtml = item; } else if (itemType === "markdown") { itemHtml = renderContent(item, "markdown", theme); } else { itemHtml = `