fix(tunes): Make label an alias for title in tunes menu item (#2198)

* Make label an alias for title in tunes item

* Cleanup

* Update version and changelog

* Update changelog

* Move resolveAlias to utils

* Add fallback for popover item title

* Lint

* Lint

* Add fallback icon and title to popover

* Update version

* Lint

* Fix changelog

* Fallback to empty string

This reverts commit ae9d643557.

* Fix changelog again

* Cleanup

* Add deprecated
This commit is contained in:
Tatiana Fomina 2022-12-14 22:46:36 +02:00 committed by GitHub
parent c97f0842f5
commit 870e265af0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 252 additions and 42 deletions

View file

@ -1,5 +1,9 @@
# Changelog
### 2.26.4
- `Improvement`*Menu Config* — Property `label` renamed to `title`.
### 2.26.3
- `Fix`*Paste Module* — fix for a problem with specifying of `pasteConfig().tags` in upper case [#2208](https://github.com/codex-team/editor.js/issues/2208).
@ -9,6 +13,7 @@
- `Fix`*Menu Config* — Installed tunes are rendered above default tunes again.
### 2.26.1
- `Improvement`*Menu Config* — Now it becomes possible to create toggle groups.
### 2.26.0

View file

@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.26.3",
"version": "2.26.4",
"description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editor.js",
"types": "./types/index.d.ts",
@ -94,7 +94,7 @@
"url": "https://opencollective.com/editorjs"
},
"dependencies": {
"@codexteam/icons": "^0.0.4",
"@codexteam/icons": "0.1.0",
"codex-notifier": "^1.1.2",
"codex-tooltip": "^1.0.5",
"html-janitor": "^2.0.4",

View file

@ -3,8 +3,9 @@
* @classdesc Editor's default tune that moves up selected block
* @copyright <CodeX Team> 2018
*/
import { API, BlockTune, PopoverItem } from '../../../types';
import { API, BlockTune } from '../../../types';
import { IconCross } from '@codexteam/icons';
import { TunesMenuConfig } from '../../../types/tools';
/**
*
@ -34,13 +35,13 @@ export default class DeleteTune implements BlockTune {
/**
* Tune's appearance in block settings menu
*/
public render(): PopoverItem {
public render(): TunesMenuConfig {
return {
icon: IconCross,
label: this.api.i18n.t('Delete'),
title: this.api.i18n.t('Delete'),
name: 'delete',
confirmation: {
label: this.api.i18n.t('Click to delete'),
title: this.api.i18n.t('Click to delete'),
onActivate: (): void => this.handleClick(),
},
};

View file

@ -4,9 +4,10 @@
* @copyright <CodeX Team> 2018
*/
import { API, BlockTune, PopoverItem } from '../../../types';
import { API, BlockTune } from '../../../types';
import Popover from '../utils/popover';
import { IconChevronDown } from '@codexteam/icons';
import { TunesMenuConfig } from '../../../types/tools';
/**
@ -44,10 +45,10 @@ export default class MoveDownTune implements BlockTune {
/**
* Tune's appearance in block settings menu
*/
public render(): PopoverItem {
public render(): TunesMenuConfig {
return {
icon: IconChevronDown,
label: this.api.i18n.t('Move down'),
title: this.api.i18n.t('Move down'),
onActivate: (item, event): void => this.handleClick(event),
name: 'move-down',
};

View file

@ -3,9 +3,10 @@
* @classdesc Editor's default tune that moves up selected block
* @copyright <CodeX Team> 2018
*/
import { API, BlockTune, PopoverItem } from '../../../types';
import { API, BlockTune } from '../../../types';
import Popover from '../../components/utils/popover';
import { IconChevronUp } from '@codexteam/icons';
import { TunesMenuConfig } from '../../../types/tools';
/**
*
@ -42,10 +43,10 @@ export default class MoveUpTune implements BlockTune {
/**
* Tune's appearance in block settings menu
*/
public render(): PopoverItem {
public render(): TunesMenuConfig {
return {
icon: IconChevronUp,
label: this.api.i18n.t('Move up'),
title: this.api.i18n.t('Move up'),
onActivate: (item, e): void => this.handleClick(e),
name: 'move-up',
};

View file

@ -21,6 +21,7 @@ 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 { TunesMenuConfigItem } from '../../../types/tools';
/**
* Interface describes Block class constructor argument
@ -646,7 +647,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
*/
public getTunes(): [PopoverItem[], HTMLElement] {
const customHtmlTunesContainer = document.createElement('div');
const tunesItems: PopoverItem[] = [];
const tunesItems: TunesMenuConfigItem[] = [];
/** Tool's tunes: may be defined as return value of optional renderSettings method */
const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];

View file

@ -6,6 +6,8 @@ import Popover, { PopoverEvent } from '../../utils/popover';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
import Flipper from '../../flipper';
import { TunesMenuConfigItem } from '../../../../types/tools';
import { resolveAliases } from '../../utils/resolve-aliases';
/**
* HTML Elements that used for BlockSettings
@ -114,7 +116,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
searchable: true,
filterLabel: I18n.ui(I18nInternalNS.ui.popover, 'Filter'),
nothingFoundLabel: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
items: tunesItems,
items: tunesItems.map(tune => this.resolveTuneAliases(tune)),
customContent: customHtmlTunesContainer,
customContentFlippableItems: this.getControls(customHtmlTunesContainer),
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
@ -192,4 +194,19 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
private onOverlayClicked = (): void => {
this.close();
};
/**
* Resolves aliases in tunes menu items
*
* @param item - item with resolved aliases
*/
private resolveTuneAliases(item: TunesMenuConfigItem): TunesMenuConfigItem {
const result = resolveAliases(item, { label: 'title' });
if (item.confirmation) {
result.confirmation = this.resolveTuneAliases(item.confirmation);
}
return result;
}
}

View file

@ -243,7 +243,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
const toPopoverItem = (toolboxItem: ToolboxConfigEntry, tool: BlockTool): PopoverItem => {
return {
icon: toolboxItem.icon,
label: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
title: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
name: tool.name,
onActivate: (): void => {
this.toolButtonActivated(tool.name, toolboxItem.data);

View file

@ -6,6 +6,7 @@ import EventsDispatcher from './events';
import { isMobileScreen, keyCodes, cacheable } from '../utils';
import ScrollLocker from './scroll-locker';
import { PopoverItem, PopoverItemWithConfirmation } from '../../../types';
import { IconDotCircle } from '@codexteam/icons';
/**
* Event that can be triggered by the Popover
@ -434,11 +435,11 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
el.dataset.itemName = item.name;
}
const label = Dom.make('div', Popover.CSS.itemLabel, {
innerHTML: item.label,
innerHTML: item.title || '',
});
el.appendChild(Dom.make('div', Popover.CSS.itemIcon, {
innerHTML: item.icon || item.name.substring(0, 1).toUpperCase(),
innerHTML: item.icon || IconDotCircle,
}));
el.appendChild(label);

View file

@ -0,0 +1,23 @@
/**
* Resolves aliases in specified object according to passed aliases info
*
* @example resolveAliases(obj, { label: 'title' })
* here 'label' is alias for 'title'
* @param obj - object with aliases to be resolved
* @param aliases - object with aliases info where key is an alias property name and value is an aliased property name
*/
export function resolveAliases<ObjectType>(obj: ObjectType, aliases: { [alias: string]: string }): ObjectType {
const result = {} as ObjectType;
Object.keys(obj).forEach(property => {
const aliasedProperty = aliases[property];
if (aliasedProperty !== undefined) {
result[aliasedProperty] = obj[property];
} else {
result[property] = obj[property];
}
});
return result;
}

View file

@ -6,7 +6,7 @@ import { IconSearch } from '@codexteam/icons';
* Item that could be searched
*/
interface SearchableItem {
label: string;
title?: string;
}
/**
@ -145,7 +145,7 @@ export default class SearchInput {
* @param item - item to be checked
*/
private checkItem(item: SearchableItem): boolean {
const text = item.label.toLowerCase();
const text = item.title?.toLowerCase() || '';
const query = this.searchQuery.toLowerCase();
return text.includes(query);

View file

@ -413,6 +413,87 @@ describe('Editor Tools Api', () => {
.get('.ce-popover')
.should('contain.text', sampleText);
});
it('should support label alias', () => {
/** Tool with single tunes menu entry configured */
class TestTool {
/** Returns toolbox config as list of entries */
public static get toolbox(): ToolboxConfigEntry {
return {
title: 'Test tool',
icon: ICON,
};
}
/** Returns configuration for block tunes menu */
public renderSettings(): TunesMenuConfig {
return [
{
icon: ICON,
name: 'testToolTune1',
onActivate: (): void => {},
// Set text via title property
title: 'Test tool tune 1',
},
{
icon: ICON,
name: 'testToolTune2',
onActivate: (): void => {},
// Set test via label property
label: 'Test tool tune 2',
},
];
}
/** Save method stub */
public save(): void {}
/** Renders a block */
public render(): HTMLElement {
const element = document.createElement('div');
element.contentEditable = 'true';
element.setAttribute('data-name', 'testBlock');
return element;
}
}
cy.createEditor({
tools: {
testTool: TestTool,
},
}).as('editorInstance');
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-toolbar__plus')
.click();
// Insert test tool block
cy.get('[data-cy=editorjs]')
.get(`[data-item-name="testTool"]`)
.click();
cy.get('[data-cy=editorjs]')
.get('[data-name=testBlock]')
.type('some text')
.click();
// Open block tunes
cy.get('[data-cy=editorjs]')
.get('.ce-toolbar__settings-btn')
.click();
// Expect both tunes to have correct text
cy.get('[data-item-name=testToolTune1]').contains('Test tool tune 1');
cy.get('[data-item-name=testToolTune2]').contains('Test tool tune 2');
});
});
/**

View file

@ -13,7 +13,7 @@ describe('Editor Tunes Api', () => {
public render(): TunesMenuConfig {
return {
icon: 'ICON',
label: 'Test tune',
title: 'Test tune',
name: 'testTune',
onActivate: (): void => { },
@ -54,13 +54,13 @@ describe('Editor Tunes Api', () => {
return [
{
icon: 'ICON1',
label: 'Tune entry 1',
title: 'Tune entry 1',
name: 'testTune1',
onActivate: (): void => { },
}, {
icon: 'ICON2',
label: 'Tune entry 2',
title: 'Tune entry 2',
name: 'testTune2',
onActivate: (): void => { },
@ -134,6 +134,59 @@ describe('Editor Tunes Api', () => {
.should('contain.text', sampleText);
});
it('should support label alias', () => {
/** Test tune that should appear be rendered in block tunes menu */
class TestTune {
/** Set Tool is Tune */
public static readonly isTune = true;
/** Tune's appearance in block settings menu */
public render(): TunesMenuConfig {
return [
{
icon: 'ICON1',
name: 'testTune1',
onActivate: (): void => { },
// Set text via title property
title: 'Tune entry 1',
}, {
icon: 'ICON2',
name: 'testTune2',
onActivate: (): void => { },
// Set text via label property
label: 'Tune entry 2',
},
];
}
/** Save method stub */
public save(): void {}
}
cy.createEditor({
tools: {
testTune: TestTune,
},
tunes: [ 'testTune' ],
}).as('editorInstance');
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.type('some text')
.click();
cy.get('[data-cy=editorjs]')
.get('.ce-toolbar__settings-btn')
.click();
/** Check both tunes have correct text */
cy.get('[data-item-name=testTune1]').contains('Tune entry 1');
cy.get('[data-item-name=testTune2]').contains('Tune entry 2');
});
it('should display installed tunes above default tunes', () => {
/** Test tune that should appear be rendered in block tunes menu */
class TestTune {

View file

@ -29,7 +29,7 @@ class SomePlugin {
public static get toolbox(): PopoverItem {
return {
icon: '₷',
label: 'Some tool',
title: 'Some tool',
// eslint-disable-next-line @typescript-eslint/no-empty-function
onActivate: (): void => {},
};

View file

@ -6,9 +6,9 @@ import { PopoverItem } from '../../../../types';
describe('Popover', () => {
it('should support confirmation chains', () => {
const actionIcon = 'Icon 1';
const actionLabel = 'Action';
const actionTitle = 'Action';
const confirmActionIcon = 'Icon 2';
const confirmActionLabel = 'Confirm action';
const confirmActionTitle = 'Confirm action';
/**
* Confirmation is moved to separate variable to be able to test it's callback execution.
@ -16,14 +16,14 @@ describe('Popover', () => {
*/
const confirmation = {
icon: confirmActionIcon,
label: confirmActionLabel,
title: confirmActionTitle,
onActivate: cy.stub(),
};
const items: PopoverItem[] = [
{
icon: actionIcon,
label: actionLabel,
title: actionTitle,
name: 'testItem',
confirmation,
},
@ -45,7 +45,7 @@ describe('Popover', () => {
cy.get('[data-item-name=testItem]')
.get('.ce-popover__item-label')
.should('have.text', actionLabel);
.should('have.text', actionTitle);
// First click on item
cy.get('[data-item-name=testItem]').click();
@ -58,7 +58,7 @@ describe('Popover', () => {
// Check label has changed
cy.get('[data-item-name=testItem]')
.get('.ce-popover__item-label')
.should('have.text', confirmActionLabel);
.should('have.text', confirmActionTitle);
// Second click
cy.get('[data-item-name=testItem]')
@ -74,7 +74,7 @@ describe('Popover', () => {
const items: PopoverItem[] = [
{
icon: 'Icon',
label: 'Label',
title: 'Title',
isActive: true,
name: 'testItem',
onActivate: (): void => {},
@ -101,7 +101,7 @@ describe('Popover', () => {
const items: PopoverItem[] = [
{
icon: 'Icon',
label: 'Label',
title: 'Title',
isDisabled: true,
name: 'testItem',
onActivate: cy.stub(),
@ -133,7 +133,7 @@ describe('Popover', () => {
const items: PopoverItem[] = [
{
icon: 'Icon',
label: 'Label',
title: 'Title',
closeOnActivate: true,
name: 'testItem',
onActivate: (): void => {},
@ -163,7 +163,7 @@ describe('Popover', () => {
const items: PopoverItem[] = [
{
icon: 'Icon',
label: 'Label',
title: 'Title',
toggle: true,
name: 'testItem',
onActivate: (): void => {},
@ -190,7 +190,7 @@ describe('Popover', () => {
const items: PopoverItem[] = [
{
icon: 'Icon 1',
label: 'Label 1',
title: 'Title 1',
toggle: 'group-name',
name: 'testItem1',
isActive: true,
@ -198,7 +198,7 @@ describe('Popover', () => {
},
{
icon: 'Icon 2',
label: 'Label 2',
title: 'Title 2',
toggle: 'group-name',
name: 'testItem2',
onActivate: (): void => {},
@ -238,7 +238,7 @@ describe('Popover', () => {
const items: PopoverItem[] = [
{
icon: 'Icon',
label: 'Label',
title: 'Title',
toggle: 'key',
name: 'testItem',
onActivate: (): void => {},

View file

@ -5,7 +5,7 @@ interface PopoverItemBase {
/**
* Displayed text
*/
label: string;
title?: string;
/**
* Item icon to be appeared near a title
@ -54,7 +54,7 @@ export interface PopoverItemWithConfirmation extends PopoverItemBase {
* Popover item parameters that should be applied on item activation.
* May be used to ask user for confirmation before executing popover item activation handler.
*/
confirmation: Partial<PopoverItem>;
confirmation: PopoverItem;
onActivate?: never;
}

View file

@ -28,11 +28,36 @@ export interface ToolboxConfigEntry {
data?: BlockToolData
}
/**
* Represents single Tunes Menu item
*/
export type TunesMenuConfigItem = PopoverItem & {
/**
* Tune displayed text.
*/
title?: string;
/**
* Tune displayed text.
* Alias for title property
*
* @deprecated - use title property instead
*/
label?: string
/**
* Menu item parameters that should be applied on item activation.
* May be used to ask user for confirmation before executing menu item activation handler.
*/
confirmation?: TunesMenuConfigItem;
}
/**
* Tool may specify its tunes configuration
* that can contain either one or multiple entries
*/
export type TunesMenuConfig = PopoverItem | PopoverItem[];
export type TunesMenuConfig = TunesMenuConfigItem | TunesMenuConfigItem[];
/**
* Object passed to the Tool's constructor by {@link EditorConfig#tools}

View file

@ -852,9 +852,10 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@codexteam/icons@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.4.tgz#8b72dcd3f3a1b0d880bdceb2abebd74b46d3ae13"
"@codexteam/icons@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.1.0.tgz#a02885fe8699f69902d05b077b5f1cd48a2ca6b9"
integrity sha512-jW1fWnwtWzcP4FBGsaodbJY3s1ZaRU+IJy1pvJ7ygNQxkQinybJcwXoyt0a5mWwu/4w30A42EWhCrZn8lp4fdw==
"@codexteam/shortcuts@^1.1.1":
version "1.2.0"