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