Skip to content

Commit

Permalink
Merge branch 'main' into implement-transformations
Browse files Browse the repository at this point in the history
  • Loading branch information
nikmel2803 authored Aug 31, 2024
2 parents 078ec4a + e3d47f4 commit 951dde0
Show file tree
Hide file tree
Showing 27 changed files with 597 additions and 309 deletions.
34 changes: 34 additions & 0 deletions packages/core/src/api/SelectionAPI.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
22 changes: 22 additions & 0 deletions packages/core/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Original file line number Diff line number Diff line change
@@ -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<InlineToolName, InlineTool>;
}

/**
* Class for event that is being fired after the selection is changed
*/
export class SelectionChangedCoreEvent extends CoreEventBase<SelectionChangedCoreEventPayload> {
/**
* SelectionChangedCoreEvent constructor function
* @param payload - SelectionChangedCoreEvent event payload with updated caret index
*/
constructor(payload: SelectionChangedCoreEventPayload) {
super(CoreEventType.SelectionChanged, payload);
}
}
1 change: 1 addition & 0 deletions packages/core/src/components/EventBus/core-events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './BlockAddedCoreEvent.js';
export * from './BlockRemovedCoreEvent.js';
export * from './ToolLoadedCoreEvent.js';
export * from './CoreEventType.js';
export * from './SelectionChangedCoreEvent.js';
101 changes: 101 additions & 0 deletions packages/core/src/components/SelectionManager.ts
Original file line number Diff line number Diff line change
@@ -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<InlineToolName, InlineTool> = 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));
};
}
38 changes: 18 additions & 20 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { EditorJSModel } from '@editorjs/model';
import { EditorJSModel, EventType } from '@editorjs/model';
import type { ContainerInstance } from 'typedi';
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
Expand Down Expand Up @@ -57,14 +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
*/
#inlineToolbar: InlineToolbar;

/**
* @param config - Editor configuration
*/
Expand All @@ -91,20 +84,24 @@ 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);

this.#inlineToolbar = new InlineToolbar(this.#model, this.#formattingAdapter, this.#toolsManager.inlineTools, this.#config.holder);
this.#iocContainer.set(InlineToolbar, this.#inlineToolbar);
if (config.onModelUpdate !== undefined) {
this.#model.addEventListener(EventType.Changed, () => {
config.onModelUpdate?.(this.#model);
});
}

this.#prepareUI();

this.#iocContainer.get(BlocksManager);

/**
* @todo avait when isReady API is implemented
*/
void this.#toolsManager.prepareTools();

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

/**
Expand All @@ -114,6 +111,7 @@ export default class Core {
const editorUI = this.#iocContainer.get(EditorUI);

this.#iocContainer.get(ToolboxUI);
this.#iocContainer.get(InlineToolbarUI);

editorUI.render();
}
Expand Down
7 changes: 0 additions & 7 deletions packages/core/src/tools/facades/InlineToolFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ export class InlineToolFacade extends BaseToolFacade<ToolType.Inline, IInlineToo
return this.constructable[InternalInlineToolSettings.Title];
}

/**
* Checks if actions element could be rendered by tool
*/
public get hasActions(): boolean {
return 'renderActions' in this.constructable.prototype;
}

/**
* Constructs new InlineTool instance from constructable
*/
Expand Down
5 changes: 0 additions & 5 deletions packages/core/src/tools/internal/inline-tools/link/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,6 @@ export default class LinkInlineTool implements InlineTool {

linkInput.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Enter') {
/**
* Remove link input, when data formed and trigger callback
*/
linkInput.remove();

callback({ link: linkInput.value });
}
});
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/ui/Editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
});
}

/**
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/ui/InlineToolbar/InlineToolbarRenderedUIEvent.ts
Original file line number Diff line number Diff line change
@@ -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<InlineToolbarRenderedUIEventPayload> {
/**
* ToolboxRenderedUIEvent constructor function
* @param payload - ToolboxRendered event payload
*/
constructor(payload: InlineToolbarRenderedUIEventPayload) {
super('inline-toolbar:rendered', payload);
}
}
Loading

0 comments on commit 951dde0

Please sign in to comment.