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');