Un-bundled progressive-enhancement glue: - DOMContentLoaded → mount cm6 on every textarea[data-editor-language]. - Each <form> gets one capture-phase submit handler that copies every contained editor's getValue() into its textarea.value before the browser serializes the form (submit-time copy bridge). - The textarea with class files-editor-content (the files-modal textarea) exposes its controller as window.__filesEditor for files-overlay.js's getValue / setContent / setLanguage calls. - 'auto' language resolves from the modal's filename input ([data-editor-filename]); a language [data-editor-language-select] dropdown lets the user override. - Vocab fetched lazily on the first srccfg mount; cached for the page. Falls through silently if window.__editor isn't defined (bundle failed to load), keeping the raw textarea visible — no-JS fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
3 KiB
JavaScript
84 lines
3 KiB
JavaScript
// Un-bundled. Driven by data-editor-language attrs on <textarea>.
|
|
// Mounts cm6 (from editor.bundle.js exporting window.__editor),
|
|
// installs one capture-phase submit handler per <form>, and exposes
|
|
// a named alias for the files-editor modal.
|
|
(function () {
|
|
"use strict";
|
|
if (!window.__editor || typeof window.__editor.mount !== "function") {
|
|
return; // bundle didn't load — graceful no-JS fallback
|
|
}
|
|
|
|
let vocabPromise = null;
|
|
function loadSrccfgVocab() {
|
|
if (!vocabPromise) {
|
|
vocabPromise = fetch("/static/data/srccfg-vocab.json", { credentials: "same-origin" })
|
|
.then(r => r.ok ? r.json() : Promise.reject(new Error("vocab fetch failed: " + r.status)))
|
|
.catch(err => { console.warn("[editor] vocab load failed", err); return null; });
|
|
}
|
|
return vocabPromise;
|
|
}
|
|
|
|
function resolveAutoLanguage(filenameInput) {
|
|
const name = (filenameInput && filenameInput.value || "").toLowerCase();
|
|
if (name.endsWith(".cfg")) return "srccfg";
|
|
if (name.endsWith(".sh")) return "bash";
|
|
return "plain";
|
|
}
|
|
|
|
async function mountOne(textarea) {
|
|
let lang = textarea.getAttribute("data-editor-language") || "plain";
|
|
let filenameInput = null;
|
|
let dropdown = null;
|
|
if (lang === "auto") {
|
|
const modal = textarea.closest("#files-editor-modal") || document;
|
|
filenameInput = modal.querySelector("[data-editor-filename]");
|
|
dropdown = modal.querySelector("[data-editor-language-select]");
|
|
lang = resolveAutoLanguage(filenameInput);
|
|
}
|
|
|
|
const vocab = (lang === "srccfg") ? await loadSrccfgVocab() : null;
|
|
const controller = window.__editor.mount(textarea, { language: lang, vocab });
|
|
|
|
// Submit-time copy bridge
|
|
const form = textarea.closest("form");
|
|
if (form && !form.__editorSubmitBound) {
|
|
form.__editorSubmitBound = true;
|
|
form.addEventListener("submit", () => {
|
|
for (const ta of form.querySelectorAll("textarea[data-editor-language]")) {
|
|
if (ta.__editorController) ta.value = ta.__editorController.getValue();
|
|
}
|
|
}, true /* capture phase */);
|
|
}
|
|
textarea.__editorController = controller;
|
|
|
|
// Files-modal hooks
|
|
if (textarea.classList.contains("files-editor-content")) {
|
|
window.__filesEditor = controller;
|
|
if (dropdown) {
|
|
dropdown.addEventListener("change", () => {
|
|
const v = dropdown.value;
|
|
controller.setLanguage(v === "auto" ? resolveAutoLanguage(filenameInput) : v);
|
|
});
|
|
}
|
|
if (filenameInput) {
|
|
filenameInput.addEventListener("input", () => {
|
|
if (!dropdown || dropdown.value === "auto") {
|
|
controller.setLanguage(resolveAutoLanguage(filenameInput));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
for (const ta of document.querySelectorAll("textarea[data-editor-language]")) {
|
|
mountOne(ta).catch(err => console.error("[editor] mount failed", err));
|
|
}
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|