Skip to content

Commit f78972e

Browse files
feat(popover): custom content becomes a popover item (#2707)
* Add custom item * Remove customcontent parameter from popover * Tests * Cleanup * Cleanup * Lint * Cleanup * Rename custom to html, add enum with item types * Fix tests * Add order test * Update jsdoc * Update changelog * Fix issue with html item not hiding on search * Fix flipper issue * Update changelog
1 parent bd1de56 commit f78972e

File tree

15 files changed

+419
-179
lines changed

15 files changed

+419
-179
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- `Fix` - Caret lost after block conversion on mobile devices.
1616
- `Improvement` - The API `blocks.convert()` now returns the new block API
1717
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
18+
- `New`*Menu Config* – New item type – HTML
1819

1920
### 2.29.1
2021

src/components/block/index.ts

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import BlockTune from '../tools/tune';
2121
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
2222
import ToolsCollection from '../tools/collection';
2323
import EventsDispatcher from '../utils/events';
24-
import { TunesMenuConfig, TunesMenuConfigItem } from '../../../types/tools';
24+
import { TunesMenuConfigItem } from '../../../types/tools';
2525
import { isMutationBelongsToElement } from '../utils/mutations';
2626
import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events';
2727
import { RedactorDomChangedPayload } from '../events/RedactorDomChanged';
2828
import { convertBlockDataToString, isSameBlockData } from '../utils/blocks';
29+
import { PopoverItemType } from '../utils/popover';
2930

3031
/**
3132
* Interface describes Block class constructor argument
@@ -610,29 +611,28 @@ export default class Block extends EventsDispatcher<BlockEvents> {
610611
}
611612

612613
/**
613-
* Returns data to render in tunes menu.
614-
* Splits block tunes into 3 groups: block specific tunes, common tunes
615-
* and custom html that is produced by combining tunes html from both previous groups
614+
* Returns data to render in Block Tunes menu.
615+
* Splits block tunes into 2 groups: block specific tunes and common tunes
616616
*/
617617
public getTunes(): {
618618
toolTunes: PopoverItemParams[];
619619
commonTunes: PopoverItemParams[];
620-
customHtmlTunes: HTMLElement
621620
} {
622-
const customHtmlTunesContainer = document.createElement('div');
621+
const toolTunesPopoverParams: TunesMenuConfigItem[] = [];
623622
const commonTunesPopoverParams: TunesMenuConfigItem[] = [];
624623

625624
/** Tool's tunes: may be defined as return value of optional renderSettings method */
626625
const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];
627626

628-
/** Separate custom html from Popover items params for tool's tunes */
629-
const {
630-
items: toolTunesPopoverParams,
631-
htmlElement: toolTunesHtmlElement,
632-
} = this.getTunesDataSegregated(tunesDefinedInTool);
633-
634-
if (toolTunesHtmlElement !== undefined) {
635-
customHtmlTunesContainer.appendChild(toolTunesHtmlElement);
627+
if ($.isElement(tunesDefinedInTool)) {
628+
toolTunesPopoverParams.push({
629+
type: PopoverItemType.Html,
630+
element: tunesDefinedInTool,
631+
});
632+
} else if (Array.isArray(tunesDefinedInTool)) {
633+
toolTunesPopoverParams.push(...tunesDefinedInTool);
634+
} else {
635+
toolTunesPopoverParams.push(tunesDefinedInTool);
636636
}
637637

638638
/** Common tunes: combination of default tunes (move up, move down, delete) and third-party tunes connected via tunes api */
@@ -643,28 +643,24 @@ export default class Block extends EventsDispatcher<BlockEvents> {
643643

644644
/** Separate custom html from Popover items params for common tunes */
645645
commonTunes.forEach(tuneConfig => {
646-
const {
647-
items,
648-
htmlElement,
649-
} = this.getTunesDataSegregated(tuneConfig);
650-
651-
if (htmlElement !== undefined) {
652-
customHtmlTunesContainer.appendChild(htmlElement);
653-
}
654-
655-
if (items !== undefined) {
656-
commonTunesPopoverParams.push(...items);
646+
if ($.isElement(tuneConfig)) {
647+
commonTunesPopoverParams.push({
648+
type: PopoverItemType.Html,
649+
element: tuneConfig,
650+
});
651+
} else if (Array.isArray(tuneConfig)) {
652+
commonTunesPopoverParams.push(...tuneConfig);
653+
} else {
654+
commonTunesPopoverParams.push(tuneConfig);
657655
}
658656
});
659657

660658
return {
661659
toolTunes: toolTunesPopoverParams,
662660
commonTunes: commonTunesPopoverParams,
663-
customHtmlTunes: customHtmlTunesContainer,
664661
};
665662
}
666663

667-
668664
/**
669665
* Update current input index with selection anchor node
670666
*/
@@ -750,25 +746,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
750746
return convertBlockDataToString(blockData, this.tool.conversionConfig);
751747
}
752748

753-
/**
754-
* Determines if tool's tunes settings are custom html or popover params and separates one from another by putting to different object fields
755-
*
756-
* @param tunes - tool's tunes config
757-
*/
758-
private getTunesDataSegregated(tunes: HTMLElement | TunesMenuConfig): { htmlElement?: HTMLElement; items: PopoverItemParams[] } {
759-
const result = { } as { htmlElement?: HTMLElement; items: PopoverItemParams[] };
760-
761-
if ($.isElement(tunes)) {
762-
result.htmlElement = tunes as HTMLElement;
763-
} else if (Array.isArray(tunes)) {
764-
result.items = tunes as PopoverItemParams[];
765-
} else {
766-
result.items = [ tunes ];
767-
}
768-
769-
return result;
770-
}
771-
772749
/**
773750
* Make default Block wrappers and put Tool`s content there
774751
*

src/components/modules/toolbar/blockSettings.ts

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
77
import Flipper from '../../flipper';
88
import { TunesMenuConfigItem } from '../../../../types/tools';
99
import { resolveAliases } from '../../utils/resolve-aliases';
10-
import { type Popover, PopoverDesktop, PopoverMobile, PopoverItemParams, PopoverItemDefaultParams } from '../../utils/popover';
10+
import { type Popover, PopoverDesktop, PopoverMobile, PopoverItemParams, PopoverItemDefaultParams, PopoverItemType } from '../../utils/popover';
1111
import { PopoverEvent } from '../../utils/popover/popover.types';
1212
import { isMobileScreen } from '../../utils';
1313
import { EditorMobileLayoutToggled } from '../../events';
@@ -124,7 +124,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
124124
this.Editor.BlockSelection.clearCache();
125125

126126
/** Get tool's settings data */
127-
const { toolTunes, commonTunes, customHtmlTunes } = targetBlock.getTunes();
127+
const { toolTunes, commonTunes } = targetBlock.getTunes();
128128

129129
/** Tell to subscribers that block settings is opened */
130130
this.eventsDispatcher.emit(this.events.opened);
@@ -134,8 +134,6 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
134134
this.popover = new PopoverClass({
135135
searchable: true,
136136
items: await this.getTunesItems(targetBlock, commonTunes, toolTunes),
137-
customContent: customHtmlTunes,
138-
customContentFlippableItems: this.getControls(customHtmlTunes),
139137
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
140138
messages: {
141139
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
@@ -212,7 +210,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
212210
if (toolTunes !== undefined && toolTunes.length > 0) {
213211
items.push(...toolTunes);
214212
items.push({
215-
type: 'separator',
213+
type: PopoverItemType.Separator,
216214
});
217215
}
218216

@@ -227,7 +225,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
227225
},
228226
});
229227
items.push({
230-
type: 'separator',
228+
type: PopoverItemType.Separator,
231229
});
232230
}
233231

@@ -314,28 +312,13 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
314312
this.close();
315313
};
316314

317-
/**
318-
* Returns list of buttons and inputs inside specified container
319-
*
320-
* @param container - container to query controls inside of
321-
*/
322-
private getControls(container: HTMLElement): HTMLElement[] {
323-
const { StylesAPI } = this.Editor;
324-
/** Query buttons and inputs inside tunes html */
325-
const controls = container.querySelectorAll<HTMLElement>(
326-
`.${StylesAPI.classes.settingsButton}, ${$.allInputsSelector}`
327-
);
328-
329-
return Array.from(controls);
330-
}
331-
332315
/**
333316
* Resolves aliases in tunes menu items
334317
*
335318
* @param item - item with resolved aliases
336319
*/
337320
private resolveTuneAliases(item: TunesMenuConfigItem): PopoverItemParams {
338-
if (item.type === 'separator') {
321+
if (item.type === PopoverItemType.Separator || item.type === PopoverItemType.Html) {
339322
return item;
340323
}
341324
const result = resolveAliases(item, { label: 'title' });

src/components/ui/toolbox.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BlockToolAPI } from '../block';
33
import Shortcuts from '../utils/shortcuts';
44
import BlockTool from '../tools/block';
55
import ToolsCollection from '../tools/collection';
6-
import { API, BlockToolData, ToolboxConfigEntry, PopoverItem, BlockAPI } from '../../../types';
6+
import { API, BlockToolData, ToolboxConfigEntry, PopoverItemParams, BlockAPI } from '../../../types';
77
import EventsDispatcher from '../utils/events';
88
import I18n from '../i18n';
99
import { I18nInternalNS } from '../i18n/namespace-internal';
@@ -303,11 +303,11 @@ export default class Toolbox extends EventsDispatcher<ToolboxEventMap> {
303303
* Returns list of items that will be displayed in toolbox
304304
*/
305305
@_.cacheable
306-
private get toolboxItemsToBeDisplayed(): PopoverItem[] {
306+
private get toolboxItemsToBeDisplayed(): PopoverItemParams[] {
307307
/**
308308
* Maps tool data to popover item structure
309309
*/
310-
const toPopoverItem = (toolboxItem: ToolboxConfigEntry, tool: BlockTool): PopoverItem => {
310+
const toPopoverItem = (toolboxItem: ToolboxConfigEntry, tool: BlockTool): PopoverItemParams => {
311311
return {
312312
icon: toolboxItem.icon,
313313
title: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
@@ -320,7 +320,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEventMap> {
320320
};
321321

322322
return this.toolsToBeDisplayed
323-
.reduce<PopoverItem[]>((result, tool) => {
323+
.reduce<PopoverItemParams[]>((result, tool) => {
324324
if (Array.isArray(tool.toolbox)) {
325325
tool.toolbox.forEach(item => {
326326
result.push(toPopoverItem(item, tool));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { bem } from '../../../../bem';
2+
3+
/**
4+
* Popover item block CSS class constructor
5+
*/
6+
const className = bem('ce-popover-item-html');
7+
8+
/**
9+
* CSS class names to be used in popover item class
10+
*/
11+
export const css = {
12+
root: className(),
13+
hidden: className(null, 'hidden'),
14+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { PopoverItem } from '../popover-item';
2+
import { PopoverItemHtmlParams } from '../popover-item.types';
3+
import { css } from './popover-item-html.const';
4+
import Dom from '../../../../../dom';
5+
6+
/**
7+
* Represents popover item with custom html content
8+
*/
9+
export class PopoverItemHtml extends PopoverItem {
10+
/**
11+
* Item html elements
12+
*/
13+
private nodes: { root: HTMLElement };
14+
15+
/**
16+
* Constructs the instance
17+
*
18+
* @param params – instance parameters
19+
*/
20+
constructor(params: PopoverItemHtmlParams) {
21+
super();
22+
23+
this.nodes = {
24+
root: Dom.make('div', css.root),
25+
};
26+
27+
this.nodes.root.appendChild(params.element);
28+
}
29+
30+
/**
31+
* Returns popover item root element
32+
*/
33+
public getElement(): HTMLElement {
34+
return this.nodes.root;
35+
}
36+
37+
/**
38+
* Toggles item hidden state
39+
*
40+
* @param isHidden - true if item should be hidden
41+
*/
42+
public toggleHidden(isHidden: boolean): void {
43+
this.nodes.root?.classList.toggle(css.hidden, isHidden);
44+
}
45+
46+
/**
47+
* Returns list of buttons and inputs inside custom content
48+
*/
49+
public getControls(): HTMLElement[] {
50+
/** Query buttons and inputs inside custom html */
51+
const controls = this.nodes.root.querySelectorAll<HTMLElement>(
52+
`button, ${Dom.allInputsSelector}`
53+
);
54+
55+
return Array.from(controls);
56+
}
57+
}

src/components/utils/popover/components/popover-item/popover-item.types.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
/**
2+
* Popover item types
3+
*/
4+
export enum PopoverItemType {
5+
/** Regular item with icon, title and other properties */
6+
Default = 'default',
7+
8+
/** Gray line used to separate items from each other */
9+
Separator = 'separator',
10+
11+
/** Item with custom html content */
12+
Html = 'html'
13+
}
114

215
/**
316
* Represents popover item separator.
@@ -7,7 +20,22 @@ export interface PopoverItemSeparatorParams {
720
/**
821
* Item type
922
*/
10-
type: 'separator'
23+
type: PopoverItemType.Separator
24+
}
25+
26+
/**
27+
* Represents popover item with custom html content
28+
*/
29+
export interface PopoverItemHtmlParams {
30+
/**
31+
* Item type
32+
*/
33+
type: PopoverItemType.Html;
34+
35+
/**
36+
* Custom html content to be displayed in the popover
37+
*/
38+
element: HTMLElement
1139
}
1240

1341
/**
@@ -17,7 +45,7 @@ interface PopoverItemDefaultBaseParams {
1745
/**
1846
* Item type
1947
*/
20-
type?: 'default';
48+
type?: PopoverItemType.Default;
2149

2250
/**
2351
* Displayed text
@@ -119,5 +147,8 @@ export type PopoverItemDefaultParams =
119147
/**
120148
* Represents single popover item
121149
*/
122-
export type PopoverItemParams = PopoverItemDefaultParams | PopoverItemSeparatorParams;
150+
export type PopoverItemParams =
151+
PopoverItemDefaultParams |
152+
PopoverItemSeparatorParams |
153+
PopoverItemHtmlParams;
123154

0 commit comments

Comments
 (0)