feat(editor-v2): editor.js glue (mount, submit-capture, files alias)
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>
This commit is contained in:
parent
921168722b
commit
e4f863415e
1 changed files with 84 additions and 0 deletions
84
l4d2web/l4d2web/static/js/editor.js
Normal file
84
l4d2web/l4d2web/static/js/editor.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// 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();
|
||||
}
|
||||
})();
|
||||
Loading…
Reference in a new issue