Skip to content
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

Feature: allow for plugins to reload dynamically #390

Merged
merged 10 commits into from
Oct 6, 2022
1 change: 1 addition & 0 deletions addon/components/ce/content-editable.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<div
{{did-insert this.insertedEditorElement}}
{{will-destroy this.teardown}}
{{did-update this.refreshPlugins @plugins}}
contenteditable='true'
class={{@class}}
...attributes
Expand Down
9 changes: 9 additions & 0 deletions addon/components/ce/content-editable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ export default class ContentEditable extends Component<ContentEditableArgs> {
}
}

@action
async refreshPlugins() {
if (this.editorView) {
const transaction = this.editorView?.currentState.createTransaction();
await transaction.setPlugins(this.args.plugins, this.editorView);
this.editorView.dispatch(transaction);
}
}

@action
teardown() {
this.editorView?.tearDown();
Expand Down
33 changes: 25 additions & 8 deletions addon/components/rdfa/rdfa-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import RdfaConfirmationPlugin from '@lblod/ember-rdfa-editor/plugins/rdfa-confir
import { View } from '@lblod/ember-rdfa-editor/core/view';
import { ViewController } from '@lblod/ember-rdfa-editor/core/controllers/view-controller';
import { Serializable } from '@lblod/ember-rdfa-editor/utils/render-spec';
import Transaction from '@lblod/ember-rdfa-editor/core/state/transaction';
import { isPluginStep } from '@lblod/ember-rdfa-editor/core/state/steps/step';

export type PluginConfig =
| string
Expand Down Expand Up @@ -122,14 +124,7 @@ export default class RdfaEditor extends Component<RdfaEditorArgs> {
@action
handleRawEditorInit(view: View) {
this.controller = new ViewController('rdfaEditorComponent', view);
this.toolbarMiddleWidgets =
this.controller.currentState.widgetMap.get('toolbarMiddle') || [];
this.toolbarRightWidgets =
this.controller.currentState.widgetMap.get('toolbarRight') || [];
this.sidebarWidgets =
this.controller.currentState.widgetMap.get('sidebar') || [];
this.insertSidebarWidgets =
this.controller.currentState.widgetMap.get('insertSidebar') || [];
this.updateWidgets();
this.toolbarController = new ViewController('toolbar', view);
this.inlineComponentController = new ViewController(
'inline-component-manager',
Expand All @@ -141,6 +136,9 @@ export default class RdfaEditor extends Component<RdfaEditorArgs> {
if (this.args.rdfaEditorInit) {
this.args.rdfaEditorInit(rdfaDocument);
}
this.controller.addTransactionDispatchListener(
this.onTransactionDispatch.bind(this)
elpoelma marked this conversation as resolved.
Show resolved Hide resolved
);
this.editorLoading = false;
}

Expand Down Expand Up @@ -198,4 +196,23 @@ export default class RdfaEditor extends Component<RdfaEditorArgs> {
});
}
}

onTransactionDispatch(transaction: Transaction) {
if (transaction.steps.some((step) => isPluginStep(step))) {
this.updateWidgets();
}
}

updateWidgets() {
if (this.controller) {
this.toolbarMiddleWidgets =
this.controller.currentState.widgetMap.get('toolbarMiddle') || [];
this.toolbarRightWidgets =
this.controller.currentState.widgetMap.get('toolbarRight') || [];
this.sidebarWidgets =
this.controller.currentState.widgetMap.get('sidebar') || [];
this.insertSidebarWidgets =
this.controller.currentState.widgetMap.get('insertSidebar') || [];
}
}
}
8 changes: 0 additions & 8 deletions addon/core/controllers/controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import LiveMarkSet, {
LiveMarkSetArgs,
} from '@lblod/ember-rdfa-editor/core/model/marks/live-mark-set';
import { MarkSpec } from '@lblod/ember-rdfa-editor/core/model/marks/mark';
import MarksRegistry from '@lblod/ember-rdfa-editor/core/model/marks/marks-registry';
import ModelElement, {
ElementType,
Expand All @@ -22,7 +21,6 @@ import Transaction, {
TransactionStepListener,
} from '../state/transaction';
import { View } from '../view';
import { InlineComponentSpec } from '../model/inline-components/model-inline-component';
import ModelNode from '../model/nodes/model-node';
import { EditorUtils } from '@lblod/ember-rdfa-editor/core/controllers/view-controller';
import { MarkInstanceEntry } from '../model/marks/marks-manager';
Expand Down Expand Up @@ -84,12 +82,6 @@ export default interface Controller {

dispatchTransaction(tr: Transaction, updateView?: boolean): void;

registerWidget(spec: WidgetSpec): void;

registerMark(spec: MarkSpec): void;

registerInlineComponent(component: InlineComponentSpec): void;

modelToView(node: ModelNode): Node | null;

onEvent<E extends AnyEventName>(
Expand Down
24 changes: 1 addition & 23 deletions addon/core/controllers/view-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import GenTreeWalker, {
TreeWalkerFactory,
} from '@lblod/ember-rdfa-editor/utils/gen-tree-walker';
import Datastore from '@lblod/ember-rdfa-editor/utils/datastore/datastore';
import { MarkSpec } from '@lblod/ember-rdfa-editor/core/model/marks/mark';
import { AttributeSpec } from '@lblod/ember-rdfa-editor/utils/render-spec';
import ModelElement, {
ElementType,
} from '@lblod/ember-rdfa-editor/core/model/nodes/model-element';
Expand All @@ -22,16 +20,12 @@ import Transaction, {
import LiveMarkSet, {
LiveMarkSetArgs,
} from '@lblod/ember-rdfa-editor/core/model/marks/live-mark-set';
import { InlineComponentSpec } from '@lblod/ember-rdfa-editor/core/model/inline-components/model-inline-component';
import MapUtils from '@lblod/ember-rdfa-editor/utils/map-utils';
import ModelNode from '@lblod/ember-rdfa-editor/core/model/nodes/model-node';
import {
EditorEventListener,
ListenerConfig,
} from '@lblod/ember-rdfa-editor/utils/event-bus';
import Controller, {
WidgetSpec,
} from '@lblod/ember-rdfa-editor/core/controllers/controller';
import Controller from '@lblod/ember-rdfa-editor/core/controllers/controller';
import { toFilterSkipFalse } from '@lblod/ember-rdfa-editor/utils/model-tree-walker';
import { MarkInstanceEntry } from '../model/marks/marks-manager';

Expand Down Expand Up @@ -131,26 +125,10 @@ export class ViewController implements Controller {
return new ModelElement(type);
}

registerInlineComponent(component: InlineComponentSpec) {
this.currentState.inlineComponentsRegistry.registerComponent(component);
// this._rawEditor.registerComponent(component);
}

getMarksFor(owner: string): Set<MarkInstanceEntry> {
return this.currentState.marksManager.getMarksByOwner(owner);
}

registerWidget(spec: WidgetSpec): void {
MapUtils.setOrPush(this.currentState.widgetMap, spec.desiredLocation, {
controller: this,
...spec,
});
}

registerMark(spec: MarkSpec<AttributeSpec>): void {
this.marksRegistry.registerMark(spec);
}

getConfig(key: string): string | null {
return this.currentState.config.get(key) || null;
}
Expand Down
9 changes: 8 additions & 1 deletion addon/core/model/editor-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* they use the exact same interface to do their thing.
*/
import Controller from '@lblod/ember-rdfa-editor/core/controllers/controller';
import Transaction from '../state/transaction';

export interface EditorPlugin {
controller?: Controller;
Expand Down Expand Up @@ -42,7 +43,13 @@ export interface EditorPlugin {
* However, it is not advised to depend on this fact.
* @param controller Each plugin receives a unique controller instance. It is their only interface to the editor.
*/
initialize(controller: Controller, options: unknown): Promise<void>;
initialize(
transaction: Transaction,
controller: Controller,
options: unknown
): Promise<void>;

willDestroy?(transaction: Transaction): Promise<void>;

handleEvent?(event: InputEvent): { handled: boolean };
}
Expand Down
50 changes: 50 additions & 0 deletions addon/core/state/steps/plugin-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import State, { SayState } from '../index';
import { BaseStep, StepType } from './step';
import ModelPosition from '@lblod/ember-rdfa-editor/core/model/model-position';
import ModelRange from '@lblod/ember-rdfa-editor/core/model/model-range';
import { ResolvedPluginConfig } from '@lblod/ember-rdfa-editor/components/rdfa/rdfa-editor';
import { View } from '../../view';

export default class PluginStep implements BaseStep {
private readonly _type: StepType = 'plugin-step';

private _resultState: State;

readonly configs: ResolvedPluginConfig[] = [];

readonly view: View;

constructor(
initialState: State,
configs: ResolvedPluginConfig[],
view: View
) {
this.configs = configs;
this.view = view;
const state = new SayState({
...initialState,
widgetMap: new Map(),
});
const plugins = this.configs.map((config) => config.instance);
this._resultState = new SayState({
elpoelma marked this conversation as resolved.
Show resolved Hide resolved
...state,
plugins,
});
}

get type(): StepType {
return this._type;
}

get resultState(): State {
return this._resultState;
}

mapPosition(position: ModelPosition): ModelPosition {
return position;
}

mapRange(range: ModelRange): ModelRange {
return range;
}
}
13 changes: 11 additions & 2 deletions addon/core/state/steps/step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SelectionStep from './selection-step';
import ModelPosition from '@lblod/ember-rdfa-editor/core/model/model-position';
import ModelRange from '@lblod/ember-rdfa-editor/core/model/model-range';
import { LeftOrRight } from '@lblod/ember-rdfa-editor/core/model/range-mapper';
import PluginStep from './plugin-step';

export interface BaseStep {
readonly type: StepType;
Expand All @@ -15,9 +16,13 @@ export interface BaseStep {
mapRange(range: ModelRange, bias?: LeftOrRight): ModelRange;
}

export type StepType = 'operation-step' | 'selection-step' | 'config-step';
export type StepType =
| 'operation-step'
| 'selection-step'
| 'config-step'
| 'plugin-step';

export type Step = SelectionStep | OperationStep | ConfigStep;
export type Step = SelectionStep | OperationStep | ConfigStep | PluginStep;

export function isSelectionStep(step: Step): step is SelectionStep {
return step.type === 'selection-step';
Expand All @@ -31,6 +36,10 @@ export function isOperationStep(step: Step): step is OperationStep {
return step.type === 'operation-step';
}

export function isPluginStep(step: Step): step is SelectionStep {
return step.type === 'plugin-step';
}

export type StepResult = {
state: State;
};
37 changes: 34 additions & 3 deletions addon/core/state/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import RangeMapper, { LeftOrRight } from '../model/range-mapper';
import { HtmlReaderContext, readHtml } from '../model/readers/html-reader';
import SelectionReader from '../model/readers/selection-reader';
import { getWindowSelection } from '../../utils/dom-helpers';
import { InitializedPlugin } from '../model/editor-plugin';
import { NotImplementedError } from '../../utils/errors';
import { View } from '../view';
import InsertOperation from '@lblod/ember-rdfa-editor/core/model/operations/insert-operation';
Expand All @@ -37,6 +36,12 @@ import ConfigStep from './steps/config-step';
import { createLogger } from '@lblod/ember-rdfa-editor/utils/logging-utils';
import Operation from '@lblod/ember-rdfa-editor/core/model/operations/operation';
import MarksManager from '../model/marks/marks-manager';
import { ViewController } from '../controllers/view-controller';
import { ResolvedPluginConfig } from '@lblod/ember-rdfa-editor/components/rdfa/rdfa-editor';
import PluginStep from './steps/plugin-step';
import Controller, { WidgetSpec } from '../controllers/controller';
import { InlineComponentSpec } from '../model/inline-components/model-inline-component';
import MapUtils from '@lblod/ember-rdfa-editor/utils/map-utils';

interface TextInsertion {
range: ModelRange;
Expand Down Expand Up @@ -148,8 +153,19 @@ export default class Transaction {
return this._workingCopy.marksManager;
}

setPlugins(plugins: InitializedPlugin[]): void {
this._workingCopy.plugins = plugins;
async setPlugins(configs: ResolvedPluginConfig[], view: View): Promise<void> {
for (const plugin of this.workingCopy.plugins) {
if (plugin.willDestroy) {
await plugin.willDestroy(this);
}
}
const step = new PluginStep(this.workingCopy, configs, view);
this.commitStep(step);
for (const config of configs) {
const plugin = config.instance;
const controller = new ViewController(plugin.name, view);
await plugin.initialize(this, controller, config.options);
}
}

setBaseIRI(iri: string): void {
Expand Down Expand Up @@ -663,6 +679,21 @@ export default class Transaction {
this._commandCache = undefined;
}

registerWidget(spec: WidgetSpec, controller: Controller): void {
MapUtils.setOrPush(this.workingCopy.widgetMap, spec.desiredLocation, {
controller,
...spec,
});
}

registerMark(spec: MarkSpec<AttributeSpec>): void {
this.workingCopy.marksRegistry.registerMark(spec);
}

registerInlineComponent(component: InlineComponentSpec) {
this.workingCopy.inlineComponentsRegistry.registerComponent(component);
}

get commands(): CommandExecutor {
if (!this._commandCache) {
this._commandCache = commandMapToCommandExecutor(
Expand Down
23 changes: 1 addition & 22 deletions addon/core/view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
} from '@lblod/ember-rdfa-editor/input/input-handler';
import { ViewController } from '@lblod/ember-rdfa-editor/core/controllers/view-controller';
import { ResolvedPluginConfig } from '@lblod/ember-rdfa-editor/components/rdfa/rdfa-editor';
import { InitializedPlugin } from '@lblod/ember-rdfa-editor/core/model/editor-plugin';

export type Dispatch = (transaction: Transaction) => void;

Expand Down Expand Up @@ -252,28 +251,8 @@ export async function createEditorView({
}: EditorArgs): Promise<View> {
const state = initialState;
const view = createView({ domRoot, dispatch, initialState: state });
const initPlugins = await initializePlugins(view, plugins);
const tr = view.currentState.createTransaction();
tr.setPlugins(initPlugins);
await tr.setPlugins(plugins, view);
view.dispatch(tr);
return view;
}

/**
* Before use, plugins need to be initialized, which provides them with the controller
* they need to interact with the editor. Since plugins often interact with backends,
* this is async.
* */
async function initializePlugins(
view: View,
configs: ResolvedPluginConfig[]
): Promise<InitializedPlugin[]> {
const result: InitializedPlugin[] = [];
for (const config of configs) {
const plugin = config.instance;
const controller = new ViewController(plugin.name, view);
await plugin.initialize(controller, config.options);
result.push(plugin);
}
return result;
}
7 changes: 6 additions & 1 deletion addon/plugins/anchor/anchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { eventTargetRange } from '@lblod/ember-rdfa-editor/input/utils';
import Controller from '@lblod/ember-rdfa-editor/core/controllers/controller';
import ModelPosition from '@lblod/ember-rdfa-editor/core/model/model-position';
import { EditorPlugin } from '@lblod/ember-rdfa-editor/core/model/editor-plugin';
import Transaction from '@lblod/ember-rdfa-editor/core/state/transaction';

export class AnchorPlugin implements EditorPlugin {
controller!: Controller;

get name() {
return 'anchor';
}
initialize(_controller: Controller, _options: unknown): Promise<void> {
initialize(
_transaction: Transaction,
_controller: Controller,
_options: unknown
): Promise<void> {
this.controller = _controller;
return Promise.resolve();
}
Expand Down
Loading