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.
- `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

View file

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

View file

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

View file

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

View file

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

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

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 { 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;
}

View file

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

View file

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

View file

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

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 { 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 => {},

View file

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