function encodePickedPath(p) { if (p === '/' || p === '') return '/'; return '/' + p.replace(/^\/+/, '').split('/').map(encodeURIComponent).join('/'); } // postReplace POSTs to action with the optional form body, then loads target // into the current history entry — so the action and its result occupy one // entry instead of two, and back-navigation skips past the stale pre-mutation // snapshot in bfcache. body may be null for empty POSTs. // // We can't just call window.location.replace(target): when target differs from // the current URL only by fragment, the browser updates the URL bar without // re-fetching, so a server-side mutation wouldn't be reflected. Instead, // rewrite the current entry's URL via history.replaceState, then reload — the // reload always re-fetches and preserves the (new) URL including its fragment. function postReplace(action, body, target) { var init = { method: 'POST', redirect: 'manual' }; if (body) { init.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; init.body = body; } fetch(action, init).then(function (res) { if (res.type === 'opaqueredirect' || res.ok) { window.history.replaceState(null, '', target); window.location.reload(); return; } return res.text().then(function (msg) { alert(msg || ('Request failed (' + res.status + ')')); }); }).catch(function () { alert('Network error'); }); } function promptPageName(title, initial, confirmLabel, onName) { var input = document.createElement('input'); input.type = 'text'; input.className = 'input'; input.placeholder = 'Page name'; if (initial) input.value = initial; openModal({ title: title, body: input, confirm: { label: confirmLabel, onConfirm: function () { var name = input.value.trim(); if (!name) return; onName(name); } } }); } function newPage() { var current = decodeURIComponent(window.location.pathname).replace(/\/+$/, '') || '/'; openTreePicker({ title: 'New page — where?', mode: 'folder', initialPath: current, preselect: current, hideFiles: true, confirmLabel: 'NEXT', onSelect: function (parentPath) { promptPageName('New page — name?', '', 'CREATE', function (name) { var base = parentPath === '/' ? '/' : encodePickedPath(parentPath) + '/'; window.location.href = base + encodeURIComponent(name) + '/?edit'; }); } }); } function movePage() { var current = decodeURIComponent(window.location.pathname).replace(/\/+$/, ''); if (!current) return; var segs = current.split('/').filter(Boolean); var currentName = segs[segs.length - 1] || ''; var parent = '/' + segs.slice(0, -1).join('/'); if (parent === '/') parent = '/'; openTreePicker({ title: 'Move — new parent?', mode: 'folder', initialPath: parent, preselect: parent, hideFiles: true, confirmLabel: 'NEXT', onSelect: function (newParent) { var input = document.createElement('input'); input.type = 'text'; input.className = '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 = 'row'; 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 target = encodePickedPath(dest) + '/'; closeModal(); postReplace(action, null, target); } } }); } }); } function deletePage() { var decodedPath = decodeURIComponent(window.location.pathname); openModal({ title: 'Delete page', body: 'Delete ' + decodedPath + ' and everything inside it?', confirm: { label: 'DELETE', danger: true, enterConfirms: false, onConfirm: function () { var p = window.location.pathname.replace(/\/+$/, ''); var idx = p.lastIndexOf('/'); var parent = idx > 0 ? p.substring(0, idx + 1) : '/'; closeModal(); postReplace(window.location.pathname + '?delete=1', null, parent); } }, cancel: { autofocus: true }, swapButtons: true }); }