feat(files): file-row click opens editor via URL-addressable modal

files-overlay.js no longer fetches /files/content JSON and populates
the inline <dialog>; it calls window.openModal(<edit-url>) which the
modal-router handles end-to-end. Binary files retain the old inline
dialog path (binary replace deferred from pilot scope).

Added document-level event delegation for .files-editor-save and
.files-editor-delete inside #modal-content so save/delete work on the
server-rendered editor DOM (reads data-rel-path from the textarea;
calls window.__filesEditor.getValue() for content; calls closeModal()
on success). The inline dialog's own save/delete handlers are untouched
and continue to serve the create-new-file and binary-replace flows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-17 13:13:38 +02:00
parent 7829d1ca95
commit 64cf203890
No known key found for this signature in database

View file

@ -589,6 +589,57 @@
}
});
// ---------- modal-content save / delete (URL-addressable modal) ----------
// The new server-rendered editor (loaded into #modal-content) has its own
// .files-editor-save and .files-editor-delete buttons. Those are not the
// same elements as editorEls.saveBtn / editorEls.deleteBtn (which live in
// the old inline dialog still present for create-new-file / binary flows).
// Use event delegation so the handlers fire on dynamically swapped content.
document.addEventListener("click", async (event) => {
const modalContent = document.getElementById("modal-content");
if (!modalContent) return;
const saveBtn = event.target.closest(".files-editor-save");
if (saveBtn && modalContent.contains(saveBtn)) {
const ta = modalContent.querySelector("textarea[data-rel-path]");
if (!ta) return;
const relPath = ta.dataset.relPath;
if (!relPath) return;
const content = (window.__filesEditor && window.__filesEditor.getValue)
? window.__filesEditor.getValue()
: ta.value;
const r = await postJson(`${baseUrl}/files/save`, { path: relPath, content });
if (r.ok) {
if (typeof window.closeModal === "function") window.closeModal();
scheduleRefresh(parentOf(relPath));
} else {
alert((r.body && r.body.error) || `Save failed (HTTP ${r.status}).`);
}
return;
}
const deleteBtn = event.target.closest(".files-editor-delete");
if (deleteBtn && modalContent.contains(deleteBtn)) {
const ta = modalContent.querySelector("textarea[data-rel-path]");
if (!ta) return;
const relPath = ta.dataset.relPath;
if (!relPath) return;
if (!confirm(`Delete ${relPath}?`)) return;
const fd = new FormData();
fd.append("path", relPath);
fd.append("csrf_token", csrfToken);
const r = await postForm(`${baseUrl}/files/delete`, fd);
if (r.ok) {
if (typeof window.closeModal === "function") window.closeModal();
scheduleRefresh(parentOf(relPath));
} else {
alert((r.body && r.body.error) || `Delete failed (HTTP ${r.status}).`);
}
return;
}
});
// ---------- new-folder modal --------------------------------------------
function openNewFolder(targetFolder) {
@ -994,7 +1045,20 @@
window.location.href = url;
} else if (op === "edit") {
const editable = action.dataset.editable === "1";
openEditorForFile(path, editable);
if (editable) {
// Editable text files: open via URL-addressable modal.
const editUrl = `/overlays/${overlayId}/files/edit?path=${encodeURIComponent(path)}`;
if (typeof window.openModal === "function") {
window.openModal(editUrl);
} else {
// Graceful fallback if modal-router didn't load — full-page navigation
// still hits the same route and renders the standalone editor page.
window.location.href = editUrl;
}
} else {
// Binary files: keep old inline dialog (binary replace deferred from pilot).
openEditorForFile(path, false);
}
} else if (op === "delete") {
const kind = action.dataset.rowKind;
const name = action.dataset.rowName || basename(path);