Skip to content

Commit 9e09a59

Browse files
committed
Implement Tools loading via .use() method
1 parent bffd733 commit 9e09a59

File tree

7 files changed

+157
-57
lines changed

7 files changed

+157
-57
lines changed

packages/core/src/index.ts

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { CollaborationManager } from '@editorjs/collaboration-manager';
2+
import type { ToolSettings } from '@editorjs/editorjs/types/tools/index';
23
import { type DocumentId, EditorJSModel, EventType } from '@editorjs/model';
34
import type { ContainerInstance } from 'typedi';
45
import { Container } from 'typedi';
5-
import { CoreEventType, EventBus, UiComponentType } from '@editorjs/sdk';
6+
import {
7+
type BlockToolConstructor,
8+
CoreEventType,
9+
EventBus,
10+
type InlineToolConstructor,
11+
UiComponentType
12+
} from '@editorjs/sdk';
13+
import { Paragraph } from './tools/internal/block-tools/paragraph/index';
14+
import type { ExtendedToolSettings } from './tools/ToolsFactory';
615
import { composeDataFromVersion2 } from './utils/composeDataFromVersion2.js';
716
import ToolsManager from './tools/ToolsManager.js';
817
import { CaretAdapter, FormattingAdapter } from '@editorjs/dom-adapters';
@@ -69,7 +78,7 @@ export default class Core {
6978
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
7079
this.#iocContainer = Container.of(Math.floor(Math.random() * 1e10).toString());
7180

72-
this.validateConfig(config);
81+
this.#validateConfig(config);
7382

7483
this.#config = config as CoreConfigValidated;
7584

@@ -119,44 +128,79 @@ export default class Core {
119128
eventBus.addEventListener(`core:${CoreEventType.Redo}`, () => {
120129
this.#collaborationManager.redo();
121130
});
131+
132+
// @ts-expect-error - weird TS error, will resolve later
133+
this.use(Paragraph);
122134
}
123135

124136
/**
125-
* Initialize and injects Plugin into the container
137+
* Injects Tool constructor and it's config into the container
138+
* @param tool
139+
* @param config
140+
*/
141+
public use(tool: BlockToolConstructor | InlineToolConstructor, config?: Omit<ToolSettings, 'class'>): Core;
142+
/**
143+
* Injects Plugin into the container to initialize on Editor's init
126144
* @param plugin - allows to pass any implementation of editor plugins
127145
*/
128-
public use(plugin: EditorjsPluginConstructor): Core {
129-
const pluginType = plugin.type;
130-
131-
this.#iocContainer.set(pluginType, plugin);
146+
public use(plugin: EditorjsPluginConstructor): Core;
147+
/**
148+
* Overloaded method to register Editor.js Plugins/Tools/etc
149+
* @param pluginOrTool - entity to register
150+
* @param toolConfig - entity configuration
151+
*/
152+
public use(
153+
pluginOrTool: BlockToolConstructor | InlineToolConstructor | EditorjsPluginConstructor,
154+
toolConfig?: Omit<ToolSettings, 'class'>
155+
): Core {
156+
const pluginType = pluginOrTool.type;
157+
158+
switch (pluginType) {
159+
case 'tool':
160+
this.#iocContainer.set({
161+
id: pluginType,
162+
multiple: true,
163+
value: [pluginOrTool, toolConfig],
164+
});
165+
break;
166+
default:
167+
this.#iocContainer.set(pluginType, pluginOrTool);
168+
}
132169

133170
return this;
134171
}
135172

136173
/**
137174
* Initializes the core
138175
*/
139-
public initialize(): void {
140-
const { blocks } = composeDataFromVersion2(this.#config.data ?? { blocks: [] });
176+
public async initialize(): Promise<void> {
177+
try {
178+
const { blocks } = composeDataFromVersion2(this.#config.data ?? { blocks: [] });
141179

142-
this.initializePlugins();
180+
this.#initializePlugins();
143181

144-
this.#toolsManager.prepareTools()
145-
.then(() => {
146-
this.#model.initializeDocument({ blocks });
147-
})
148-
.then(() => {
149-
this.#collaborationManager.connect();
150-
})
151-
.catch((error) => {
152-
console.error('Editor.js initialization failed', error);
153-
});
182+
await this.#initializeTools();
183+
184+
this.#model.initializeDocument({ blocks });
185+
this.#collaborationManager.connect();
186+
} catch (error) {
187+
console.error('Editor.js initialization failed', error);
188+
}
189+
}
190+
191+
/**
192+
* Initalizes loaded tools
193+
*/
194+
async #initializeTools(): Promise<void> {
195+
const tools = this.#iocContainer.getMany<[ BlockToolConstructor | InlineToolConstructor, ExtendedToolSettings]>('tool');
196+
197+
return this.#toolsManager.prepareTools(tools);
154198
}
155199

156200
/**
157201
* Initialize all registered UI plugins
158202
*/
159-
private initializePlugins(): void {
203+
#initializePlugins(): void {
160204
/**
161205
* Get all registered plugin types from the container
162206
*/
@@ -166,7 +210,7 @@ export default class Core {
166210
const plugin = this.#iocContainer.get<EditorjsPluginConstructor>(pluginType);
167211

168212
if (plugin !== undefined && typeof plugin === 'function') {
169-
this.initializePlugin(plugin);
213+
this.#initializePlugin(plugin);
170214
}
171215
}
172216
}
@@ -175,7 +219,7 @@ export default class Core {
175219
* Create instance of plugin
176220
* @param plugin - Plugin constructor to initialize
177221
*/
178-
private initializePlugin(plugin: EditorjsPluginConstructor): void {
222+
#initializePlugin(plugin: EditorjsPluginConstructor): void {
179223
const eventBus = this.#iocContainer.get(EventBus);
180224
const api = this.#iocContainer.get(EditorAPI);
181225

@@ -190,7 +234,7 @@ export default class Core {
190234
* Validate configuration
191235
* @param config - Editor configuration
192236
*/
193-
private validateConfig(config: CoreConfig): void {
237+
#validateConfig(config: CoreConfig): void {
194238
if (config.holder === undefined) {
195239
const holder = document.getElementById(DEFAULT_HOLDER_ID);
196240

packages/core/src/tools/ToolsFactory.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,43 @@ import type {
1111
ToolConstructable,
1212
EditorConfig,
1313
InlineToolConstructable,
14-
BlockTuneConstructable
14+
BlockTuneConstructable, ToolSettings
1515
} from '@editorjs/editorjs';
1616

17-
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
1817
type ToolConstructor = typeof InlineToolFacade | typeof BlockToolFacade | typeof BlockTuneFacade;
1918

19+
export type ExtendedToolSettings = ToolSettings & {
20+
/**
21+
* Flag shows if a Tool is an internal tool
22+
* @todo do we need this distinction any more?
23+
*/
24+
isInternal: boolean;
25+
};
26+
2027
/**
2128
* Factory to construct classes to work with tools
2229
*/
2330
export class ToolsFactory {
2431
/**
2532
* Tools configuration specified by user
2633
*/
27-
private config: UnifiedToolConfig;
34+
#config: UnifiedToolConfig;
2835

2936
/**
3037
* EditorJS API Module
3138
*/
3239

33-
private api: EditorAPI;
40+
#api: EditorAPI;
3441

3542
/**
3643
* EditorJS configuration
3744
*/
38-
private editorConfig: EditorConfig;
45+
#editorConfig: EditorConfig;
46+
47+
/**
48+
* Map of tool settings
49+
*/
50+
#toolsSettings = new Map<string, ExtendedToolSettings>();
3951

4052
/**
4153
* ToolsFactory
@@ -49,20 +61,38 @@ export class ToolsFactory {
4961
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5062
api: any
5163
) {
52-
this.api = api;
53-
this.config = config;
54-
this.editorConfig = editorConfig;
64+
this.#api = api;
65+
this.#config = config;
66+
this.#editorConfig = editorConfig;
67+
}
68+
69+
/**
70+
* Register tools in the factory
71+
* @param tools - tools to register in the factory
72+
*/
73+
public setTools(tools: [InlineToolConstructor | BlockToolConstructor, ExtendedToolSettings][]): void {
74+
tools.forEach(([tool, settings]) => {
75+
this.#toolsSettings.set(tool.name, {
76+
...settings,
77+
class: tool,
78+
});
79+
});
5580
}
5681

5782
/**
5883
* Returns Tool object based on it's type
5984
* @param name - tool name
6085
*/
61-
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
6286
public get(name: string): InlineToolFacade | BlockToolFacade | BlockTuneFacade {
63-
const { class: constructable, isInternal = false, ...config } = this.config[name];
87+
const toolSettings = this.#toolsSettings.get(name);
88+
89+
if (!toolSettings) {
90+
throw new Error(`Tool ${name} is not registered`);
91+
}
92+
93+
const { class: constructable, isInternal = false, ...config } = toolSettings;
6494

65-
const Constructor = this.getConstructor(constructable);
95+
const Constructor = this.#getConstructor(constructable!);
6696
// const isTune = constructable[InternalTuneSettings.IsTune];
6797

6898
return new Constructor({
@@ -71,8 +101,8 @@ export class ToolsFactory {
71101
config,
72102
api: {},
73103
// api: this.api.getMethodsForTool(name, isTune),
74-
isDefault: name === this.editorConfig.defaultBlock,
75-
defaultPlaceholder: this.editorConfig.placeholder,
104+
isDefault: name === this.#editorConfig.defaultBlock,
105+
defaultPlaceholder: this.#editorConfig.placeholder,
76106
isInternal,
77107
/**
78108
* @todo implement api.getMethodsForTool
@@ -85,8 +115,7 @@ export class ToolsFactory {
85115
* Find appropriate Tool object constructor for Tool constructable
86116
* @param constructable - Tools constructable
87117
*/
88-
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
89-
private getConstructor(constructable: ToolConstructable | BlockToolConstructor | InlineToolConstructor): ToolConstructor {
118+
#getConstructor(constructable: ToolConstructable | BlockToolConstructor | InlineToolConstructor): ToolConstructor {
90119
switch (true) {
91120
case (constructable as InlineToolConstructable)[InternalInlineToolSettings.IsInline]:
92121
return InlineToolFacade;

packages/core/src/tools/ToolsManager.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
import 'reflect-metadata';
66
import { deepMerge, isFunction, isObject, PromiseQueue } from '@editorjs/helpers';
77
import { Inject, Service } from 'typedi';
8-
import { ToolsFactory } from './ToolsFactory.js';
8+
import { type ExtendedToolSettings, ToolsFactory } from './ToolsFactory.js';
99
import { Paragraph } from './internal/block-tools/paragraph/index.js';
1010
import type {
1111
EditorConfig,
@@ -33,8 +33,6 @@ import LinkInlineTool from './internal/inline-tools/link/index.js';
3333
*/
3434
@Service()
3535
export default class ToolsManager {
36-
#tools: EditorConfig['tools'];
37-
3836
/**
3937
* ToolsFactory instance
4038
*/
@@ -122,9 +120,9 @@ export default class ToolsManager {
122120

123121
/**
124122
* Calls tools prepare method if it exists and adds tools to relevant collection (available or unavailable tools)
125-
* @returns Promise<void>
123+
* @param tools - tools to prepare and their settings
126124
*/
127-
public async prepareTools(): Promise<void> {
125+
public async prepareTools(tools: [InlineToolConstructor | BlockToolConstructor, ExtendedToolSettings][]): Promise<void> {
128126
const promiseQueue = new PromiseQueue();
129127

130128
const setToAvailableToolsCollection = (toolName: string, tool: ToolFacadeClass): void => {
@@ -135,15 +133,20 @@ export default class ToolsManager {
135133
}));
136134
};
137135

138-
Object.entries(this.#config).forEach(([toolName, config]) => {
139-
if (isFunction(config.class.prepare)) {
136+
this.#factory.setTools(tools);
137+
138+
tools.forEach(([toolConstructor, config]) => {
139+
const toolName = toolConstructor.name;
140+
141+
// eslint-disable-next-line @typescript-eslint/unbound-method
142+
if (isFunction(toolConstructor.prepare)) {
140143
void promiseQueue.add(async () => {
141144
try {
142145
/**
143146
* TypeScript doesn't get type guard here, so non-null assertion is used
144147
*/
145-
await config.class.prepare!({
146-
toolName: toolName,
148+
await toolConstructor.prepare!({
149+
toolName,
147150
config: config,
148151
});
149152

packages/core/src/tools/internal/block-tools/paragraph/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export type ParagraphConfig = ToolConfig<{
2626
* Base text block tool
2727
*/
2828
export class Paragraph implements BlockTool<ParagraphData, ParagraphConfig> {
29+
public static type = 'tool';
30+
31+
public static name = 'paragraph';
32+
2933
/**
3034
* Adapter for linking block data with the DOM
3135
*/

packages/sdk/src/entities/BlockTool.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type { BlockTool as BlockToolVersion2, BlockToolConstructable as BlockToolConstructableV2, ToolConfig } from '@editorjs/editorjs';
1+
import type {
2+
BlockTool as BlockToolVersion2,
3+
BlockToolConstructable as BlockToolConstructableV2,
4+
ToolConfig
5+
} from '@editorjs/editorjs';
26
import type { BlockToolConstructorOptions as BlockToolConstructorOptionsVersion2 } from '@editorjs/editorjs';
37
import type { ValueSerialized } from '@editorjs/model';
48
import type { BlockToolAdapter } from './BlockToolAdapter.js';
@@ -11,7 +15,6 @@ export interface BlockToolConstructorOptions<
1115
* Data structure describing the tool's input/output data
1216
*/
1317
Data extends BlockToolData = BlockToolData,
14-
1518
/**
1619
* User-end configuration for the tool
1720
*/
@@ -46,7 +49,6 @@ export type BlockTool<
4649
*/
4750
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
4851
Data extends BlockToolData = any,
49-
5052
/**
5153
* User-end configuration for the tool
5254
*
@@ -59,7 +61,21 @@ export type BlockTool<
5961
/**
6062
* Block Tool constructor class
6163
*/
62-
export type BlockToolConstructor = BlockToolConstructableV2 & (new (options: BlockToolConstructorOptions) => BlockTool);
64+
export type BlockToolConstructor<
65+
/**
66+
* Data structure describing the tool's input/output data
67+
*/
68+
Data extends BlockToolData = BlockToolData,
69+
/**
70+
* User-end configuration for the tool
71+
*/
72+
Config extends ToolConfig = ToolConfig
73+
> = BlockToolConstructableV2 & (new (options: BlockToolConstructorOptions<Data, Config>) => BlockTool) & {
74+
/**
75+
* Property specifies that the class is a Tool
76+
*/
77+
type: 'tool';
78+
};
6379

6480
/**
6581
* Data structure describing the tool's input/output data

packages/sdk/src/entities/EventBus/events/core/SelectionChangedCoreEvent.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export interface SelectionChangedCoreEventPayload {
1111
/**
1212
* Updated caret index
1313
*/
14-
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
1514
readonly index: Index | null;
1615

1716
/**

0 commit comments

Comments
 (0)