From 3210d7c1e9370c6401fec8e0f35dc85467c9c2ee Mon Sep 17 00:00:00 2001 From: Artem Nistuley Date: Tue, 19 May 2026 16:31:56 +0300 Subject: [PATCH] fix: skip empty open when defaults are off and keep editor raw --- .../components/context-menu/ContextMenu.vue | 24 +++++++--- .../v1/components/context-menu/utils.js | 2 +- .../context-menu-empty-config.spec.ts | 47 +++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 tests/behavior/tests/slash-menu/context-menu-empty-config.spec.ts diff --git a/packages/super-editor/src/editors/v1/components/context-menu/ContextMenu.vue b/packages/super-editor/src/editors/v1/components/context-menu/ContextMenu.vue index 81e1930d64..d0ed82adbd 100644 --- a/packages/super-editor/src/editors/v1/components/context-menu/ContextMenu.vue +++ b/packages/super-editor/src/editors/v1/components/context-menu/ContextMenu.vue @@ -559,20 +559,30 @@ onMounted(() => { // Prevent opening the menu in read-only mode const readOnly = !props.editor?.isEditable; if (readOnly) return; - isOpen.value = true; - menuPosition.value = event.menuPosition; - searchQuery.value = ''; + let nextSections = sections.value; // Set sections and selectedId when menu opens if (!currentContext.value) { const context = await getEditorContext(props.editor); currentContext.value = context; // Store context for later use - sections.value = getItems({ ...context, trigger: 'slash' }); - selectedId.value = flattenedItems.value[0]?.id || null; + nextSections = getItems({ ...context, trigger: 'slash' }); } else if (sections.value.length === 0) { const trigger = currentContext.value.event?.type === 'contextmenu' ? 'click' : 'slash'; - sections.value = getItems({ ...currentContext.value, trigger }); - selectedId.value = flattenedItems.value[0]?.id || null; + nextSections = getItems({ ...currentContext.value, trigger }); + } + + if (!nextSections.length) { + const state = props.editor?.state; + if (state) { + props.editor.dispatch(state.tr.setMeta(ContextMenuPluginKey, { type: 'close' })); + } + return; } + + sections.value = nextSections; + menuPosition.value = event.menuPosition; + searchQuery.value = ''; + selectedId.value = flattenedItems.value[0]?.id || null; + isOpen.value = true; }; props.editor.on('contextMenu:open', contextMenuOpenHandler); diff --git a/packages/super-editor/src/editors/v1/components/context-menu/utils.js b/packages/super-editor/src/editors/v1/components/context-menu/utils.js index dc7d47edf6..130b00be64 100644 --- a/packages/super-editor/src/editors/v1/components/context-menu/utils.js +++ b/packages/super-editor/src/editors/v1/components/context-menu/utils.js @@ -227,7 +227,7 @@ export async function getEditorContext(editor, event) { node, event, trigger: event ? 'click' : 'slash', - editor, + editor: markRaw(editor), trackedChanges, proofingContext, tocAncestor, diff --git a/tests/behavior/tests/slash-menu/context-menu-empty-config.spec.ts b/tests/behavior/tests/slash-menu/context-menu-empty-config.spec.ts new file mode 100644 index 0000000000..c05ee70df9 --- /dev/null +++ b/tests/behavior/tests/slash-menu/context-menu-empty-config.spec.ts @@ -0,0 +1,47 @@ +import { test, expect } from '../../fixtures/superdoc.js'; +import { rightClickAtDocPos } from '../../helpers/editor-interactions.js'; + +test.use({ config: { toolbar: 'full' } }); + +test('right-click does not open empty context menu when defaults are disabled and no custom items exist', async ({ + superdoc, + page, +}) => { + const errors: string[] = []; + const onPageError = (error: Error) => errors.push(`pageerror:${error.message}`); + const onConsole = (msg: import('@playwright/test').ConsoleMessage) => { + if (msg.type() === 'error') { + errors.push(`console:${msg.text()}`); + } + }; + + page.on('pageerror', onPageError); + page.on('console', onConsole); + + try { + await page.evaluate(() => { + const editor = (window as any).editor; + if (!editor?.options) { + throw new Error('Expected window.editor.options to be available.'); + } + editor.options.contextMenuConfig = { + includeDefaultItems: false, + customItems: [], + }; + }); + + await superdoc.type('Context menu should stay hidden'); + await superdoc.waitForStable(); + + const pos = await superdoc.findTextPos('Context'); + await rightClickAtDocPos(page, pos + 1); + await superdoc.waitForStable(); + + await expect(page.locator('.context-menu')).toHaveCount(0); + expect(errors.join('\n')).not.toContain('#storySessionManager'); + expect(errors.join('\n')).not.toContain('Cannot read private member'); + } finally { + page.off('pageerror', onPageError); + page.off('console', onConsole); + } +});