diff --git a/docs/public/editor/index.html b/docs/public/editor/index.html index da6cd99ff3..6ad25d9144 100644 --- a/docs/public/editor/index.html +++ b/docs/public/editor/index.html @@ -15,50 +15,22 @@ overflow: hidden; } -/* Editor textarea sizing & behavior */ -.editor-textarea { - flex: 1; - width: 100%; +/* CodeMirror editor sizing */ +.cm-editor { height: 100%; - padding: 12px 16px 12px 60px; font-size: 13px; - line-height: 1.6; - border: none; - resize: none; - outline: none; - tab-size: 2; - -moz-tab-size: 2; - overflow: auto; - background: var(--bgColor-default, var(--color-canvas-default)); - color: var(--fgColor-default, var(--color-fg-default)); } -.editor-textarea::placeholder { - color: var(--fgColor-muted, var(--color-fg-muted)); -} - -/* Line numbers gutter */ -.line-numbers { - position: absolute; - top: 0; - left: 0; - width: 48px; - height: 100%; - overflow: hidden; - z-index: 2; - background: var(--bgColor-muted, var(--color-canvas-subtle)); - border-right: 1px solid var(--borderColor-default, var(--color-border-default)); +.cm-editor .cm-scroller { + overflow: auto; + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + line-height: 1.6; } -.line-numbers-inner { - padding: 12px 0; +.cm-editor .cm-gutters { font-size: 13px; line-height: 1.6; - text-align: right; - user-select: none; - color: var(--fgColor-muted, var(--color-fg-muted)); } -.line-numbers-inner div { - padding-right: 12px; - height: 20.8px; +.cm-editor.cm-focused { + outline: none; } /* Draggable divider */ @@ -101,16 +73,6 @@ background: var(--fgColor-accent, var(--color-accent-fg)); } -/* Output pre sizing */ -.output-pre { - margin: 0; - padding: 12px 16px; - font-size: 13px; - line-height: 1.6; - white-space: pre; - min-height: 100%; -} - /* Loading overlay & spinner */ .loading-overlay { position: fixed; @@ -278,20 +240,7 @@ Ctrl+Enter -
-
-
-
- -
+
@@ -306,11 +255,11 @@ Compiled Output (.lock.yml) -
+
Compiled YAML will appear here
-

+        
       
@@ -342,6 +291,13 @@ // gh-aw Playground - Application Logic // ================================================================ +import { EditorView, basicSetup } from 'https://esm.sh/codemirror@6.0.2'; +import { EditorState, Compartment } from 'https://esm.sh/@codemirror/state@6.5.4'; +import { keymap } from 'https://esm.sh/@codemirror/view@6.39.14'; +import { yaml } from 'https://esm.sh/@codemirror/lang-yaml@6.1.2'; +import { markdown } from 'https://esm.sh/@codemirror/lang-markdown@6.5.0'; +import { indentUnit } from 'https://esm.sh/@codemirror/language@6.12.1'; +import { oneDark } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3'; import { createWorkerCompiler } from '/gh-aw/wasm/compiler-loader.js'; // --------------------------------------------------------------- @@ -367,9 +323,9 @@ // --------------------------------------------------------------- const $ = (id) => document.getElementById(id); -const editor = $('editor'); -const outputPre = $('outputPre'); +const editorMount = $('editorMount'); const outputPlaceholder = $('outputPlaceholder'); +const outputMount = $('outputMount'); const outputContainer = $('outputContainer'); const compileBtn = $('compileBtn'); const statusBadge = $('statusBadge'); @@ -380,7 +336,6 @@ const errorText = $('errorText'); const warningBanner = $('warningBanner'); const warningText = $('warningText'); -const lineNumbersInner = $('lineNumbersInner'); const themeToggle = $('themeToggle'); const toggleTrack = $('toggleTrack'); const divider = $('divider'); @@ -401,12 +356,19 @@ // --------------------------------------------------------------- // Theme (uses Primer's data-color-mode) // --------------------------------------------------------------- +const editorThemeConfig = new Compartment(); +const outputThemeConfig = new Compartment(); + function getPreferredTheme() { const saved = localStorage.getItem('gh-aw-playground-theme'); if (saved) return saved; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } +function cmThemeFor(theme) { + return theme === 'dark' ? oneDark : []; +} + function setTheme(theme) { document.documentElement.setAttribute('data-color-mode', theme); localStorage.setItem('gh-aw-playground-theme', theme); @@ -419,8 +381,55 @@ sunIcon.style.display = 'none'; moonIcon.style.display = 'block'; } + + // Update CodeMirror themes + const cmTheme = cmThemeFor(theme); + editorView.dispatch({ effects: editorThemeConfig.reconfigure(cmTheme) }); + outputView.dispatch({ effects: outputThemeConfig.reconfigure(cmTheme) }); } +// --------------------------------------------------------------- +// CodeMirror: Input Editor (Markdown with YAML frontmatter) +// --------------------------------------------------------------- +const editorView = new EditorView({ + doc: DEFAULT_CONTENT, + extensions: [ + basicSetup, + markdown(), + EditorState.tabSize.of(2), + indentUnit.of(' '), + editorThemeConfig.of(cmThemeFor(getPreferredTheme())), + keymap.of([{ + key: 'Mod-Enter', + run: () => { doCompile(); return true; } + }]), + EditorView.updateListener.of(update => { + if (update.docChanged && autoCompile && isReady) { + scheduleCompile(); + } + }), + ], + parent: editorMount, +}); + +// --------------------------------------------------------------- +// CodeMirror: Output View (YAML, read-only) +// --------------------------------------------------------------- +const outputView = new EditorView({ + doc: '', + extensions: [ + basicSetup, + yaml(), + EditorState.readOnly.of(true), + EditorView.editable.of(false), + outputThemeConfig.of(cmThemeFor(getPreferredTheme())), + ], + parent: outputMount, +}); + +// --------------------------------------------------------------- +// Apply initial theme + listen for changes +// --------------------------------------------------------------- setTheme(getPreferredTheme()); themeToggle.addEventListener('click', () => { @@ -467,57 +476,6 @@ } } -// --------------------------------------------------------------- -// Line numbers -// --------------------------------------------------------------- -function updateLineNumbers() { - const lines = editor.value.split('\n').length; - let html = ''; - for (let i = 1; i <= lines; i++) { - html += '
' + i + '
'; - } - lineNumbersInner.innerHTML = html; -} - -function syncLineNumberScroll() { - const lineNumbers = $('lineNumbers'); - lineNumbers.scrollTop = editor.scrollTop; -} - -// --------------------------------------------------------------- -// Editor setup -// --------------------------------------------------------------- -editor.value = DEFAULT_CONTENT; -updateLineNumbers(); - -editor.addEventListener('input', () => { - updateLineNumbers(); - if (autoCompile && isReady) { - scheduleCompile(); - } -}); - -editor.addEventListener('scroll', syncLineNumberScroll); - -// Tab key inserts 2 spaces -editor.addEventListener('keydown', (e) => { - if (e.key === 'Tab') { - e.preventDefault(); - const start = editor.selectionStart; - const end = editor.selectionEnd; - const val = editor.value; - editor.value = val.substring(0, start) + ' ' + val.substring(end); - editor.selectionStart = editor.selectionEnd = start + 2; - editor.dispatchEvent(new Event('input')); - } - - // Ctrl+Enter or Cmd+Enter to compile - if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { - e.preventDefault(); - doCompile(); - } -}); - // --------------------------------------------------------------- // Auto-compile toggle // --------------------------------------------------------------- @@ -545,10 +503,10 @@ compileTimer = null; } - const md = editor.value; + const md = editorView.state.doc.toString(); if (!md.trim()) { - outputPre.classList.add('d-none'); - outputPlaceholder.classList.remove('d-none'); + outputMount.style.display = 'none'; + outputPlaceholder.style.display = 'flex'; outputPlaceholder.textContent = 'Compiled YAML will appear here'; currentYaml = ''; return; @@ -572,9 +530,13 @@ } else { setStatus('ready', 'Ready'); currentYaml = result.yaml; - outputPre.textContent = result.yaml; - outputPre.classList.remove('d-none'); - outputPlaceholder.classList.add('d-none'); + + // Update output CodeMirror view + outputView.dispatch({ + changes: { from: 0, to: outputView.state.doc.length, insert: result.yaml } + }); + outputMount.style.display = 'block'; + outputPlaceholder.style.display = 'none'; if (result.warnings && result.warnings.length > 0) { warningText.textContent = result.warnings.join('\n');