-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate InlineToolbar UI and business logic #91
Changes from all commits
7ea146f
d15588e
3a7f004
21d7ecf
5b5f38e
0b8ba02
fe1b213
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
} | ||
} |
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 |
---|---|---|
@@ -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); | ||
} | ||
} |
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment required |
||
|
||
/** | ||
* @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)); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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(); | ||
|
||
Comment on lines
-90
to
-94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ?why this change is needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to keep link input there, doesn't matter atm as UX will be updated anyway |
||
callback({ link: linkInput.value }); | ||
} | ||
}); | ||
|
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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think, that comment, about who would call selection api will be helpfull
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anybody who needs it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anybody who needs it, that's public API