Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 60 additions & 53 deletions src/components/inline-tools/inline-tool-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
}

return;
}
/**
* Unlink icon pressed
*/
if (parentAnchor) {
this.selection.expandToTag(parentAnchor);
this.unlink();
this.closeActions();
this.checkState();
this.toolbar.close();

return;
}

this.toggleActions();
Expand All @@ -202,23 +188,30 @@ 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();

/**
* Fill input value with link href
*/
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;
Expand All @@ -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();
}

Expand All @@ -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;
}
Expand All @@ -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();
}
Expand All @@ -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();
Expand Down
24 changes: 16 additions & 8 deletions src/components/modules/toolbar/inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
eventsDispatcher,
});

window.requestIdleCallback(() => {
this.make();
}, { timeout: 2000 });
window.requestIdleCallback(
() => {
this.make();
},
{ timeout: 2000 }
);
}

/**
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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: [
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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()!);
});
}

Expand Down
27 changes: 11 additions & 16 deletions src/components/utils/popover/popover-inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@
* once you select <a> tag content in text
*/
this.items
.forEach((item) => {
if (!(item instanceof PopoverItemDefault) && !(item instanceof PopoverItemHtml)) {
return;
}

if (item.hasChildren && item.isChildrenOpen) {
this.showNestedItems(item);
}
});
.forEach((item) => {

Check failure on line 56 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 6 spaces but found 4
if (!(item instanceof PopoverItemDefault) && !(item instanceof PopoverItemHtml)) {

Check failure on line 57 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 8 spaces but found 6
return;

Check failure on line 58 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 10 spaces but found 8
}

Check failure on line 59 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 8 spaces but found 6

if (item.hasChildren && item.isChildrenOpen) {

Check failure on line 61 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 8 spaces but found 6
this.showNestedItems(item);

Check failure on line 62 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 10 spaces but found 8
}

Check failure on line 63 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 8 spaces but found 6
});

Check failure on line 64 in src/components/utils/popover/popover-inline.ts

View workflow job for this annotation

GitHub Actions / ESlint

Expected indentation of 6 spaces but found 4
}

/**
Expand Down Expand Up @@ -166,13 +166,8 @@
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();
}
Expand Down
Loading