From 870e265af0e2114c0a1504a70148ea3c5e28d959 Mon Sep 17 00:00:00 2001 From: Tatiana Fomina Date: Wed, 14 Dec 2022 22:46:36 +0200 Subject: [PATCH] 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 ae9d6435575a3559c52f32c667a8dd6e6b71f4e7. * Fix changelog again * Cleanup * Add deprecated --- docs/CHANGELOG.md | 5 ++ package.json | 4 +- .../block-tunes/block-tune-delete.ts | 9 ++- .../block-tunes/block-tune-move-down.ts | 7 +- .../block-tunes/block-tune-move-up.ts | 7 +- src/components/block/index.ts | 3 +- .../modules/toolbar/blockSettings.ts | 19 ++++- src/components/ui/toolbox.ts | 2 +- src/components/utils/popover.ts | 5 +- src/components/utils/resolve-aliases.ts | 23 ++++++ src/components/utils/search-input.ts | 4 +- test/cypress/tests/api/tools.spec.ts | 81 +++++++++++++++++++ test/cypress/tests/api/tunes.spec.ts | 59 +++++++++++++- test/cypress/tests/utils/flipper.spec.ts | 2 +- test/cypress/tests/utils/popover.spec.ts | 26 +++--- types/configs/popover.d.ts | 4 +- types/tools/tool-settings.d.ts | 27 ++++++- yarn.lock | 7 +- 18 files changed, 252 insertions(+), 42 deletions(-) create mode 100644 src/components/utils/resolve-aliases.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a9672493..3ced3167 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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 diff --git a/package.json b/package.json index 286d9036..42092af3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/block-tunes/block-tune-delete.ts b/src/components/block-tunes/block-tune-delete.ts index 781e00a5..904eefae 100644 --- a/src/components/block-tunes/block-tune-delete.ts +++ b/src/components/block-tunes/block-tune-delete.ts @@ -3,8 +3,9 @@ * @classdesc Editor's default tune that moves up selected block * @copyright 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(), }, }; diff --git a/src/components/block-tunes/block-tune-move-down.ts b/src/components/block-tunes/block-tune-move-down.ts index 805ee5f9..d97523de 100644 --- a/src/components/block-tunes/block-tune-move-down.ts +++ b/src/components/block-tunes/block-tune-move-down.ts @@ -4,9 +4,10 @@ * @copyright 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', }; diff --git a/src/components/block-tunes/block-tune-move-up.ts b/src/components/block-tunes/block-tune-move-up.ts index 47950a99..1bdb0916 100644 --- a/src/components/block-tunes/block-tune-move-up.ts +++ b/src/components/block-tunes/block-tune-move-up.ts @@ -3,9 +3,10 @@ * @classdesc Editor's default tune that moves up selected block * @copyright 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', }; diff --git a/src/components/block/index.ts b/src/components/block/index.ts index 3d81c2dc..8bed7fce 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -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 { */ 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() : []; diff --git a/src/components/modules/toolbar/blockSettings.ts b/src/components/modules/toolbar/blockSettings.ts index 9629a1f5..a1fe4b50 100644 --- a/src/components/modules/toolbar/blockSettings.ts +++ b/src/components/modules/toolbar/blockSettings.ts @@ -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 { 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 { 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; + } } diff --git a/src/components/ui/toolbox.ts b/src/components/ui/toolbox.ts index 1d0c25db..5f5900ab 100644 --- a/src/components/ui/toolbox.ts +++ b/src/components/ui/toolbox.ts @@ -243,7 +243,7 @@ export default class Toolbox extends EventsDispatcher { 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); diff --git a/src/components/utils/popover.ts b/src/components/utils/popover.ts index ad051f9c..245910df 100644 --- a/src/components/utils/popover.ts +++ b/src/components/utils/popover.ts @@ -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 { 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); diff --git a/src/components/utils/resolve-aliases.ts b/src/components/utils/resolve-aliases.ts new file mode 100644 index 00000000..b529a914 --- /dev/null +++ b/src/components/utils/resolve-aliases.ts @@ -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(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; +} diff --git a/src/components/utils/search-input.ts b/src/components/utils/search-input.ts index 00fbc74b..18d3b546 100644 --- a/src/components/utils/search-input.ts +++ b/src/components/utils/search-input.ts @@ -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); diff --git a/test/cypress/tests/api/tools.spec.ts b/test/cypress/tests/api/tools.spec.ts index 85ec28ae..94a982a6 100644 --- a/test/cypress/tests/api/tools.spec.ts +++ b/test/cypress/tests/api/tools.spec.ts @@ -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'); + }); }); /** diff --git a/test/cypress/tests/api/tunes.spec.ts b/test/cypress/tests/api/tunes.spec.ts index 3c121380..542aff89 100644 --- a/test/cypress/tests/api/tunes.spec.ts +++ b/test/cypress/tests/api/tunes.spec.ts @@ -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 { diff --git a/test/cypress/tests/utils/flipper.spec.ts b/test/cypress/tests/utils/flipper.spec.ts index 4945ff71..3e6eae7a 100644 --- a/test/cypress/tests/utils/flipper.spec.ts +++ b/test/cypress/tests/utils/flipper.spec.ts @@ -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 => {}, }; diff --git a/test/cypress/tests/utils/popover.spec.ts b/test/cypress/tests/utils/popover.spec.ts index fd5a2bed..2df50364 100644 --- a/test/cypress/tests/utils/popover.spec.ts +++ b/test/cypress/tests/utils/popover.spec.ts @@ -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 => {}, diff --git a/types/configs/popover.d.ts b/types/configs/popover.d.ts index a34fe44c..6689fce2 100644 --- a/types/configs/popover.d.ts +++ b/types/configs/popover.d.ts @@ -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; + confirmation: PopoverItem; onActivate?: never; } diff --git a/types/tools/tool-settings.d.ts b/types/tools/tool-settings.d.ts index c9771627..fa26c882 100644 --- a/types/tools/tool-settings.d.ts +++ b/types/tools/tool-settings.d.ts @@ -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} diff --git a/yarn.lock b/yarn.lock index efd5df20..b07a1487 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"