Files
canine/app/javascript/controllers/yaml_editor_controller.js
2025-07-25 16:00:41 -07:00

75 lines
2.0 KiB
JavaScript

import { Controller } from "@hotwired/stimulus"
import { EditorView, basicSetup } from "codemirror"
import { EditorState } from "@codemirror/state"
import { yaml } from "@codemirror/lang-yaml"
import { oneDark } from "@codemirror/theme-one-dark"
import { indentWithTab } from "@codemirror/commands"
import { keymap } from "@codemirror/view"
export default class extends Controller {
static targets = ["textarea", "editor"]
connect() {
this.setupEditor()
}
disconnect() {
if (this.editorView) {
this.editorView.destroy()
}
}
setupEditor() {
const initialContent = this.textareaTarget.value || ''
// Create the editor state with YAML syntax highlighting and dark theme
const state = EditorState.create({
doc: initialContent,
extensions: [
basicSetup,
yaml(),
oneDark,
keymap.of([indentWithTab]),
EditorView.theme({
"&": {
fontSize: "14px",
border: "1px solid #374151",
borderRadius: "0.5rem"
},
"&.cm-focused": {
outline: "2px solid #3b82f6",
outlineOffset: "-1px"
},
".cm-content": {
padding: "12px",
minHeight: "250px"
},
".cm-scroller": {
fontFamily: "'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace"
}
}),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
this.updateTextarea(update.state.doc.toString())
}
})
]
})
// Create the editor view
this.editorView = new EditorView({
state,
parent: this.editorTarget
})
// Hide the original textarea
this.textareaTarget.style.display = 'none'
}
updateTextarea(content) {
this.textareaTarget.value = content
// Dispatch input event to ensure form validation works
this.textareaTarget.dispatchEvent(new Event('input', { bubbles: true }))
}
}