mirror of
https://github.com/codex-team/editor.js
synced 2024-06-10 09:52:36 +02:00
Compare commits
2 commits
bd1de56ef3
...
50f43bb35d
Author | SHA1 | Date | |
---|---|---|---|
50f43bb35d | |||
f78972ee09 |
|
@ -12,6 +12,8 @@ export default defineConfig({
|
||||||
// We've imported your old cypress plugins here.
|
// We've imported your old cypress plugins here.
|
||||||
// You may want to clean this up later by importing these.
|
// You may want to clean this up later by importing these.
|
||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
|
on('file:preprocessor', require('cypress-vite')(config));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin for cypress that adds better terminal output for easier debugging.
|
* Plugin for cypress that adds better terminal output for easier debugging.
|
||||||
* Prints cy commands, browser console logs, cy.request and cy.intercept data. Great for your pipelines.
|
* Prints cy commands, browser console logs, cy.request and cy.intercept data. Great for your pipelines.
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
- `Fix` - Caret lost after block conversion on mobile devices.
|
- `Fix` - Caret lost after block conversion on mobile devices.
|
||||||
- `Improvement` - The API `blocks.convert()` now returns the new block API
|
- `Improvement` - The API `blocks.convert()` now returns the new block API
|
||||||
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
|
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
|
||||||
|
- `New` – *Menu Config* – New item type – HTML
|
||||||
|
|
||||||
### 2.29.1
|
### 2.29.1
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
"cypress-intellij-reporter": "^0.0.7",
|
"cypress-intellij-reporter": "^0.0.7",
|
||||||
"cypress-plugin-tab": "^1.0.5",
|
"cypress-plugin-tab": "^1.0.5",
|
||||||
"cypress-terminal-report": "^5.3.2",
|
"cypress-terminal-report": "^5.3.2",
|
||||||
|
"cypress-vite": "^1.5.0",
|
||||||
"eslint": "^8.37.0",
|
"eslint": "^8.37.0",
|
||||||
"eslint-config-codex": "^1.7.1",
|
"eslint-config-codex": "^1.7.1",
|
||||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||||
|
|
|
@ -21,11 +21,12 @@ 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 { TunesMenuConfig, TunesMenuConfigItem } from '../../../types/tools';
|
import { 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, isSameBlockData } from '../utils/blocks';
|
import { convertBlockDataToString, isSameBlockData } from '../utils/blocks';
|
||||||
|
import { PopoverItemType } from '../utils/popover';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface describes Block class constructor argument
|
* Interface describes Block class constructor argument
|
||||||
|
@ -610,29 +611,28 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns data to render in tunes menu.
|
* Returns data to render in Block Tunes menu.
|
||||||
* Splits block tunes into 3 groups: block specific tunes, common tunes
|
* Splits block tunes into 2 groups: block specific tunes and common tunes
|
||||||
* and custom html that is produced by combining tunes html from both previous groups
|
|
||||||
*/
|
*/
|
||||||
public getTunes(): {
|
public getTunes(): {
|
||||||
toolTunes: PopoverItemParams[];
|
toolTunes: PopoverItemParams[];
|
||||||
commonTunes: PopoverItemParams[];
|
commonTunes: PopoverItemParams[];
|
||||||
customHtmlTunes: HTMLElement
|
|
||||||
} {
|
} {
|
||||||
const customHtmlTunesContainer = document.createElement('div');
|
const toolTunesPopoverParams: TunesMenuConfigItem[] = [];
|
||||||
const commonTunesPopoverParams: 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 */
|
if ($.isElement(tunesDefinedInTool)) {
|
||||||
const {
|
toolTunesPopoverParams.push({
|
||||||
items: toolTunesPopoverParams,
|
type: PopoverItemType.Html,
|
||||||
htmlElement: toolTunesHtmlElement,
|
element: tunesDefinedInTool,
|
||||||
} = this.getTunesDataSegregated(tunesDefinedInTool);
|
});
|
||||||
|
} else if (Array.isArray(tunesDefinedInTool)) {
|
||||||
if (toolTunesHtmlElement !== undefined) {
|
toolTunesPopoverParams.push(...tunesDefinedInTool);
|
||||||
customHtmlTunesContainer.appendChild(toolTunesHtmlElement);
|
} else {
|
||||||
|
toolTunesPopoverParams.push(tunesDefinedInTool);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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 */
|
||||||
|
@ -643,28 +643,24 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
|
|
||||||
/** Separate custom html from Popover items params for common tunes */
|
/** Separate custom html from Popover items params for common tunes */
|
||||||
commonTunes.forEach(tuneConfig => {
|
commonTunes.forEach(tuneConfig => {
|
||||||
const {
|
if ($.isElement(tuneConfig)) {
|
||||||
items,
|
commonTunesPopoverParams.push({
|
||||||
htmlElement,
|
type: PopoverItemType.Html,
|
||||||
} = this.getTunesDataSegregated(tuneConfig);
|
element: tuneConfig,
|
||||||
|
});
|
||||||
if (htmlElement !== undefined) {
|
} else if (Array.isArray(tuneConfig)) {
|
||||||
customHtmlTunesContainer.appendChild(htmlElement);
|
commonTunesPopoverParams.push(...tuneConfig);
|
||||||
}
|
} else {
|
||||||
|
commonTunesPopoverParams.push(tuneConfig);
|
||||||
if (items !== undefined) {
|
|
||||||
commonTunesPopoverParams.push(...items);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toolTunes: toolTunesPopoverParams,
|
toolTunes: toolTunesPopoverParams,
|
||||||
commonTunes: commonTunesPopoverParams,
|
commonTunes: commonTunesPopoverParams,
|
||||||
customHtmlTunes: customHtmlTunesContainer,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update current input index with selection anchor node
|
* Update current input index with selection anchor node
|
||||||
*/
|
*/
|
||||||
|
@ -750,25 +746,6 @@ 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
|
||||||
*
|
*
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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, PopoverItemParams, PopoverItemDefaultParams } from '../../utils/popover';
|
import { type Popover, PopoverDesktop, PopoverMobile, PopoverItemParams, PopoverItemDefaultParams, PopoverItemType } 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';
|
||||||
|
@ -124,7 +124,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
this.Editor.BlockSelection.clearCache();
|
this.Editor.BlockSelection.clearCache();
|
||||||
|
|
||||||
/** Get tool's settings data */
|
/** Get tool's settings data */
|
||||||
const { toolTunes, commonTunes, customHtmlTunes } = targetBlock.getTunes();
|
const { toolTunes, commonTunes } = 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);
|
||||||
|
@ -134,8 +134,6 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
this.popover = new PopoverClass({
|
this.popover = new PopoverClass({
|
||||||
searchable: true,
|
searchable: true,
|
||||||
items: await this.getTunesItems(targetBlock, commonTunes, toolTunes),
|
items: await this.getTunesItems(targetBlock, commonTunes, toolTunes),
|
||||||
customContent: customHtmlTunes,
|
|
||||||
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'),
|
||||||
|
@ -212,7 +210,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
if (toolTunes !== undefined && toolTunes.length > 0) {
|
if (toolTunes !== undefined && toolTunes.length > 0) {
|
||||||
items.push(...toolTunes);
|
items.push(...toolTunes);
|
||||||
items.push({
|
items.push({
|
||||||
type: 'separator',
|
type: PopoverItemType.Separator,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +225,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
items.push({
|
items.push({
|
||||||
type: 'separator',
|
type: PopoverItemType.Separator,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,28 +312,13 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
this.close();
|
this.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns list of buttons and inputs inside specified container
|
|
||||||
*
|
|
||||||
* @param container - container to query controls inside of
|
|
||||||
*/
|
|
||||||
private getControls(container: HTMLElement): HTMLElement[] {
|
|
||||||
const { StylesAPI } = this.Editor;
|
|
||||||
/** Query buttons and inputs inside tunes html */
|
|
||||||
const controls = container.querySelectorAll<HTMLElement>(
|
|
||||||
`.${StylesAPI.classes.settingsButton}, ${$.allInputsSelector}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return Array.from(controls);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves aliases in tunes menu items
|
* Resolves aliases in tunes menu items
|
||||||
*
|
*
|
||||||
* @param item - item with resolved aliases
|
* @param item - item with resolved aliases
|
||||||
*/
|
*/
|
||||||
private resolveTuneAliases(item: TunesMenuConfigItem): PopoverItemParams {
|
private resolveTuneAliases(item: TunesMenuConfigItem): PopoverItemParams {
|
||||||
if (item.type === 'separator') {
|
if (item.type === PopoverItemType.Separator || item.type === PopoverItemType.Html) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
const result = resolveAliases(item, { label: 'title' });
|
const result = resolveAliases(item, { label: 'title' });
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { BlockToolAPI } from '../block';
|
||||||
import Shortcuts from '../utils/shortcuts';
|
import Shortcuts from '../utils/shortcuts';
|
||||||
import BlockTool from '../tools/block';
|
import BlockTool from '../tools/block';
|
||||||
import ToolsCollection from '../tools/collection';
|
import ToolsCollection from '../tools/collection';
|
||||||
import { API, BlockToolData, ToolboxConfigEntry, PopoverItem, BlockAPI } from '../../../types';
|
import { API, BlockToolData, ToolboxConfigEntry, PopoverItemParams, BlockAPI } from '../../../types';
|
||||||
import EventsDispatcher from '../utils/events';
|
import EventsDispatcher from '../utils/events';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { I18nInternalNS } from '../i18n/namespace-internal';
|
import { I18nInternalNS } from '../i18n/namespace-internal';
|
||||||
|
@ -303,11 +303,11 @@ export default class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
||||||
* Returns list of items that will be displayed in toolbox
|
* Returns list of items that will be displayed in toolbox
|
||||||
*/
|
*/
|
||||||
@_.cacheable
|
@_.cacheable
|
||||||
private get toolboxItemsToBeDisplayed(): PopoverItem[] {
|
private get toolboxItemsToBeDisplayed(): PopoverItemParams[] {
|
||||||
/**
|
/**
|
||||||
* Maps tool data to popover item structure
|
* Maps tool data to popover item structure
|
||||||
*/
|
*/
|
||||||
const toPopoverItem = (toolboxItem: ToolboxConfigEntry, tool: BlockTool): PopoverItem => {
|
const toPopoverItem = (toolboxItem: ToolboxConfigEntry, tool: BlockTool): PopoverItemParams => {
|
||||||
return {
|
return {
|
||||||
icon: toolboxItem.icon,
|
icon: toolboxItem.icon,
|
||||||
title: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
|
title: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
|
||||||
|
@ -320,7 +320,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.toolsToBeDisplayed
|
return this.toolsToBeDisplayed
|
||||||
.reduce<PopoverItem[]>((result, tool) => {
|
.reduce<PopoverItemParams[]>((result, tool) => {
|
||||||
if (Array.isArray(tool.toolbox)) {
|
if (Array.isArray(tool.toolbox)) {
|
||||||
tool.toolbox.forEach(item => {
|
tool.toolbox.forEach(item => {
|
||||||
result.push(toPopoverItem(item, tool));
|
result.push(toPopoverItem(item, tool));
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { bem } from '../../../../bem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Popover item block CSS class constructor
|
||||||
|
*/
|
||||||
|
const className = bem('ce-popover-item-html');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS class names to be used in popover item class
|
||||||
|
*/
|
||||||
|
export const css = {
|
||||||
|
root: className(),
|
||||||
|
hidden: className(null, 'hidden'),
|
||||||
|
};
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { PopoverItem } from '../popover-item';
|
||||||
|
import { PopoverItemHtmlParams } from '../popover-item.types';
|
||||||
|
import { css } from './popover-item-html.const';
|
||||||
|
import Dom from '../../../../../dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents popover item with custom html content
|
||||||
|
*/
|
||||||
|
export class PopoverItemHtml extends PopoverItem {
|
||||||
|
/**
|
||||||
|
* Item html elements
|
||||||
|
*/
|
||||||
|
private nodes: { root: HTMLElement };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the instance
|
||||||
|
*
|
||||||
|
* @param params – instance parameters
|
||||||
|
*/
|
||||||
|
constructor(params: PopoverItemHtmlParams) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.nodes = {
|
||||||
|
root: Dom.make('div', css.root),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.nodes.root.appendChild(params.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns popover item root element
|
||||||
|
*/
|
||||||
|
public getElement(): HTMLElement {
|
||||||
|
return this.nodes.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles item hidden state
|
||||||
|
*
|
||||||
|
* @param isHidden - true if item should be hidden
|
||||||
|
*/
|
||||||
|
public toggleHidden(isHidden: boolean): void {
|
||||||
|
this.nodes.root?.classList.toggle(css.hidden, isHidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of buttons and inputs inside custom content
|
||||||
|
*/
|
||||||
|
public getControls(): HTMLElement[] {
|
||||||
|
/** Query buttons and inputs inside custom html */
|
||||||
|
const controls = this.nodes.root.querySelectorAll<HTMLElement>(
|
||||||
|
`button, ${Dom.allInputsSelector}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return Array.from(controls);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Popover item types
|
||||||
|
*/
|
||||||
|
export enum PopoverItemType {
|
||||||
|
/** Regular item with icon, title and other properties */
|
||||||
|
Default = 'default',
|
||||||
|
|
||||||
|
/** Gray line used to separate items from each other */
|
||||||
|
Separator = 'separator',
|
||||||
|
|
||||||
|
/** Item with custom html content */
|
||||||
|
Html = 'html'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents popover item separator.
|
* Represents popover item separator.
|
||||||
|
@ -7,7 +20,22 @@ export interface PopoverItemSeparatorParams {
|
||||||
/**
|
/**
|
||||||
* Item type
|
* Item type
|
||||||
*/
|
*/
|
||||||
type: 'separator'
|
type: PopoverItemType.Separator
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents popover item with custom html content
|
||||||
|
*/
|
||||||
|
export interface PopoverItemHtmlParams {
|
||||||
|
/**
|
||||||
|
* Item type
|
||||||
|
*/
|
||||||
|
type: PopoverItemType.Html;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom html content to be displayed in the popover
|
||||||
|
*/
|
||||||
|
element: HTMLElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +45,7 @@ interface PopoverItemDefaultBaseParams {
|
||||||
/**
|
/**
|
||||||
* Item type
|
* Item type
|
||||||
*/
|
*/
|
||||||
type?: 'default';
|
type?: PopoverItemType.Default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displayed text
|
* Displayed text
|
||||||
|
@ -119,5 +147,8 @@ export type PopoverItemDefaultParams =
|
||||||
/**
|
/**
|
||||||
* Represents single popover item
|
* Represents single popover item
|
||||||
*/
|
*/
|
||||||
export type PopoverItemParams = PopoverItemDefaultParams | PopoverItemSeparatorParams;
|
export type PopoverItemParams =
|
||||||
|
PopoverItemDefaultParams |
|
||||||
|
PopoverItemSeparatorParams |
|
||||||
|
PopoverItemHtmlParams;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PopoverItem, PopoverItemDefault, PopoverItemSeparator } from './components/popover-item';
|
import { PopoverItem, PopoverItemDefault, PopoverItemSeparator, PopoverItemType } from './components/popover-item';
|
||||||
import Dom from '../../dom';
|
import Dom from '../../dom';
|
||||||
import { SearchInput, SearchInputEvent, SearchableItem } from './components/search-input';
|
import { SearchInput, SearchInputEvent, SearchableItem } from './components/search-input';
|
||||||
import EventsDispatcher from '../events';
|
import EventsDispatcher from '../events';
|
||||||
|
@ -6,6 +6,7 @@ import Listeners from '../listeners';
|
||||||
import { PopoverEventMap, PopoverMessages, PopoverParams, PopoverEvent, PopoverNodes } from './popover.types';
|
import { PopoverEventMap, PopoverMessages, PopoverParams, PopoverEvent, PopoverNodes } from './popover.types';
|
||||||
import { css } from './popover.const';
|
import { css } from './popover.const';
|
||||||
import { PopoverItemParams } from './components/popover-item';
|
import { PopoverItemParams } from './components/popover-item';
|
||||||
|
import { PopoverItemHtml } from './components/popover-item/popover-item-html/popover-item-html';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class responsible for rendering popover and handling its behaviour
|
* Class responsible for rendering popover and handling its behaviour
|
||||||
|
@ -27,10 +28,9 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
protected nodes: Nodes;
|
protected nodes: Nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of usual interactive popover items that can be clicked, hovered, etc.
|
* List of default popover items that are searchable and may have confirmation state
|
||||||
* (excluding separators)
|
|
||||||
*/
|
*/
|
||||||
protected get itemsInteractive(): PopoverItemDefault[] {
|
protected get itemsDefault(): PopoverItemDefault[] {
|
||||||
return this.items.filter(item => item instanceof PopoverItemDefault) as PopoverItemDefault[];
|
return this.items.filter(item => item instanceof PopoverItemDefault) as PopoverItemDefault[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,10 +97,6 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
|
|
||||||
this.nodes.popover.appendChild(this.nodes.popoverContainer);
|
this.nodes.popover.appendChild(this.nodes.popoverContainer);
|
||||||
|
|
||||||
if (params.customContent) {
|
|
||||||
this.addCustomContent(params.customContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.searchable) {
|
if (params.searchable) {
|
||||||
this.addSearch();
|
this.addSearch();
|
||||||
}
|
}
|
||||||
|
@ -131,7 +127,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
this.nodes.popover.classList.remove(css.popoverOpened);
|
this.nodes.popover.classList.remove(css.popoverOpened);
|
||||||
this.nodes.popover.classList.remove(css.popoverOpenTop);
|
this.nodes.popover.classList.remove(css.popoverOpenTop);
|
||||||
|
|
||||||
this.itemsInteractive.forEach(item => item.reset());
|
this.itemsDefault.forEach(item => item.reset());
|
||||||
|
|
||||||
if (this.search !== undefined) {
|
if (this.search !== undefined) {
|
||||||
this.search.clear();
|
this.search.clear();
|
||||||
|
@ -155,8 +151,10 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
protected buildItems(items: PopoverItemParams[]): Array<PopoverItem> {
|
protected buildItems(items: PopoverItemParams[]): Array<PopoverItem> {
|
||||||
return items.map(item => {
|
return items.map(item => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'separator':
|
case PopoverItemType.Separator:
|
||||||
return new PopoverItemSeparator();
|
return new PopoverItemSeparator();
|
||||||
|
case PopoverItemType.Html:
|
||||||
|
return new PopoverItemHtml(item);
|
||||||
default:
|
default:
|
||||||
return new PopoverItemDefault(item);
|
return new PopoverItemDefault(item);
|
||||||
}
|
}
|
||||||
|
@ -169,7 +167,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
* @param event - event to retrieve popover item from
|
* @param event - event to retrieve popover item from
|
||||||
*/
|
*/
|
||||||
protected getTargetItem(event: Event): PopoverItemDefault | undefined {
|
protected getTargetItem(event: Event): PopoverItemDefault | undefined {
|
||||||
return this.itemsInteractive.find(el => {
|
return this.itemsDefault.find(el => {
|
||||||
const itemEl = el.getElement();
|
const itemEl = el.getElement();
|
||||||
|
|
||||||
if (itemEl === null) {
|
if (itemEl === null) {
|
||||||
|
@ -197,14 +195,13 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
|
|
||||||
if (item instanceof PopoverItemDefault) {
|
if (item instanceof PopoverItemDefault) {
|
||||||
isHidden = !data.items.includes(item);
|
isHidden = !data.items.includes(item);
|
||||||
} else if (item instanceof PopoverItemSeparator) {
|
} else if (item instanceof PopoverItemSeparator || item instanceof PopoverItemHtml) {
|
||||||
/** Should hide separators if nothing found message displayed or if there is some search query applied */
|
/** Should hide separators if nothing found message displayed or if there is some search query applied */
|
||||||
isHidden = isNothingFound || !isEmptyQuery;
|
isHidden = isNothingFound || !isEmptyQuery;
|
||||||
}
|
}
|
||||||
item.toggleHidden(isHidden);
|
item.toggleHidden(isHidden);
|
||||||
});
|
});
|
||||||
this.toggleNothingFoundMessage(isNothingFound);
|
this.toggleNothingFoundMessage(isNothingFound);
|
||||||
this.toggleCustomContent(isEmptyQuery);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -212,7 +209,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
*/
|
*/
|
||||||
private addSearch(): void {
|
private addSearch(): void {
|
||||||
this.search = new SearchInput({
|
this.search = new SearchInput({
|
||||||
items: this.itemsInteractive,
|
items: this.itemsDefault,
|
||||||
placeholder: this.messages.search,
|
placeholder: this.messages.search,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -225,17 +222,6 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
this.nodes.popoverContainer.insertBefore(searchElement, this.nodes.popoverContainer.firstChild);
|
this.nodes.popoverContainer.insertBefore(searchElement, this.nodes.popoverContainer.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds custom html content to the popover
|
|
||||||
*
|
|
||||||
* @param content - html content to append
|
|
||||||
*/
|
|
||||||
private addCustomContent(content: HTMLElement): void {
|
|
||||||
this.nodes.customContent = content;
|
|
||||||
this.nodes.customContent.classList.add(css.customContent);
|
|
||||||
this.nodes.popoverContainer.insertBefore(content, this.nodes.popoverContainer.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles clicks inside popover
|
* Handles clicks inside popover
|
||||||
*
|
*
|
||||||
|
@ -259,7 +245,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cleanup other items state */
|
/** Cleanup other items state */
|
||||||
this.itemsInteractive.filter(x => x !== item).forEach(x => x.reset());
|
this.itemsDefault.filter(x => x !== item).forEach(x => x.reset());
|
||||||
|
|
||||||
item.handleClick();
|
item.handleClick();
|
||||||
|
|
||||||
|
@ -279,15 +265,6 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
this.nodes.nothingFoundMessage.classList.toggle(css.nothingFoundMessageDisplayed, isDisplayed);
|
this.nodes.nothingFoundMessage.classList.toggle(css.nothingFoundMessageDisplayed, isDisplayed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles custom content visibility
|
|
||||||
*
|
|
||||||
* @param isDisplayed - true if custom content should be displayed
|
|
||||||
*/
|
|
||||||
private toggleCustomContent(isDisplayed: boolean): void {
|
|
||||||
this.nodes.customContent?.classList.toggle(css.customContentHidden, isDisplayed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - Toggles item active state, if clicked popover item has property 'toggle' set to true.
|
* - Toggles item active state, if clicked popover item has property 'toggle' set to true.
|
||||||
*
|
*
|
||||||
|
@ -302,7 +279,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof clickedItem.toggle === 'string') {
|
if (typeof clickedItem.toggle === 'string') {
|
||||||
const itemsInToggleGroup = this.itemsInteractive.filter(item => item.toggle === clickedItem.toggle);
|
const itemsInToggleGroup = this.itemsDefault.filter(item => item.toggle === clickedItem.toggle);
|
||||||
|
|
||||||
/** If there's only one item in toggle group, toggle it */
|
/** If there's only one item in toggle group, toggle it */
|
||||||
if (itemsInToggleGroup.length === 1) {
|
if (itemsInToggleGroup.length === 1) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { css } from './popover.const';
|
||||||
import { SearchInputEvent, SearchableItem } from './components/search-input';
|
import { SearchInputEvent, SearchableItem } from './components/search-input';
|
||||||
import { cacheable } from '../../utils';
|
import { cacheable } from '../../utils';
|
||||||
import { PopoverItemDefault } from './components/popover-item';
|
import { PopoverItemDefault } from './components/popover-item';
|
||||||
|
import { PopoverItemHtml } from './components/popover-item/popover-item-html/popover-item-html';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Desktop popover.
|
* Desktop popover.
|
||||||
|
@ -18,11 +19,6 @@ export class PopoverDesktop extends PopoverAbstract {
|
||||||
*/
|
*/
|
||||||
public flipper: Flipper;
|
public flipper: Flipper;
|
||||||
|
|
||||||
/**
|
|
||||||
* List of html elements inside custom content area that should be available for keyboard navigation
|
|
||||||
*/
|
|
||||||
private customContentFlippableItems: HTMLElement[] | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to nested popover if exists.
|
* Reference to nested popover if exists.
|
||||||
* Undefined by default, PopoverDesktop when exists and null after destroyed.
|
* Undefined by default, PopoverDesktop when exists and null after destroyed.
|
||||||
|
@ -63,10 +59,6 @@ export class PopoverDesktop extends PopoverAbstract {
|
||||||
this.nodes.popover.classList.add(css.popoverNested);
|
this.nodes.popover.classList.add(css.popoverNested);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.customContentFlippableItems) {
|
|
||||||
this.customContentFlippableItems = params.customContentFlippableItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.scopeElement !== undefined) {
|
if (params.scopeElement !== undefined) {
|
||||||
this.scopeElement = params.scopeElement;
|
this.scopeElement = params.scopeElement;
|
||||||
}
|
}
|
||||||
|
@ -148,10 +140,10 @@ export class PopoverDesktop extends PopoverAbstract {
|
||||||
public hide(): void {
|
public hide(): void {
|
||||||
super.hide();
|
super.hide();
|
||||||
|
|
||||||
this.flipper.deactivate();
|
|
||||||
|
|
||||||
this.destroyNestedPopoverIfExists();
|
this.destroyNestedPopoverIfExists();
|
||||||
|
|
||||||
|
this.flipper.deactivate();
|
||||||
|
|
||||||
this.previouslyHoveredItem = null;
|
this.previouslyHoveredItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,23 +275,28 @@ export class PopoverDesktop extends PopoverAbstract {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list of elements available for keyboard navigation.
|
* Returns list of elements available for keyboard navigation.
|
||||||
* Contains both usual popover items elements and custom html content.
|
|
||||||
*/
|
*/
|
||||||
private get flippableElements(): HTMLElement[] {
|
private get flippableElements(): HTMLElement[] {
|
||||||
const popoverItemsElements = this.itemsInteractive.map(item => item.getElement());
|
const result = this.items
|
||||||
const customContentControlsElements = this.customContentFlippableItems || [];
|
.map(item => {
|
||||||
|
if (item instanceof PopoverItemDefault) {
|
||||||
|
return item.getElement();
|
||||||
|
}
|
||||||
|
if (item instanceof PopoverItemHtml) {
|
||||||
|
return item.getControls();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
.filter(item => item !== undefined && item !== null);
|
||||||
|
|
||||||
/**
|
return result as HTMLElement[];
|
||||||
* Combine elements inside custom content area with popover items elements
|
|
||||||
*/
|
|
||||||
return customContentControlsElements.concat(popoverItemsElements as HTMLElement[]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called on flipper navigation
|
* Called on flipper navigation
|
||||||
*/
|
*/
|
||||||
private onFlip = (): void => {
|
private onFlip = (): void => {
|
||||||
const focusedItem = this.itemsInteractive.find(item => item.isFocused);
|
const focusedItem = this.itemsDefault.find(item => item.isFocused);
|
||||||
|
|
||||||
focusedItem?.onFocus();
|
focusedItem?.onFocus();
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,8 +17,6 @@ export const css = {
|
||||||
search: className('search'),
|
search: className('search'),
|
||||||
nothingFoundMessage: className('nothing-found-message'),
|
nothingFoundMessage: className('nothing-found-message'),
|
||||||
nothingFoundMessageDisplayed: className('nothing-found-message', 'displayed'),
|
nothingFoundMessageDisplayed: className('nothing-found-message', 'displayed'),
|
||||||
customContent: className('custom-content'),
|
|
||||||
customContentHidden: className('custom-content', 'hidden'),
|
|
||||||
items: className('items'),
|
items: className('items'),
|
||||||
overlay: className('overlay'),
|
overlay: className('overlay'),
|
||||||
overlayHidden: className('overlay', 'hidden'),
|
overlayHidden: className('overlay', 'hidden'),
|
||||||
|
|
|
@ -15,16 +15,6 @@ export interface PopoverParams {
|
||||||
*/
|
*/
|
||||||
scopeElement?: HTMLElement;
|
scopeElement?: HTMLElement;
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrary html element to be inserted before items list
|
|
||||||
*/
|
|
||||||
customContent?: HTMLElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of html elements inside custom content area that should be available for keyboard navigation
|
|
||||||
*/
|
|
||||||
customContentFlippableItems?: HTMLElement[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if popover should contain search field
|
* True if popover should contain search field
|
||||||
*/
|
*/
|
||||||
|
@ -92,9 +82,6 @@ export interface PopoverNodes {
|
||||||
|
|
||||||
/** Popover items wrapper */
|
/** Popover items wrapper */
|
||||||
items: HTMLElement;
|
items: HTMLElement;
|
||||||
|
|
||||||
/** Custom html content area */
|
|
||||||
customContent: HTMLElement | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__search, &__custom-content:not(:empty) {
|
&__search {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,18 +151,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__custom-content:not(:empty) {
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
@media (--not-mobile) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__custom-content--hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--nested {
|
&--nested {
|
||||||
.ce-popover__container {
|
.ce-popover__container {
|
||||||
/* Variable --nesting-level is set via js in showNestedPopoverForItem() method */
|
/* Variable --nesting-level is set via js in showNestedPopoverForItem() method */
|
||||||
|
@ -210,6 +198,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ce-popover-item-html {
|
||||||
|
&--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ce-popover-item {
|
.ce-popover-item {
|
||||||
--border-radius: 6px;
|
--border-radius: 6px;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
|
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
|
||||||
import Header from '@editorjs/header';
|
import Header from '@editorjs/header';
|
||||||
import { ToolboxConfig } from '../../../../types';
|
import { ToolboxConfig } from '../../../../types';
|
||||||
|
import { TunesMenuConfig } from '../../../../types/tools';
|
||||||
|
|
||||||
|
|
||||||
describe('BlockTunes', function () {
|
describe('BlockTunes', function () {
|
||||||
|
@ -344,4 +345,97 @@ describe('BlockTunes', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Tunes order', () => {
|
||||||
|
it('should display block specific tunes before common tunes', () => {
|
||||||
|
/**
|
||||||
|
* Tool with several toolbox entries configured
|
||||||
|
*/
|
||||||
|
class TestTool {
|
||||||
|
/**
|
||||||
|
* TestTool contains several toolbox options
|
||||||
|
*/
|
||||||
|
public static get toolbox(): ToolboxConfig {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: 'Title 1',
|
||||||
|
icon: 'Icon1',
|
||||||
|
data: {
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool can render itself
|
||||||
|
*/
|
||||||
|
public render(): HTMLDivElement {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
div.innerText = 'Some text';
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public renderSettings(): TunesMenuConfig {
|
||||||
|
return {
|
||||||
|
icon: 'Icon',
|
||||||
|
title: 'Tune',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
/** Check there are more than 1 tune */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item')
|
||||||
|
.should('have.length.above', 1);
|
||||||
|
|
||||||
|
/** Check the first tune is tool specific tune */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item:first-child')
|
||||||
|
.contains('Tune')
|
||||||
|
.should('exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PopoverDesktop as Popover } from '../../../../src/components/utils/popover';
|
import { PopoverDesktop as Popover, PopoverItemType } from '../../../../src/components/utils/popover';
|
||||||
import { PopoverItemParams } from '../../../../types';
|
import { PopoverItemParams } from '../../../../types';
|
||||||
import { TunesMenuConfig } from '../../../../types/tools';
|
import { TunesMenuConfig } from '../../../../types/tools';
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ describe('Popover', () => {
|
||||||
.should('have.class', 'ce-popover-item--disabled')
|
.should('have.class', 'ce-popover-item--disabled')
|
||||||
.click()
|
.click()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (items[0].type !== 'default') {
|
if (items[0].type !== PopoverItemType.Default) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check onActivate callback has never been called
|
// Check onActivate callback has never been called
|
||||||
|
@ -244,22 +244,149 @@ describe('Popover', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render custom html content', () => {
|
it('should display item with custom html', () => {
|
||||||
const customHtml = document.createElement('div');
|
/**
|
||||||
|
* Block Tune with html as return type of render() method
|
||||||
|
*/
|
||||||
|
class TestTune {
|
||||||
|
public static isTune = true;
|
||||||
|
|
||||||
customHtml.setAttribute('data-cy-name', 'customContent');
|
/** Tune control displayed in block tunes popover */
|
||||||
customHtml.innerText = 'custom html content';
|
public render(): HTMLElement {
|
||||||
const popover = new Popover({
|
const button = document.createElement('button');
|
||||||
customContent: customHtml,
|
|
||||||
items: [],
|
button.classList.add('ce-settings__button');
|
||||||
|
button.innerText = 'Tune';
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create editor instance */
|
||||||
|
cy.createEditor({
|
||||||
|
tools: {
|
||||||
|
testTool: TestTune,
|
||||||
|
},
|
||||||
|
tunes: [ 'testTool' ],
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Hello',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.document().then(doc => {
|
/** Open block tunes menu */
|
||||||
doc.body.append(popover.getElement());
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.cdx-block')
|
||||||
|
.click();
|
||||||
|
|
||||||
/* Check custom content exists in the popover */
|
cy.get('[data-cy=editorjs]')
|
||||||
cy.get('[data-cy-name=customContent]');
|
.get('.ce-toolbar__settings-btn')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Check item with custom html content is displayed */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover .ce-popover-item-html')
|
||||||
|
.contains('Tune')
|
||||||
|
.should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support flipping between custom content items', () => {
|
||||||
|
/**
|
||||||
|
* Block Tune with html as return type of render() method
|
||||||
|
*/
|
||||||
|
class TestTune1 {
|
||||||
|
public static isTune = true;
|
||||||
|
|
||||||
|
/** Tune control displayed in block tunes popover */
|
||||||
|
public render(): HTMLElement {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
|
||||||
|
button.classList.add('ce-settings__button');
|
||||||
|
button.innerText = 'Tune1';
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block Tune with html as return type of render() method
|
||||||
|
*/
|
||||||
|
class TestTune2 {
|
||||||
|
public static isTune = true;
|
||||||
|
|
||||||
|
/** Tune control displayed in block tunes popover */
|
||||||
|
public render(): HTMLElement {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
|
||||||
|
button.classList.add('ce-settings__button');
|
||||||
|
button.innerText = 'Tune2';
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create editor instance */
|
||||||
|
cy.createEditor({
|
||||||
|
tools: {
|
||||||
|
testTool1: TestTune1,
|
||||||
|
testTool2: TestTune2,
|
||||||
|
},
|
||||||
|
tunes: ['testTool1', 'testTool2'],
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Hello',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Open block tunes menu */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.cdx-block')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-toolbar__settings-btn')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Press Tab */
|
||||||
|
// eslint-disable-next-line cypress/require-data-selectors -- cy.tab() not working here
|
||||||
|
cy.get('body').tab();
|
||||||
|
|
||||||
|
/** Check the first custom html item is focused */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover .ce-popover-item-html .ce-settings__button')
|
||||||
|
.contains('Tune1')
|
||||||
|
.should('have.class', 'ce-popover-item--focused');
|
||||||
|
|
||||||
|
/** Press Tab */
|
||||||
|
// eslint-disable-next-line cypress/require-data-selectors -- cy.tab() not working here
|
||||||
|
cy.get('body').tab();
|
||||||
|
|
||||||
|
/** Check the second custom html item is focused */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover .ce-popover-item-html .ce-settings__button')
|
||||||
|
.contains('Tune2')
|
||||||
|
.should('have.class', 'ce-popover-item--focused');
|
||||||
|
|
||||||
|
/** Press Tab */
|
||||||
|
// eslint-disable-next-line cypress/require-data-selectors -- cy.tab() not working here
|
||||||
|
cy.get('body').tab();
|
||||||
|
|
||||||
|
/** Check that default popover item got focused */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('[data-item-name=move-up]')
|
||||||
|
.should('have.class', 'ce-popover-item--focused');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display nested popover (desktop)', () => {
|
it('should display nested popover (desktop)', () => {
|
||||||
|
@ -454,7 +581,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 {
|
||||||
// @ts-expect-error type is not specified on purpose to test the back compatibility
|
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
title: 'Tune',
|
title: 'Tune',
|
||||||
|
@ -464,7 +590,6 @@ describe('Popover', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Create editor instance */
|
/** Create editor instance */
|
||||||
cy.createEditor({
|
cy.createEditor({
|
||||||
tools: {
|
tools: {
|
||||||
|
@ -515,7 +640,7 @@ describe('Popover', () => {
|
||||||
name: 'test-item',
|
name: 'test-item',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: PopoverItemType.Separator,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -577,7 +702,7 @@ describe('Popover', () => {
|
||||||
name: 'test-item-1',
|
name: 'test-item-1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: PopoverItemType.Separator,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
|
@ -664,7 +789,7 @@ describe('Popover', () => {
|
||||||
name: 'test-item-1',
|
name: 'test-item-1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: PopoverItemType.Separator,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onActivate: (): void => {},
|
onActivate: (): void => {},
|
||||||
|
|
9
types/tools/tool-settings.d.ts
vendored
9
types/tools/tool-settings.d.ts
vendored
|
@ -1,6 +1,6 @@
|
||||||
import { ToolConfig } from './tool-config';
|
import { ToolConfig } from './tool-config';
|
||||||
import { ToolConstructable, BlockToolData } from './index';
|
import { ToolConstructable, BlockToolData } from './index';
|
||||||
import { PopoverItemDefaultParams, PopoverItemSeparatorParams, PopoverItemParams } from '../configs';
|
import { PopoverItemDefaultParams, PopoverItemSeparatorParams, PopoverItemHtmlParams } from '../configs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool may specify its toolbox configuration
|
* Tool may specify its toolbox configuration
|
||||||
|
@ -57,10 +57,15 @@ export type TunesMenuConfigDefaultItem = PopoverItemDefaultParams & {
|
||||||
*/
|
*/
|
||||||
export type TunesMenuConfigSeparatorItem = PopoverItemSeparatorParams;
|
export type TunesMenuConfigSeparatorItem = PopoverItemSeparatorParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents single Tunes Menu item with custom HTML contect
|
||||||
|
*/
|
||||||
|
export type TunesMenuConfigHtmlItem = PopoverItemHtmlParams;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all Tunes Menu item types
|
* Union of all Tunes Menu item types
|
||||||
*/
|
*/
|
||||||
export type TunesMenuConfigItem = TunesMenuConfigDefaultItem | TunesMenuConfigSeparatorItem;
|
export type TunesMenuConfigItem = TunesMenuConfigDefaultItem | TunesMenuConfigSeparatorItem | TunesMenuConfigHtmlItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool may specify its tunes configuration
|
* Tool may specify its tunes configuration
|
||||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -1424,6 +1424,21 @@ chokidar@3.5.3:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
chokidar@^3.5.3:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||||
|
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||||
|
dependencies:
|
||||||
|
anymatch "~3.1.2"
|
||||||
|
braces "~3.0.2"
|
||||||
|
glob-parent "~5.1.2"
|
||||||
|
is-binary-path "~2.1.0"
|
||||||
|
is-glob "~4.0.1"
|
||||||
|
normalize-path "~3.0.0"
|
||||||
|
readdirp "~3.6.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
ci-info@^3.2.0:
|
ci-info@^3.2.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
||||||
|
@ -1689,6 +1704,14 @@ cypress-terminal-report@^5.3.2:
|
||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
tv4 "^1.3.0"
|
tv4 "^1.3.0"
|
||||||
|
|
||||||
|
cypress-vite@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cypress-vite/-/cypress-vite-1.5.0.tgz#471ecc1175c7ab51b3b132c595dc3c7e222fe944"
|
||||||
|
integrity sha512-vvTMqJZgI3sN2ylQTi4OQh8LRRjSrfrIdkQD5fOj+EC/e9oHkxS96lif1SyDF1PwailG1tnpJE+VpN6+AwO/rg==
|
||||||
|
dependencies:
|
||||||
|
chokidar "^3.5.3"
|
||||||
|
debug "^4.3.4"
|
||||||
|
|
||||||
cypress@^13.7.1:
|
cypress@^13.7.1:
|
||||||
version "13.7.1"
|
version "13.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.7.1.tgz#d1208eb04efd46ef52a30480a5da71a03373261a"
|
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.7.1.tgz#d1208eb04efd46ef52a30480a5da71a03373261a"
|
||||||
|
|
Loading…
Reference in a new issue