/** * Generates editor.html — a live Mermaid editor similar to mermaid.live. * * Usage: bun run editor.ts * * The generated HTML is fully self-contained: * - Bundles the mermaid renderer client-side * - Live rendering on every keystroke (debounced) * - URL hash sharing (base64-encoded source) * - Theme switcher with all built-in themes * - Sample presets by diagram category * - Download SVG / Copy link * * Source files are organized in editor/: * - editor/css/ — modular CSS components * - editor/js/ — modular JS modules * - editor/html/ — HTML partials (topbar, left-panel, right-panel) */ import { THEMES } from './src/theme.ts' const THEME_LABELS: Record = { 'zinc-dark': 'Zinc Dark', 'tokyo-night': 'Tokyo Night', 'tokyo-night-storm': 'Tokyo Storm', 'tokyo-night-light': 'Tokyo Light', 'catppuccin-mocha': 'Catppuccin', 'catppuccin-latte': 'Latte', 'nord': 'Nord', 'nord-light': 'Nord Light', 'dracula': 'Dracula', 'github-light': 'GitHub', 'github-dark': 'GitHub Dark', 'solarized-light': 'Solarized', 'solarized-dark': 'Solar Dark', 'one-dark': 'One Dark', } // ── File helpers ────────────────────────────────────────────────────────────── const editorDir = new URL('./editor/', import.meta.url).pathname async function readFile(relativePath: string): Promise { const file = Bun.file(editorDir + relativePath) return file.text() } async function readCssFiles(): Promise { const order = [ 'css/variables.css', 'css/topbar.css', 'css/panels.css', 'css/code-editor.css', 'css/preview.css', 'css/config-panel.css', 'css/color-picker.css', 'css/font-picker.css', 'css/export.css', 'css/misc.css', ] const parts = await Promise.all(order.map(f => readFile(f))) return parts.join('\n\n') } async function readJsFiles(): Promise { const order = [ 'js/helpers.js', 'js/state.js', 'js/elements.js', 'js/sharing.js', 'js/rendering.js', 'js/zoom.js', 'js/pan.js', 'js/editor-helpers.js', 'js/config-panel.js', 'js/color-picker.js', 'js/font-picker.js', 'js/tabs.js', 'js/buttons.js', 'js/export.js', 'js/resize.js', 'js/toast.js', 'js/dark-mode.js', 'js/init.js', ] const parts = await Promise.all(order.map(f => readFile(f))) return parts.join('\n\n') } async function readHtmlPartials(themeItems: string): Promise<{ topbar: string leftPanel: string rightPanel: string }> { const [topbar, leftPanel, rightPanel] = await Promise.all([ readFile('html/topbar.html'), readFile('html/left-panel.html'), readFile('html/right-panel.html'), ]) return { topbar: topbar.replace('{{THEME_ITEMS}}', themeItems), leftPanel, rightPanel, } } // ── Main ────────────────────────────────────────────────────────────────────── async function generateEditorHtml(): Promise { const buildResult = await Bun.build({ entrypoints: [new URL('./src/browser.ts', import.meta.url).pathname], target: 'browser', format: 'esm', minify: true, }) if (!buildResult.success) { console.error('Bundle failed:', buildResult.logs) process.exit(1) } const bundleJs = await buildResult.outputs[0]!.text() console.log(`Browser bundle: ${(bundleJs.length / 1024).toFixed(1)} KB`) const themeItems = [ ``, ...Object.keys(THEMES).map( key => `` ), ].join('\n ') const [css, appJs, html] = await Promise.all([ readCssFiles(), readJsFiles(), readHtmlPartials(themeItems), ]) return ` Beautiful Mermaid — Live Editor ${html.topbar}
${html.leftPanel}
${html.rightPanel}
` } const result = await generateEditorHtml() const outPath = new URL('./editor.html', import.meta.url).pathname await Bun.write(outPath, result) console.log(`Written to ${outPath} (${(result.length / 1024).toFixed(1)} KB)`)