mirror of
https://github.com/codex-team/editor.js
synced 2024-06-10 09:52:36 +02:00
Compare commits
11 commits
d93cdea7d3
...
a180b1e862
Author | SHA1 | Date | |
---|---|---|---|
a180b1e862 | |||
19c94f9e94 | |||
c4c17e20ab | |||
28c0214ae2 | |||
16fd53151b | |||
11e6a3c4a9 | |||
005e17058e | |||
af24b9c1a1 | |||
f398168610 | |||
a70eeae01a | |||
6fe4b44eba |
|
@ -15,6 +15,7 @@
|
|||
- `Fix` - Caret lost after block conversion on mobile devices.
|
||||
- `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
|
||||
- `New` – *Menu Config* – New item type – HTML
|
||||
|
||||
### 2.29.1
|
||||
|
||||
|
|
|
@ -21,11 +21,12 @@ import BlockTune from '../tools/tune';
|
|||
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||
import ToolsCollection from '../tools/collection';
|
||||
import EventsDispatcher from '../utils/events';
|
||||
import { TunesMenuConfig, TunesMenuConfigItem } from '../../../types/tools';
|
||||
import { TunesMenuConfigItem } from '../../../types/tools';
|
||||
import { isMutationBelongsToElement } from '../utils/mutations';
|
||||
import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events';
|
||||
import { RedactorDomChangedPayload } from '../events/RedactorDomChanged';
|
||||
import { convertBlockDataToString, isSameBlockData } from '../utils/blocks';
|
||||
import { PopoverItemType } from '../utils/popover';
|
||||
|
||||
/**
|
||||
* Interface describes Block class constructor argument
|
||||
|
@ -610,9 +611,8 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns data to render in tunes menu.
|
||||
* 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
|
||||
* Returns data to render in Block Tunes menu.
|
||||
* Splits block tunes into 2 groups: block specific tunes and common tunes
|
||||
*/
|
||||
public getTunes(): {
|
||||
toolTunes: PopoverItemParams[];
|
||||
|
@ -626,7 +626,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
|||
|
||||
if ($.isElement(tunesDefinedInTool)) {
|
||||
toolTunesPopoverParams.push({
|
||||
type: 'custom',
|
||||
type: PopoverItemType.Html,
|
||||
element: tunesDefinedInTool,
|
||||
});
|
||||
} else if (Array.isArray(tunesDefinedInTool)) {
|
||||
|
@ -645,7 +645,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
|||
commonTunes.forEach(tuneConfig => {
|
||||
if ($.isElement(tuneConfig)) {
|
||||
commonTunesPopoverParams.push({
|
||||
type: 'custom',
|
||||
type: PopoverItemType.Html,
|
||||
element: tuneConfig,
|
||||
});
|
||||
} else if (Array.isArray(tuneConfig)) {
|
||||
|
@ -661,7 +661,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update current input index with selection anchor node
|
||||
*/
|
||||
|
@ -747,25 +746,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
|||
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
|
||||
*
|
||||
|
|
|
@ -7,7 +7,7 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
|
|||
import Flipper from '../../flipper';
|
||||
import { TunesMenuConfigItem } from '../../../../types/tools';
|
||||
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 { isMobileScreen } from '../../utils';
|
||||
import { EditorMobileLayoutToggled } from '../../events';
|
||||
|
@ -210,7 +210,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
if (toolTunes !== undefined && toolTunes.length > 0) {
|
||||
items.push(...toolTunes);
|
||||
items.push({
|
||||
type: 'separator',
|
||||
type: PopoverItemType.Separator,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -225,7 +225,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
},
|
||||
});
|
||||
items.push({
|
||||
type: 'separator',
|
||||
type: PopoverItemType.Separator,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -312,28 +312,13 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
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
|
||||
*
|
||||
* @param item - item with resolved aliases
|
||||
*/
|
||||
private resolveTuneAliases(item: TunesMenuConfigItem): PopoverItemParams {
|
||||
if (item.type === 'separator') {
|
||||
if (item.type === PopoverItemType.Separator || item.type === PopoverItemType.Html) {
|
||||
return item;
|
||||
}
|
||||
const result = resolveAliases(item, { label: 'title' });
|
||||
|
|
|
@ -3,7 +3,7 @@ import { bem } from '../../../../bem';
|
|||
/**
|
||||
* Popover item block CSS class constructor
|
||||
*/
|
||||
const className = bem('ce-popover-item-custom');
|
||||
const className = bem('ce-popover-item-html');
|
||||
|
||||
/**
|
||||
* CSS class names to be used in popover item class
|
|
@ -1,12 +1,12 @@
|
|||
import { PopoverItem } from '../popover-item';
|
||||
import { PopoverItemCustomParams } from '../popover-item.types';
|
||||
import { css } from './popover-item-custom.const';
|
||||
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 PopoverItemCustom extends PopoverItem {
|
||||
export class PopoverItemHtml extends PopoverItem {
|
||||
/**
|
||||
* Item html elements
|
||||
*/
|
||||
|
@ -17,7 +17,7 @@ export class PopoverItemCustom extends PopoverItem {
|
|||
*
|
||||
* @param params – instance parameters
|
||||
*/
|
||||
constructor(params: PopoverItemCustomParams) {
|
||||
constructor(params: PopoverItemHtmlParams) {
|
||||
super();
|
||||
|
||||
this.nodes = {
|
|
@ -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.
|
||||
|
@ -7,17 +20,17 @@ export interface PopoverItemSeparatorParams {
|
|||
/**
|
||||
* Item type
|
||||
*/
|
||||
type: 'separator'
|
||||
type: PopoverItemType.Separator
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents popover item with custom html content
|
||||
*/
|
||||
export interface PopoverItemCustomParams {
|
||||
export interface PopoverItemHtmlParams {
|
||||
/**
|
||||
* Item type
|
||||
*/
|
||||
type: 'custom';
|
||||
type: PopoverItemType.Html;
|
||||
|
||||
/**
|
||||
* Custom html content to be displayed in the popover
|
||||
|
@ -32,7 +45,7 @@ interface PopoverItemDefaultBaseParams {
|
|||
/**
|
||||
* Item type
|
||||
*/
|
||||
type?: 'default';
|
||||
type?: PopoverItemType.Default;
|
||||
|
||||
/**
|
||||
* Displayed text
|
||||
|
@ -137,5 +150,5 @@ export type PopoverItemDefaultParams =
|
|||
export type PopoverItemParams =
|
||||
PopoverItemDefaultParams |
|
||||
PopoverItemSeparatorParams |
|
||||
PopoverItemCustomParams;
|
||||
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 { SearchInput, SearchInputEvent, SearchableItem } from './components/search-input';
|
||||
import EventsDispatcher from '../events';
|
||||
|
@ -6,7 +6,7 @@ import Listeners from '../listeners';
|
|||
import { PopoverEventMap, PopoverMessages, PopoverParams, PopoverEvent, PopoverNodes } from './popover.types';
|
||||
import { css } from './popover.const';
|
||||
import { PopoverItemParams } from './components/popover-item';
|
||||
import { PopoverItemCustom } from './components/popover-item/popover-item-custom/popover-item-custom';
|
||||
import { PopoverItemHtml } from './components/popover-item/popover-item-html/popover-item-html';
|
||||
|
||||
/**
|
||||
* Class responsible for rendering popover and handling its behaviour
|
||||
|
@ -28,7 +28,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|||
protected nodes: Nodes;
|
||||
|
||||
/**
|
||||
* List of default popover items that can are searchable and may have confirmation state
|
||||
* List of default popover items that are searchable and may have confirmation state
|
||||
*/
|
||||
protected get itemsDefault(): PopoverItemDefault[] {
|
||||
return this.items.filter(item => item instanceof PopoverItemDefault) as PopoverItemDefault[];
|
||||
|
@ -151,10 +151,10 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|||
protected buildItems(items: PopoverItemParams[]): Array<PopoverItem> {
|
||||
return items.map(item => {
|
||||
switch (item.type) {
|
||||
case 'separator':
|
||||
case PopoverItemType.Separator:
|
||||
return new PopoverItemSeparator();
|
||||
case 'custom':
|
||||
return new PopoverItemCustom(item);
|
||||
case PopoverItemType.Html:
|
||||
return new PopoverItemHtml(item);
|
||||
default:
|
||||
return new PopoverItemDefault(item);
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|||
|
||||
if (item instanceof PopoverItemDefault) {
|
||||
isHidden = !data.items.includes(item);
|
||||
} else if (item instanceof PopoverItemSeparator || item instanceof PopoverItemCustom) {
|
||||
} else if (item instanceof PopoverItemSeparator || item instanceof PopoverItemHtml) {
|
||||
/** Should hide separators if nothing found message displayed or if there is some search query applied */
|
||||
isHidden = isNothingFound || !isEmptyQuery;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { css } from './popover.const';
|
|||
import { SearchInputEvent, SearchableItem } from './components/search-input';
|
||||
import { cacheable } from '../../utils';
|
||||
import { PopoverItemDefault } from './components/popover-item';
|
||||
import { PopoverItemCustom } from './components/popover-item/popover-item-custom/popover-item-custom';
|
||||
import { PopoverItemHtml } from './components/popover-item/popover-item-html/popover-item-html';
|
||||
|
||||
/**
|
||||
* Desktop popover.
|
||||
|
@ -140,10 +140,10 @@ export class PopoverDesktop extends PopoverAbstract {
|
|||
public hide(): void {
|
||||
super.hide();
|
||||
|
||||
this.flipper.deactivate();
|
||||
|
||||
this.destroyNestedPopoverIfExists();
|
||||
|
||||
this.flipper.deactivate();
|
||||
|
||||
this.previouslyHoveredItem = null;
|
||||
}
|
||||
|
||||
|
@ -282,7 +282,7 @@ export class PopoverDesktop extends PopoverAbstract {
|
|||
if (item instanceof PopoverItemDefault) {
|
||||
return item.getElement();
|
||||
}
|
||||
if (item instanceof PopoverItemCustom) {
|
||||
if (item instanceof PopoverItemHtml) {
|
||||
return item.getControls();
|
||||
}
|
||||
})
|
||||
|
|
|
@ -198,7 +198,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ce-popover-item-custom {
|
||||
.ce-popover-item-html {
|
||||
&--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
|
||||
import Header from '@editorjs/header';
|
||||
import { ToolboxConfig } from '../../../../types';
|
||||
import { TunesMenuConfig } from '../../../../types/tools';
|
||||
|
||||
|
||||
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 { TunesMenuConfig } from '../../../../types/tools';
|
||||
|
||||
|
@ -115,7 +115,7 @@ describe('Popover', () => {
|
|||
.should('have.class', 'ce-popover-item--disabled')
|
||||
.click()
|
||||
.then(() => {
|
||||
if (items[0].type !== 'default') {
|
||||
if (items[0].type !== PopoverItemType.Default) {
|
||||
return;
|
||||
}
|
||||
// Check onActivate callback has never been called
|
||||
|
@ -244,7 +244,7 @@ describe('Popover', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should display custom content item', () => {
|
||||
it('should display item with custom html', () => {
|
||||
/**
|
||||
* Block Tune with html as return type of render() method
|
||||
*/
|
||||
|
@ -291,7 +291,7 @@ describe('Popover', () => {
|
|||
|
||||
/** Check item with custom html content is displayed */
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('.ce-popover .ce-popover-item-custom')
|
||||
.get('.ce-popover .ce-popover-item-html')
|
||||
.contains('Tune')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
@ -365,7 +365,7 @@ describe('Popover', () => {
|
|||
|
||||
/** Check the first custom html item is focused */
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('.ce-popover .ce-popover-item-custom .ce-settings__button')
|
||||
.get('.ce-popover .ce-popover-item-html .ce-settings__button')
|
||||
.contains('Tune1')
|
||||
.should('have.class', 'ce-popover-item--focused');
|
||||
|
||||
|
@ -375,7 +375,7 @@ describe('Popover', () => {
|
|||
|
||||
/** Check the second custom html item is focused */
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('.ce-popover .ce-popover-item-custom .ce-settings__button')
|
||||
.get('.ce-popover .ce-popover-item-html .ce-settings__button')
|
||||
.contains('Tune2')
|
||||
.should('have.class', 'ce-popover-item--focused');
|
||||
|
||||
|
@ -581,7 +581,6 @@ describe('Popover', () => {
|
|||
/** Tool data displayed in block tunes popover */
|
||||
public render(): TunesMenuConfig {
|
||||
return {
|
||||
// @ts-expect-error type is not specified on purpose to test the back compatibility
|
||||
onActivate: (): void => {},
|
||||
icon: 'Icon',
|
||||
title: 'Tune',
|
||||
|
@ -591,7 +590,6 @@ describe('Popover', () => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/** Create editor instance */
|
||||
cy.createEditor({
|
||||
tools: {
|
||||
|
@ -642,7 +640,7 @@ describe('Popover', () => {
|
|||
name: 'test-item',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: PopoverItemType.Separator,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -704,7 +702,7 @@ describe('Popover', () => {
|
|||
name: 'test-item-1',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: PopoverItemType.Separator,
|
||||
},
|
||||
{
|
||||
onActivate: (): void => {},
|
||||
|
@ -791,7 +789,7 @@ describe('Popover', () => {
|
|||
name: 'test-item-1',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: PopoverItemType.Separator,
|
||||
},
|
||||
{
|
||||
onActivate: (): void => {},
|
||||
|
|
6
types/tools/tool-settings.d.ts
vendored
6
types/tools/tool-settings.d.ts
vendored
|
@ -1,6 +1,6 @@
|
|||
import { ToolConfig } from './tool-config';
|
||||
import { ToolConstructable, BlockToolData } from './index';
|
||||
import { PopoverItemDefaultParams, PopoverItemSeparatorParams, PopoverItemParams, PopoverItemCustomParams } from '../configs';
|
||||
import { PopoverItemDefaultParams, PopoverItemSeparatorParams, PopoverItemHtmlParams } from '../configs';
|
||||
|
||||
/**
|
||||
* Tool may specify its toolbox configuration
|
||||
|
@ -60,12 +60,12 @@ export type TunesMenuConfigSeparatorItem = PopoverItemSeparatorParams;
|
|||
/**
|
||||
* Represents single Tunes Menu item with custom HTML contect
|
||||
*/
|
||||
export type TunesMenuConfigCustomItem = PopoverItemCustomParams;
|
||||
export type TunesMenuConfigHtmlItem = PopoverItemHtmlParams;
|
||||
|
||||
/**
|
||||
* Union of all Tunes Menu item types
|
||||
*/
|
||||
export type TunesMenuConfigItem = TunesMenuConfigDefaultItem | TunesMenuConfigSeparatorItem | TunesMenuConfigCustomItem;
|
||||
export type TunesMenuConfigItem = TunesMenuConfigDefaultItem | TunesMenuConfigSeparatorItem | TunesMenuConfigHtmlItem;
|
||||
|
||||
/**
|
||||
* Tool may specify its tunes configuration
|
||||
|
|
Loading…
Reference in a new issue