fix(files): support rename-on-save in URL-addressable modal

Task 9's new save delegation read only the editor content, not the
filename input — so typing a new filename and clicking Save silently
discarded the rename and wrote to the original path. Matches the
legacy save handler's payload.new_path contract: if the user edited
the filename, compose new_path = parent/filename and send it. 409
conflict (destination exists) shows an alert and keeps the modal
open so the user can adjust.

Also exposes rawText in fetchJson return so plain-text server error
messages (e.g. "destination already exists") reach the alert call.

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

View file

@ -89,13 +89,14 @@
options.credentials = "same-origin";
const response = await fetch(url, options);
let body = null;
let rawText = "";
try {
const text = await response.text();
body = text ? JSON.parse(text) : null;
rawText = await response.text();
body = rawText ? JSON.parse(rawText) : null;
} catch (_e) {
body = null;
}
return { ok: response.ok, status: response.status, body };
return { ok: response.ok, status: response.status, body, rawText };
}
async function postJson(url, payload) {
@ -609,12 +610,34 @@
const content = (window.__filesEditor && window.__filesEditor.getValue)
? window.__filesEditor.getValue()
: ta.value;
const r = await postJson(`${baseUrl}/files/save`, { path: relPath, content });
// Rename-on-save: if the user edited the filename input, compose the
// new path (sibling rename only — joining parent of relPath with the
// new filename). Send payload.new_path so the server moves and writes
// atomically. Matches the legacy save handler's contract.
const filenameInput = modalContent.querySelector("[data-editor-filename]");
const editedFilename = filenameInput ? filenameInput.value.trim() : "";
const originalLeaf = relPath.split("/").pop() || relPath;
const parent = relPath.includes("/") ? relPath.slice(0, relPath.lastIndexOf("/")) : "";
let newPath = null;
if (editedFilename && editedFilename !== originalLeaf) {
newPath = parent ? `${parent}/${editedFilename}` : editedFilename;
}
const payload = { path: relPath, content };
if (newPath) payload.new_path = newPath;
const r = await postJson(`${baseUrl}/files/save`, payload);
if (r.ok) {
if (typeof window.closeModal === "function") window.closeModal();
scheduleRefresh(parentOf(relPath));
scheduleRefresh(parentOf(newPath || relPath));
} else if (r.status === 409) {
// Conflict (destination already exists) — show error and keep modal
// open so the user can pick a different filename.
alert(r.rawText || `Conflict: destination already exists.`);
return;
} else {
alert((r.body && r.body.error) || `Save failed (HTTP ${r.status}).`);
alert((r.body && r.body.error) || r.rawText || `Save failed (HTTP ${r.status}).`);
}
return;
}