Serve delete, move and merge dialogs as htmx fragments

The server renders the dialogs (?dialog=delete|move) and answers a move
that needs confirmation with the merge dialog itself, so the client no
longer builds action URLs or interprets X-Merge-Available. Successful
mutations send HX-Redirect, which hx.js turns into a history replace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 16:37:46 +02:00
co-authored by Claude Opus 5.5
parent 465aed47b3
commit 3a3fb3b51f
11 changed files with 371 additions and 230 deletions
+9 -7
View File
@@ -14,8 +14,7 @@ import (
// The one exception is a merge: when the source carries a page (index.md) and
// the destination folder has none, the source's contents can fill the empty
// container without clobbering anything. That merge only proceeds when merge
// is true; otherwise the client is told a merge is available so it can ask the
// user to confirm.
// is true; otherwise the client is asked to confirm it.
func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, srcFsPath, dstURL string, merge bool) {
oldPath := normalizeMovePath(srcURL)
if oldPath == "/" {
@@ -51,9 +50,12 @@ func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, src
return
}
if !merge {
// Ask the client to confirm the merge. The header lets it tell
// this apart from an ordinary conflict.
w.Header().Set("X-Merge-Available", "1")
// Ask the user to confirm: htmx clients get the merge dialog as
// the next step; others a conflict they can retry with &merge=1.
if isHX(r) {
renderDialog(w, "merge", struct{ Dest string }{newPath})
return
}
http.Error(w, "destination already exists — merge folders?", http.StatusConflict)
return
}
@@ -94,7 +96,7 @@ func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, src
folderIndexRenameSubtree(strings.TrimPrefix(oldPath, "/"), strings.TrimPrefix(newPath, "/"))
}
http.Redirect(w, r, fileURL(strings.TrimPrefix(newPath, "/"))+"/", http.StatusSeeOther)
redirectAfter(w, r, pageURL(newPath))
}
// canMergeMove reports whether moving onto an existing destination should
@@ -169,7 +171,7 @@ func (h *handler) handleDelete(w http.ResponseWriter, r *http.Request, urlPath,
return
}
folderIndexRemoveSubtree(strings.TrimPrefix(normalizeMovePath(urlPath), "/"))
http.Redirect(w, r, parentURL(urlPath), http.StatusSeeOther)
redirectAfter(w, r, pageURL(parentURL(urlPath)))
}
// normalizeMovePath returns the absolute path with any trailing slash removed,