Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actions/lint/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ runs:
- name: Setup environment
uses: ./.github/actions/setup

- name: Emit declaration for ESLint TypeScript integration
shell: bash
run: yarn workspace ${{ inputs.package-name }} build:declaration

- name: Run ESLint check
shell: bash
run: yarn workspace ${{ inputs.package-name }} lint:ci
1 change: 1 addition & 0 deletions packages/collaboration-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"type": "module",
"scripts": {
"build": "tsc --build tsconfig.build.json",
"build:declaration": "yarn build --emitDeclarationOnly",
"dev": "tsc --project tsconfig.build.json --watch",
"lint": "eslint ./src",
"lint:ci": "yarn lint --max-warnings 0",
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "yarn clear && tsc --build tsconfig.build.json",
"build:declaration": "yarn build --emitDeclarationOnly",
"dev": "yarn build --watch",
"lint": "eslint ./src",
"lint:ci": "eslint --config ./eslint.config.mjs ./src --max-warnings 0",
Expand Down
100 changes: 75 additions & 25 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ import { CollaborationManager } from '@editorjs/collaboration-manager';
import { type DocumentId, EditorJSModel, EventType } from '@editorjs/model';
import type { ContainerInstance } from 'typedi';
import { Container } from 'typedi';
import { CoreEventType, EventBus, UiComponentType } from '@editorjs/sdk';
import {
type BlockToolConstructor,
CoreEventType,
EventBus,
type InlineToolConstructor,
ToolType,
UiComponentType
} from '@editorjs/sdk';
import type { ToolSettings } from './tools/ToolsFactory';
import { composeDataFromVersion2 } from './utils/composeDataFromVersion2.js';
import ToolsManager from './tools/ToolsManager.js';
import { CaretAdapter, FormattingAdapter } from '@editorjs/dom-adapters';
import type { CoreConfigValidated, CoreConfig, EditorjsPluginConstructor } from '@editorjs/sdk';
import type { CoreConfigValidated, CoreConfig, EditorjsPluginConstructor, BlockTuneConstructor, ToolConstructable } from '@editorjs/sdk';
import { BlocksManager } from './components/BlockManager.js';
import { SelectionManager } from './components/SelectionManager.js';
import { EditorAPI } from './api/index.js';
import { generateId } from './utils/uid.js';
import { Paragraph, BoldInlineTool, LinkInlineTool, ItalicInlineTool } from './tools/internal';

/**
* If no holder is provided via config, the editor will be appended to the element with this id
Expand Down Expand Up @@ -69,7 +78,7 @@ export default class Core {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
this.#iocContainer = Container.of(Math.floor(Math.random() * 1e10).toString());

this.validateConfig(config);
this.#validateConfig(config);

this.#config = config as CoreConfigValidated;

Expand Down Expand Up @@ -119,44 +128,85 @@ export default class Core {
eventBus.addEventListener(`core:${CoreEventType.Redo}`, () => {
this.#collaborationManager.redo();
});

this.use(Paragraph);
this.use(BoldInlineTool);
this.use(ItalicInlineTool);
this.use(LinkInlineTool);
}

/**
* Initialize and injects Plugin into the container
* Injects Tool constructor and it's config into the container
* @param tool - Tool constructor class
* @param config - Tool's config
*/
public use(tool: ToolConstructable, config?: Omit<ToolSettings, 'class'>): Core;
/**
* Injects Plugin into the container to initialize on Editor's init
* @param plugin - allows to pass any implementation of editor plugins
*/
public use(plugin: EditorjsPluginConstructor): Core {
const pluginType = plugin.type;

this.#iocContainer.set(pluginType, plugin);
public use(plugin: EditorjsPluginConstructor): Core;
/**
* Overloaded method to register Editor.js Plugins/Tools/etc
* @param pluginOrTool - entity to register
* @param toolConfig - entity configuration
*/
public use(
pluginOrTool: ToolConstructable | EditorjsPluginConstructor,
toolConfig?: Omit<ToolSettings, 'class'>
): Core {
const pluginType = pluginOrTool.type;

switch (pluginType) {
case ToolType.Block:
case ToolType.Inline:
case ToolType.Tune:
this.#iocContainer.set({
id: pluginType,
multiple: true,
value: [pluginOrTool, toolConfig],
});
break;
default:
this.#iocContainer.set(pluginType, pluginOrTool);
}

return this;
}

/**
* Initializes the core
*/
public initialize(): void {
const { blocks } = composeDataFromVersion2(this.#config.data ?? { blocks: [] });
public async initialize(): Promise<void> {
try {
const { blocks } = composeDataFromVersion2(this.#config.data ?? { blocks: [] });

this.initializePlugins();
this.#initializePlugins();

this.#toolsManager.prepareTools()
.then(() => {
this.#model.initializeDocument({ blocks });
})
.then(() => {
this.#collaborationManager.connect();
})
.catch((error) => {
console.error('Editor.js initialization failed', error);
});
await this.#initializeTools();

this.#model.initializeDocument({ blocks });
this.#collaborationManager.connect();
} catch (error) {
console.error('Editor.js initialization failed', error);
}
}

/**
* Initalizes loaded tools
*/
async #initializeTools(): Promise<void> {
const blockTools = this.#iocContainer.getMany<[ BlockToolConstructor, ToolSettings]>(ToolType.Block);
const inlineTools = this.#iocContainer.getMany<[ InlineToolConstructor, ToolSettings]>(ToolType.Inline);
const blockTunes = this.#iocContainer.getMany<[ BlockTuneConstructor, ToolSettings]>(ToolType.Tune);

return this.#toolsManager.prepareTools([...blockTools, ...inlineTools, ...blockTunes]);
}

/**
* Initialize all registered UI plugins
*/
private initializePlugins(): void {
#initializePlugins(): void {
/**
* Get all registered plugin types from the container
*/
Expand All @@ -166,7 +216,7 @@ export default class Core {
const plugin = this.#iocContainer.get<EditorjsPluginConstructor>(pluginType);

if (plugin !== undefined && typeof plugin === 'function') {
this.initializePlugin(plugin);
this.#initializePlugin(plugin);
}
}
}
Expand All @@ -175,7 +225,7 @@ export default class Core {
* Create instance of plugin
* @param plugin - Plugin constructor to initialize
*/
private initializePlugin(plugin: EditorjsPluginConstructor): void {
#initializePlugin(plugin: EditorjsPluginConstructor): void {
const eventBus = this.#iocContainer.get(EventBus);
const api = this.#iocContainer.get(EditorAPI);

Expand All @@ -190,7 +240,7 @@ export default class Core {
* Validate configuration
* @param config - Editor configuration
*/
private validateConfig(config: CoreConfig): void {
#validateConfig(config: CoreConfig): void {
if (config.holder === undefined) {
const holder = document.getElementById(DEFAULT_HOLDER_ID);

Expand Down
82 changes: 55 additions & 27 deletions packages/core/src/tools/ToolsFactory.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,98 @@
/* eslint-disable jsdoc/informative-docs */
import type { BlockToolConstructor, EditorAPI, InlineToolConstructor, UnifiedToolConfig } from '@editorjs/sdk';
import type { EditorAPI } from '@editorjs/sdk';
import { ToolType } from '@editorjs/sdk';
import type { ToolConstructable } from '@editorjs/sdk';
import {
InternalInlineToolSettings,
InternalTuneSettings,
InlineToolFacade,
BlockTuneFacade,
BlockToolFacade
} from '@editorjs/sdk'; ;
} from '@editorjs/sdk';
import type {
ToolConstructable,
EditorConfig,
InlineToolConstructable,
BlockTuneConstructable
ToolSettings as ToolSettingsV2
} from '@editorjs/editorjs';

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

/**
* Need this utility type to override some V2 options
*/
export type ToolSettings = Omit<ToolSettingsV2, 'constructable' | 'class'> & {
/**
* Redefine constructable to match V3
*/
class: ToolConstructable;
};

/**
* Factory to construct classes to work with tools
*/
export class ToolsFactory {
/**
* Tools configuration specified by user
*/
private config: UnifiedToolConfig;
#config: Record<string, ToolSettings>;

/**
* EditorJS API Module
*/

private api: EditorAPI;
#api: EditorAPI;

/**
* EditorJS configuration
*/
private editorConfig: EditorConfig;
#editorConfig: EditorConfig;

/**
* Map of tool settings
*/
#toolsSettings = new Map<string, ToolSettings>();

/**
* ToolsFactory
* @param config - unified tools config for user`s and internal tools
* @param config - unified tools config for user's and internal tools
* @param editorConfig - full Editor.js configuration
* @param api - EditorJS module with all Editor methods
*/
constructor(
config: UnifiedToolConfig,
config: Record<string, ToolSettings>,
editorConfig: EditorConfig,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api: any
) {
this.api = api;
this.config = config;
this.editorConfig = editorConfig;
this.#api = api;
this.#config = config;
this.#editorConfig = editorConfig;
}

/**
* Register tools in the factory
* @param tools - tools to register in the factory
*/
public setTools(tools: [ToolConstructable, ToolSettings][]): void {
tools.forEach(([tool, settings]) => {
this.#toolsSettings.set(tool.name, {
...settings,
class: tool,
});
});
}

/**
* Returns Tool object based on it's type
* @param name - tool name
*/
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
public get(name: string): InlineToolFacade | BlockToolFacade | BlockTuneFacade {
const { class: constructable, isInternal = false, ...config } = this.config[name];
const toolSettings = this.#toolsSettings.get(name);

if (!toolSettings) {
throw new Error(`Tool ${name} is not registered`);
}

const { class: constructable, ...config } = toolSettings;

const Constructor = this.getConstructor(constructable);
const Constructor = this.#getConstructor(constructable);
// const isTune = constructable[InternalTuneSettings.IsTune];

return new Constructor({
Expand All @@ -71,9 +101,8 @@ export class ToolsFactory {
config,
api: {},
// api: this.api.getMethodsForTool(name, isTune),
isDefault: name === this.editorConfig.defaultBlock,
defaultPlaceholder: this.editorConfig.placeholder,
isInternal,
isDefault: name === this.#editorConfig.defaultBlock,
defaultPlaceholder: this.#editorConfig.placeholder,
/**
* @todo implement api.getMethodsForTool
*/
Expand All @@ -85,12 +114,11 @@ export class ToolsFactory {
* Find appropriate Tool object constructor for Tool constructable
* @param constructable - Tools constructable
*/
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
private getConstructor(constructable: ToolConstructable | BlockToolConstructor | InlineToolConstructor): ToolConstructor {
switch (true) {
case (constructable as InlineToolConstructable)[InternalInlineToolSettings.IsInline]:
#getConstructor(constructable: ToolConstructable): ToolConstructor {
switch (constructable.type) {
case ToolType.Inline:
return InlineToolFacade;
case (constructable as BlockTuneConstructable)[InternalTuneSettings.IsTune]:
case ToolType.Tune:
return BlockTuneFacade;
default:
return BlockToolFacade;
Expand Down
Loading
Loading