fix local office resource editing
- add local-folder OnlyOffice sign/callback writeback and edit-tab handling - align main resource tabs, attachment edit menu, slash isolation, and filetree context behavior - record Sidex/Hermes gap reviews and Reasonix task checklists
This commit is contained in:
@@ -465,6 +465,8 @@ pub(crate) fn render_document_title_controller_script() -> &'static str {
|
||||
document.title = title;
|
||||
const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
|
||||
if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
|
||||
const pageTabTitle = document.querySelector('[data-mnote-main-tab="page"] .mnote-main-tab-title');
|
||||
if (pageTabTitle instanceof HTMLElement) pageTabTitle.textContent = title;
|
||||
}
|
||||
if (documentId) {
|
||||
const escapedId = cssEscape(documentId);
|
||||
@@ -2616,6 +2618,16 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
document.title = title;
|
||||
const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
|
||||
if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
|
||||
const pageTab = document.querySelector('[data-mnote-main-tab="page"]');
|
||||
if (pageTab instanceof HTMLElement) {
|
||||
pageTab.setAttribute('data-document-id', documentId);
|
||||
pageTab.setAttribute('data-workspace-id', workspaceId);
|
||||
const pageTabTitle = pageTab.querySelector('.mnote-main-tab-title');
|
||||
if (pageTabTitle instanceof HTMLElement) pageTabTitle.textContent = title;
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('mnote:primary-document-activated', {
|
||||
detail: { documentId, workspaceId, title }
|
||||
}));
|
||||
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) => {
|
||||
@@ -2986,12 +2998,20 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
const anchorElement = anchorNode && anchorNode.nodeType === Node.ELEMENT_NODE
|
||||
? anchorNode
|
||||
: anchorNode?.parentElement || null;
|
||||
const roots = Array.from(document.querySelectorAll(ROOT_SELECTOR)).filter((node) => node instanceof HTMLElement);
|
||||
const roots = Array.from(document.querySelectorAll(ROOT_SELECTOR)).filter((node) => (
|
||||
node instanceof HTMLElement
|
||||
&& node.offsetParent !== null
|
||||
&& node.getAttribute('data-mnote-side-target-unsupported') !== 'true'
|
||||
));
|
||||
if (anchorElement instanceof Element) {
|
||||
const activeRoot = roots.find((root) => root.contains(anchorElement));
|
||||
if (activeRoot) return activeRoot;
|
||||
}
|
||||
return roots.find((root) => root.offsetParent !== null) || roots[0] || null;
|
||||
const focused = document.activeElement instanceof Element
|
||||
? roots.find((root) => root.contains(document.activeElement))
|
||||
: null;
|
||||
if (focused) return focused;
|
||||
return roots.find((root) => root.querySelector('.ProseMirror:focus-within')) || roots[0] || null;
|
||||
};
|
||||
|
||||
const slashMenuAnchorFromSelection = (root) => {
|
||||
@@ -3042,12 +3062,42 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
return fallback();
|
||||
};
|
||||
|
||||
const positionSlashMenuForRoot = (root) => {
|
||||
if (!(root instanceof HTMLElement)) root = activeEditorRootForSlashMenu();
|
||||
if (!(root instanceof HTMLElement)) return;
|
||||
const menu = root.querySelector('[data-testid="mnote-leptos-tiptap-slash-menu"]')
|
||||
|| document.querySelector('[data-testid="mnote-leptos-tiptap-slash-menu"]');
|
||||
const setSlashMenuInactive = (menu, inactive) => {
|
||||
if (!(menu instanceof HTMLElement)) return;
|
||||
if (inactive) {
|
||||
if (menu.getAttribute('data-mnote-slash-inactive') !== 'true') {
|
||||
menu.setAttribute('data-mnote-slash-inactive', 'true');
|
||||
}
|
||||
if (menu.style.display !== 'none') menu.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (menu.getAttribute('data-mnote-slash-inactive') === 'true') {
|
||||
menu.removeAttribute('data-mnote-slash-inactive');
|
||||
}
|
||||
if (menu.style.display === 'none') menu.style.display = '';
|
||||
};
|
||||
|
||||
const hideSlashMenusOutsideRoot = (activeRoot) => {
|
||||
document.querySelectorAll(`${ROOT_SELECTOR} [data-testid="mnote-leptos-tiptap-slash-menu"]`).forEach((menu) => {
|
||||
const root = menu.closest(ROOT_SELECTOR);
|
||||
if (root !== activeRoot) setSlashMenuInactive(menu, true);
|
||||
});
|
||||
};
|
||||
|
||||
const positionSlashMenuForRoot = (root) => {
|
||||
const activeRoot = activeEditorRootForSlashMenu();
|
||||
if (!(root instanceof HTMLElement)) root = activeRoot;
|
||||
if (!(root instanceof HTMLElement)) return;
|
||||
if (activeRoot instanceof HTMLElement && root !== activeRoot) {
|
||||
const inactiveMenu = root.querySelector('[data-testid="mnote-leptos-tiptap-slash-menu"]');
|
||||
setSlashMenuInactive(inactiveMenu, true);
|
||||
hideSlashMenusOutsideRoot(activeRoot);
|
||||
return;
|
||||
}
|
||||
hideSlashMenusOutsideRoot(root);
|
||||
const menu = root.querySelector('[data-testid="mnote-leptos-tiptap-slash-menu"]');
|
||||
if (!(menu instanceof HTMLElement)) return;
|
||||
setSlashMenuInactive(menu, false);
|
||||
const anchor = slashMenuAnchorFromSelection(root);
|
||||
const gap = 8;
|
||||
const menuWidth = Math.min(316, Math.max(160, window.innerWidth - 16));
|
||||
@@ -3611,6 +3661,18 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
}
|
||||
};
|
||||
|
||||
const refreshExistingOfficeResourceTab = (entry, input) => {
|
||||
if (!entry || entry.kind !== 'office') return false;
|
||||
const nextHref = String(input.officeUrl || input.href || '').trim();
|
||||
if (!nextHref) return false;
|
||||
const frame = entry.panel?.querySelector?.('iframe.mnote-resource-tab-frame');
|
||||
const currentHref = frame instanceof HTMLIFrameElement
|
||||
? String(frame.getAttribute('src') || frame.src || '').trim()
|
||||
: '';
|
||||
if (currentHref !== nextHref) openPassiveResourceTab(entry, input);
|
||||
return true;
|
||||
};
|
||||
|
||||
const openMindmapResourceTab = async (entry, input) => {
|
||||
const documentId = String(input.documentId || currentDocumentId() || '').trim();
|
||||
const mindmapId = String(input.mindmapId || input.assetId || '').trim();
|
||||
@@ -3668,6 +3730,9 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
const title = String(input.title || input.fileName || input.assetId || '资源').trim() || '资源';
|
||||
if (root instanceof HTMLElement) {
|
||||
root.replaceChildren();
|
||||
document.querySelectorAll('[data-document-pane="true"][data-pane-role="secondary"] [data-testid="mnote-leptos-tiptap-slash-menu"]').forEach((node) => {
|
||||
if (node instanceof HTMLElement) node.remove();
|
||||
});
|
||||
root.setAttribute('data-editor-host-kind', 'unsupported_resource_side_target');
|
||||
root.setAttribute('data-mnote-side-target-unsupported', 'true');
|
||||
root.setAttribute('data-mnote-side-target-asset-id', String(input.assetId || ''));
|
||||
@@ -3691,6 +3756,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
if (!objectIdentity) return false;
|
||||
const existing = resourceTabRegistry.get(objectIdentity);
|
||||
if (existing) {
|
||||
refreshExistingOfficeResourceTab(existing, input);
|
||||
activateMainEditorTab(objectIdentity);
|
||||
return true;
|
||||
}
|
||||
@@ -3754,6 +3820,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
const descriptor = descriptorFromCurrentUrl('primary', id, { workspaceId, sourceKind, rootUri });
|
||||
await replacePaneDocument('primary', descriptor);
|
||||
updatePrimaryUrl(descriptor, url instanceof URL ? url : null);
|
||||
activateMainEditorTab('');
|
||||
return true;
|
||||
},
|
||||
openPrimaryMindmap: async ({ documentId, mindmapId, workspaceId, url } = {}) => {
|
||||
@@ -3778,6 +3845,11 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
},
|
||||
resolveResourceOpen: (input) => resolveResourceOpen(input),
|
||||
openResourceInActiveTab: openResourceInActiveTab,
|
||||
activatePageTab: () => {
|
||||
bindMainEditorPageTab();
|
||||
activateMainEditorTab('');
|
||||
return true;
|
||||
},
|
||||
openResourceAsSideTarget: async (input = {}) => {
|
||||
const resolved = resolveResourceOpen({ ...input, openTarget: 'side' });
|
||||
if (resolved.editorKind === 'markdown' || resolved.editorKind === 'text' || resolved.editorKind === 'code') {
|
||||
@@ -4466,12 +4538,25 @@ mod tests {
|
||||
assert!(html.contains("data-mnote-main-tab-strip"));
|
||||
assert!(html.contains("class=\"mnote-main-tab-badge\""));
|
||||
assert!(!html.contains(">description</span><span class=\"mnote-main-tab-title\""));
|
||||
assert!(html.contains("[data-mnote-main-tab=\"page\"] .mnote-main-tab-title"));
|
||||
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("positionSlashMenuForRoot"));
|
||||
assert!(html.contains("menu.style.position = 'fixed';"));
|
||||
assert!(html.contains("installGlobalSlashMenuPositioning();"));
|
||||
assert!(html.contains("data-mnote-slash-positioned', 'host'"));
|
||||
assert!(html.contains(
|
||||
"const menu = root.querySelector('[data-testid=\"mnote-leptos-tiptap-slash-menu\"]');"
|
||||
));
|
||||
assert!(html.contains("const setSlashMenuInactive = (menu, inactive) =>"));
|
||||
assert!(html.contains("const hideSlashMenusOutsideRoot = (activeRoot) =>"));
|
||||
assert!(html.contains("data-mnote-slash-inactive"));
|
||||
assert!(html.contains("if (activeRoot instanceof HTMLElement && root !== activeRoot)"));
|
||||
assert!(!html.contains(
|
||||
"|| document.querySelector('[data-testid=\"mnote-leptos-tiptap-slash-menu\"]')"
|
||||
));
|
||||
assert!(html.contains("data-mnote-side-target-unsupported') !== 'true'"));
|
||||
assert!(
|
||||
html.contains("runtimeDescriptor.root.addEventListener(STATE_EVENT, view.onState);")
|
||||
);
|
||||
@@ -4509,6 +4594,12 @@ mod tests {
|
||||
assert!(html.contains("resourceTabRegistry.delete(key)"));
|
||||
assert!(html.contains("activateMainEditorTab(lastActiveResourceTabKey())"));
|
||||
assert!(html.contains("resourceTabBadgeKind(input, kind)"));
|
||||
assert!(html.contains("const refreshExistingOfficeResourceTab = (entry, input) =>"));
|
||||
assert!(html.contains("if (!entry || entry.kind !== 'office') return false;"));
|
||||
assert!(
|
||||
html.contains("if (currentHref !== nextHref) openPassiveResourceTab(entry, input);")
|
||||
);
|
||||
assert!(html.contains("refreshExistingOfficeResourceTab(existing, input);"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -4769,7 +4860,7 @@ mod tests {
|
||||
assert!(html.contains("data-row-kind=\"markdown\""));
|
||||
assert!(html.contains("data-page-openable=\"false\""));
|
||||
assert!(html.contains("fileAction === 'open' && rowKind === 'folder'"));
|
||||
assert!(html.contains("btn.getAttribute('data-page-openable') === 'false'"));
|
||||
assert!(html.contains("openTrigger.getAttribute('data-page-openable') === 'false'"));
|
||||
assert!(html.contains("data-mnote-action=\"open-local-folder\""));
|
||||
assert!(html.contains("refreshSessionFromExternalFileChange"));
|
||||
assert!(html.contains("refreshSessionFromExternalChange"));
|
||||
|
||||
Reference in New Issue
Block a user