Skip to content

Commit

Permalink
Add Toolbox & ToolboxUI
Browse files Browse the repository at this point in the history
  • Loading branch information
gohabereg committed Aug 30, 2024
1 parent 1c8dcf2 commit 4f8555a
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 23 deletions.
39 changes: 39 additions & 0 deletions packages/core/src/api/BlocksAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'reflect-metadata';
import { Inject, Service } from 'typedi';
import { BlocksManager } from '../components/BlockManager';
import { BlockToolData, ToolConfig } from '@editorjs/editorjs';
import { CoreConfigValidated } from '../entities';

@Service()
export class BlocksAPI {
#blocksManager: BlocksManager;
#config: CoreConfigValidated;

constructor(
blocksManager: BlocksManager,
@Inject('EditorConfig') config: CoreConfigValidated
) {
this.#blocksManager = blocksManager;
this.#config = config;
}

public insert(
type: string = this.#config.defaultBlock,
data: BlockToolData = {},
/**
* Not used but left for compatibility
*/
_config: ToolConfig = {},
index?: number,
needToFocus?: boolean,
replace?: boolean,
id?: string
): void {
this.#blocksManager.insert({
type,
data,
index,
replace,
});
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
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 } 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';

interface InsertBlockParameters {
// id?: string;
type?: string;
data?: BlockToolData;
index?: number;
// needToFocus?: boolean;
replace?: boolean;
// tunes?: {[name: string]: BlockTuneData};
}

/**
* BlocksManager is responsible for
Expand Down Expand Up @@ -34,28 +45,55 @@ export class BlocksManager {
*/
#toolsManager: ToolsManager;

#config: CoreConfigValidated;

/**
* BlocksManager constructor
* All parameters are injected thorugh the IoC container
* @param model - Editor's Document Model instance
* @param editorUI - Editor's UI class instance
* @param caretAdapter - Caret Adapter instance
* @param toolsManager - Tools manager instance
* @param config
*/
constructor(
model: EditorJSModel,
editorUI: EditorUI,
caretAdapter: CaretAdapter,
toolsManager: ToolsManager
toolsManager: ToolsManager,
@Inject('EditorConfig') config: CoreConfigValidated
) {
this.#model = model;
this.#editorUI = editorUI;
this.#caretAdapter = caretAdapter;
this.#toolsManager = toolsManager;
this.#config = config;

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

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 @@ -89,15 +127,15 @@ export class BlocksManager {

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

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

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

const block = tool.create({
adapter: blockToolAdapter,
data: data,
data: data.data,
block: {} as BlockAPI,
readOnly: false,
});
Expand All @@ -107,7 +145,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
18 changes: 18 additions & 0 deletions packages/core/src/components/Toolbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'reflect-metadata';
import { Service } from 'typedi';
import ToolsManager from '../tools/ToolsManager.js';
import { ToolboxUI } from '../ui/Toolbox/index.js';

@Service()
export class Toolbox {
constructor(
toolsManager: ToolsManager,
toolboxUI: ToolboxUI
) {
const blockTools = toolsManager.blockTools;

blockTools.forEach((blockTool) => {
toolboxUI.addTool(blockTool);
});
}
}
21 changes: 20 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, InlineToolsAdapter } 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 @@ -92,11 +95,23 @@ export default class Core {
this.#inlineToolbar = new InlineToolbar(this.#model, this.#inlineToolsAdapter, 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 });
}

#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 @@ -121,6 +136,10 @@ export default class Core {
throw new Error('Editor configuration blocks should be an array');
}
}

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

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

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 Expand Up @@ -67,7 +71,7 @@ export class EditorUI {
* @param index - index to validate
*/
#validateIndex(index: number): void {
if (index < 0 || index > this.#blocks.length) {
if (index < 0 || index > this.#blocks.length + 1) {
throw new Error('Index out of bounds');
}
}
Expand Down
36 changes: 36 additions & 0 deletions packages/core/src/ui/Toolbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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';

@Service()
export class ToolboxUI {
#blocksAPI: BlocksAPI;

constructor(blocksAPI: BlocksAPI) {
this.#blocksAPI = blocksAPI;
}

#nodes: Record<string, HTMLElement> = {};

render(): HTMLElement {
this.#nodes.holder = make('div');

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

return this.#nodes.holder;
}

addTool(tool: BlockToolFacade) {
const toolButton = make('button');

toolButton.textContent = tool.name;

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

this.#nodes.holder.appendChild(toolButton);
}
}
40 changes: 28 additions & 12 deletions packages/model/src/entities/BlockNode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,11 @@ export class BlockNode extends EventBus {
* @param value - The new value of the ValueNode
*/
public updateValue<T = unknown>(dataKey: DataKey, value: T): void {
this.#validateKey(dataKey, ValueNode);
try {
this.#validateKey(dataKey, ValueNode);
} catch (_) {
this.#data[dataKey] = this.#createValueNode(dataKey);
}

const node = get(this.#data, dataKey as string) as ValueNode<T>;

Expand All @@ -186,7 +190,11 @@ export class BlockNode extends EventBus {
* @param [start] - char index where to insert text
*/
public insertText(dataKey: DataKey, text: string, start?: number): void {
this.#validateKey(dataKey, TextNode);
try {
this.#validateKey(dataKey, TextNode);
} catch (_) {
this.#data[dataKey] = this.#createTextNode(dataKey);
}

const node = get(this.#data, dataKey as string) as TextNode;

Expand Down Expand Up @@ -287,18 +295,10 @@ export class BlockNode extends EventBus {
if (NODE_TYPE_HIDDEN_PROP in value) {
switch (value[NODE_TYPE_HIDDEN_PROP]) {
case BlockChildType.Value: {
const node = new ValueNode({ value });

this.#listenAndBubbleValueNodeEvent(node, key as DataKey);

return node;
return this.#createValueNode(createDataKey(key), value);
}
case BlockChildType.Text: {
const node = new TextNode(value as TextNodeSerialized);

this.#listenAndBubbleTextNodeEvent(node, key as DataKey);

return node;
return this.#createTextNode(createDataKey(key), value as TextNodeSerialized);
}
}
}
Expand All @@ -316,6 +316,22 @@ export class BlockNode extends EventBus {
this.#data = mapObject(data, mapSerializedToNodes);
}

#createTextNode(key: DataKey, value?: TextNodeSerialized) {

Check failure on line 319 in packages/model/src/entities/BlockNode/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing return type on function

Check warning on line 319 in packages/model/src/entities/BlockNode/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing JSDoc comment
const node = new TextNode(value);

this.#listenAndBubbleTextNodeEvent(node, key);

return node;
}

#createValueNode(key: DataKey, value?: BlockNodeDataSerializedValue) {

Check failure on line 327 in packages/model/src/entities/BlockNode/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing return type on function

Check warning on line 327 in packages/model/src/entities/BlockNode/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing JSDoc comment
const node = new ValueNode({ value });

this.#listenAndBubbleValueNodeEvent(node, key);

return node;
};

/**
* Validates data key and node type
*
Expand Down

0 comments on commit 4f8555a

Please sign in to comment.