The sidebar tree is part of the page (chain to the current page expanded, active row marked), so it no longer paints late and the sessionStorage snapshot is gone; only the rail's scroll position is kept. Folders are native <details> that lazy-load their children as HTML via htmx. The tree picker hosts the same server-rendered rows with radio inputs, so ?tree returns HTML and the JSON listing is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.6 KiB
JavaScript
68 lines
2.6 KiB
JavaScript
// openTreePicker shows a modal to pick a wiki path. The tree is server-rendered
|
||
// (?tree&pick=…, see tree.go and tree.html): each selectable row carries a
|
||
// radio named "pick", folders expand natively and lazy-load via htmx. This
|
||
// file only hosts the tree in a modal and reports the checked row.
|
||
//
|
||
// opts: title, confirmLabel, mode ('folder' | 'file' | 'any'), initialPath
|
||
// (expanded on open), preselect (checked on open), hideFiles, allowRoot
|
||
// (default true: offer "/" in folder modes), onSelect(path, kind).
|
||
(function () {
|
||
function openTreePicker(opts) {
|
||
opts = opts || {};
|
||
var mode = opts.mode || 'folder';
|
||
|
||
var container = document.createElement('div');
|
||
var treeEl = document.createElement('div');
|
||
treeEl.className = 'tree-picker panel';
|
||
treeEl.textContent = '…';
|
||
var selectedPathEl = document.createElement('div');
|
||
selectedPathEl.className = 'tree-selected-path muted';
|
||
selectedPathEl.textContent = ' ';
|
||
container.appendChild(treeEl);
|
||
container.appendChild(selectedPathEl);
|
||
|
||
function checked() {
|
||
return treeEl.querySelector('input[name="pick"]:checked');
|
||
}
|
||
|
||
var handle = openModal({
|
||
title: opts.title || 'Pick',
|
||
body: container,
|
||
confirm: {
|
||
label: opts.confirmLabel || 'SELECT',
|
||
initiallyDisabled: true,
|
||
onConfirm: function () {
|
||
var pick = checked();
|
||
if (!pick) return;
|
||
handle.close();
|
||
if (opts.onSelect) opts.onSelect(pick.value, pick.dataset.kind);
|
||
}
|
||
}
|
||
});
|
||
|
||
function sync() {
|
||
var pick = checked();
|
||
selectedPathEl.textContent = pick ? pick.value : ' ';
|
||
handle.setConfirmDisabled(!pick);
|
||
}
|
||
treeEl.addEventListener('change', sync);
|
||
|
||
var url = '/?tree&pick=' + mode +
|
||
(opts.hideFiles ? '' : '&files=1') +
|
||
(opts.allowRoot !== false ? '&root=1' : '') +
|
||
'&expandTo=' + encodeURIComponent(opts.initialPath || '/') +
|
||
(opts.preselect ? '&select=' + encodeURIComponent(opts.preselect) : '');
|
||
htmx.ajax('GET', url, { target: treeEl, swap: 'innerHTML' }).then(function () {
|
||
sync();
|
||
var pick = checked();
|
||
if (pick) {
|
||
try { pick.closest('.tree-row').scrollIntoView({ block: 'nearest' }); } catch (e) {}
|
||
}
|
||
});
|
||
|
||
return handle;
|
||
}
|
||
|
||
window.openTreePicker = openTreePicker;
|
||
})();
|