Compare commits

...

11 commits

Author SHA1 Message Date
Tanya Fomina a180b1e862 Update changelog 2024-05-04 18:29:46 +03:00
Tanya Fomina 19c94f9e94 Fix flipper issue 2024-05-04 18:21:44 +03:00
Tanya Fomina c4c17e20ab Fix issue with html item not hiding on search 2024-05-04 18:07:14 +03:00
Tanya Fomina 28c0214ae2 Update changelog 2024-05-04 17:58:28 +03:00
Tanya Fomina 16fd53151b Update jsdoc 2024-05-04 17:57:14 +03:00
Tanya Fomina 11e6a3c4a9 Add order test 2024-05-04 17:55:22 +03:00
Tanya Fomina 005e17058e Fix tests 2024-05-04 17:20:55 +03:00
Tanya Fomina af24b9c1a1 Rename custom to html, add enum with item types 2024-05-04 17:15:19 +03:00
Tanya Fomina f398168610 Cleanup 2024-05-04 17:06:56 +03:00
Tanya Fomina a70eeae01a Lint 2024-05-04 16:58:09 +03:00
Tanya Fomina 6fe4b44eba Cleanup 2024-05-04 16:56:15 +03:00
12 changed files with 152 additions and 81 deletions

View file

@ -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

View file

@ -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,9 +611,8 @@ 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[];
@ -626,7 +626,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
if ($.isElement(tunesDefinedInTool)) { if ($.isElement(tunesDefinedInTool)) {
toolTunesPopoverParams.push({ toolTunesPopoverParams.push({
type: 'custom', type: PopoverItemType.Html,
element: tunesDefinedInTool, element: tunesDefinedInTool,
}); });
} else if (Array.isArray(tunesDefinedInTool)) { } else if (Array.isArray(tunesDefinedInTool)) {
@ -645,7 +645,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
commonTunes.forEach(tuneConfig => { commonTunes.forEach(tuneConfig => {
if ($.isElement(tuneConfig)) { if ($.isElement(tuneConfig)) {
commonTunesPopoverParams.push({ commonTunesPopoverParams.push({
type: 'custom', type: PopoverItemType.Html,
element: tuneConfig, element: tuneConfig,
}); });
} else if (Array.isArray(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 * 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); 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
* *

View file

@ -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';
@ -210,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,
}); });
} }
@ -225,7 +225,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
}, },
}); });
items.push({ items.push({
type: 'separator', type: PopoverItemType.Separator,
}); });
} }
@ -312,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' });

View file

@ -3,7 +3,7 @@ import { bem } from '../../../../bem';
/** /**
* Popover item block CSS class constructor * 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 * CSS class names to be used in popover item class

View file

@ -1,12 +1,12 @@
import { PopoverItem } from '../popover-item'; import { PopoverItem } from '../popover-item';
import { PopoverItemCustomParams } from '../popover-item.types'; import { PopoverItemHtmlParams } from '../popover-item.types';
import { css } from './popover-item-custom.const'; import { css } from './popover-item-html.const';
import Dom from '../../../../../dom'; import Dom from '../../../../../dom';
/** /**
* Represents popover item with custom html content * Represents popover item with custom html content
*/ */
export class PopoverItemCustom extends PopoverItem { export class PopoverItemHtml extends PopoverItem {
/** /**
* Item html elements * Item html elements
*/ */
@ -17,7 +17,7 @@ export class PopoverItemCustom extends PopoverItem {
* *
* @param params instance parameters * @param params instance parameters
*/ */
constructor(params: PopoverItemCustomParams) { constructor(params: PopoverItemHtmlParams) {
super(); super();
this.nodes = { this.nodes = {

View file

@ -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,17 +20,17 @@ export interface PopoverItemSeparatorParams {
/** /**
* Item type * Item type
*/ */
type: 'separator' type: PopoverItemType.Separator
} }
/** /**
* Represents popover item with custom html content * Represents popover item with custom html content
*/ */
export interface PopoverItemCustomParams { export interface PopoverItemHtmlParams {
/** /**
* Item type * Item type
*/ */
type: 'custom'; type: PopoverItemType.Html;
/** /**
* Custom html content to be displayed in the popover * Custom html content to be displayed in the popover
@ -32,7 +45,7 @@ interface PopoverItemDefaultBaseParams {
/** /**
* Item type * Item type
*/ */
type?: 'default'; type?: PopoverItemType.Default;
/** /**
* Displayed text * Displayed text
@ -137,5 +150,5 @@ export type PopoverItemDefaultParams =
export type PopoverItemParams = export type PopoverItemParams =
PopoverItemDefaultParams | PopoverItemDefaultParams |
PopoverItemSeparatorParams | PopoverItemSeparatorParams |
PopoverItemCustomParams; PopoverItemHtmlParams;

View file

@ -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,7 +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 { 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 * Class responsible for rendering popover and handling its behaviour
@ -28,7 +28,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
protected nodes: Nodes; 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[] { protected get itemsDefault(): PopoverItemDefault[] {
return this.items.filter(item => item instanceof PopoverItemDefault) as 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> { 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 'custom': case PopoverItemType.Html:
return new PopoverItemCustom(item); return new PopoverItemHtml(item);
default: default:
return new PopoverItemDefault(item); return new PopoverItemDefault(item);
} }
@ -195,7 +195,7 @@ 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 || 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 */ /** Should hide separators if nothing found message displayed or if there is some search query applied */
isHidden = isNothingFound || !isEmptyQuery; isHidden = isNothingFound || !isEmptyQuery;
} }

View file

@ -7,7 +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 { 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. * Desktop popover.
@ -140,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;
} }
@ -282,7 +282,7 @@ export class PopoverDesktop extends PopoverAbstract {
if (item instanceof PopoverItemDefault) { if (item instanceof PopoverItemDefault) {
return item.getElement(); return item.getElement();
} }
if (item instanceof PopoverItemCustom) { if (item instanceof PopoverItemHtml) {
return item.getControls(); return item.getControls();
} }
}) })

View file

@ -198,7 +198,7 @@
} }
} }
.ce-popover-item-custom { .ce-popover-item-html {
&--hidden { &--hidden {
display: none; display: none;
} }

View file

@ -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');
});
});
}); });

View file

@ -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,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 * Block Tune with html as return type of render() method
*/ */
@ -291,7 +291,7 @@ describe('Popover', () => {
/** Check item with custom html content is displayed */ /** Check item with custom html content is displayed */
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('.ce-popover .ce-popover-item-custom') .get('.ce-popover .ce-popover-item-html')
.contains('Tune') .contains('Tune')
.should('be.visible'); .should('be.visible');
}); });
@ -365,7 +365,7 @@ describe('Popover', () => {
/** Check the first custom html item is focused */ /** Check the first custom html item is focused */
cy.get('[data-cy=editorjs]') 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') .contains('Tune1')
.should('have.class', 'ce-popover-item--focused'); .should('have.class', 'ce-popover-item--focused');
@ -375,7 +375,7 @@ describe('Popover', () => {
/** Check the second custom html item is focused */ /** Check the second custom html item is focused */
cy.get('[data-cy=editorjs]') 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') .contains('Tune2')
.should('have.class', 'ce-popover-item--focused'); .should('have.class', 'ce-popover-item--focused');
@ -581,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',
@ -591,7 +590,6 @@ describe('Popover', () => {
} }
} }
/** Create editor instance */ /** Create editor instance */
cy.createEditor({ cy.createEditor({
tools: { tools: {
@ -642,7 +640,7 @@ describe('Popover', () => {
name: 'test-item', name: 'test-item',
}, },
{ {
type: 'separator', type: PopoverItemType.Separator,
}, },
]; ];
} }
@ -704,7 +702,7 @@ describe('Popover', () => {
name: 'test-item-1', name: 'test-item-1',
}, },
{ {
type: 'separator', type: PopoverItemType.Separator,
}, },
{ {
onActivate: (): void => {}, onActivate: (): void => {},
@ -791,7 +789,7 @@ describe('Popover', () => {
name: 'test-item-1', name: 'test-item-1',
}, },
{ {
type: 'separator', type: PopoverItemType.Separator,
}, },
{ {
onActivate: (): void => {}, onActivate: (): void => {},

View file

@ -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, PopoverItemCustomParams } from '../configs'; import { PopoverItemDefaultParams, PopoverItemSeparatorParams, PopoverItemHtmlParams } from '../configs';
/** /**
* Tool may specify its toolbox configuration * Tool may specify its toolbox configuration
@ -60,12 +60,12 @@ export type TunesMenuConfigSeparatorItem = PopoverItemSeparatorParams;
/** /**
* Represents single Tunes Menu item with custom HTML contect * Represents single Tunes Menu item with custom HTML contect
*/ */
export type TunesMenuConfigCustomItem = PopoverItemCustomParams; export type TunesMenuConfigHtmlItem = PopoverItemHtmlParams;
/** /**
* Union of all Tunes Menu item types * 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 * Tool may specify its tunes configuration