Make page rewrite optional
This commit is contained in:
+39
-9
@@ -56,16 +56,46 @@ function movePage() {
|
||||
preselect: parent,
|
||||
hideFiles: true,
|
||||
confirmLabel: 'NEXT',
|
||||
allowRoot: false,
|
||||
onSelect: function (newParent) {
|
||||
promptPageName('Move — new name?', currentName, 'MOVE', function (name) {
|
||||
var dest = (newParent === '/' ? '' : newParent) + '/' + name;
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = window.location.pathname + '?move=' +
|
||||
encodeURIComponent(dest);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
var input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'modal-input';
|
||||
input.placeholder = 'Page name';
|
||||
input.value = currentName;
|
||||
|
||||
var linksCheckbox = document.createElement('input');
|
||||
linksCheckbox.type = 'checkbox';
|
||||
linksCheckbox.id = 'move-update-links';
|
||||
|
||||
var linksLabel = document.createElement('label');
|
||||
linksLabel.htmlFor = linksCheckbox.id;
|
||||
linksLabel.className = 'modal-checkbox';
|
||||
linksLabel.appendChild(linksCheckbox);
|
||||
linksLabel.appendChild(document.createTextNode('Update links'));
|
||||
|
||||
var body = document.createDocumentFragment();
|
||||
body.appendChild(input);
|
||||
body.appendChild(linksLabel);
|
||||
|
||||
openModal({
|
||||
title: 'Move — new name?',
|
||||
body: body,
|
||||
confirm: {
|
||||
label: 'MOVE',
|
||||
onConfirm: function () {
|
||||
var name = input.value.trim();
|
||||
if (!name) return;
|
||||
var dest = (newParent === '/' ? '' : newParent) + '/' + name;
|
||||
var action = window.location.pathname + '?move=' +
|
||||
encodeURIComponent(dest);
|
||||
if (linksCheckbox.checked) action += '&links=1';
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = action;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -626,6 +626,12 @@ hr {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.modal-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* === Tree picker === */
|
||||
.tree-picker {
|
||||
max-height: 60vh;
|
||||
|
||||
@@ -291,7 +291,7 @@ func (h *handler) handlePost(w http.ResponseWriter, r *http.Request, urlPath, fs
|
||||
return
|
||||
}
|
||||
if _, ok := query["move"]; ok {
|
||||
h.handleMove(w, r, urlPath, fsPath, query.Get("move"))
|
||||
h.handleMove(w, r, urlPath, fsPath, query.Get("move"), query.Has("links"))
|
||||
return
|
||||
}
|
||||
if query.Has("toggle") {
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// handleMove moves the folder at srcFsPath (wiki URL srcURL) to dstURL and
|
||||
// rewrites every [[...]] wiki link across the tree that targets the old path
|
||||
// or any descendant. All rewritten files are held in memory for rollback.
|
||||
func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, srcFsPath, dstURL string) {
|
||||
// handleMove moves the folder at srcFsPath (wiki URL srcURL) to dstURL. When
|
||||
// updateLinks is true it also rewrites every [[...]] wiki link across the
|
||||
// tree that targets the old path or any descendant; rewritten files are held
|
||||
// in memory for rollback.
|
||||
func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, srcFsPath, dstURL string, updateLinks bool) {
|
||||
oldPath := normalizeMovePath(srcURL)
|
||||
if oldPath == "/" {
|
||||
http.Error(w, "cannot move wiki root", http.StatusBadRequest)
|
||||
@@ -44,35 +45,38 @@ func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, src
|
||||
return
|
||||
}
|
||||
|
||||
// Phase 1: walk the tree and rewrite every index.md that references the
|
||||
// moved path. Keep the pre-rewrite bytes in memory so we can revert on
|
||||
// failure. The walker only reads directory listings and files literally
|
||||
// named index.md; hidden directories are pruned. A cheap substring check
|
||||
// skips parsing files that cannot contain a relevant link.
|
||||
// Phase 1: optionally walk the tree and rewrite every index.md that
|
||||
// references the moved path. Keep the pre-rewrite bytes in memory so we
|
||||
// can revert on failure. The walker only reads directory listings and
|
||||
// files literally named index.md; hidden directories are pruned. A cheap
|
||||
// substring check skips parsing files that cannot contain a relevant
|
||||
// link.
|
||||
rewritten := map[string][]byte{}
|
||||
needle := []byte("[[" + oldPath)
|
||||
walkErr := walkIndexFiles(h.root, func(fsPath string) error {
|
||||
orig, err := os.ReadFile(fsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Contains(orig, needle) {
|
||||
if updateLinks {
|
||||
needle := []byte("[[" + oldPath)
|
||||
walkErr := walkIndexFiles(h.root, func(fsPath string) error {
|
||||
orig, err := os.ReadFile(fsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Contains(orig, needle) {
|
||||
return nil
|
||||
}
|
||||
updated, changed := rewriteWikiLinks(orig, oldPath, newPath)
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
if err := writeFileAtomic(fsPath, updated, 0644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", fsPath, err)
|
||||
}
|
||||
rewritten[fsPath] = orig
|
||||
return nil
|
||||
})
|
||||
if walkErr != nil {
|
||||
rollbackRewrites(rewritten)
|
||||
http.Error(w, "rewrite failed: "+walkErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
updated, changed := rewriteWikiLinks(orig, oldPath, newPath)
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
if err := writeFileAtomic(fsPath, updated, 0644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", fsPath, err)
|
||||
}
|
||||
rewritten[fsPath] = orig
|
||||
return nil
|
||||
})
|
||||
if walkErr != nil {
|
||||
rollbackRewrites(rewritten)
|
||||
http.Error(w, "rewrite failed: "+walkErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Phase 2: create intermediate parent folders for the destination.
|
||||
|
||||
Reference in New Issue
Block a user