mirror of
https://github.com/codex-team/editor.js
synced 2024-06-10 09:52:36 +02:00
feat(block tunes): Conversion Menu in Block Tunes (#2692)
* Support delimiter * Rename types, move types to popover-item folder * Fix ts errors * Add tests * Review fixes * Review fixes 2 * Fix delimiter while search * Fix flipper issue * Fix block tunes types * Fix types * tmp * Fixes * Make search input emit event * Fix types * Rename delimiter to separator * Update chengelog * Add convert to to block tunes * i18n * Lint * Fix tests * Fix tests 2 * Tests * Add caching * Rename * Fix for miltiple toolbox entries * Update changelog * Update changelog * Fix popover test * Fix flipper tests * Fix popover tests * Remove type: 'default' * Create isSameBlockData util * Add testcase
This commit is contained in:
parent
4118dc3aea
commit
7821e35302
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
– `New` – Block Tunes now supports nesting items
|
– `New` – Block Tunes now supports nesting items
|
||||||
– `New` – Block Tunes now supports separator items
|
– `New` – Block Tunes now supports separator items
|
||||||
|
– `New` – "Convert to" control is now also available in Block Tunes
|
||||||
|
|
||||||
### 2.30.0
|
### 2.30.0
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,11 @@ import BlockTune from '../tools/tune';
|
||||||
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||||
import ToolsCollection from '../tools/collection';
|
import ToolsCollection from '../tools/collection';
|
||||||
import EventsDispatcher from '../utils/events';
|
import EventsDispatcher from '../utils/events';
|
||||||
import { TunesMenuConfigItem } from '../../../types/tools';
|
import { TunesMenuConfig, TunesMenuConfigItem } from '../../../types/tools';
|
||||||
import { isMutationBelongsToElement } from '../utils/mutations';
|
import { isMutationBelongsToElement } from '../utils/mutations';
|
||||||
import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events';
|
import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events';
|
||||||
import { RedactorDomChangedPayload } from '../events/RedactorDomChanged';
|
import { RedactorDomChangedPayload } from '../events/RedactorDomChanged';
|
||||||
import { convertBlockDataToString } from '../utils/blocks';
|
import { convertBlockDataToString, isSameBlockData } from '../utils/blocks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface describes Block class constructor argument
|
* Interface describes Block class constructor argument
|
||||||
|
@ -229,7 +229,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
tunesData,
|
tunesData,
|
||||||
}: BlockConstructorOptions, eventBus?: EventsDispatcher<EditorEventMap>) {
|
}: BlockConstructorOptions, eventBus?: EventsDispatcher<EditorEventMap>) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.name = tool.name;
|
this.name = tool.name;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.settings = tool.settings;
|
this.settings = tool.settings;
|
||||||
|
@ -612,34 +611,60 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns data to render in tunes menu.
|
* Returns data to render in tunes menu.
|
||||||
* Splits block tunes settings into 2 groups: popover items and custom html.
|
* Splits block tunes into 3 groups: block specific tunes, common tunes
|
||||||
|
* and custom html that is produced by combining tunes html from both previous groups
|
||||||
*/
|
*/
|
||||||
public getTunes(): [PopoverItemParams[], HTMLElement] {
|
public getTunes(): {
|
||||||
|
toolTunes: PopoverItemParams[];
|
||||||
|
commonTunes: PopoverItemParams[];
|
||||||
|
customHtmlTunes: HTMLElement
|
||||||
|
} {
|
||||||
const customHtmlTunesContainer = document.createElement('div');
|
const customHtmlTunesContainer = document.createElement('div');
|
||||||
const tunesItems: TunesMenuConfigItem[] = [];
|
const commonTunesPopoverParams: TunesMenuConfigItem[] = [];
|
||||||
|
|
||||||
/** Tool's tunes: may be defined as return value of optional renderSettings method */
|
/** Tool's tunes: may be defined as return value of optional renderSettings method */
|
||||||
const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];
|
const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];
|
||||||
|
|
||||||
|
/** Separate custom html from Popover items params for tool's tunes */
|
||||||
|
const {
|
||||||
|
items: toolTunesPopoverParams,
|
||||||
|
htmlElement: toolTunesHtmlElement,
|
||||||
|
} = this.getTunesDataSegregated(tunesDefinedInTool);
|
||||||
|
|
||||||
|
if (toolTunesHtmlElement !== undefined) {
|
||||||
|
customHtmlTunesContainer.appendChild(toolTunesHtmlElement);
|
||||||
|
}
|
||||||
|
|
||||||
/** Common tunes: combination of default tunes (move up, move down, delete) and third-party tunes connected via tunes api */
|
/** Common tunes: combination of default tunes (move up, move down, delete) and third-party tunes connected via tunes api */
|
||||||
const commonTunes = [
|
const commonTunes = [
|
||||||
...this.tunesInstances.values(),
|
...this.tunesInstances.values(),
|
||||||
...this.defaultTunesInstances.values(),
|
...this.defaultTunesInstances.values(),
|
||||||
].map(tuneInstance => tuneInstance.render());
|
].map(tuneInstance => tuneInstance.render());
|
||||||
|
|
||||||
[tunesDefinedInTool, commonTunes].flat().forEach(rendered => {
|
/** Separate custom html from Popover items params for common tunes */
|
||||||
if ($.isElement(rendered)) {
|
commonTunes.forEach(tuneConfig => {
|
||||||
customHtmlTunesContainer.appendChild(rendered);
|
const {
|
||||||
} else if (Array.isArray(rendered)) {
|
items,
|
||||||
tunesItems.push(...rendered);
|
htmlElement,
|
||||||
} else {
|
} = this.getTunesDataSegregated(tuneConfig);
|
||||||
tunesItems.push(rendered);
|
|
||||||
|
if (htmlElement !== undefined) {
|
||||||
|
customHtmlTunesContainer.appendChild(htmlElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items !== undefined) {
|
||||||
|
commonTunesPopoverParams.push(...items);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return [tunesItems, customHtmlTunesContainer];
|
return {
|
||||||
|
toolTunes: toolTunesPopoverParams,
|
||||||
|
commonTunes: commonTunesPopoverParams,
|
||||||
|
customHtmlTunes: customHtmlTunesContainer,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update current input index with selection anchor node
|
* Update current input index with selection anchor node
|
||||||
*/
|
*/
|
||||||
|
@ -711,11 +736,8 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
const blockData = await this.data;
|
const blockData = await this.data;
|
||||||
const toolboxItems = toolboxSettings;
|
const toolboxItems = toolboxSettings;
|
||||||
|
|
||||||
return toolboxItems.find((item) => {
|
return toolboxItems?.find((item) => {
|
||||||
return Object.entries(item.data)
|
return isSameBlockData(item.data, blockData);
|
||||||
.some(([propName, propValue]) => {
|
|
||||||
return blockData[propName] && _.equals(blockData[propName], propValue);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -728,6 +750,25 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
return convertBlockDataToString(blockData, this.tool.conversionConfig);
|
return convertBlockDataToString(blockData, this.tool.conversionConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if tool's tunes settings are custom html or popover params and separates one from another by putting to different object fields
|
||||||
|
*
|
||||||
|
* @param tunes - tool's tunes config
|
||||||
|
*/
|
||||||
|
private getTunesDataSegregated(tunes: HTMLElement | TunesMenuConfig): { htmlElement?: HTMLElement; items: PopoverItemParams[] } {
|
||||||
|
const result = { } as { htmlElement?: HTMLElement; items: PopoverItemParams[] };
|
||||||
|
|
||||||
|
if ($.isElement(tunes)) {
|
||||||
|
result.htmlElement = tunes as HTMLElement;
|
||||||
|
} else if (Array.isArray(tunes)) {
|
||||||
|
result.items = tunes as PopoverItemParams[];
|
||||||
|
} else {
|
||||||
|
result.items = [ tunes ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make default Block wrappers and put Tool`s content there
|
* Make default Block wrappers and put Tool`s content there
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
},
|
},
|
||||||
"popover": {
|
"popover": {
|
||||||
"Filter": "",
|
"Filter": "",
|
||||||
"Nothing found": ""
|
"Nothing found": "",
|
||||||
|
"Convert to": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolNames": {
|
"toolNames": {
|
||||||
|
|
|
@ -7,10 +7,13 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||||
import Flipper from '../../flipper';
|
import Flipper from '../../flipper';
|
||||||
import { TunesMenuConfigItem } from '../../../../types/tools';
|
import { TunesMenuConfigItem } from '../../../../types/tools';
|
||||||
import { resolveAliases } from '../../utils/resolve-aliases';
|
import { resolveAliases } from '../../utils/resolve-aliases';
|
||||||
import { type Popover, PopoverDesktop, PopoverMobile } from '../../utils/popover';
|
import { type Popover, PopoverDesktop, PopoverMobile, PopoverItemParams, PopoverItemDefaultParams } from '../../utils/popover';
|
||||||
import { PopoverEvent } from '../../utils/popover/popover.types';
|
import { PopoverEvent } from '../../utils/popover/popover.types';
|
||||||
import { isMobileScreen } from '../../utils';
|
import { isMobileScreen } from '../../utils';
|
||||||
import { EditorMobileLayoutToggled } from '../../events';
|
import { EditorMobileLayoutToggled } from '../../events';
|
||||||
|
import * as _ from '../../utils';
|
||||||
|
import { IconReplace } from '@codexteam/icons';
|
||||||
|
import { isSameBlockData } from '../../utils/blocks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML Elements that used for BlockSettings
|
* HTML Elements that used for BlockSettings
|
||||||
|
@ -105,7 +108,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
*
|
*
|
||||||
* @param targetBlock - near which Block we should open BlockSettings
|
* @param targetBlock - near which Block we should open BlockSettings
|
||||||
*/
|
*/
|
||||||
public open(targetBlock: Block = this.Editor.BlockManager.currentBlock): void {
|
public async open(targetBlock: Block = this.Editor.BlockManager.currentBlock): Promise<void> {
|
||||||
this.opened = true;
|
this.opened = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,10 +123,8 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
this.Editor.BlockSelection.selectBlock(targetBlock);
|
this.Editor.BlockSelection.selectBlock(targetBlock);
|
||||||
this.Editor.BlockSelection.clearCache();
|
this.Editor.BlockSelection.clearCache();
|
||||||
|
|
||||||
/**
|
/** Get tool's settings data */
|
||||||
* Fill Tool's settings
|
const { toolTunes, commonTunes, customHtmlTunes } = targetBlock.getTunes();
|
||||||
*/
|
|
||||||
const [tunesItems, customHtmlTunesContainer] = targetBlock.getTunes();
|
|
||||||
|
|
||||||
/** Tell to subscribers that block settings is opened */
|
/** Tell to subscribers that block settings is opened */
|
||||||
this.eventsDispatcher.emit(this.events.opened);
|
this.eventsDispatcher.emit(this.events.opened);
|
||||||
|
@ -132,9 +133,9 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
|
|
||||||
this.popover = new PopoverClass({
|
this.popover = new PopoverClass({
|
||||||
searchable: true,
|
searchable: true,
|
||||||
items: tunesItems.map(tune => this.resolveTuneAliases(tune)),
|
items: await this.getTunesItems(targetBlock, commonTunes, toolTunes),
|
||||||
customContent: customHtmlTunesContainer,
|
customContent: customHtmlTunes,
|
||||||
customContentFlippableItems: this.getControls(customHtmlTunesContainer),
|
customContentFlippableItems: this.getControls(customHtmlTunes),
|
||||||
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
|
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
|
||||||
messages: {
|
messages: {
|
||||||
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
|
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
|
||||||
|
@ -197,6 +198,117 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of items to be displayed in block tunes menu.
|
||||||
|
* Merges tool specific tunes, conversion menu and common tunes in one list in predefined order
|
||||||
|
*
|
||||||
|
* @param currentBlock – block we are about to open block tunes for
|
||||||
|
* @param commonTunes – common tunes
|
||||||
|
* @param toolTunes - tool specific tunes
|
||||||
|
*/
|
||||||
|
private async getTunesItems(currentBlock: Block, commonTunes: TunesMenuConfigItem[], toolTunes?: TunesMenuConfigItem[]): Promise<PopoverItemParams[]> {
|
||||||
|
const items = [] as TunesMenuConfigItem[];
|
||||||
|
|
||||||
|
if (toolTunes !== undefined && toolTunes.length > 0) {
|
||||||
|
items.push(...toolTunes);
|
||||||
|
items.push({
|
||||||
|
type: 'separator',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertToItems = await this.getConvertToItems(currentBlock);
|
||||||
|
|
||||||
|
if (convertToItems.length > 0) {
|
||||||
|
items.push({
|
||||||
|
icon: IconReplace,
|
||||||
|
title: I18n.ui(I18nInternalNS.ui.popover, 'Convert to'),
|
||||||
|
children: {
|
||||||
|
items: convertToItems,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
items.push({
|
||||||
|
type: 'separator',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(...commonTunes);
|
||||||
|
|
||||||
|
return items.map(tune => this.resolveTuneAliases(tune));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of all available conversion menu items
|
||||||
|
*
|
||||||
|
* @param currentBlock - block we are about to open block tunes for
|
||||||
|
*/
|
||||||
|
private async getConvertToItems(currentBlock: Block): Promise<PopoverItemDefaultParams[]> {
|
||||||
|
const conversionEntries = Array.from(this.Editor.Tools.blockTools.entries());
|
||||||
|
|
||||||
|
const resultItems: PopoverItemDefaultParams[] = [];
|
||||||
|
|
||||||
|
const blockData = await currentBlock.data;
|
||||||
|
|
||||||
|
conversionEntries.forEach(([toolName, tool]) => {
|
||||||
|
const conversionConfig = tool.conversionConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip tools without «import» rule specified
|
||||||
|
*/
|
||||||
|
if (!conversionConfig || !conversionConfig.import) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tool.toolbox?.forEach((toolboxItem) => {
|
||||||
|
/**
|
||||||
|
* Skip tools that don't pass 'toolbox' property
|
||||||
|
*/
|
||||||
|
if (_.isEmpty(toolboxItem) || !toolboxItem.icon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shouldSkip = false;
|
||||||
|
|
||||||
|
if (toolboxItem.data !== undefined) {
|
||||||
|
/**
|
||||||
|
* When a tool has several toolbox entries, we need to make sure we do not add
|
||||||
|
* toolbox item with the same data to the resulting array. This helps exclude duplicates
|
||||||
|
*/
|
||||||
|
const hasSameData = isSameBlockData(toolboxItem.data, blockData);
|
||||||
|
|
||||||
|
shouldSkip = hasSameData;
|
||||||
|
} else {
|
||||||
|
shouldSkip = toolName === currentBlock.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (shouldSkip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultItems.push({
|
||||||
|
icon: toolboxItem.icon,
|
||||||
|
title: toolboxItem.title,
|
||||||
|
name: toolName,
|
||||||
|
onActivate: () => {
|
||||||
|
const { BlockManager, BlockSelection, Caret } = this.Editor;
|
||||||
|
|
||||||
|
BlockManager.convert(this.Editor.BlockManager.currentBlock, toolName, toolboxItem.data);
|
||||||
|
|
||||||
|
BlockSelection.clearSelection();
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
Caret.setToBlock(this.Editor.BlockManager.currentBlock, Caret.positions.END);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return resultItems;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles popover close event
|
* Handles popover close event
|
||||||
*/
|
*/
|
||||||
|
@ -224,7 +336,10 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
*
|
*
|
||||||
* @param item - item with resolved aliases
|
* @param item - item with resolved aliases
|
||||||
*/
|
*/
|
||||||
private resolveTuneAliases(item: TunesMenuConfigItem): TunesMenuConfigItem {
|
private resolveTuneAliases(item: TunesMenuConfigItem): PopoverItemParams {
|
||||||
|
if (item.type === 'separator') {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
const result = resolveAliases(item, { label: 'title' });
|
const result = resolveAliases(item, { label: 'title' });
|
||||||
|
|
||||||
if (item.confirmation) {
|
if (item.confirmation) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { ConversionConfig } from '../../../types/configs/conversion-config';
|
import type { ConversionConfig } from '../../../types/configs/conversion-config';
|
||||||
import type { BlockToolData } from '../../../types/tools/block-tool-data';
|
import type { BlockToolData } from '../../../types/tools/block-tool-data';
|
||||||
import type Block from '../block';
|
import type Block from '../block';
|
||||||
import { isFunction, isString, log } from '../utils';
|
import { isFunction, isString, log, equals } from '../utils';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if block has valid conversion config for export or import.
|
* Check if block has valid conversion config for export or import.
|
||||||
|
@ -19,6 +20,18 @@ export function isBlockConvertable(block: Block, direction: 'export' | 'import')
|
||||||
return isFunction(conversionProp) || isString(conversionProp);
|
return isFunction(conversionProp) || isString(conversionProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that all the properties of the first block data exist in second block data with the same values.
|
||||||
|
*
|
||||||
|
* @param data1 – first block data
|
||||||
|
* @param data2 – second block data
|
||||||
|
*/
|
||||||
|
export function isSameBlockData(data1: BlockToolData, data2: BlockToolData): boolean {
|
||||||
|
return Object.entries(data1).some((([propName, propValue]) => {
|
||||||
|
return data2[propName] && equals(data2[propName], propValue);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if two blocks could be merged.
|
* Check if two blocks could be merged.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,7 +17,7 @@ interface PopoverItemDefaultBaseParams {
|
||||||
/**
|
/**
|
||||||
* Item type
|
* Item type
|
||||||
*/
|
*/
|
||||||
type: 'default';
|
type?: 'default';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displayed text
|
* Displayed text
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
|
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
|
||||||
|
import Header from '@editorjs/header';
|
||||||
|
import { ToolboxConfig } from '../../../../types';
|
||||||
|
|
||||||
|
|
||||||
describe('BlockTunes', function () {
|
describe('BlockTunes', function () {
|
||||||
describe('Search', () => {
|
describe('Search', () => {
|
||||||
|
@ -104,4 +107,185 @@ describe('BlockTunes', function () {
|
||||||
.should('have.class', 'ce-block--selected');
|
.should('have.class', 'ce-block--selected');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Convert to', () => {
|
||||||
|
it('should display Convert to inside Block Tunes', () => {
|
||||||
|
cy.createEditor({
|
||||||
|
tools: {
|
||||||
|
header: Header,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Open block tunes menu */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.cdx-block')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-toolbar__settings-btn')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Check "Convert to" option is present */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item')
|
||||||
|
.contains('Convert to')
|
||||||
|
.should('exist');
|
||||||
|
|
||||||
|
/** Click "Convert to" option*/
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item')
|
||||||
|
.contains('Convert to')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Check nected popover with "Heading" option is present */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover--nested [data-item-name=header]')
|
||||||
|
.should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display Convert to inside Block Tunes if there is nothing to convert to', () => {
|
||||||
|
/** Editor instance with single default tool */
|
||||||
|
cy.createEditor({
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Open block tunes menu */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.cdx-block')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-toolbar__settings-btn')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Check "Convert to" option is not present */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item')
|
||||||
|
.contains('Convert to')
|
||||||
|
.should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display tool with the same data in "Convert to" menu', () => {
|
||||||
|
/**
|
||||||
|
* Tool with several toolbox entries configured
|
||||||
|
*/
|
||||||
|
class TestTool {
|
||||||
|
/**
|
||||||
|
* Tool is convertable
|
||||||
|
*/
|
||||||
|
public static get conversionConfig(): { import: string } {
|
||||||
|
return {
|
||||||
|
import: 'text',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestTool contains several toolbox options
|
||||||
|
*/
|
||||||
|
public static get toolbox(): ToolboxConfig {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: 'Title 1',
|
||||||
|
icon: 'Icon1',
|
||||||
|
data: {
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Title 2',
|
||||||
|
icon: 'Icon2',
|
||||||
|
data: {
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool can render itself
|
||||||
|
*/
|
||||||
|
public render(): HTMLDivElement {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
div.innerText = 'Some text';
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool can save it's data
|
||||||
|
*/
|
||||||
|
public save(): { text: string; level: number } {
|
||||||
|
return {
|
||||||
|
text: 'Some text',
|
||||||
|
level: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Editor instance with TestTool installed and one block of TestTool type */
|
||||||
|
cy.createEditor({
|
||||||
|
tools: {
|
||||||
|
testTool: TestTool,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'testTool',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Open block tunes menu */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-block')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-toolbar__settings-btn')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Open "Convert to" menu */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item')
|
||||||
|
.contains('Convert to')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Check TestTool option with SAME data is NOT present */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover--nested [data-item-name=testTool]')
|
||||||
|
.contains('Title 1')
|
||||||
|
.should('not.exist');
|
||||||
|
|
||||||
|
/** Check TestTool option with DIFFERENT data IS present */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover--nested [data-item-name=testTool]')
|
||||||
|
.contains('Title 2')
|
||||||
|
.should('exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PopoverItem } from '../../../../types/index.js';
|
import { PopoverItemParams } from '../../../../types/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock of some Block Tool
|
* Mock of some Block Tool
|
||||||
|
@ -26,7 +26,7 @@ class SomePlugin {
|
||||||
/**
|
/**
|
||||||
* Used to display our tool in the Toolbox
|
* Used to display our tool in the Toolbox
|
||||||
*/
|
*/
|
||||||
public static get toolbox(): PopoverItem {
|
public static get toolbox(): PopoverItemParams {
|
||||||
return {
|
return {
|
||||||
icon: '₷',
|
icon: '₷',
|
||||||
title: 'Some tool',
|
title: 'Some tool',
|
||||||
|
@ -34,6 +34,15 @@ class SomePlugin {
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts data from the plugin's UI
|
||||||
|
*/
|
||||||
|
public save(): {data: string} {
|
||||||
|
return {
|
||||||
|
data: '123',
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Flipper', () => {
|
describe('Flipper', () => {
|
||||||
|
@ -71,15 +80,16 @@ describe('Flipper', () => {
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('.cdx-some-plugin')
|
.get('.cdx-some-plugin')
|
||||||
// Open tunes menu
|
// Open tunes menu
|
||||||
.trigger('keydown', { code: 'Slash', ctrlKey: true })
|
.trigger('keydown', { code: 'Slash',
|
||||||
|
ctrlKey: true })
|
||||||
// Navigate to delete button (the second button)
|
// Navigate to delete button (the second button)
|
||||||
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE })
|
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE })
|
||||||
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE });
|
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether we focus the Delete Tune or not
|
* Check whether we focus the Move Up Tune or not
|
||||||
*/
|
*/
|
||||||
cy.get('[data-item-name="delete"]')
|
cy.get('[data-item-name="move-up"]')
|
||||||
.should('have.class', 'ce-popover-item--focused');
|
.should('have.class', 'ce-popover-item--focused');
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
|
|
|
@ -16,7 +16,6 @@ describe('Popover', () => {
|
||||||
* (Inside popover null value is set to confirmation property, so, object becomes unavailable otherwise)
|
* (Inside popover null value is set to confirmation property, so, object becomes unavailable otherwise)
|
||||||
*/
|
*/
|
||||||
const confirmation: PopoverItemParams = {
|
const confirmation: PopoverItemParams = {
|
||||||
type: 'default',
|
|
||||||
icon: confirmActionIcon,
|
icon: confirmActionIcon,
|
||||||
title: confirmActionTitle,
|
title: confirmActionTitle,
|
||||||
onActivate: cy.stub(),
|
onActivate: cy.stub(),
|
||||||
|
@ -24,7 +23,6 @@ describe('Popover', () => {
|
||||||
|
|
||||||
const items: PopoverItemParams[] = [
|
const items: PopoverItemParams[] = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: actionIcon,
|
icon: actionIcon,
|
||||||
title: actionTitle,
|
title: actionTitle,
|
||||||
name: 'testItem',
|
name: 'testItem',
|
||||||
|
@ -73,7 +71,6 @@ describe('Popover', () => {
|
||||||
it('should render the items with true isActive property value as active', () => {
|
it('should render the items with true isActive property value as active', () => {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -98,7 +95,6 @@ describe('Popover', () => {
|
||||||
it('should not execute item\'s onActivate callback if the item is disabled', () => {
|
it('should not execute item\'s onActivate callback if the item is disabled', () => {
|
||||||
const items: PopoverItemParams[] = [
|
const items: PopoverItemParams[] = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
|
@ -131,7 +127,6 @@ describe('Popover', () => {
|
||||||
it('should close once item with closeOnActivate property set to true is activated', () => {
|
it('should close once item with closeOnActivate property set to true is activated', () => {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
closeOnActivate: true,
|
closeOnActivate: true,
|
||||||
|
@ -159,7 +154,6 @@ describe('Popover', () => {
|
||||||
it('should highlight as active the item with toggle property set to true once activated', () => {
|
it('should highlight as active the item with toggle property set to true once activated', () => {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
toggle: true,
|
toggle: true,
|
||||||
|
@ -184,7 +178,6 @@ describe('Popover', () => {
|
||||||
it('should perform radiobutton-like behavior among the items that have toggle property value set to the same string value', () => {
|
it('should perform radiobutton-like behavior among the items that have toggle property value set to the same string value', () => {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon 1',
|
icon: 'Icon 1',
|
||||||
title: 'Title 1',
|
title: 'Title 1',
|
||||||
toggle: 'group-name',
|
toggle: 'group-name',
|
||||||
|
@ -193,7 +186,6 @@ describe('Popover', () => {
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon 2',
|
icon: 'Icon 2',
|
||||||
title: 'Title 2',
|
title: 'Title 2',
|
||||||
toggle: 'group-name',
|
toggle: 'group-name',
|
||||||
|
@ -231,7 +223,6 @@ describe('Popover', () => {
|
||||||
it('should toggle item if it is the only item in toggle group', () => {
|
it('should toggle item if it is the only item in toggle group', () => {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
toggle: 'key',
|
toggle: 'key',
|
||||||
|
@ -279,7 +270,6 @@ describe('Popover', () => {
|
||||||
/** Tool data displayed in block tunes popover */
|
/** Tool data displayed in block tunes popover */
|
||||||
public render(): TunesMenuConfig {
|
public render(): TunesMenuConfig {
|
||||||
return {
|
return {
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
toggle: 'key',
|
toggle: 'key',
|
||||||
|
@ -287,7 +277,6 @@ describe('Popover', () => {
|
||||||
children: {
|
children: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
name: 'nested-test-item',
|
name: 'nested-test-item',
|
||||||
|
@ -357,7 +346,6 @@ describe('Popover', () => {
|
||||||
/** Tool data displayed in block tunes popover */
|
/** Tool data displayed in block tunes popover */
|
||||||
public render(): TunesMenuConfig {
|
public render(): TunesMenuConfig {
|
||||||
return {
|
return {
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune',
|
title: 'Tune',
|
||||||
toggle: 'key',
|
toggle: 'key',
|
||||||
|
@ -365,7 +353,6 @@ describe('Popover', () => {
|
||||||
children: {
|
children: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
name: 'nested-test-item',
|
name: 'nested-test-item',
|
||||||
|
@ -521,7 +508,6 @@ describe('Popover', () => {
|
||||||
public render(): TunesMenuConfig {
|
public render(): TunesMenuConfig {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune',
|
title: 'Tune',
|
||||||
|
@ -585,7 +571,6 @@ describe('Popover', () => {
|
||||||
public render(): TunesMenuConfig {
|
public render(): TunesMenuConfig {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune 1',
|
title: 'Tune 1',
|
||||||
|
@ -595,7 +580,6 @@ describe('Popover', () => {
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune 2',
|
title: 'Tune 2',
|
||||||
|
@ -633,7 +617,8 @@ describe('Popover', () => {
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
/** Press Tab */
|
/** Press Tab */
|
||||||
cy.tab();
|
// eslint-disable-next-line cypress/require-data-selectors -- cy.tab() not working here
|
||||||
|
cy.get('body').tab();
|
||||||
|
|
||||||
/** Check first item is focused */
|
/** Check first item is focused */
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
|
@ -648,7 +633,8 @@ describe('Popover', () => {
|
||||||
.should('not.exist');
|
.should('not.exist');
|
||||||
|
|
||||||
/** Press Tab */
|
/** Press Tab */
|
||||||
cy.tab();
|
// eslint-disable-next-line cypress/require-data-selectors -- cy.tab() not working here
|
||||||
|
cy.get('body').tab();
|
||||||
|
|
||||||
/** Check first item is not focused */
|
/** Check first item is not focused */
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
|
@ -672,7 +658,6 @@ describe('Popover', () => {
|
||||||
public render(): TunesMenuConfig {
|
public render(): TunesMenuConfig {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune 1',
|
title: 'Tune 1',
|
||||||
|
@ -682,7 +667,6 @@ describe('Popover', () => {
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'default',
|
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune 2',
|
title: 'Tune 2',
|
||||||
|
|
Loading…
Reference in a new issue