feat: restore Wolai workspace navigation and assets

This commit is contained in:
Agent Board
2026-07-15 23:12:15 +08:00
parent 31a160fcc5
commit 6f9c7d3b58
48 changed files with 6734 additions and 517 deletions
@@ -470,18 +470,33 @@ import {
const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
window.dispatchEvent(new CustomEvent('mnote:primary-document-activated', {
detail: { documentId, workspaceId, title }
detail: {
documentId,
workspaceId,
title,
sourceKind: bootstrap.sourceKind || '',
rootUri: bootstrap.rootUri || '',
pageBlockNavigation: runtimeDescriptor.pageBlockNavigation === true,
}
}));
document.documentElement.removeAttribute('data-mnote-side-target-unsupported');
document.documentElement.removeAttribute('data-mnote-side-target-asset-id');
document.querySelectorAll('.tree-row[data-active="true"], .tree-row[data-selected="true"]').forEach((row) => {
const activeTreeTab = document.querySelector('[data-mnote-sidebar-tree-tab][aria-selected="true"]');
const activeTreeMode = activeTreeTab instanceof HTMLElement
&& activeTreeTab.getAttribute('data-mnote-sidebar-tree-tab') === 'filetree'
? 'filetree'
: 'page';
const activeTreeRows = activeTreeMode === 'filetree'
? '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]'
: '#sidebar-tree-root .tree-row[data-shell-mode="page"]';
document.querySelectorAll(`${activeTreeRows}[data-active="true"], ${activeTreeRows}[data-selected="true"]`).forEach((row) => {
if (row instanceof HTMLElement) {
row.setAttribute('data-active', 'false');
row.setAttribute('data-selected', 'false');
}
});
const escapedId = window.CSS && typeof window.CSS.escape === 'function' ? window.CSS.escape(documentId) : String(documentId).replace(/["\\]/g, '\\$&');
document.querySelectorAll(`.tree-row[data-node-id="${escapedId}"], .tree-row[data-document-id="${escapedId}"], .tree-row[data-doc-id="${escapedId}"]`).forEach((row) => {
document.querySelectorAll(`${activeTreeRows}[data-node-id="${escapedId}"], ${activeTreeRows}[data-document-id="${escapedId}"], ${activeTreeRows}[data-doc-id="${escapedId}"]`).forEach((row) => {
if (!(row instanceof HTMLElement)) return;
if (row.getAttribute('data-shell-mode') === 'filetree') {
row.setAttribute('data-selected', String(row.getAttribute('data-row-id') === `doc:${documentId}`));
@@ -610,7 +625,7 @@ import {
}
const bootstrap = buildBootstrapFromAggregate(aggregate, descriptor, paneRole);
syncPageAggregateScript({ pageAggregateScriptId: bootstrap.pageAggregateScriptId }, aggregate);
const runtimeDescriptor = { paneRole, root, observability, aggregate, bootstrap };
const runtimeDescriptor = { paneRole, root, observability, aggregate, bootstrap, pageBlockNavigation: descriptor.pageBlockNavigation === true };
updatePaneChrome(runtimeDescriptor);
if (paneRole === 'secondary' && workspace instanceof HTMLElement) {
workspace.setAttribute('data-has-secondary-pane', 'true');
@@ -677,6 +692,70 @@ import {
pushUrlState(url);
};
const openPrimaryDocumentFromPageBlockHref = (href) => {
const raw = typeof href === 'string' ? href.trim() : '';
if (!raw) return false;
let target = null;
try {
target = new URL(raw, window.location.href);
} catch (_) {
return false;
}
if (target.origin !== window.location.origin || !target.pathname.startsWith('/documents/')) return false;
const encodedId = target.pathname.slice('/documents/'.length).split('/')[0] || '';
const documentId = decodeURIComponent(encodedId).trim();
if (!documentId || typeof window.__mnoteDocumentPaneRuntime?.openPrimaryDocument !== 'function') return false;
const current = currentUrl();
const workspaceId = target.searchParams.get('workspaceId') || current.searchParams.get('workspaceId') || '';
const sourceKind = target.searchParams.get('sourceKind') || current.searchParams.get('sourceKind') || '';
const rootUri = target.searchParams.get('rootUri') || current.searchParams.get('rootUri') || '';
void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({
documentId,
workspaceId,
sourceKind,
rootUri,
url: target,
pageBlockNavigation: true,
}).catch((error) => {
console.warn('mnote page block pane navigation failed, fallback to full navigation', error);
window.location.assign(target.pathname + target.search + target.hash);
});
return true;
};
const installPageBlockOpenCapture = () => {
if (window.__mnotePageBlockOpenCaptureInstalled === true) return;
window.__mnotePageBlockOpenCaptureInstalled = true;
document.addEventListener('mousedown', (event) => {
if (event.button !== 0) return;
const target = event.target;
const anchor = target instanceof Element ? target.closest('a.mnote-page-block-link') : null;
if (!(anchor instanceof HTMLAnchorElement)) return;
const href = anchor.getAttribute('href') || '';
if (!href) return;
// Tiptap's Link extension handles mouse down before the click bubble. Keep
// the target aside so that it cannot trigger a native document navigation.
anchor.setAttribute('data-mnote-page-block-href', href);
anchor.removeAttribute('href');
}, true);
document.addEventListener('click', (event) => {
const target = event.target;
const anchor = target instanceof Element ? target.closest('a.mnote-page-block-link') : null;
if (!(anchor instanceof HTMLAnchorElement)) return;
const href = anchor.getAttribute('data-mnote-page-block-href') || anchor.getAttribute('href') || '';
if (!href) return;
anchor.removeAttribute('data-mnote-page-block-href');
anchor.removeAttribute('href');
if (!openPrimaryDocumentFromPageBlockHref(href)) {
anchor.setAttribute('href', href);
return;
}
event.preventDefault();
event.stopPropagation();
if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
}, true);
};
const setSecondaryEditorHostVisible = (visible) => {
const host = document.querySelector('[data-testid="mnote-secondary-editor-tab-host"]');
if (host instanceof HTMLElement) host.hidden = !visible;
@@ -973,10 +1052,11 @@ import {
};
window.__mnoteDocumentPaneRuntime = {
openPrimaryDocument: async ({ documentId, workspaceId, sourceKind, rootUri, url } = {}) => {
openPrimaryDocument: async ({ documentId, workspaceId, sourceKind, rootUri, url, pageBlockNavigation } = {}) => {
const id = typeof documentId === 'string' ? documentId.trim() : '';
if (!id) return false;
const descriptor = descriptorFromCurrentUrl('primary', id, { workspaceId, sourceKind, rootUri });
descriptor.pageBlockNavigation = pageBlockNavigation === true;
await replacePaneDocument('primary', descriptor);
activateMainEditorTab('', 'primary');
updatePrimaryUrl(descriptor, url instanceof URL ? url : null);
@@ -1037,6 +1117,7 @@ import {
return 1;
},
};
installPageBlockOpenCapture();
bindMainEditorPageTab('primary');
bindMainEditorPageTab('secondary');