Skip to content

Commit

Permalink
Merge branch 'main' into inline-tool-adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
e11sy committed Aug 30, 2024
2 parents 4efb7c4 + c04c0ea commit 24307b0
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 31 deletions.
67 changes: 67 additions & 0 deletions packages/core/src/api/BlocksAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'reflect-metadata';
import { Inject, Service } from 'typedi';
import { BlocksManager } from '../components/BlockManager.js';
import { BlockToolData, ToolConfig } from '@editorjs/editorjs';
import { CoreConfigValidated } from '../entities/index.js';

/**
* Blocks API
* - provides methods to work with blocks
*/
@Service()
export class BlocksAPI {
/**
* BlocksManager instance to work with blocks
*/
#blocksManager: BlocksManager;

/**
* EditorJS configuration
*/
#config: CoreConfigValidated;

/**
* BlocksAPI class constructor
* @param blocksManager - BlocksManager instance to work with blocks
* @param config - EditorJS configuration
*/
constructor(
blocksManager: BlocksManager,
@Inject('EditorConfig') config: CoreConfigValidated
) {
this.#blocksManager = blocksManager;
this.#config = config;
}

/**
* Inserts a new block to the editor
* @param type - Block tool name to insert
* @param data - Block's initial data
* @param _config - not used but left for compatibility
* @param index - index to insert block at
* @param needToFocus - flag indicates if new block should be focused @todo implement
* @param replace - flag indicates if block at index should be replaced @todo implement
* @param id - id of the inserted block @todo implement
*/
public insert(
type: string = this.#config.defaultBlock,
data: BlockToolData = {},
/**
* Not used but left for compatibility
*/
_config: ToolConfig = {},
index?: number,
needToFocus?: boolean,
replace?: boolean,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
id?: string
): void {
this.#blocksManager.insert({
type,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data,
index,
replace,
});
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
import { BlockAddedEvent, BlockRemovedEvent, EditorJSModel, EventType, ModelEvents } from '@editorjs/model';
import 'reflect-metadata';
import { Service } from 'typedi';
import { EditorUI } from './ui/Editor/index.js';
import { Inject, Service } from 'typedi';
import { EditorUI } from '../ui/Editor/index.js';
import { BlockToolAdapter, CaretAdapter, FormattingAdapter } from '@editorjs/dom-adapters';
import ToolsManager from './tools/ToolsManager.js';
import { BlockAPI } from '@editorjs/editorjs';
import ToolsManager from '../tools/ToolsManager.js';
import { BlockAPI, BlockToolData } from '@editorjs/editorjs';
import { CoreConfigValidated } from '../entities/Config.js';

/**
* Parameters for the BlocksManager.insert() method
*/
interface InsertBlockParameters {
// id?: string;
/**
* Block tool name to insert
*/
type?: string;
/**
* Block's initial data
*/
data?: BlockToolData;
/**
* Index to insert block at
*/
index?: number;
// needToFocus?: boolean;
/**
* Flag indicates if block at index should be replaced
*/
replace?: boolean;
// tunes?: {[name: string]: BlockTuneData};
}

/**
* BlocksManager is responsible for
Expand Down Expand Up @@ -34,6 +60,11 @@ export class BlocksManager {
*/
#toolsManager: ToolsManager;

/**
* Editor's validated user configuration
*/
#config: CoreConfigValidated;

/**
* Will be passed to BlockToolAdapter for rendering inputs` formatted text
*/
Expand All @@ -47,23 +78,56 @@ export class BlocksManager {
* @param caretAdapter - Caret Adapter instance
* @param toolsManager - Tools manager instance
* @param formattingAdapter - will be passed to BlockToolAdapter for rendering inputs` formatted text
* @param config - Editor validated configuration
*/
constructor(
model: EditorJSModel,
editorUI: EditorUI,
caretAdapter: CaretAdapter,
toolsManager: ToolsManager,
formattingAdapter: FormattingAdapter
formattingAdapter: FormattingAdapter,
@Inject('EditorConfig') config: CoreConfigValidated
) {
this.#model = model;
this.#editorUI = editorUI;
this.#caretAdapter = caretAdapter;
this.#toolsManager = toolsManager;
this.#formattingAdapter = formattingAdapter;
this.#config = config;

this.#model.addEventListener(EventType.Changed, event => this.#handleModelUpdate(event));
}

/**
* Inserts a new block to the editor at the specified index
* @param parameters - method paramaters object
* @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.replace - flag indicates if block at index should be replaced
*/
public insert({
// id = undefined,
type = this.#config.defaultBlock,
data = {},
index,
// needToFocus = true,
replace = false,
// tunes = {},
}: InsertBlockParameters = {}): void {
let newIndex = index;

if (newIndex === undefined) {
newIndex = this.#model.length + (replace ? 0 : 1);
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.#model.addBlock({
...data,
name: type,
}, index);
}

/**
* Handles model update events
* Filters only BlockAddedEvent and BlockRemovedEvent
Expand Down Expand Up @@ -97,15 +161,15 @@ export class BlocksManager {

const blockToolAdapter = new BlockToolAdapter(this.#model, this.#caretAdapter, index.blockIndex, this.#formattingAdapter);

const tool = this.#toolsManager.blockTools.get(event.detail.data.name);
const tool = this.#toolsManager.blockTools.get(data.name);

if (tool === undefined) {
throw new Error(`[BlockManager] Block Tool ${event.detail.data.name} not found`);
}

const block = tool.create({
adapter: blockToolAdapter,
data: data,
data: data.data,
block: {} as BlockAPI,
readOnly: false,
});
Expand All @@ -115,7 +179,7 @@ export class BlocksManager {

this.#editorUI.addBlock(blockElement, index.blockIndex);
} catch (error) {
console.error(`[BlockManager] Block Tool ${event.detail.data.name} failed to render`, error);
console.error(`[BlockManager] Block Tool ${data.name} failed to render`, error);
}
}

Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/components/Toolbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'reflect-metadata';
import { Service } from 'typedi';
import ToolsManager from '../tools/ToolsManager.js';
import { ToolboxUI } from '../ui/Toolbox/index.js';

/**
* Class responsible for Toolbox business logic
* - calls ToolboxUI to render the tools buttons in the toolbox
*/
@Service()
export class Toolbox {
/**
* Toolbox class constructor, all parameters are injected through the IoC container
* @param toolsManager - ToolsManager instance to get block tools
* @param toolboxUI - ToolboxUI instance to render tools buttons in the toolbox
*/
constructor(
toolsManager: ToolsManager,
toolboxUI: ToolboxUI
) {
const blockTools = toolsManager.blockTools;

blockTools.forEach((blockTool) => {
toolboxUI.addTool(blockTool);
});
}
}
24 changes: 23 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ 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 './BlockManager.js';
import { BlocksManager } from './components/BlockManager.js';
import { EditorUI } from './ui/Editor/index.js';
import { ToolboxUI } from './ui/Toolbox/index.js';
import { Toolbox } from './components/Toolbox.js';

/**
* If no holder is provided via config, the editor will be appended to the element with this id
Expand Down Expand Up @@ -94,11 +97,26 @@ export default class Core {
this.#inlineToolbar = new InlineToolbar(this.#model, this.#formattingAdapter, this.#toolsManager.inlineTools, this.#config.holder);
this.#iocContainer.set(InlineToolbar, this.#inlineToolbar);

this.#prepareUI();

this.#iocContainer.get(BlocksManager);
this.#iocContainer.get(Toolbox);

this.#model.initializeDocument({ blocks });
}

/**
* Renders Editor`s UI
*/
#prepareUI(): void {
const editorUI = this.#iocContainer.get(EditorUI);
const toolboxUI = this.#iocContainer.get(ToolboxUI);

editorUI.render();

editorUI.addToolbox(toolboxUI.render());
}

/**
* Validate configuration
* @param config - Editor configuration
Expand All @@ -123,6 +141,10 @@ export default class Core {
throw new Error('Editor configuration blocks should be an array');
}
}

if (config.defaultBlock === undefined) {
config.defaultBlock = 'paragraph';
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/ui/Editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export class EditorUI {
// will add UI to holder element
}

/**
* Adds toolbox to the editor UI
* @param toolboxElement - toolbox HTML element to add to the page
*/
public addToolbox(toolboxElement: HTMLElement): void {
this.#holder.appendChild(toolboxElement);
}

/**
* Renders block's content on the page
* @param blockElement - block HTML element to add to the page
Expand Down
60 changes: 60 additions & 0 deletions packages/core/src/ui/Toolbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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';

/**
* UI module responsible for rendering the toolbox
* - renders tool buttons in the toolbox
* - listens to the click event on the tool buttons to insert blocks
*/
@Service()
export class ToolboxUI {
/**
* BlocksAPI instance to insert blocks
* @todo replace with the full Editor API
*/
#blocksAPI: BlocksAPI;

/**
* Object with Toolbox HTML nodes
*/
#nodes: Record<string, HTMLElement> = {};

/**
* ToolboxUI class constructor
* @param blocksAPI - BlocksAPI instance to insert blocks
*/
constructor(blocksAPI: BlocksAPI) {
this.#blocksAPI = blocksAPI;
}

/**
* Renders Toolbox UI
* @returns Toolbox HTML element
*/
public render(): HTMLElement {
this.#nodes.holder = make('div');

this.#nodes.holder.style.display = 'flex';

return this.#nodes.holder;
}

/**
* Renders tool button in the toolbox
* @param tool - Block tool to add to the toolbox
*/
public addTool(tool: BlockToolFacade): void {
const toolButton = make('button');

toolButton.textContent = tool.name;

toolButton.addEventListener('click', () => {
void this.#blocksAPI.insert(tool.name);
});

this.#nodes.holder.appendChild(toolButton);
}
}
Loading

0 comments on commit 24307b0

Please sign in to comment.