align workbench open editors tabs
This commit is contained in:
@@ -3343,6 +3343,62 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
});
|
||||
};
|
||||
|
||||
const openEditorsSnapshotEntry = (entry, key) => ({
|
||||
objectIdentity: String(entry?.objectIdentity || key || '').trim(),
|
||||
title: String(entry?.title || '资源').trim() || '资源',
|
||||
kind: normalizeResourceTabKind(entry),
|
||||
badgeKind: entry?.tab instanceof HTMLElement
|
||||
? String(entry.tab.getAttribute('data-mnote-tab-badge-kind') || resourceTabBadgeKind(entry, entry.kind)).trim()
|
||||
: resourceTabBadgeKind(entry, entry?.kind),
|
||||
active: entry?.tab instanceof HTMLElement
|
||||
? entry.tab.getAttribute('aria-selected') === 'true'
|
||||
: false,
|
||||
dirtyGuard: resourceTabCloseGuardReason(entry?.session),
|
||||
assetId: String(entry?.assetId || entry?.session?.assetId || '').trim(),
|
||||
path: String(entry?.path || entry?.session?.resourcePath || '').trim(),
|
||||
});
|
||||
|
||||
const buildOpenEditorsSnapshot = () => {
|
||||
const nodes = resourceTabHostNodes();
|
||||
const pageTitle = nodes.pageTab instanceof HTMLElement
|
||||
? String(nodes.pageTab.querySelector('.mnote-main-tab-title')?.textContent || '页面').trim()
|
||||
: '页面';
|
||||
const pageEntry = {
|
||||
objectIdentity: 'page',
|
||||
documentId: String(nodes.pageTab?.getAttribute?.('data-document-id') || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(nodes.pageTab?.getAttribute?.('data-workspace-id') || currentWebShellWorkspaceId() || '').trim(),
|
||||
title: pageTitle || '页面',
|
||||
kind: 'page',
|
||||
badgeKind: 'code',
|
||||
active: nodes.pageTab instanceof HTMLElement
|
||||
? nodes.pageTab.getAttribute('aria-selected') === 'true'
|
||||
: false,
|
||||
dirtyGuard: '',
|
||||
};
|
||||
const resources = [];
|
||||
resourceTabRegistry.forEach((entry, key) => {
|
||||
resources.push(openEditorsSnapshotEntry(entry, key));
|
||||
});
|
||||
return {
|
||||
schema: 'mnote.open_editors_snapshot.v1',
|
||||
generatedAt: Date.now(),
|
||||
activeObjectIdentity: pageEntry.active
|
||||
? 'page'
|
||||
: (resources.find((entry) => entry.active)?.objectIdentity || ''),
|
||||
editors: [pageEntry, ...resources],
|
||||
resourceEditors: resources,
|
||||
};
|
||||
};
|
||||
|
||||
const syncOpenEditorsSnapshot = () => {
|
||||
const snapshot = buildOpenEditorsSnapshot();
|
||||
window.__mnoteOpenEditorsSnapshot = snapshot;
|
||||
document.documentElement.setAttribute('data-mnote-open-editors-count', String(snapshot.editors.length));
|
||||
document.documentElement.setAttribute('data-mnote-active-editor', snapshot.activeObjectIdentity || '');
|
||||
window.dispatchEvent(new CustomEvent('mnote:open-editors-snapshot', { detail: snapshot }));
|
||||
return snapshot;
|
||||
};
|
||||
|
||||
const showResourceTabCloseGuardNotice = (entry, reason) => {
|
||||
const nodes = resourceTabHostNodes();
|
||||
const messages = {
|
||||
@@ -3445,17 +3501,56 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
});
|
||||
syncActiveResourceFileTreeRow(activeResource);
|
||||
syncActiveResourceUrlState(activeResource);
|
||||
syncOpenEditorsSnapshot();
|
||||
};
|
||||
|
||||
const bindMainEditorTabStrip = () => {
|
||||
const nodes = resourceTabHostNodes();
|
||||
if (!(nodes.strip instanceof HTMLElement)) return;
|
||||
if (nodes.strip.getAttribute('data-mnote-tab-strip-bound') === 'true') return;
|
||||
nodes.strip.setAttribute('data-mnote-tab-strip-bound', 'true');
|
||||
const collectTabs = () => Array.from(nodes.strip.querySelectorAll('[data-mnote-main-tab]'))
|
||||
.filter((tab) => tab instanceof HTMLElement && tab.isConnected);
|
||||
const selectedIndex = (tabs) => tabs.findIndex((tab) => tab.getAttribute('aria-selected') === 'true');
|
||||
nodes.strip.addEventListener('keydown', (event) => {
|
||||
const tabs = collectTabs();
|
||||
if (!tabs.length) return;
|
||||
const focusedIndex = tabs.findIndex((tab) => tab === document.activeElement);
|
||||
const baseIndex = focusedIndex >= 0 ? focusedIndex : Math.max(0, selectedIndex(tabs));
|
||||
let targetIndex = -1;
|
||||
if (event.key === 'ArrowRight') {
|
||||
targetIndex = (baseIndex + 1) % tabs.length;
|
||||
} else if (event.key === 'ArrowLeft') {
|
||||
targetIndex = (baseIndex - 1 + tabs.length) % tabs.length;
|
||||
} else if (event.key === 'Home') {
|
||||
targetIndex = 0;
|
||||
} else if (event.key === 'End') {
|
||||
targetIndex = tabs.length - 1;
|
||||
} else if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
const tab = tabs[baseIndex];
|
||||
if (tab instanceof HTMLElement) tab.click();
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const target = tabs[targetIndex];
|
||||
if (target instanceof HTMLElement) target.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const bindMainEditorPageTab = () => {
|
||||
const nodes = resourceTabHostNodes();
|
||||
if (!(nodes.pageTab instanceof HTMLElement)) return;
|
||||
bindMainEditorTabStrip();
|
||||
if (nodes.pageTab.getAttribute('data-mnote-page-tab-bound') === 'true') return;
|
||||
nodes.pageTab.setAttribute('data-mnote-page-tab-bound', 'true');
|
||||
nodes.pageTab.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
activateMainEditorTab('');
|
||||
});
|
||||
syncOpenEditorsSnapshot();
|
||||
};
|
||||
|
||||
const closeResourceTab = (objectIdentity) => {
|
||||
@@ -3481,7 +3576,10 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
if (entry.tab instanceof HTMLElement) entry.tab.remove();
|
||||
if (entry.panel instanceof HTMLElement) entry.panel.remove();
|
||||
resourceTabRegistry.delete(key);
|
||||
activateMainEditorTab(lastActiveResourceTabKey());
|
||||
const nextKey = lastActiveResourceTabKey();
|
||||
activateMainEditorTab(nextKey);
|
||||
const nextTab = nextKey ? resourceTabRegistry.get(nextKey)?.tab : resourceTabHostNodes().pageTab;
|
||||
if (nextTab instanceof HTMLElement) nextTab.focus();
|
||||
};
|
||||
|
||||
const markResourceTabError = (entry) => {
|
||||
@@ -3861,6 +3959,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
closeSecondaryPane(url instanceof URL ? url : currentUrl());
|
||||
return true;
|
||||
},
|
||||
getOpenEditorsSnapshot: () => buildOpenEditorsSnapshot(),
|
||||
refreshDocument: async ({ documentId, workspaceId, source } = {}) => {
|
||||
const targets = Array.from(documentSessionRegistry.values()).filter((session) => (
|
||||
sessionMatchesDocumentWorkspace(session, documentId, workspaceId)
|
||||
@@ -4542,6 +4641,10 @@ mod tests {
|
||||
assert!(html.contains("pageTab.setAttribute('data-document-id', documentId);"));
|
||||
assert!(html.contains("data-mnote-tab-badge-kind"));
|
||||
assert!(html.contains("resourceTabBadgeKind(input, kind)"));
|
||||
assert!(html.contains("mnote.open_editors_snapshot.v1"));
|
||||
assert!(html.contains("getOpenEditorsSnapshot"));
|
||||
assert!(html.contains("bindMainEditorTabStrip"));
|
||||
assert!(html.contains("data-mnote-tab-strip-bound"));
|
||||
assert!(html.contains("positionSlashMenuForRoot"));
|
||||
assert!(html.contains("menu.style.position = 'fixed';"));
|
||||
assert!(html.contains("installGlobalSlashMenuPositioning();"));
|
||||
@@ -4592,7 +4695,8 @@ mod tests {
|
||||
assert!(html.contains("resourceTabCloseGuardReason"));
|
||||
assert!(html.contains("closeResourceTab"));
|
||||
assert!(html.contains("resourceTabRegistry.delete(key)"));
|
||||
assert!(html.contains("activateMainEditorTab(lastActiveResourceTabKey())"));
|
||||
assert!(html.contains("const nextKey = lastActiveResourceTabKey();"));
|
||||
assert!(html.contains("if (nextTab instanceof HTMLElement) nextTab.focus();"));
|
||||
assert!(html.contains("resourceTabBadgeKind(input, kind)"));
|
||||
assert!(html.contains("const refreshExistingOfficeResourceTab = (entry, input) =>"));
|
||||
assert!(html.contains("if (!entry || entry.kind !== 'office') return false;"));
|
||||
|
||||
Reference in New Issue
Block a user