mirror of
https://github.com/codex-team/editor.js
synced 2026-03-15 15:15:47 +01:00
fix(inline-tools): fix issue with not being able to unlink
This commit is contained in:
parent
530ec56bb8
commit
b35f22e9a4
3 changed files with 87 additions and 77 deletions
|
|
@ -67,8 +67,8 @@ export default class LinkInlineTool implements InlineTool {
|
|||
* Elements
|
||||
*/
|
||||
private nodes: {
|
||||
button: HTMLButtonElement;
|
||||
input: HTMLInputElement;
|
||||
button: HTMLButtonElement | null;
|
||||
input: HTMLInputElement | null;
|
||||
} = {
|
||||
button: null,
|
||||
input: null,
|
||||
|
|
@ -147,49 +147,35 @@ export default class LinkInlineTool implements InlineTool {
|
|||
|
||||
/**
|
||||
* Handle clicks on the Inline Toolbar icon
|
||||
*
|
||||
* @param {Range} range - range to wrap with link
|
||||
*/
|
||||
public surround(range: Range): void {
|
||||
public surround(): void {
|
||||
/**
|
||||
* Range will be null when user makes second click on the 'link icon' to close opened input
|
||||
*/
|
||||
if (range) {
|
||||
/**
|
||||
* Save selection before change focus to the input
|
||||
*/
|
||||
if (!this.inputOpened) {
|
||||
/** Create blue background instead of selection */
|
||||
this.selection.setFakeBackground();
|
||||
this.selection.save();
|
||||
} else {
|
||||
this.selection.restore();
|
||||
this.selection.removeFakeBackground();
|
||||
}
|
||||
const parentAnchor = this.selection.findParentTag('A');
|
||||
/**
|
||||
* Save selection before change focus to the input
|
||||
*/
|
||||
if (!this.inputOpened) {
|
||||
/** Create blue background instead of selection */
|
||||
this.selection.setFakeBackground();
|
||||
this.selection.save();
|
||||
} else {
|
||||
this.selection.restore();
|
||||
this.selection.removeFakeBackground();
|
||||
}
|
||||
const parentAnchor = this.selection.findParentTag('A');
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* Unlink icon pressed
|
||||
*/
|
||||
if (parentAnchor) {
|
||||
this.selection.expandToTag(parentAnchor);
|
||||
this.unlink();
|
||||
this.closeActions();
|
||||
this.checkState();
|
||||
this.toolbar.close();
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleActions();
|
||||
|
|
@ -202,9 +188,11 @@ export default class LinkInlineTool implements InlineTool {
|
|||
const anchorTag = this.selection.findParentTag('A');
|
||||
|
||||
if (anchorTag) {
|
||||
this.nodes.button.innerHTML = IconUnlink;
|
||||
this.nodes.button.classList.add(this.CSS.buttonUnlink);
|
||||
this.nodes.button.classList.add(this.CSS.buttonActive);
|
||||
if (this.nodes.button) {
|
||||
this.nodes.button.innerHTML = IconUnlink;
|
||||
this.nodes.button.classList.add(this.CSS.buttonUnlink);
|
||||
this.nodes.button.classList.add(this.CSS.buttonActive);
|
||||
}
|
||||
this.openActions();
|
||||
|
||||
/**
|
||||
|
|
@ -212,13 +200,18 @@ export default class LinkInlineTool implements InlineTool {
|
|||
*/
|
||||
const hrefAttr = anchorTag.getAttribute('href');
|
||||
|
||||
this.nodes.input.defaultValue = hrefAttr !== 'null' ? hrefAttr : '';
|
||||
if (this.nodes.input) {
|
||||
this.nodes.input.defaultValue =
|
||||
hrefAttr !== null && hrefAttr !== 'null' ? hrefAttr : '';
|
||||
}
|
||||
|
||||
this.selection.save();
|
||||
} else {
|
||||
this.nodes.button.innerHTML = IconLink;
|
||||
this.nodes.button.classList.remove(this.CSS.buttonUnlink);
|
||||
this.nodes.button.classList.remove(this.CSS.buttonActive);
|
||||
if (this.nodes.button) {
|
||||
this.nodes.button.innerHTML = IconLink;
|
||||
this.nodes.button.classList.remove(this.CSS.buttonUnlink);
|
||||
this.nodes.button.classList.remove(this.CSS.buttonActive);
|
||||
}
|
||||
}
|
||||
|
||||
return !!anchorTag;
|
||||
|
|
@ -228,6 +221,16 @@ export default class LinkInlineTool implements InlineTool {
|
|||
* Function called with Inline Toolbar closing
|
||||
*/
|
||||
public clear(): void {
|
||||
/**
|
||||
* Restore the original text selection if fake background was set
|
||||
* (e.g. when user was typing a URL and switched to another tool).
|
||||
* This must happen before closeActions() so the browser selection
|
||||
* is on the text, not stuck in the input field.
|
||||
*/
|
||||
if (this.selection.isFakeBackgroundEnabled) {
|
||||
this.selection.restore();
|
||||
this.selection.removeFakeBackground();
|
||||
}
|
||||
this.closeActions();
|
||||
}
|
||||
|
||||
|
|
@ -253,9 +256,11 @@ export default class LinkInlineTool implements InlineTool {
|
|||
* @param {boolean} needFocus - on link creation we need to focus input. On editing - nope.
|
||||
*/
|
||||
private openActions(needFocus = false): void {
|
||||
this.nodes.input.classList.add(this.CSS.inputShowed);
|
||||
if (needFocus) {
|
||||
this.nodes.input.focus();
|
||||
if (this.nodes.input) {
|
||||
this.nodes.input.classList.add(this.CSS.inputShowed);
|
||||
if (needFocus) {
|
||||
this.nodes.input.focus();
|
||||
}
|
||||
}
|
||||
this.inputOpened = true;
|
||||
}
|
||||
|
|
@ -280,8 +285,10 @@ export default class LinkInlineTool implements InlineTool {
|
|||
currentSelection.restore();
|
||||
}
|
||||
|
||||
this.nodes.input.classList.remove(this.CSS.inputShowed);
|
||||
this.nodes.input.value = '';
|
||||
if (this.nodes.input) {
|
||||
this.nodes.input.classList.remove(this.CSS.inputShowed);
|
||||
this.nodes.input.value = '';
|
||||
}
|
||||
if (clearSavedSelection) {
|
||||
this.selection.clearSaved();
|
||||
}
|
||||
|
|
@ -294,7 +301,7 @@ export default class LinkInlineTool implements InlineTool {
|
|||
* @param {KeyboardEvent} event - enter keydown event
|
||||
*/
|
||||
private enterPressed(event: KeyboardEvent): void {
|
||||
let value = this.nodes.input.value || '';
|
||||
let value = (this.nodes.input && this.nodes.input.value) || '';
|
||||
|
||||
if (!value.trim()) {
|
||||
this.selection.restore();
|
||||
|
|
|
|||
|
|
@ -68,9 +68,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
eventsDispatcher,
|
||||
});
|
||||
|
||||
window.requestIdleCallback(() => {
|
||||
this.make();
|
||||
}, { timeout: 2000 });
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
this.make();
|
||||
},
|
||||
{ timeout: 2000 }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -233,7 +236,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
* Prevent InlineToolbar from overflowing the content zone on the right side
|
||||
*/
|
||||
if (realRightCoord > this.Editor.UI.contentRect.right) {
|
||||
newCoords.x = this.Editor.UI.contentRect.right -popoverWidth - wrapperOffset.x;
|
||||
newCoords.x = this.Editor.UI.contentRect.right - popoverWidth - wrapperOffset.x;
|
||||
}
|
||||
|
||||
this.nodes.wrapper!.style.left = Math.floor(newCoords.x) + 'px';
|
||||
|
|
@ -415,7 +418,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
const actions = instance.renderActions();
|
||||
|
||||
(popoverItem as WithChildren<PopoverItemHtmlParams>).children = {
|
||||
isOpen: instance.checkState?.(SelectionUtils.get()),
|
||||
isOpen: instance.checkState?.(SelectionUtils.get()!),
|
||||
/** Disable keyboard navigation in actions, as it might conflict with enter press handling */
|
||||
isFlippable: false,
|
||||
items: [
|
||||
|
|
@ -424,12 +427,17 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
element: actions,
|
||||
},
|
||||
],
|
||||
onClose: () => {
|
||||
if (_.isFunction(instance.clear)) {
|
||||
instance.clear();
|
||||
}
|
||||
},
|
||||
};
|
||||
} else {
|
||||
/**
|
||||
* Legacy inline tools might perform some UI mutating logic in checkState method, so, call it just in case
|
||||
*/
|
||||
instance.checkState?.(SelectionUtils.get());
|
||||
instance.checkState?.(SelectionUtils.get()!);
|
||||
}
|
||||
|
||||
popoverItems.push(popoverItem);
|
||||
|
|
@ -541,7 +549,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
*/
|
||||
// if (SelectionUtils.isCollapsed) return;
|
||||
|
||||
if (!currentBlock.tool.enabledInlineTools) {
|
||||
if (currentBlock.tool.enabledInlineTools === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -573,7 +581,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
*/
|
||||
private checkToolsState(): void {
|
||||
this.tools?.forEach((toolInstance) => {
|
||||
toolInstance.checkState?.(SelectionUtils.get());
|
||||
toolInstance.checkState?.(SelectionUtils.get()!);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,15 +53,15 @@ export class PopoverInline extends PopoverDesktop {
|
|||
* once you select <a> tag content in text
|
||||
*/
|
||||
this.items
|
||||
.forEach((item) => {
|
||||
if (!(item instanceof PopoverItemDefault) && !(item instanceof PopoverItemHtml)) {
|
||||
return;
|
||||
}
|
||||
.forEach((item) => {
|
||||
if (!(item instanceof PopoverItemDefault) && !(item instanceof PopoverItemHtml)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.hasChildren && item.isChildrenOpen) {
|
||||
this.showNestedItems(item);
|
||||
}
|
||||
});
|
||||
if (item.hasChildren && item.isChildrenOpen) {
|
||||
this.showNestedItems(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,13 +166,8 @@ export class PopoverInline extends PopoverDesktop {
|
|||
protected override handleItemClick(item: PopoverItem): void {
|
||||
if (item !== this.nestedPopoverTriggerItem) {
|
||||
/**
|
||||
* In case tool had special handling for toggling button (like link tool which modifies selection)
|
||||
* we need to call handleClick on nested popover trigger item
|
||||
*/
|
||||
this.nestedPopoverTriggerItem?.handleClick();
|
||||
|
||||
/**
|
||||
* Then close the nested popover
|
||||
* Close the nested popover without triggering the tool's action.
|
||||
* The onChildrenClose callback will handle any necessary UI cleanup.
|
||||
*/
|
||||
super.destroyNestedPopoverIfExists();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue