From c10eddd9ca3da3328d8c4dad35e4ff057beb5939 Mon Sep 17 00:00:00 2001 From: Prathmesh Yelne Date: Tue, 9 Jun 2026 22:25:35 +0530 Subject: [PATCH] fix(inline-tool-link): restore unlink behavior and add dynamic tooltip - Remove the !this.inputOpened guard that prevented unlinking when the link input was already open (which it always is when an anchor is detected via checkState). - Add getTitle() to LinkInlineTool for dynamic tooltip: shows 'Unlink' when selection is inside a link, 'Link' otherwise. - Update inline toolbar to read dynamic title from tool instances. Closes #3005 --- .../inline-tools/inline-tool-link.ts | 30 +++++++++---------- src/components/modules/toolbar/inline.ts | 12 ++++++++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/components/inline-tools/inline-tool-link.ts b/src/components/inline-tools/inline-tool-link.ts index 0bef25c73..78d1db507 100644 --- a/src/components/inline-tools/inline-tool-link.ts +++ b/src/components/inline-tools/inline-tool-link.ts @@ -124,6 +124,7 @@ export default class LinkInlineTool implements InlineTool { this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier); this.nodes.button.innerHTML = IconLink; + this.nodes.button.title = LinkInlineTool.title; return this.nodes.button; } @@ -172,21 +173,11 @@ export default class LinkInlineTool implements InlineTool { * Unlink icon pressed */ if (parentAnchor) { - /** - * If input is not opened, treat click as explicit unlink action. - * If input is opened (e.g., programmatic close when switching tools), avoid unlinking. - */ - if (!this.inputOpened) { - this.selection.expandToTag(parentAnchor); - this.unlink(); - this.closeActions(); - this.checkState(); - this.toolbar.close(); - } else { - /** Only close actions without clearing saved selection to preserve user state */ - this.closeActions(false); - this.checkState(); - } + this.selection.expandToTag(parentAnchor); + this.unlink(); + this.closeActions(); + this.checkState(); + this.toolbar.close(); return; } @@ -205,6 +196,7 @@ export default class LinkInlineTool implements InlineTool { this.nodes.button.innerHTML = IconUnlink; this.nodes.button.classList.add(this.CSS.buttonUnlink); this.nodes.button.classList.add(this.CSS.buttonActive); + this.nodes.button.title = 'Unlink'; this.openActions(); /** @@ -219,6 +211,7 @@ export default class LinkInlineTool implements InlineTool { this.nodes.button.innerHTML = IconLink; this.nodes.button.classList.remove(this.CSS.buttonUnlink); this.nodes.button.classList.remove(this.CSS.buttonActive); + this.nodes.button.title = 'Link'; } return !!anchorTag; @@ -238,6 +231,13 @@ export default class LinkInlineTool implements InlineTool { return 'CMD+K'; } + /** + * Returns dynamic title based on whether the current selection is inside a link + */ + public getTitle(): string { + return this.selection.findParentTag('A') ? 'Unlink' : 'Link'; + } + /** * Show/close link input */ diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 5aa5f7dab..2c7bfeea4 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -432,6 +432,18 @@ export default class InlineToolbar extends Module { instance.checkState?.(SelectionUtils.get()); } + /** + * Allow inline tools to provide a dynamic title based on current selection state + */ + const dynamicTitle = (instance as { getTitle?: () => string }).getTitle?.(); + + if (dynamicTitle !== undefined) { + (popoverItem as PopoverItemHtmlParams).hint = { + ...(popoverItem as PopoverItemHtmlParams).hint, + title: dynamicTitle, + }; + } + popoverItems.push(popoverItem); } else if (item.type === PopoverItemType.Html) { /**