diff --git a/docs/tools.md b/docs/tools.md index 7cc4fd210..8570fad2f 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -449,7 +449,7 @@ It can be a `String` or a `Function`. `String` means a key of your Tool data object that should be used as string to export. -`Function` is a method that accepts your Tool data and compose a string to export from it. See example below: +`Function` is a method that accepts your Tool data and compose a string or object to export from it. See example below: ```js class ListTool { @@ -484,7 +484,7 @@ It can be a `String` or a `Function`. `String` means the key in tool data that will be filled by an exported string. For example, `import: 'text'` means that `constructor` of your block will accept a `data` object with `text` property filled with string composed by original block. -`Function` allows you to specify own logic, how a string should be converted to your tool data. For example: +`Function` allows you to specify own logic, how a string or object should be converted to your tool data. For example: ```js class ListTool { diff --git a/src/components/block/index.ts b/src/components/block/index.ts index 36c4aa2ae..d4e4cd2b2 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -26,7 +26,7 @@ import { isMutationBelongsToElement } from '../utils/mutations'; import type { EditorEventMap } from '../events'; import { FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events'; import type { RedactorDomChangedPayload } from '../events/RedactorDomChanged'; -import { convertBlockDataToString, isSameBlockData } from '../utils/blocks'; +import { convertBlockDataForExport, isSameBlockData } from '../utils/blocks'; import { PopoverItemType } from '@/types/utils/popover/popover-item-type'; /** @@ -729,12 +729,12 @@ export default class Block extends EventsDispatcher { } /** - * Exports Block data as string using conversion config + * Exports Block data using conversion config */ - public async exportDataAsString(): Promise { + public async exportData(): Promise { const blockData = await this.data; - return convertBlockDataToString(blockData, this.tool.conversionConfig); + return convertBlockDataForExport(blockData, this.tool.conversionConfig); } /** diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index 48fec0499..5b5c931dd 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -19,7 +19,7 @@ import { BlockMovedMutationType } from '../../../types/events/block/BlockMoved'; import { BlockChangedMutationType } from '../../../types/events/block/BlockChanged'; import { BlockChanged } from '../events'; import { clean, sanitizeBlocks } from '../utils/sanitizer'; -import { convertStringToBlockData, isBlockConvertable } from '../utils/blocks'; +import { convertExportToBlockData, isBlockConvertable } from '../utils/blocks'; import PromiseQueue from '../utils/promise-queue'; /** @@ -501,10 +501,12 @@ export default class BlockManager extends Module { * 2) Blocks with different Tools if they provides conversionConfig */ } else if (targetBlock.mergeable && isBlockConvertable(blockToMerge, 'export') && isBlockConvertable(targetBlock, 'import')) { - const blockToMergeDataStringified = await blockToMerge.exportDataAsString(); - const cleanData = clean(blockToMergeDataStringified, targetBlock.tool.sanitizeConfig); + let blockToMergeExportData = await blockToMerge.exportData(); + if (_.isString(blockToMergeExportData)) { + blockToMergeExportData = clean(blockToMergeExportData, targetBlock.tool.sanitizeConfig); + } - blockToMergeData = convertStringToBlockData(cleanData, targetBlock.tool.conversionConfig); + blockToMergeData = convertExportToBlockData(blockToMergeExportData, targetBlock.tool.conversionConfig); } if (blockToMergeData === undefined) { @@ -848,22 +850,24 @@ export default class BlockManager extends Module { } /** - * Using Conversion Config "export" we get a stringified version of the Block data + * Using Conversion Config "export" we get a exported version of the Block data */ - const exportedData = await blockToConvert.exportDataAsString(); + let exportedData = await blockToConvert.exportData(); /** - * Clean exported data with replacing sanitizer config + * Clean exported data, if it is a string, with replacing sanitizer config */ - const cleanData: string = clean( - exportedData, - replacingTool.sanitizeConfig - ); + if (_.isString(exportedData)) { + exportedData = clean( + exportedData, + replacingTool.sanitizeConfig + ); + } /** * Now using Conversion Config "import" we compose a new Block data */ - let newBlockData = convertStringToBlockData(cleanData, replacingTool.conversionConfig, replacingTool.settings); + let newBlockData = convertExportToBlockData(exportedData, replacingTool.conversionConfig, replacingTool.settings); /** * Optional data overrides. diff --git a/src/components/utils/blocks.ts b/src/components/utils/blocks.ts index 710cfa5f8..e59a1358d 100644 --- a/src/components/utils/blocks.ts +++ b/src/components/utils/blocks.ts @@ -4,7 +4,7 @@ import type { SavedData } from '../../../types/data-formats'; import type { BlockToolData } from '../../../types/tools/block-tool-data'; import type Block from '../block'; import type BlockToolAdapter from '../tools/block'; -import { isFunction, isString, log, equals, isEmpty } from '../utils'; +import { isFunction, isString, log, equals, isEmpty, isUndefined } from '../utils'; import { isToolConvertable } from './tools'; @@ -60,6 +60,8 @@ export async function getConvertibleToolsForBlock(block: BlockAPI, allBlockTools return []; } + const exportData = convertBlockDataForExport(blockData, blockTool.conversionConfig); + return allBlockTools.reduce((result, tool) => { /** * Skip tools without «import» rule specified @@ -68,6 +70,14 @@ export async function getConvertibleToolsForBlock(block: BlockAPI, allBlockTools return result; } + /** + * Checking that the block is not empty after conversion + */ + const importData = convertExportToBlockData(exportData, tool.conversionConfig); + if (isUndefined(importData) || isEmpty(importData)) { + return result; + } + /** * Skip tools that does not specify toolbox */ @@ -149,7 +159,7 @@ export function areBlocksMergeable(targetBlock: Block, blockToMerge: Block): boo * @param blockData - block data to convert * @param conversionConfig - tool's conversion config */ -export function convertBlockDataToString(blockData: BlockToolData, conversionConfig?: ConversionConfig ): string { +export function convertBlockDataForExport(blockData: BlockToolData, conversionConfig?: ConversionConfig ): string | object { const exportProp = conversionConfig?.export; if (isFunction(exportProp)) { @@ -162,7 +172,7 @@ export function convertBlockDataToString(blockData: BlockToolData, conversionCon */ if (exportProp !== undefined) { log('Conversion «export» property must be a string or function. ' + - 'String means key of saved data object to export. Function should export processed string to export.'); + 'String means key of saved data object to export. Function should export processed string or object to export.'); } return ''; @@ -170,20 +180,25 @@ export function convertBlockDataToString(blockData: BlockToolData, conversionCon } /** - * Using conversionConfig, convert string to block data. + * Using conversionConfig, convert export string|object to block data. * - * @param stringToImport - string to convert + * @param dataToImport - string|object to convert * @param conversionConfig - tool's conversion config * @param targetToolConfig - target tool config, used in conversionConfig.import method */ -export function convertStringToBlockData(stringToImport: string, conversionConfig?: ConversionConfig, targetToolConfig?: ToolConfig): BlockToolData { +export function convertExportToBlockData(dataToImport: string | object, conversionConfig?: ConversionConfig, targetToolConfig?: ToolConfig): BlockToolData { const importProp = conversionConfig?.import; if (isFunction(importProp)) { - return importProp(stringToImport, targetToolConfig); - } else if (isString(importProp)) { + try { + return importProp(dataToImport, targetToolConfig); + } catch (err) { + log('Conversion «import» function returned an error'); + return {}; + } + } else if (isString(importProp) && isString(dataToImport)) { return { - [importProp]: stringToImport, + [importProp]: dataToImport, }; } else { /** @@ -191,7 +206,7 @@ export function convertStringToBlockData(stringToImport: string, conversionConfi */ if (importProp !== undefined) { log('Conversion «import» property must be a string or function. ' + - 'String means key of tool data to import. Function accepts a imported string and return composed tool data.'); + 'String means key of tool data to import. Function accepts a imported string or object and return composed tool data.'); } return {}; diff --git a/types/configs/conversion-config.ts b/types/configs/conversion-config.ts index 0f7e27480..02e7ade98 100644 --- a/types/configs/conversion-config.ts +++ b/types/configs/conversion-config.ts @@ -5,7 +5,7 @@ import type { BlockToolData, ToolConfig } from '../tools'; */ export interface ConversionConfig { /** - * How to import string to this Tool. + * How to import data to this Tool. * * Can be a String or Function: * @@ -22,5 +22,5 @@ export interface ConversionConfig { * 1. String — which property of saved Tool data should be used as exported string. * 2. Function — accepts saved Tool data and create a string to export */ - export?: ((data: BlockToolData) => string) | string; + export?: ((data: BlockToolData) => string | object) | string; }