From e3d47f4072c8a7e1522ebafa0fa5871845377003 Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Sat, 31 Aug 2024 02:53:15 +0300 Subject: [PATCH] Separate InlineToolbar UI and business logic (#91) * Separate InlineToolbar UI and business logic * Remova hasActions from InlineToolFacade * Fix lint * Add docs after review * Merge main --- packages/core/src/api/SelectionAPI.ts | 34 +++ packages/core/src/api/index.ts | 22 ++ .../EventBus/core-events/CoreEventType.ts | 12 +- .../core-events/SelectionChangedCoreEvent.ts | 33 +++ .../components/EventBus/core-events/index.ts | 1 + .../core/src/components/SelectionManager.ts | 101 +++++++ packages/core/src/index.ts | 19 +- .../src/tools/facades/InlineToolFacade.ts | 7 - .../tools/internal/inline-tools/link/index.ts | 5 - packages/core/src/ui/Editor/index.ts | 5 + .../InlineToolbarRenderedUIEvent.ts | 25 ++ packages/core/src/ui/InlineToolbar/index.ts | 264 ++++++++---------- packages/core/src/ui/Toolbox/index.ts | 21 +- packages/core/tsconfig.build.json | 3 + .../src/FormattingAdapter/index.ts | 28 +- 15 files changed, 374 insertions(+), 206 deletions(-) create mode 100644 packages/core/src/api/SelectionAPI.ts create mode 100644 packages/core/src/api/index.ts create mode 100644 packages/core/src/components/EventBus/core-events/SelectionChangedCoreEvent.ts create mode 100644 packages/core/src/components/SelectionManager.ts create mode 100644 packages/core/src/ui/InlineToolbar/InlineToolbarRenderedUIEvent.ts diff --git a/packages/core/src/api/SelectionAPI.ts b/packages/core/src/api/SelectionAPI.ts new file mode 100644 index 0000000..7985ab7 --- /dev/null +++ b/packages/core/src/api/SelectionAPI.ts @@ -0,0 +1,34 @@ +import 'reflect-metadata'; +import { Service } from 'typedi'; + +import { SelectionManager } from '../components/SelectionManager.js'; +import { createInlineToolName } from '@editorjs/model'; +import { InlineToolFormatData } from '@editorjs/sdk'; + +/** + * Selection API class + * - provides methods to work with selection + */ +@Service() +export class SelectionAPI { + #selectionManager: SelectionManager; + + /** + * SelectionAPI class constructor + * @param selectionManager - SelectionManager instance to work with selection and inline fotmatting + */ + constructor( + selectionManager: SelectionManager + ) { + this.#selectionManager = selectionManager; + }; + + /** + * Applies passed inline tool to the current selection + * @param toolName - Inline Tool name from the config to apply on the current selection + * @param data - Inline Tool data to apply to the current selection (eg. link data) + */ + public applyInlineToolForCurrentSelection(toolName: string, data?: InlineToolFormatData): void { + this.#selectionManager.applyInlineToolForCurrentSelection(createInlineToolName(toolName), data); + } +} diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts new file mode 100644 index 0000000..de45445 --- /dev/null +++ b/packages/core/src/api/index.ts @@ -0,0 +1,22 @@ +import 'reflect-metadata'; +import { Inject, Service } from 'typedi'; +import { BlocksAPI } from './BlocksAPI.js'; +import { SelectionAPI } from './SelectionAPI.js'; + +/** + * Class gathers all Editor's APIs + */ +@Service() +export class EditorAPI { + /** + * Blocks API instance to work with blocks + */ + @Inject() + public blocks!: BlocksAPI; + + /** + * Selection API instance to work with selection and inline formatting + */ + @Inject() + public selection!: SelectionAPI; +} diff --git a/packages/core/src/components/EventBus/core-events/CoreEventType.ts b/packages/core/src/components/EventBus/core-events/CoreEventType.ts index 4d12b11..105251d 100644 --- a/packages/core/src/components/EventBus/core-events/CoreEventType.ts +++ b/packages/core/src/components/EventBus/core-events/CoreEventType.ts @@ -13,5 +13,15 @@ export enum CoreEventType { /** * Event is fired when a tool is loaded */ - ToolLoaded = 'tool:loaded' + ToolLoaded = 'tool:loaded', + + /** + * Event is fired when InlineTool instance is created + */ + InlineToolCreated = 'tool:inline-tool-created', + + /** + * Event is fired when the selection is changed + */ + SelectionChanged = 'selection:changed' } diff --git a/packages/core/src/components/EventBus/core-events/SelectionChangedCoreEvent.ts b/packages/core/src/components/EventBus/core-events/SelectionChangedCoreEvent.ts new file mode 100644 index 0000000..80c4951 --- /dev/null +++ b/packages/core/src/components/EventBus/core-events/SelectionChangedCoreEvent.ts @@ -0,0 +1,33 @@ +import type { InlineTool } from '@editorjs/sdk'; +import { CoreEventBase } from './CoreEventBase.js'; +import { CoreEventType } from './CoreEventType.js'; +import type { Index, InlineToolName } from '@editorjs/model'; + +/** + * Payload of SelectionChangedCoreEvent custom event + * Contains updated caret index and available inline tools + */ +export interface SelectionChangedCoreEventPayload { + /** + * Updated caret index + */ + readonly index: Index | null; + + /** + * Inline tools available for the current selection + */ + readonly availableInlineTools: Map; +} + +/** + * Class for event that is being fired after the selection is changed + */ +export class SelectionChangedCoreEvent extends CoreEventBase { + /** + * SelectionChangedCoreEvent constructor function + * @param payload - SelectionChangedCoreEvent event payload with updated caret index + */ + constructor(payload: SelectionChangedCoreEventPayload) { + super(CoreEventType.SelectionChanged, payload); + } +} diff --git a/packages/core/src/components/EventBus/core-events/index.ts b/packages/core/src/components/EventBus/core-events/index.ts index 64b8a71..24c6900 100644 --- a/packages/core/src/components/EventBus/core-events/index.ts +++ b/packages/core/src/components/EventBus/core-events/index.ts @@ -2,3 +2,4 @@ export * from './BlockAddedCoreEvent.js'; export * from './BlockRemovedCoreEvent.js'; export * from './ToolLoadedCoreEvent.js'; export * from './CoreEventType.js'; +export * from './SelectionChangedCoreEvent.js'; diff --git a/packages/core/src/components/SelectionManager.ts b/packages/core/src/components/SelectionManager.ts new file mode 100644 index 0000000..3c2cbed --- /dev/null +++ b/packages/core/src/components/SelectionManager.ts @@ -0,0 +1,101 @@ +import 'reflect-metadata'; +import { FormattingAdapter } from '@editorjs/dom-adapters'; +import type { CaretManagerEvents, InlineToolName } from '@editorjs/model'; +import { CaretManagerCaretUpdatedEvent, Index, EditorJSModel, createInlineToolData, createInlineToolName } from '@editorjs/model'; +import { EventType } from '@editorjs/model'; +import { Service } from 'typedi'; +import { CoreEventType, EventBus, ToolLoadedCoreEvent } from './EventBus/index.js'; +import { SelectionChangedCoreEvent } from './EventBus/core-events/SelectionChangedCoreEvent.js'; +import { InlineTool, InlineToolFormatData } from '@editorjs/sdk'; + +/** + * SelectionManager responsible for handling selection changes and applying inline tools formatting + */ +@Service() +export class SelectionManager { + /** + * Editor model instance + * Used for interactions with stored data + */ + #model: EditorJSModel; + + /** + * FormattingAdapter instance + * Used for inline tools attaching and format apply + */ + #formattingAdapter: FormattingAdapter; + + /** + * EventBus instance to exchange events between components + */ + #eventBus: EventBus; + + /** + * Inline Tools instances available for use + */ + #inlineTools: Map = new Map(); + + /** + * @param model - editor model instance + * @param formattingAdapter - needed for applying format to the model + * @param eventBus - EventBus instance to exchange events between components + */ + constructor( + model: EditorJSModel, + formattingAdapter: FormattingAdapter, + eventBus: EventBus + ) { + this.#model = model; + this.#formattingAdapter = formattingAdapter; + this.#eventBus = eventBus; + + this.#eventBus.addEventListener(`core:${CoreEventType.ToolLoaded}`, (event: ToolLoadedCoreEvent) => { + const { tool } = event.detail; + + if (!tool.isInline()) { + return; + } + + const toolInstance = tool.create(); + const name = createInlineToolName(tool.name); + + this.#inlineTools.set(name, toolInstance); + + this.#formattingAdapter.attachTool(name, toolInstance); + }); + + this.#model.addEventListener(EventType.CaretManagerUpdated, (event: CaretManagerEvents) => this.#handleCaretManagerUpdate(event)); + } + + /** + * Handle changes of the caret selection + * @param event - CaretManager event + */ + #handleCaretManagerUpdate(event: CaretManagerEvents): void { + switch (true) { + case event instanceof CaretManagerCaretUpdatedEvent: + this.#eventBus.dispatchEvent(new SelectionChangedCoreEvent({ + index: event.detail.index !== null ? Index.parse(event.detail.index) : null, + /** + * @todo implement filter by current BlockTool configuration + */ + availableInlineTools: this.#inlineTools, + })); + break; + default: + break; + } + } + + /** + * Apply format with data formed in toolbar + * @param toolName - name of the inline tool, whose format would be applied + * @param data - fragment data for the current selection + */ + public applyInlineToolForCurrentSelection(toolName: InlineToolName, data: InlineToolFormatData = {}): void { + /** + * @todo pass to applyFormat inline tool data formed in toolbar + */ + this.#formattingAdapter.applyFormat(toolName, createInlineToolData(data)); + }; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5573c10..06e2b9d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,12 +4,13 @@ import { Container } from 'typedi'; import { composeDataFromVersion2 } from './utils/composeDataFromVersion2.js'; import ToolsManager from './tools/ToolsManager.js'; import { CaretAdapter, FormattingAdapter } from '@editorjs/dom-adapters'; -import { InlineToolbar } from './ui/InlineToolbar/index.js'; import type { CoreConfigValidated } from './entities/Config.js'; import type { CoreConfig } from '@editorjs/sdk'; import { BlocksManager } from './components/BlockManager.js'; import { EditorUI } from './ui/Editor/index.js'; import { ToolboxUI } from './ui/Toolbox/index.js'; +import { InlineToolbarUI } from './ui/InlineToolbar/index.js'; +import { SelectionManager } from './components/SelectionManager.js'; /** * If no holder is provided via config, the editor will be appended to the element with this id @@ -57,16 +58,6 @@ export default class Core { */ #formattingAdapter: FormattingAdapter; - /** - * @todo inline toolbar should subscripe on selection change event called by EventBus - * Inline toolbar is responsible for handling selection changes - * When model selection changes, it determines, whenever to show toolbar element, - * Which calls apply format method of the adapter - * - * null when inline toolbar is not initialized - */ - #inlineToolbar: InlineToolbar | null = null; - /** * @param config - Editor configuration */ @@ -93,7 +84,7 @@ export default class Core { this.#formattingAdapter = new FormattingAdapter(this.#model, this.#caretAdapter); this.#iocContainer.set(FormattingAdapter, this.#formattingAdapter); - + this.#iocContainer.get(SelectionManager); this.#iocContainer.get(BlocksManager); if (config.onModelUpdate !== undefined) { @@ -106,9 +97,6 @@ export default class Core { this.#toolsManager.prepareTools() .then(() => { - this.#inlineToolbar = new InlineToolbar(this.#model, this.#formattingAdapter, this.#toolsManager.inlineTools, this.#config.holder); - this.#iocContainer.set(InlineToolbar, this.#inlineToolbar); - this.#model.initializeDocument({ blocks }); }) .catch((error) => { @@ -123,6 +111,7 @@ export default class Core { const editorUI = this.#iocContainer.get(EditorUI); this.#iocContainer.get(ToolboxUI); + this.#iocContainer.get(InlineToolbarUI); editorUI.render(); } diff --git a/packages/core/src/tools/facades/InlineToolFacade.ts b/packages/core/src/tools/facades/InlineToolFacade.ts index 54e7320..d21c932 100644 --- a/packages/core/src/tools/facades/InlineToolFacade.ts +++ b/packages/core/src/tools/facades/InlineToolFacade.ts @@ -23,13 +23,6 @@ export class InlineToolFacade extends BaseToolFacade { if (event.key === 'Enter') { - /** - * Remove link input, when data formed and trigger callback - */ - linkInput.remove(); - callback({ link: linkInput.value }); } }); diff --git a/packages/core/src/ui/Editor/index.ts b/packages/core/src/ui/Editor/index.ts index 15d5cf6..a2c971c 100644 --- a/packages/core/src/ui/Editor/index.ts +++ b/packages/core/src/ui/Editor/index.ts @@ -4,6 +4,7 @@ import { CoreConfigValidated } from '../../entities/index.js'; import { EventBus } from '../../components/EventBus/index.js'; import { BlockAddedCoreEvent, CoreEventType } from '../../components/EventBus/index.js'; import { ToolboxRenderedUIEvent } from '../Toolbox/index.js'; +import { InlineToolbarRenderedUIEvent } from '../InlineToolbar/InlineToolbarRenderedUIEvent.js'; /** * Editor's main UI renderer for HTML environment @@ -48,6 +49,10 @@ export class EditorUI { this.#eventBus.addEventListener(`ui:toolbox:rendered`, (event: ToolboxRenderedUIEvent) => { this.#addToolbox(event.detail.toolbox); }); + + this.#eventBus.addEventListener(`ui:inline-toolbar:rendered`, (event: InlineToolbarRenderedUIEvent) => { + this.#holder.appendChild(event.detail.toolbar); + }); } /** diff --git a/packages/core/src/ui/InlineToolbar/InlineToolbarRenderedUIEvent.ts b/packages/core/src/ui/InlineToolbar/InlineToolbarRenderedUIEvent.ts new file mode 100644 index 0000000..3d4e9f7 --- /dev/null +++ b/packages/core/src/ui/InlineToolbar/InlineToolbarRenderedUIEvent.ts @@ -0,0 +1,25 @@ +import { UIEventBase } from '../../components/EventBus/index.js'; + +/** + * Payload of the InlineToolbarRenderedUIEvent + * Contains InlineToolbar HTML element + */ +export interface InlineToolbarRenderedUIEventPayload { + /** + * Toolbox HTML element + */ + readonly toolbar: HTMLElement; +} + +/** + * Class for event that is being fired after the inline toolbar is rendered + */ +export class InlineToolbarRenderedUIEvent extends UIEventBase { + /** + * ToolboxRenderedUIEvent constructor function + * @param payload - ToolboxRendered event payload + */ + constructor(payload: InlineToolbarRenderedUIEventPayload) { + super('inline-toolbar:rendered', payload); + } +} diff --git a/packages/core/src/ui/InlineToolbar/index.ts b/packages/core/src/ui/InlineToolbar/index.ts index a2f8143..cc0377f 100644 --- a/packages/core/src/ui/InlineToolbar/index.ts +++ b/packages/core/src/ui/InlineToolbar/index.ts @@ -1,204 +1,182 @@ -import type { FormattingAdapter } from '@editorjs/dom-adapters'; -import type { InlineToolFormatData } from '@editorjs/sdk'; -import type { InlineToolName } from '@editorjs/model'; -import { type EditorJSModel, type TextRange, createInlineToolData, createInlineToolName, Index } from '@editorjs/model'; -import { EventType } from '@editorjs/model'; +import 'reflect-metadata'; +import { Service } from 'typedi'; + import { make } from '@editorjs/dom'; -import type { InlineToolFacade, ToolsCollection } from '../../tools/facades/index.js'; +import { InlineToolbarRenderedUIEvent } from './InlineToolbarRenderedUIEvent.js'; +import { CoreEventType, EventBus, SelectionChangedCoreEvent } from '../../components/EventBus/index.js'; +import { EditorAPI } from '../../api/index.js'; +import { InlineTool, InlineToolFormatData } from '@editorjs/sdk'; +import { InlineToolName } from '@editorjs/model'; +import { CoreConfigValidated } from '../../entities/index.js'; /** - * Class determines, when inline toolbar should be rendered - * Handles caret selection changes - * Forms data required in certain inline tool + * Inline Toolbar UI module + * - renders the inline toolbar with available inline tools + * - listens to the selection change core event + * - handles the inline tools actions via EditorAPI */ -export class InlineToolbar { - /** - * Editor model instance - * Used for interactions with stored data - */ - #model: EditorJSModel; - +@Service() +export class InlineToolbarUI { /** - * Inline tool adapter instance - * Used for inline tools attaching and format apply + * EventBus instance to exchange events between components */ - #formattingAdapter: FormattingAdapter; + #eventBus: EventBus; /** - * Current selection range + * HTML nodes of the inline toolbar */ - #selectionRange: TextRange | undefined = undefined; + #nodes: Record = {}; /** - * Tools that would be attached to the adapter + * EditorAPI instance to apply inline tools */ - #tools: ToolsCollection; + #api: EditorAPI; /** - * Toolbar html element related to the editor + * InlineToolbarUI class constructor + * @param _config - EditorJS validated configuration, not used here + * @param api - EditorAPI instance to apply inline tools + * @param eventBus - EventBus instance to exchange events between components */ - #toolbar: HTMLElement | undefined = undefined; + constructor( + _config: CoreConfigValidated, + api: EditorAPI, + eventBus: EventBus + ) { + this.#eventBus = eventBus; + this.#api = api; - /** - * Actions of the current tool html element rendered inside of the toolbar element - */ - #actionsElement: HTMLElement | undefined = undefined; + this.#render(); - /** - * Holder element of the editor - */ - #holder: HTMLElement; + this.#eventBus.addEventListener(`core:${CoreEventType.SelectionChanged}`, (event: SelectionChangedCoreEvent) => this.#handleSelectionChange(event)); + } /** - * @param model - editor model instance - * @param formattingAdapter - needed for applying format to the model - * @param tools - tools, that should be attached to adapter - * @param holder - editor holder element + * Handles the selection change core event + * @param event - SelectionChangedCoreEvent event */ - constructor(model: EditorJSModel, formattingAdapter: FormattingAdapter, tools: ToolsCollection, holder: HTMLElement) { - this.#model = model; - this.#formattingAdapter = formattingAdapter; - this.#holder = holder; - this.#tools = tools; + #handleSelectionChange(event: SelectionChangedCoreEvent): void { + const { availableInlineTools, index } = event.detail; + const selection = window.getSelection(); + + if ( + !index + || index.textRange === undefined + || (index.textRange[0] === index.textRange[1]) + /** + * Index could contain textRange for native inputs, + * so we need to check if there are ranges in the document selection + */ + || !selection + || !selection.rangeCount + ) { + this.#hide(); - this.#attachTools(); + return; + } - this.#handleSelectionChange(); + this.#updateToolsList(availableInlineTools); + this.#move(); + this.#show(); } /** - * Handle changes of the caret selection + * Renders the Inline Toolbar UI HTML nodes */ - #handleSelectionChange(): void { - /** - * Listen to selection change ivents in model - */ - this.#model.addEventListener(EventType.CaretManagerUpdated, (event) => { - const selection = window.getSelection(); + #render(): void { + this.#nodes.holder = make('div'); - /** - * Get current input with selection - */ - if (selection) { - /** - * Check, that selection is in TEXT_NODE element, it means, that only selection in text contenteditable would render toolbar - * Native inputs do not support inline tags, but they also would have selection, this is why we need this condition - */ - if (selection.focusNode?.nodeType !== Node.TEXT_NODE) { - return; - } - } + this.#nodes.holder.style.display = 'none'; + this.#nodes.holder.style.position = 'absolute'; - if (event.detail.index !== null) { - this.#selectionRange = Index.parse(event.detail.index).textRange; + this.#nodes.buttons = make('div'); + this.#nodes.buttons.style.display = 'flex'; - this.#selectionChanged(); - } - }); + this.#nodes.holder.appendChild(this.#nodes.buttons); + + this.#nodes.actions = make('div'); + + this.#nodes.holder.appendChild(this.#nodes.actions); + + this.#eventBus.dispatchEvent(new InlineToolbarRenderedUIEvent({ toolbar: this.#nodes.holder })); } /** - * Attach all tools passed to the inline tool adapter + * Shows the Inline Toolbar */ - #attachTools(): void { - Array.from(this.#tools.entries()).forEach(([toolName, tool]) => { - this.#formattingAdapter.attachTool(toolName as InlineToolName, tool.create()); - }); + #show(): void { + this.#nodes.holder.style.display = 'block'; } /** - * Change toolbar show state if any text is selected + * Hides the Inline Toolbar */ - #selectionChanged(): void { - /** - * Show or hide inline toolbar - */ - if (this.#selectionRange !== undefined && this.#selectionRange[0] !== this.#selectionRange[1]) { - this.#createToolbarElement(); - } else { - this.#deleteToolbarElement(); - } + #hide(): void { + this.#nodes.holder.style.display = 'none'; } /** - * @todo implement EventBus for ui to subscribe on selectionChange event - * Creates inline toolbar html element + * Moves the Inline Toolbar to the current selection */ - #createToolbarElement(): void { - /** - * Before creating new toolbar element, remove existing one - */ - this.#deleteToolbarElement(); + #move(): void { + const selection = window.getSelection(); - this.#toolbar = make('div'); + if (!selection || !selection.rangeCount) { + return; + } - this.#tools.forEach((tool, toolName) => { - const inlineElementButton = make('button'); + const range = selection.getRangeAt(0); - inlineElementButton.innerHTML = toolName; + const rect = range.getBoundingClientRect(); - /** - * If tool has actions, then on click of the element button we should render actions element - * If tool has no action, then on click of the element button we should apply format - */ - if (tool.hasActions) { - inlineElementButton.addEventListener('click', (_event) => { - this.#renderToolActions(createInlineToolName(toolName)); + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + this.#nodes.holder.style.top = `${rect.top + 16}px`; + this.#nodes.holder.style.left = `${rect.left}px`; + this.#nodes.holder.style.zIndex = '1000'; + } + + /** + * Renders the list of available inline tools in the Inline Toolbar + * @param tools - Inline Tools available for the current selection + */ + #updateToolsList(tools: Map): void { + this.#nodes.buttons.innerHTML = ''; + + Array.from(tools.entries()).forEach(([name, tool]) => { + const button = make('button'); + + button.textContent = name; + + if (Object.hasOwnProperty.call(tool.constructor.prototype, 'renderActions')) { + button.addEventListener('click', () => { + this.#renderToolActions(name, tool); }); } else { - inlineElementButton.addEventListener('click', (_event) => { - this.apply(createInlineToolName(toolName), createInlineToolData({})); + button.addEventListener('click', () => { + this.#api.selection.applyInlineToolForCurrentSelection(name); }); } - if (this.#toolbar !== undefined) { - this.#toolbar.appendChild(inlineElementButton); - } + this.#nodes.buttons.appendChild(button); }); - - this.#holder.appendChild(this.#toolbar); - } - - /** - * Removes inline toolbar html element - */ - #deleteToolbarElement(): void { - this.#toolbar?.remove(); } /** - * Render actions to form data, which is required in tool - * Element that is used for forming data is rendered inside of the tool instance - * This function adds actions element to the toolbar - * @param nameOfTheTool - name of the inline tool, whose format would be applied + * Renders the actions for the inline tool + * @param name - name of the inline tool to render actions for + * @param tool - inline tool instance */ - #renderToolActions(nameOfTheTool: InlineToolName): void { - const elementWithOptions = this.#formattingAdapter.createToolActions(nameOfTheTool, (data: InlineToolFormatData): void => { - this.apply(nameOfTheTool, data); - }); - - if (this.#toolbar === undefined) { - throw new Error('InlineToolbar: can not show tool actions without toolbar'); - } + #renderToolActions(name: string, tool: InlineTool): void { + const { element } = tool.renderActions?.((data: InlineToolFormatData) => { + this.#api.selection.applyInlineToolForCurrentSelection(name, data); + }) ?? { element: null }; - /** - * If actions element already exists, replace it with new one - * This check is needed to prevent displaying of several actions elements - */ - if (this.#actionsElement !== undefined) { - this.#actionsElement.remove(); + if (element === null) { + return; } - this.#actionsElement = elementWithOptions.element; - this.#holder.appendChild(this.#actionsElement); - }; + this.#nodes.actions.innerHTML = ''; - /** - * Apply format of the inline tool to the model - * @param toolName - name of the tool which format would be applied - * @param formatData - formed data required in the inline tool - */ - public apply(toolName: InlineToolName, formatData: InlineToolFormatData): void { - this.#formattingAdapter.applyFormat(toolName, createInlineToolData(formatData)); + this.#nodes.actions.appendChild(element); } } diff --git a/packages/core/src/ui/Toolbox/index.ts b/packages/core/src/ui/Toolbox/index.ts index 0e1b032..1480256 100644 --- a/packages/core/src/ui/Toolbox/index.ts +++ b/packages/core/src/ui/Toolbox/index.ts @@ -2,9 +2,10 @@ import 'reflect-metadata'; import { Service } from 'typedi'; import { BlockToolFacade } from '../../tools/facades/index.js'; import { make } from '@editorjs/dom'; -import { BlocksAPI } from '../../api/BlocksAPI.js'; import { CoreEventType, EventBus, ToolLoadedCoreEvent } from '../../components/EventBus/index.js'; import { ToolboxRenderedUIEvent } from './ToolboxRenderedUIEvent.js'; +import { EditorAPI } from '../../api/index.js'; +import { CoreConfigValidated } from '../../entities/Config.js'; /** * UI module responsible for rendering the toolbox @@ -14,10 +15,9 @@ import { ToolboxRenderedUIEvent } from './ToolboxRenderedUIEvent.js'; @Service() export class ToolboxUI { /** - * BlocksAPI instance to insert blocks - * @todo replace with the full Editor API + * EditorAPI instance to insert blocks */ - #blocksAPI: BlocksAPI; + #api: EditorAPI; /** * EventBus instance to exchange events between components @@ -32,11 +32,16 @@ export class ToolboxUI { /** * ToolboxUI class constructor * @todo - unify the constructor parameters with the other UI plugins - * @param blocksAPI - BlocksAPI instance to insert blocks + * @param _config - EditorJS validated configuration, not used here + * @param api - EditorAPI instance to insert blocks * @param eventBus - EventBus instance to exchange events between components */ - constructor(blocksAPI: BlocksAPI, eventBus: EventBus) { - this.#blocksAPI = blocksAPI; + constructor( + _config: CoreConfigValidated, + api: EditorAPI, + eventBus: EventBus + ) { + this.#api = api; this.#eventBus = eventBus; this.#render(); @@ -73,7 +78,7 @@ export class ToolboxUI { toolButton.textContent = tool.name; toolButton.addEventListener('click', () => { - void this.#blocksAPI.insert(tool.name); + void this.#api.blocks.insert(tool.name); }); this.#nodes.holder.appendChild(toolButton); diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index 8850f3e..9bcd789 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -9,6 +9,9 @@ }, { "path": "../sdk/tsconfig.build.json" + }, + { + "path": "../dom-adapters/tsconfig.build.json" } ] } diff --git a/packages/dom-adapters/src/FormattingAdapter/index.ts b/packages/dom-adapters/src/FormattingAdapter/index.ts index 550e488..c3a72c1 100644 --- a/packages/dom-adapters/src/FormattingAdapter/index.ts +++ b/packages/dom-adapters/src/FormattingAdapter/index.ts @@ -12,7 +12,7 @@ import { } from '@editorjs/model'; import type { CaretAdapter } from '../CaretAdapter/index.js'; import { FormattingAction } from '@editorjs/model'; -import type { InlineTool, InlineToolFormatData, ActionsElementWithOptions } from '@editorjs/sdk'; +import type { InlineTool } from '@editorjs/sdk'; import { surround } from '../utils/surround.js'; /** @@ -137,32 +137,6 @@ export class FormattingAdapter { this.#tools.delete(toolName); } - /** - * Function that call tool's render actions method if it is specified, otherwise triggers callback - * If any data for tool is required - return rendered by tool data form element with options required in toolbar - * If data for tool is not required (tool don't need any data to apply format) - trigger callback with empty data - * - * @param toolName - name of the tool to check if data is required - * @param callback - callback function that should be triggered, when data completely formed - * @returns {ActionsElementWithOptions | null} rendered data form element with options required in toolbar or null if no data required - */ - public createToolActions(toolName: InlineToolName, callback: (data: InlineToolFormatData) => void): ActionsElementWithOptions { - const currentTool = this.#tools.get(toolName); - - if (currentTool === undefined) { - throw new Error(`FormattingAdapter: tool ${toolName} was not attached`); - } - - /** - * If renderActions method specified, render element and return it - */ - if (currentTool.renderActions === undefined) { - throw new Error(`FormattingAdapter: render actions method is not specified in tool ${toolName}`); - } - - return currentTool.renderActions(callback); - } - /** * Format model according to action formed by inline tool instance *