editor.js/src/components/utils/blocks.ts
Peter Savchenko 1320b047a2
feat(merge): blocks of different types can be merged (#2671)
* feature: possibilities to merge blocks of different types

* fix: remove scope change

* feat: use convert config instead of defined property

* chore:: use built-in function for type check

* fix: remove console.log

* chore: remove styling added by mistakes

* test: add testing for different blocks types merging

* fix: remove unused import

* fix: remove type argument

* fix: use existing functions for data export

* chore: update changelog

* fix: re put await

* fix: remove unnecessary check

* fix: typo in test name

* fix: re-add condition for merge

* test: add caret position test

* fix caret issues, add sanitize

* make if-else statement more clear

* upgrade cypress

* Update cypress.yml

* upd cypress to 13

* make sanitize test simpler

* patch rc version

---------

Co-authored-by: GuillaumeOnepilot <guillaume@onepilot.co>
Co-authored-by: Guillaume Leon <97881811+GuillaumeOnepilot@users.noreply.github.com>
2024-04-01 12:29:47 +03:00

109 lines
3.3 KiB
TypeScript

import type { ConversionConfig } from '../../../types/configs/conversion-config';
import type { BlockToolData } from '../../../types/tools/block-tool-data';
import type Block from '../block';
import { isFunction, isString, log } from '../utils';
/**
* Check if block has valid conversion config for export or import.
*
* @param block - block to check
* @param direction - export for block to merge from, import for block to merge to
*/
export function isBlockConvertable(block: Block, direction: 'export' | 'import'): boolean {
if (!block.tool.conversionConfig) {
return false;
}
const conversionProp = block.tool.conversionConfig[direction];
return isFunction(conversionProp) || isString(conversionProp);
}
/**
* Check if two blocks could be merged.
*
* We can merge two blocks if:
* - they have the same type
* - they have a merge function (.mergeable = true)
* - If they have valid conversions config
*
* @param targetBlock - block to merge to
* @param blockToMerge - block to merge from
*/
export function areBlocksMergeable(targetBlock: Block, blockToMerge: Block): boolean {
/**
* If target block has not 'merge' method, we can't merge blocks.
*
* Technically we can (through the conversion) but it will lead a target block delete and recreation, which is unexpected behavior.
*/
if (!targetBlock.mergeable) {
return false;
}
/**
* Tool knows how to merge own data format
*/
if (targetBlock.name === blockToMerge.name) {
return true;
}
/**
* We can merge blocks if they have valid conversion config
*/
return isBlockConvertable(blockToMerge, 'export') && isBlockConvertable(targetBlock, 'import');
}
/**
* Using conversionConfig, convert block data to string.
*
* @param blockData - block data to convert
* @param conversionConfig - tool's conversion config
*/
export function convertBlockDataToString(blockData: BlockToolData, conversionConfig?: ConversionConfig ): string {
const exportProp = conversionConfig?.export;
if (isFunction(exportProp)) {
return exportProp(blockData);
} else if (isString(exportProp)) {
return blockData[exportProp];
} else {
/**
* Tool developer provides 'export' property, but it is not correct. Warn him.
*/
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.');
}
return '';
}
}
/**
* Using conversionConfig, convert string to block data.
*
* @param stringToImport - string to convert
* @param conversionConfig - tool's conversion config
*/
export function convertStringToBlockData(stringToImport: string, conversionConfig?: ConversionConfig): BlockToolData {
const importProp = conversionConfig?.import;
if (isFunction(importProp)) {
return importProp(stringToImport);
} else if (isString(importProp)) {
return {
[importProp]: stringToImport,
};
} 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.');
}
return {};
}
}