From 4fa0cd444941be040008f13066127a173a39663b Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Fri, 11 Apr 2025 02:19:22 +0200 Subject: [PATCH] Implement some BlocksAPI method --- packages/core/src/api/BlocksAPI.ts | 52 ++++++++- packages/core/src/components/BlockManager.ts | 87 ++++++++++++++- packages/model/src/EditorJSModel.ts | 31 ++---- .../src/entities/EditorDocument/index.ts | 31 +++--- packages/sdk/src/api/BlocksAPI.ts | 100 ++++++++++++++++++ 5 files changed, 259 insertions(+), 42 deletions(-) diff --git a/packages/core/src/api/BlocksAPI.ts b/packages/core/src/api/BlocksAPI.ts index 4813fce..9c797f9 100644 --- a/packages/core/src/api/BlocksAPI.ts +++ b/packages/core/src/api/BlocksAPI.ts @@ -4,6 +4,7 @@ import { BlocksManager } from '../components/BlockManager.js'; import { BlockToolData, ToolConfig } from '@editorjs/editorjs'; import { CoreConfigValidated } from '@editorjs/sdk'; import { BlocksAPI as BlocksApiInterface } from '@editorjs/sdk'; +import { type BlockNodeSerialized, EditorDocumentSerialized } from '@editorjs/model'; /** * Blocks API @@ -28,12 +29,60 @@ export class BlocksAPI implements BlocksApiInterface { */ constructor( blocksManager: BlocksManager, - @Inject('EditorConfig') config: CoreConfigValidated + @Inject('EditorConfig') config: CoreConfigValidated ) { this.#blocksManager = blocksManager; this.#config = config; } + /** + * Remove all blocks from Document + */ + public clear(): void { + return this.#blocksManager.clear(); + } + + /** + * Render passed data + * @param document - serialized document data to render + */ + public render(document: EditorDocumentSerialized): void { + return this.#blocksManager.render(document); + } + + /** + * Removes Block by index, or current block if index is not passed + * @param index - index of a block to delete + */ + public delete(index?: number): void { + return this.#blocksManager.deleteBlock(index); + } + + /** + * Moves a block to a new index + * @param toIndex - index where the block is moved to + * @param [fromIndex] - block to move. Current block if not passed + */ + public move(toIndex: number, fromIndex?: number): void { + return this.#blocksManager.move(toIndex, fromIndex); + } + + /** + * Returns Blocks count + */ + public getBlocksCount(): number { + return this.#blocksManager.blocksCount; + } + + /** + * Inserts several Blocks to specified index + * @param blocks - array of blocks to insert + * @param [index] - index to insert blocks at. If undefined, inserts at the end + */ + public insertMany(blocks: BlockNodeSerialized[], index?: number): void { + return this.#blocksManager.insertMany(blocks, index); + } + /** * Inserts a new block to the editor * @param type - Block tool name to insert @@ -62,6 +111,7 @@ export class BlocksAPI implements BlocksApiInterface { data, index, replace, + // needToFocus, }); } } diff --git a/packages/core/src/components/BlockManager.ts b/packages/core/src/components/BlockManager.ts index 81320ff..86c56e9 100644 --- a/packages/core/src/components/BlockManager.ts +++ b/packages/core/src/components/BlockManager.ts @@ -1,4 +1,11 @@ -import { BlockAddedEvent, BlockRemovedEvent, EditorJSModel, EventType, ModelEvents } from '@editorjs/model'; +import { + BlockAddedEvent, type BlockNodeSerialized, + BlockRemovedEvent, + type EditorDocumentSerialized, + EditorJSModel, + EventType, + ModelEvents +} from '@editorjs/model'; import 'reflect-metadata'; import { Inject, Service } from 'typedi'; import { BlockToolAdapter, CaretAdapter, FormattingAdapter } from '@editorjs/dom-adapters'; @@ -68,6 +75,13 @@ export class BlocksManager { */ #formattingAdapter: FormattingAdapter; + /** + * Returns Blocks count + */ + public get blocksCount(): number { + return this.#model.length; + } + /** * BlocksManager constructor * All parameters are injected thorugh the IoC container @@ -102,6 +116,7 @@ export class BlocksManager { * @param parameters.type - block tool name to insert * @param parameters.data - block's initial data * @param parameters.index - index to insert block at + // * @param parameters.needToFocus - flag indicates if caret should be set to block after insert * @param parameters.replace - flag indicates if block at index should be replaced */ public insert({ @@ -119,10 +134,78 @@ export class BlocksManager { newIndex = this.#model.length + (replace ? 0 : 1); } + if (replace) { + this.#model.removeBlock(this.#config.userId, newIndex); + } + this.#model.addBlock(this.#config.userId, { ...data, name: type, - }, index); + }, newIndex); + } + + /** + * Inserts several Blocks to specified index + * @param blocks - array of blocks to insert + * @param [index] - index to insert blocks at. If undefined, inserts at the end + */ + public insertMany(blocks: BlockNodeSerialized[], index: number = this.#model.length + 1): void { + blocks.forEach((block, i) => this.#model.addBlock(this.#config.userId, block, index + i)); + } + + /** + * Re-initialize document + * @param document - serialized document data + */ + public render(document: EditorDocumentSerialized): void { + this.#model.initializeDocument(document); + } + + /** + * Remove all blocks from Document + */ + public clear(): void { + this.#model.clearBlocks(); + } + + /** + * Removes Block by index, or current block if index is not passed + * @param index - index of a block to delete + */ + public deleteBlock(index: number | undefined = this.#getCurrentBlockIndex()): void { + if (index === undefined) { + /** + * @todo see what happens in legacy + */ + throw new Error('No block selected to delete'); + } + + this.#model.removeBlock(this.#config.userId, index); + } + + /** + * Moves a block to a new index + * @param toIndex - index where the block is moved to + * @param [fromIndex] - block to move. Current block if not passed + */ + public move(toIndex: number, fromIndex: number | undefined = this.#getCurrentBlockIndex()): void { + if (fromIndex === undefined) { + throw new Error('No block selected to move'); + } + + const block = this.#model.serialized.blocks[fromIndex]; + + this.#model.removeBlock(this.#config.userId, fromIndex); + this.#model.addBlock(this.#config.userId, block, toIndex + (fromIndex <= toIndex ? 1 : 0)); + } + + /** + * Returns block index where user caret is placed + */ + #getCurrentBlockIndex(): number | undefined { + const caretIndex = this.#caretAdapter.userCaretIndex; + + return caretIndex?.blockIndex; } /** diff --git a/packages/model/src/EditorJSModel.ts b/packages/model/src/EditorJSModel.ts index d5ed474..6c799ea 100644 --- a/packages/model/src/EditorJSModel.ts +++ b/packages/model/src/EditorJSModel.ts @@ -1,6 +1,6 @@ // Stryker disable all -- we don't count mutation test coverage fot this file as it just proxy calls to EditorDocument /* istanbul ignore file -- we don't count test coverage fot this file as it just proxy calls to EditorDocument */ -import { type Index, IndexBuilder } from './entities/index.js'; +import { type EditorDocumentSerialized, type Index, IndexBuilder } from './entities/index.js'; import { type BlockNodeSerialized, EditorDocument } from './entities/index.js'; import { BlockAddedEvent, @@ -108,11 +108,17 @@ export class EditorJSModel extends EventBus { /** * Fills the EditorDocument with the provided blocks. * - * @param doc - document options - * @param doc.blocks - The blocks to fill the EditorDocument with. + * @param document - document data to initialize */ - public initializeDocument({ blocks }: { blocks: BlockNodeSerialized[] }): void { - this.#document.initialize(blocks); + public initializeDocument(document: Partial & Pick): void { + this.#document.initialize(document); + } + + /** + * Clear all blocks + */ + public clearBlocks(): void { + this.#document.clear(); } /** @@ -193,21 +199,6 @@ export class EditorJSModel extends EventBus { return this.#document.addBlock(...parameters); } - /** - * Moves a BlockNode from one index to another - * - * @param _userId - user identifier which is being set to the context - * @param parameters = moveBlock method parameters - * @param parameters.from - The index of the BlockNode to move - * @param parameters.to - The index to move the BlockNode to - * @throws Error if the index is out of bounds - */ - @WithContext - public moveBlock(_userId: string | number, ...parameters: Parameters): ReturnType { - return this.#document.moveBlock(...parameters); - } - - /** * Removes a BlockNode from the EditorDocument at the specified index. * diff --git a/packages/model/src/entities/EditorDocument/index.ts b/packages/model/src/entities/EditorDocument/index.ts index d8f35aa..108bfb8 100644 --- a/packages/model/src/entities/EditorDocument/index.ts +++ b/packages/model/src/entities/EditorDocument/index.ts @@ -79,14 +79,23 @@ export class EditorDocument extends EventBus { /** * Initializes EditorDocument with passed blocks * - * @param blocks - document serialized blocks + * @param document - document serialized data */ - public initialize(blocks: BlockNodeSerialized[]): void { + public initialize(document: Partial & Pick): void { this.clear(); - blocks.forEach((block) => { + if (document.identifier !== undefined) { + this.identifier = document.identifier as DocumentId; + } + + document.blocks.forEach((block) => { this.addBlock(block); }); + + if (document.properties) { + Object.entries(document.properties) + .forEach(([name, value]) => this.setProperty(name, value)); + } } /** @@ -137,22 +146,6 @@ export class EditorDocument extends EventBus { this.dispatchEvent(new BlockAddedEvent(builder.build(), blockNode.serialized, getContext()!)); } - /** - * Moves a BlockNode from one index to another - * - * @param from - The index of the BlockNode to move - * @param to - The index to move the BlockNode to - * @throws Error if the index is out of bounds - */ - public moveBlock(from: number, to: number): void { - this.#checkIndexOutOfBounds(from); - this.#checkIndexOutOfBounds(to); - - const blockToMove = this.#children.splice(from, 1)[0]; - - this.#children.splice(to, 0, blockToMove); - } - /** * Removes a BlockNode from the EditorDocument at the specified index. * diff --git a/packages/sdk/src/api/BlocksAPI.ts b/packages/sdk/src/api/BlocksAPI.ts index 5aa0bff..83fee53 100644 --- a/packages/sdk/src/api/BlocksAPI.ts +++ b/packages/sdk/src/api/BlocksAPI.ts @@ -1,4 +1,5 @@ import type { BlockToolData, ToolConfig } from '@editorjs/editorjs'; +import type { BlockNodeSerialized, EditorDocumentSerialized } from '@editorjs/model'; /** * Blocks API interface @@ -7,6 +8,7 @@ import type { BlockToolData, ToolConfig } from '@editorjs/editorjs'; export interface BlocksAPI { /** * Inserts a new block to the editor + * @todo return block api? * @param type - Block tool name to insert * @param data - Block's initial data * @param _config - not used but left for compatibility @@ -24,4 +26,102 @@ export interface BlocksAPI { replace?: boolean, id?: string ): void; + + /** + * Remove all blocks from Document + */ + clear(): void; + + /** + * Render passed data + * @param document - serialized document data to render + */ + render(document: EditorDocumentSerialized): void; + + /** + * Render passed HTML string + * @param data + * @returns + */ + // renderFromHTML(data: string): Promise; + + /** + * Removes Block by index, or current block if index is not passed + * @param index - index of a block to delete + */ + delete(index?: number): void; + + /** + * Moves a block to a new index + * @param toIndex - index where the block is moved to + * @param [fromIndex] - block to move. Current block if not passed + */ + move(toIndex: number, fromIndex?: number): void; + + /** + * Returns Block API object by passed Block index + * @param index + */ + // getBlockByIndex(index: number): BlockAPI | undefined; + + /** + * Returns Block API object by passed Block id + * @param id - id of the block + */ + // getById(id: string): BlockAPI | null; + + /** + * Returns current Block index + * @returns + */ + // getCurrentBlockIndex(): number; + + /** + * Returns the index of Block by id; + */ + // getBlockIndex(blockId: string): number; + + /** + * Get Block API object by html element + * @param element - html element to get Block by + */ + // getBlockByElement(element: HTMLElement): BlockAPI | undefined; + + /** + * Returns Blocks count + */ + getBlocksCount(): number; + + /** + * Inserts several Blocks to specified index + * @param blocks - array of blocks to insert + * @param [index] - index to insert blocks at. If undefined, inserts at the end + */ + insertMany( + blocks: BlockNodeSerialized[], + index?: number, + ): void; // BlockAPI[]; + + /** + * Creates data of an empty block with a passed type. + * @param toolName - block tool name + */ + // composeBlockData(toolName: string): Promise; + + /** + * Updates block data by id + * @param id - id of the block to update + * @param data - (optional) the new data. Can be partial. + * @param tunes - (optional) tune data + */ + // update(id: string, data?: Partial, tunes?: { [name: string]: BlockTuneData }): Promise; + + /** + * Converts block to another type. Both blocks should provide the conversionConfig. + * @param id - id of the existed block to convert. Should provide 'conversionConfig.export' method + * @param newType - new block type. Should provide 'conversionConfig.import' method + * @param dataOverrides - optional data overrides for the new block + * @throws Error if conversion is not possible + */ + // convert(id: string, newType: string, dataOverrides?: BlockToolData): Promise; }