chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
This commit is contained in:
@@ -464,6 +464,149 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft-open password vault: swap main content only, keep sidebar PageTree/FileTree DOM.
|
||||
* Full /vault navigation re-SSRs the tree and drops expand state — avoid that on left-rail click.
|
||||
*/
|
||||
function ensureVaultWorkbenchRuntimeLoaded() {
|
||||
if (window.mnote && window.mnote.vaultWorkbench && typeof window.mnote.vaultWorkbench.boot === 'function') {
|
||||
return Promise.resolve(window.mnote.vaultWorkbench);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var existing = document.querySelector('script[data-mnote-vault-runtime="1"]');
|
||||
if (existing) {
|
||||
var onLoad = function () {
|
||||
existing.removeEventListener('load', onLoad);
|
||||
existing.removeEventListener('error', onError);
|
||||
if (window.mnote && window.mnote.vaultWorkbench) resolve(window.mnote.vaultWorkbench);
|
||||
else reject(new Error('vault_workbench_runtime_missing'));
|
||||
};
|
||||
var onError = function () {
|
||||
existing.removeEventListener('load', onLoad);
|
||||
existing.removeEventListener('error', onError);
|
||||
reject(new Error('vault_workbench_runtime_load_failed'));
|
||||
};
|
||||
existing.addEventListener('load', onLoad);
|
||||
existing.addEventListener('error', onError);
|
||||
return;
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.src = '/api/mnote-browser-runtime/vault-workbench-runtime.js';
|
||||
script.defer = true;
|
||||
script.setAttribute('data-mnote-vault-runtime', '1');
|
||||
script.onload = function () {
|
||||
if (window.mnote && window.mnote.vaultWorkbench) resolve(window.mnote.vaultWorkbench);
|
||||
else reject(new Error('vault_workbench_runtime_missing'));
|
||||
};
|
||||
script.onerror = function () {
|
||||
reject(new Error('vault_workbench_runtime_load_failed'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function buildVaultWorkbenchShellHtml(workspaceId, rootUri) {
|
||||
var ws = escapeHtml(String(workspaceId || '').trim());
|
||||
var root = escapeHtml(String(rootUri || '').trim());
|
||||
return (
|
||||
'<section class="mnote-vault-workbench" data-testid="mnote-vault-workbench" data-workspace-id="' +
|
||||
ws +
|
||||
'" data-root-uri="' +
|
||||
root +
|
||||
'" data-status="active">' +
|
||||
'<header class="mnote-vault-header">' +
|
||||
'<div class="mnote-vault-header-main">' +
|
||||
'<h1>密码箱</h1>' +
|
||||
'<p class="mnote-vault-status" data-vault-status role="status" aria-live="polite"></p>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-vault-header-actions">' +
|
||||
'<button type="button" data-vault-create data-testid="vault-create">新建</button>' +
|
||||
'<button type="button" data-vault-cipher-book data-testid="vault-cipher-book">密文簿</button>' +
|
||||
'<input type="search" data-vault-search data-testid="vault-search" placeholder="搜索标题、用户名、标签、分组…" aria-label="搜索密码条目" />' +
|
||||
'<div class="mnote-vault-tabs" role="tablist">' +
|
||||
'<button type="button" role="tab" data-vault-tab="active" class="is-active" aria-selected="true">在用</button>' +
|
||||
'<button type="button" role="tab" data-vault-tab="deleted" aria-selected="false">已删除</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</header>' +
|
||||
'<div class="mnote-vault-body">' +
|
||||
'<aside class="mnote-vault-list" data-vault-list data-testid="vault-list" aria-label="密码条目列表"></aside>' +
|
||||
'<main class="mnote-vault-detail" data-vault-detail data-testid="vault-detail" aria-label="条目详情"></main>' +
|
||||
'</div>' +
|
||||
'</section>'
|
||||
);
|
||||
}
|
||||
|
||||
function markVaultNavActive() {
|
||||
document.querySelectorAll('.mnote-sidebar-nav a.active, .wolai-quick-actions a.active').forEach(function (link) {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
document
|
||||
.querySelectorAll('a[data-mnote-nav="vault"], a[data-testid="mnote-nav-vault"]')
|
||||
.forEach(function (link) {
|
||||
link.classList.add('active');
|
||||
});
|
||||
}
|
||||
|
||||
async function openVaultWorkbenchSoft(targetUrl, options) {
|
||||
var url =
|
||||
targetUrl instanceof URL
|
||||
? targetUrl
|
||||
: new URL(String(targetUrl || '/vault'), window.location.origin);
|
||||
if (url.pathname !== '/vault') {
|
||||
window.location.assign(url.pathname + url.search + url.hash);
|
||||
return false;
|
||||
}
|
||||
copyWorkspaceSourceParams(url);
|
||||
if (!url.searchParams.get('sourceKind') && (url.searchParams.get('rootUri') || currentRootUri())) {
|
||||
url.searchParams.set('sourceKind', 'local_folder');
|
||||
}
|
||||
if (!url.searchParams.get('rootUri')) {
|
||||
var bodyRoot = currentRootUri();
|
||||
if (bodyRoot) url.searchParams.set('rootUri', bodyRoot);
|
||||
}
|
||||
var rootUri = (url.searchParams.get('rootUri') || currentRootUri() || '').trim();
|
||||
var workspaceId = (
|
||||
currentWorkspaceId() ||
|
||||
resolveWorkspaceId(document.body) ||
|
||||
(document.getElementById('sidebar-tree-root') &&
|
||||
document.getElementById('sidebar-tree-root').getAttribute('data-workspace-id')) ||
|
||||
''
|
||||
).trim();
|
||||
var content = document.querySelector('article.mnote-content');
|
||||
if (!(content instanceof HTMLElement)) {
|
||||
window.location.assign(url.pathname + url.search + url.hash);
|
||||
return false;
|
||||
}
|
||||
var nextHref = url.pathname + url.search + url.hash;
|
||||
if (mnoteNavigationInFlight === 'vault:' + nextHref) return true;
|
||||
mnoteNavigationInFlight = 'vault:' + nextHref;
|
||||
try {
|
||||
// Preserve #sidebar-tree-root / #sidebar-file-tree-root; only replace main pane.
|
||||
content.innerHTML = buildVaultWorkbenchShellHtml(workspaceId, rootUri);
|
||||
if (document.body instanceof HTMLElement) {
|
||||
document.body.setAttribute('data-mnote-page', 'vault');
|
||||
if (rootUri) document.body.setAttribute('data-mnote-root-uri', rootUri);
|
||||
document.body.setAttribute('data-mnote-source-kind', 'local_folder');
|
||||
}
|
||||
document.title = '密码箱';
|
||||
markVaultNavActive();
|
||||
if (!(options && options.replaceUrl === false)) {
|
||||
window.history.pushState({ mnoteSoftNav: 'vault' }, '', nextHref);
|
||||
}
|
||||
var vaultApi = await ensureVaultWorkbenchRuntimeLoaded();
|
||||
if (vaultApi && typeof vaultApi.boot === 'function') vaultApi.boot();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('[mnote vault] soft open failed, falling back to full navigation', error);
|
||||
window.location.assign(nextHref);
|
||||
return false;
|
||||
} finally {
|
||||
if (mnoteNavigationInFlight === 'vault:' + nextHref) mnoteNavigationInFlight = '';
|
||||
}
|
||||
}
|
||||
window.__mnoteOpenVaultWorkbench = openVaultWorkbenchSoft;
|
||||
|
||||
function openCurrentNavigationPage(trigger) {
|
||||
if (currentSourceKind() === 'local_folder') {
|
||||
var rootUri = currentRootUri();
|
||||
@@ -3387,6 +3530,34 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return;
|
||||
}
|
||||
|
||||
var vaultNavLink = closestAction(
|
||||
e.target,
|
||||
'a[data-mnote-nav="vault"], a[data-testid="mnote-nav-vault"], a[href="/vault"], a[href^="/vault?"]'
|
||||
);
|
||||
if (vaultNavLink instanceof HTMLAnchorElement) {
|
||||
// Modifier / middle-click: keep native full navigation (new tab etc.).
|
||||
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
||||
try {
|
||||
var enrichUrl = new URL(vaultNavLink.getAttribute('href') || '/vault', window.location.origin);
|
||||
if (enrichUrl.pathname === '/vault') {
|
||||
copyWorkspaceSourceParams(enrichUrl);
|
||||
vaultNavLink.setAttribute('href', enrichUrl.pathname + enrichUrl.search + enrichUrl.hash);
|
||||
}
|
||||
} catch (_) {}
|
||||
} else {
|
||||
try {
|
||||
var vaultUrl = new URL(vaultNavLink.getAttribute('href') || '/vault', window.location.origin);
|
||||
if (vaultUrl.pathname === '/vault') {
|
||||
e.preventDefault();
|
||||
copyWorkspaceSourceParams(vaultUrl);
|
||||
vaultNavLink.setAttribute('href', vaultUrl.pathname + vaultUrl.search + vaultUrl.hash);
|
||||
void openVaultWorkbenchSoft(vaultUrl);
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
var localFolderTrigger = closestAction(e.target, '[data-mnote-action="open-local-folder"]');
|
||||
if (localFolderTrigger) {
|
||||
e.preventDefault();
|
||||
@@ -3908,6 +4079,15 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
sidebarFileTreeSelection.focusedRowId = rowId;
|
||||
});
|
||||
syncSidebarFileTreeSelection();
|
||||
// Shell-first home: FileTree may be a pending placeholder; hydrate after first paint.
|
||||
if (typeof sidebarTreeLiveApply.hydratePendingFileTreeShell === 'function') {
|
||||
var schedulePendingHydrate = typeof queueMicrotask === 'function'
|
||||
? queueMicrotask
|
||||
: function(fn) { setTimeout(fn, 0); };
|
||||
schedulePendingHydrate(function() {
|
||||
void sidebarTreeLiveApply.hydratePendingFileTreeShell('boot');
|
||||
});
|
||||
}
|
||||
schedulePendingLocalFolderRestoreFocus();
|
||||
var recentPageOptionsApply = { documentId: '', at: 0 };
|
||||
function applyPrimaryPageOptionsOnce(documentId) {
|
||||
|
||||
Reference in New Issue
Block a user