Fixed display of conversion menu for blocks without export rule (#2799)

* Fixed display of convert menu for blocks without export rule

According to the workflow script from the documentation:
https://editorjs.io/tools-api/#conversionconfig

* Update CHANGELOG.md

* some improvements and tests

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
This commit is contained in:
VolgaIgor 2024-09-14 00:39:19 +03:00 committed by GitHub
commit c82933616c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 102 additions and 20 deletions

View file

@ -1,5 +1,9 @@
# Changelog
### 2.30.6
`Fix` Fix the display of Convert To near blocks that do not have the conversionConfig.export rule specified
### 2.30.5
`Fix` Fix exported types

View file

@ -51,6 +51,15 @@ export async function getConvertibleToolsForBlock(block: BlockAPI, allBlockTools
const savedData = await block.save() as SavedData;
const blockData = savedData.data;
/**
* Checking that the block's tool has an «export» rule
*/
const blockTool = allBlockTools.find((tool) => tool.name === block.name);
if (blockTool !== undefined && !isToolConvertable(blockTool, 'export')) {
return [];
}
return allBlockTools.reduce((result, tool) => {
/**
* Skip tools without «import» rule specified
@ -59,12 +68,19 @@ export async function getConvertibleToolsForBlock(block: BlockAPI, allBlockTools
return result;
}
/**
* Skip tools that does not specify toolbox
*/
if (tool.toolbox === undefined) {
return result;
}
/** Filter out invalid toolbox entries */
const actualToolboxItems = tool.toolbox.filter((toolboxItem) => {
/**
* Skip items that don't pass 'toolbox' property or do not have an icon
*/
if (isEmpty(toolboxItem) || !toolboxItem.icon) {
if (isEmpty(toolboxItem) || toolboxItem.icon === undefined) {
return false;
}
@ -86,10 +102,10 @@ export async function getConvertibleToolsForBlock(block: BlockAPI, allBlockTools
result.push({
...tool,
toolbox: actualToolboxItems,
});
} as BlockToolAdapter);
return result;
}, []);
}, [] as BlockToolAdapter[]);
}

View file

@ -0,0 +1,23 @@
import type { ConversionConfig } from '@/types/configs/conversion-config';
import ToolMock from './ToolMock';
/**
* This tool has a conversionConfig, but it doesn't have export property.
*
* That means that tool can be created from string, but can't be converted to string.
*/
export class ToolWithoutConversionExport extends ToolMock {
/**
* Rules specified how our Tool can be converted to/from other Tool.
*/
public static get conversionConfig(): ConversionConfig {
return {
import: 'text', // this tool can be created from string
/**
* Here is no "export" property, so this tool can't be converted to string
*/
// export: (data) => data.text,
};
}
}

View file

@ -1,8 +1,8 @@
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
import Header from '@editorjs/header';
import type { ToolboxConfig } from '../../../../types';
import type { ConversionConfig, ToolboxConfig } from '../../../../types';
import type { MenuConfig } from '../../../../types/tools';
import { ToolWithoutConversionExport } from '../../fixtures/tools/ToolWithoutConversionExport';
describe('BlockTunes', function () {
describe('Search', () => {
@ -185,6 +185,39 @@ describe('BlockTunes', function () {
.should('not.exist');
});
it('should not display the ConvertTo control if block has no conversionConfig.export specified', () => {
cy.createEditor({
tools: {
testTool: ToolWithoutConversionExport,
},
data: {
blocks: [
{
type: 'testTool',
data: {
text: 'Some text',
},
},
],
},
}).as('editorInstance');
cy.get('@editorInstance')
.get('[data-cy=editorjs]')
.find('.ce-block')
.click();
cy.get('@editorInstance')
.get('[data-cy=editorjs]')
.find('.ce-toolbar__settings-btn')
.click();
cy.get('@editorInstance')
.get('[data-cy=editorjs]')
.find('.ce-popover-item[data-item-name=convert-to]')
.should('not.exist');
});
it('should not display tool with the same data in "Convert to" menu', () => {
/**
* Tool with several toolbox entries configured
@ -193,9 +226,10 @@ describe('BlockTunes', function () {
/**
* Tool is convertable
*/
public static get conversionConfig(): { import: string } {
public static get conversionConfig(): ConversionConfig {
return {
import: 'text',
export: 'text',
};
}

View file

@ -87,9 +87,9 @@ describe('Flipper', () => {
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE });
/**
* Check whether we focus the Move Up Tune or not
* Check whether we focus the Delete Tune or not
*/
cy.get('[data-item-name="move-up"]')
cy.get('[data-item-name="delete"]')
.should('have.class', 'ce-popover-item--focused');
cy.get('[data-cy=editorjs]')

View file

@ -1,6 +1,6 @@
import { ConversionConfig, PasteConfig, SanitizerConfig } from '../configs';
import { BlockToolData } from './block-tool-data';
import { BaseTool, BaseToolConstructable } from './tool';
import { BaseTool, BaseToolConstructable, BaseToolConstructorOptions } from './tool';
import { ToolConfig } from './tool-config';
import { API, BlockAPI, ToolboxConfig } from '../index';
import { PasteEvent } from './paste-events';
@ -83,10 +83,8 @@ export interface BlockTool extends BaseTool {
/**
* Describe constructor parameters
*/
export interface BlockToolConstructorOptions<D extends object = any, C extends object = any> {
api: API;
export interface BlockToolConstructorOptions<D extends object = any, C extends object = any> extends BaseToolConstructorOptions<C> {
data: BlockToolData<D>;
config: ToolConfig<C>;
block: BlockAPI;
readOnly: boolean;
}

23
types/tools/tool.d.ts vendored
View file

@ -9,15 +9,27 @@ import {MenuConfig} from './menu-config';
export interface BaseTool<RenderReturnType = HTMLElement> {
/**
* Tool`s render method
*
* For Inline Tools may return either HTMLElement (deprecated) or {@link MenuConfig}
*
* For Inline Tools may return either HTMLElement (deprecated) or {@link MenuConfig}
* @see https://editorjs.io/menu-config
*
*
* For Block Tools returns tool`s wrapper html element
*/
render(): RenderReturnType | Promise<RenderReturnType>;
}
export interface BaseToolConstructorOptions<C extends object = any> {
/**
* Editor.js API
*/
api: API;
/**
* Tool configuration
*/
config?: ToolConfig<C>;
}
export interface BaseToolConstructable {
/**
* Define Tool type as Inline
@ -35,11 +47,6 @@ export interface BaseToolConstructable {
*/
title?: string;
/**
* Describe constructor parameters
*/
new (config: {api: API, config?: ToolConfig}): BaseTool;
/**
* Tool`s prepare method. Can be async
* @param data