From 6b7783173dc6db4001abce2f1ed64504ddfbd821 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 2 Jul 2026 23:18:23 -0400 Subject: [PATCH 1/2] fix(obsidian): prevent node tag menu from stacking on repeated hotkey presses Track the active popover in a closure variable inside setupNodeTagHotkey so that pressing the hotkey while a menu is already open closes it first and reopens at the new cursor position, rather than stacking another instance. Fixes ENG-1960. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- apps/obsidian/src/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/obsidian/src/index.ts b/apps/obsidian/src/index.ts index bd707c289..7909f4c44 100644 --- a/apps/obsidian/src/index.ts +++ b/apps/obsidian/src/index.ts @@ -282,6 +282,9 @@ export default class DiscourseGraphPlugin extends Plugin { } private setupNodeTagHotkey() { + let activePopover: NodeTagSuggestPopover | InlineNodeTypePicker | null = + null; + const nodeTagHotkeyExtension = EditorView.domEventHandlers({ keydown: (event: KeyboardEvent) => { // Access settings dynamically to handle changes @@ -293,26 +296,29 @@ export default class DiscourseGraphPlugin extends Plugin { event.preventDefault(); event.stopPropagation(); + activePopover?.close(); + activePopover = null; + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); if (activeView?.editor) { const editor = activeView.editor; const selectedText = editor.getSelection(); if (selectedText && selectedText.trim().length > 0) { - // Text is selected: open node type picker to create node from selection const picker = new InlineNodeTypePicker({ editor, nodeTypes: this.settings.nodeTypes, plugin: this, selectedText: selectedText.trim(), }); + activePopover = picker; picker.open(); } else { - // No selection: open the candidate node tag popover const popover = new NodeTagSuggestPopover( editor, this.settings.nodeTypes, ); + activePopover = popover; popover.open(); } } From 792be4b4054060aa1217d60b82d44e267f905c70 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 2 Jul 2026 23:26:08 -0400 Subject: [PATCH 2/2] fix(obsidian): close active node popover on plugin unload Promote the activePopover tracking variable to a class field so onunload() can call close() on it, cleaning up the DOM element and document-level event listeners if the plugin is unloaded while a NodeTagSuggestPopover or InlineNodeTypePicker is open. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- apps/obsidian/src/index.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/obsidian/src/index.ts b/apps/obsidian/src/index.ts index 7909f4c44..d5f57e27c 100644 --- a/apps/obsidian/src/index.ts +++ b/apps/obsidian/src/index.ts @@ -44,6 +44,10 @@ export default class DiscourseGraphPlugin extends Plugin { settings: Settings = { ...DEFAULT_SETTINGS }; private tagNodeHandler: TagNodeHandler | null = null; private fileChangeListener: FileChangeListener | null = null; + private activeNodePopover: + | NodeTagSuggestPopover + | InlineNodeTypePicker + | null = null; private currentViewActions: { leaf: WorkspaceLeaf; action: HTMLElement }[] = []; private pendingCanvasSwitches = new Set(); @@ -282,9 +286,6 @@ export default class DiscourseGraphPlugin extends Plugin { } private setupNodeTagHotkey() { - let activePopover: NodeTagSuggestPopover | InlineNodeTypePicker | null = - null; - const nodeTagHotkeyExtension = EditorView.domEventHandlers({ keydown: (event: KeyboardEvent) => { // Access settings dynamically to handle changes @@ -296,8 +297,8 @@ export default class DiscourseGraphPlugin extends Plugin { event.preventDefault(); event.stopPropagation(); - activePopover?.close(); - activePopover = null; + this.activeNodePopover?.close(); + this.activeNodePopover = null; const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); if (activeView?.editor) { @@ -311,14 +312,14 @@ export default class DiscourseGraphPlugin extends Plugin { plugin: this, selectedText: selectedText.trim(), }); - activePopover = picker; + this.activeNodePopover = picker; picker.open(); } else { const popover = new NodeTagSuggestPopover( editor, this.settings.nodeTypes, ); - activePopover = popover; + this.activeNodePopover = popover; popover.open(); } } @@ -436,6 +437,8 @@ export default class DiscourseGraphPlugin extends Plugin { } onunload() { + this.activeNodePopover?.close(); + this.activeNodePopover = null; this.cleanupViewActions(); activeDocument.body.classList.remove("dg-hide-frontmatter-ids");