-
Notifications
You must be signed in to change notification settings - Fork 260
Add syntax highlighting for YAML and Markdown in playground editor #16710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -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 @@ | |||
| <span class="kbd-hint-other">Ctrl+Enter</span> | ||||
| </kbd> | ||||
| </div> | ||||
| <div class="flex-1 d-flex position-relative" style="min-height: 0; overflow: hidden;"> | ||||
| <div class="line-numbers" id="lineNumbers"> | ||||
| <div class="line-numbers-inner text-mono" id="lineNumbersInner"></div> | ||||
| </div> | ||||
| <textarea | ||||
| class="editor-textarea text-mono" | ||||
| id="editor" | ||||
| spellcheck="false" | ||||
| autocapitalize="off" | ||||
| autocomplete="off" | ||||
| autocorrect="off" | ||||
| placeholder="Write your agentic workflow here..." | ||||
| ></textarea> | ||||
| </div> | ||||
| <div class="flex-1" style="min-height: 0; overflow: hidden;" id="editorMount"></div> | ||||
| </div> | ||||
|
|
||||
| <div class="divider" id="divider"></div> | ||||
|
|
@@ -306,11 +255,11 @@ | |||
| Compiled Output (.lock.yml) | ||||
| </span> | ||||
| </div> | ||||
| <div class="flex-1 overflow-auto color-bg-subtle" style="min-height: 0;" id="outputContainer"> | ||||
| <div class="flex-1" style="min-height: 0; overflow: hidden;" id="outputContainer"> | ||||
| <div class="d-flex flex-items-center flex-justify-center color-fg-muted f5 p-4 text-center" style="height: 100%;" id="outputPlaceholder"> | ||||
| Compiled YAML will appear here | ||||
| </div> | ||||
| <pre class="output-pre text-mono color-fg-default d-none" id="outputPre"></pre> | ||||
| <div id="outputMount" style="height: 100%; display: none;"></div> | ||||
| </div> | ||||
| </div> | ||||
| </div> | ||||
|
|
@@ -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), | ||||
|
||||
| EditorView.editable.of(false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intentionally keeping both. readOnly prevents state modifications but still shows a cursor and accepts focus. editable.of(false) removes the contenteditable attribute from the DOM entirely, so the output panel has no cursor, no text selection caret, and doesn't accept keyboard focus — which is the right UX for a display-only output panel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The editorMount element is referenced directly using$('editorMount') on line 408, but unlike other DOM elements (lines 325-342), it's not stored in a constant. Consider adding const editorMount = $ ('editorMount'); to the DOM Elements section for consistency with the established pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Added
const editorMount = $('editorMount')to the DOM Elements section.