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
70 changes: 50 additions & 20 deletions src/components/modules/toolbar/inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
import Shortcuts from '../../utils/shortcuts';
import type { ModuleConfig } from '../../../types-internal/module-config';
import { CommonInternalSettings } from '../../tools/base';
import type { Popover, PopoverItemHtmlParams, PopoverItemParams, WithChildren } from '../../utils/popover';
import type { PopoverItemHtmlParams, PopoverItemParams, WithChildren } from '../../utils/popover';
import { PopoverItemType } from '../../utils/popover';
import { PopoverInline } from '../../utils/popover/popover-inline';
import type InlineToolAdapter from 'src/components/tools/inline';
Expand Down Expand Up @@ -44,7 +44,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
/**
* Popover instance reference
*/
private popover: Popover | null = null;
private popover: PopoverInline | null = null;

/**
* Margin above/below the Toolbar
Expand Down Expand Up @@ -86,7 +86,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
public async tryToShow(needToClose = false): Promise<void> {
if (needToClose) {
this.close();
this.close(this.popover?.isOpen === true && this.allowedToShow());
}

if (!this.allowedToShow()) {
Expand All @@ -100,12 +100,18 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {

/**
* Hides Inline Toolbar
*
* @param preservePopover - keep the visible container for a selection refresh
*/
public close(): void {
public close(preservePopover = false): void {
if (!this.opened) {
return;
}

if (preservePopover) {
this.popover?.disable();
}

for (const [tool, toolInstance] of this.tools) {
const shortcut = this.getToolShortcut(tool.name);

Expand All @@ -123,9 +129,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {

this.tools = new Map();

this.reset();
this.opened = false;

// Link cleanup can restore or collapse the selection.
if (preservePopover && this.allowedToShow()) {
return;
}

this.reset();
this.popover?.hide();
this.popover?.destroy();
this.popover = null;
Expand All @@ -148,6 +159,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
* Removes UI and its components
*/
public destroy(): void {
this.opened = false;
this.removeAllNodes();
this.popover?.destroy();
this.popover = null;
Expand Down Expand Up @@ -186,26 +198,39 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {

this.opened = true;

if (this.popover !== null) {
this.popover.destroy();
}

this.createToolsInstances();

const popoverItems = await this.getPopoverItems();
const tools = this.tools;
const popoverItems = await this.getPopoverItems().catch((error) => {
if (this.tools === tools) {
// Only dispose the retained UI; later tools may not have rendered yet.
this.popover?.destroy();
this.popover = null;
}

this.popover = new PopoverInline({
items: popoverItems,
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
messages: {
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
search: I18n.ui(I18nInternalNS.ui.popover, 'Filter'),
},
throw error;
});

this.move(this.popover.size.width);
if (this.tools !== tools || !this.opened) {
return;
}

if (this.popover !== null) {
this.popover.updateItems(popoverItems);
} else {
this.popover = new PopoverInline({
items: popoverItems,
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
messages: {
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
search: I18n.ui(I18nInternalNS.ui.popover, 'Filter'),
},
});

this.nodes.wrapper?.append(this.popover.getElement());
}

this.nodes.wrapper?.append(this.popover.getElement());
this.move(this.popover.size.width);

this.popover.show();
}
Expand Down Expand Up @@ -363,12 +388,17 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
private async getPopoverItems(): Promise<PopoverItemParams[]> {
const popoverItems = [] as PopoverItemParams[];
const tools = this.tools;

let i = 0;

for (const [tool, instance] of this.tools) {
for (const [tool, instance] of tools) {
const renderedTool = await instance.render();

if (this.tools !== tools || !this.opened) {
return [];
}

/** Enable tool shortcut */
const shortcut = this.getToolShortcut(tool.name);

Expand Down
86 changes: 70 additions & 16 deletions src/components/utils/popover/popover-inline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isMobileScreen } from '../../utils';
import type { PopoverItem } from './components/popover-item';
import type { PopoverItem, PopoverItemParams } from './components/popover-item';
import { PopoverItemDefault, PopoverItemType } from './components/popover-item';
import { PopoverItemHtml } from './components/popover-item/popover-item-html/popover-item-html';
import { PopoverDesktop } from './popover-desktop';
Expand Down Expand Up @@ -47,21 +47,56 @@ export class PopoverInline extends PopoverDesktop {
}
);

/**
* If active popover item has children, show them.
* This is needed to display link url text (which is displayed as a nested popover content)
* 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);
}
});
this.showInitiallyOpenChildren();
}

/**
* Replaces tools without remounting the container or restarting its entry animation.
*
* @param items - freshly rendered tools for the current selection
*/
public updateItems(items: PopoverItemParams[]): void {
this.items.forEach(item => item.destroy());

this.items = this.buildItems(items);
this.nodes.items.replaceChildren(...this.items
.map(item => item.getElement())
.filter((element): element is HTMLElement => element !== null));

this.nodes.popover.classList.remove(css.popoverOpenTop, css.popoverOpenLeft);
this.showInitiallyOpenChildren();
this.nodes.popover.inert = false;
}

/**
* Prevents interaction with tools being cleared while their replacements render.
*/
public disable(): void {
this.nodes.popover.inert = true;
this.destroyNestedPopoverIfExists();
this.nestedPopoverTriggerItem = null;
this.flipper?.deactivate();
}

/**
* Whether the inline popover is open.
*/
public get isOpen(): boolean {
return this.nodes.popover.classList.contains(css.popoverOpened);
}

/**
* Open inline popovers can change size when their tools are replaced.
*/
public override get size(): { height: number; width: number } {
if (!this.isOpen) {
return super.size;
}

return {
height: this.nodes.popoverContainer.offsetHeight,
width: this.nodes.popoverContainer.offsetWidth,
};
}

/**
Expand Down Expand Up @@ -164,6 +199,10 @@ export class PopoverInline extends PopoverDesktop {
* @param item - clicked item
*/
protected override handleItemClick(item: PopoverItem): void {
if (this.nodes.popover.inert) {
return;
}

if (item !== this.nestedPopoverTriggerItem) {
/**
* In case tool had special handling for toggling button (like link tool which modifies selection)
Expand All @@ -179,4 +218,19 @@ export class PopoverInline extends PopoverDesktop {

super.handleItemClick(item);
}

/**
* Restores selection-dependent nested menus after constructing their items.
*/
private showInitiallyOpenChildren(): void {
this.items.forEach((item) => {
if (!(item instanceof PopoverItemDefault) && !(item instanceof PopoverItemHtml)) {
return;
}

if (item.hasChildren && item.isChildrenOpen) {
this.showNestedItems(item);
}
});
}
}
Loading
Loading