Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block conversion update: ability to convert objects with advanced configuration #2824

Open
wants to merge 4 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions src/components/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -729,12 +729,12 @@ export default class Block extends EventsDispatcher<BlockEvents> {
}

/**
* Exports Block data as string using conversion config
* Exports Block data using conversion config
*/
public async exportDataAsString(): Promise<string> {
public async exportData(): Promise<string|object> {
const blockData = await this.data;

return convertBlockDataToString(blockData, this.tool.conversionConfig);
return convertBlockDataForExport(blockData, this.tool.conversionConfig);
}

/**
Expand Down
28 changes: 16 additions & 12 deletions src/components/modules/blockManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
35 changes: 25 additions & 10 deletions src/components/utils/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';


Expand Down Expand Up @@ -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
Expand All @@ -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
*/
Expand Down Expand Up @@ -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)) {
Expand All @@ -162,36 +172,41 @@ 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 '';
}
}

/**
* 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 {
/**
* Tool developer provides 'import' property, but it is not correct. Warn him.
*/
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 {};
Expand Down
4 changes: 2 additions & 2 deletions types/configs/conversion-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
Expand All @@ -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;
}
Loading