1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-03 23:50:33 +02:00
Files
joplin/packages/lib/services/plugins/EditorPluginHandler.ts

128 lines
4.0 KiB
TypeScript

// The goal of this class is to simplify the integration of the `joplin.views.editor` plugin logic
// in the desktop and mobile app. See here for more information:
//
// packages/lib/services/plugins/api/JoplinViewsEditor.ts
import Logger from '@joplin/utils/Logger';
import AsyncActionQueue, { IntervalType } from '../../AsyncActionQueue';
import eventManager from '../../eventManager';
import { EditorActivationCheckFilterObject } from './api/types';
import type PluginService from './PluginService';
import WebviewController from './WebviewController';
const logger = Logger.create('EditorPluginHandler');
export interface UpdateEvent {
noteId: string;
newBody: string;
}
interface EmitActivationCheckOptions {
noteId: string;
parentWindowId: string;
}
interface SaveNoteEvent {
id: string;
body: string;
}
export type OnSaveNoteCallback = (updatedNote: SaveNoteEvent)=> void;
const makeNoteUpdateAction = (pluginService: PluginService, event: UpdateEvent, shownEditorViewIds: string[]) => {
return async () => {
for (const viewId of shownEditorViewIds) {
const controller = pluginService.viewControllerByViewId(viewId) as WebviewController;
if (controller) {
controller.emitUpdate({
noteId: event.noteId,
newBody: event.newBody,
});
}
}
};
};
export default class {
private viewUpdateAsyncQueue_ = new AsyncActionQueue(100, IntervalType.Fixed);
private lastNoteState_: UpdateEvent|null = null;
private lastShownEditorViewIds_ = '';
private lastEditorPluginShown_: string|null = null;
public constructor(
private pluginService_: PluginService,
private onSaveNote_: OnSaveNoteCallback,
) {
}
public emitUpdate(event: UpdateEvent, shownEditorViewIds: string[]) {
if (shownEditorViewIds.length === 0) return;
const isEventDifferentFrom = (other: UpdateEvent|null) => {
if (!other) return true;
return event.noteId !== other.noteId || event.newBody !== other.newBody;
};
const shownEditorViewIdsString = shownEditorViewIds.join(',');
const differentEditorViewsShown = shownEditorViewIdsString !== this.lastShownEditorViewIds_;
// lastNoteState_ often contains the last change saved by the editor. As a result,
// if `event` matches `lastNoteState_`, the event was probably caused by the last save.
// In this case, avoid sending an update event (which plugins often interpret as refreshing
// the editor):
const isDifferentFromSave = isEventDifferentFrom(this.lastNoteState_);
if (isDifferentFromSave || differentEditorViewsShown) {
logger.info('emitUpdate:', shownEditorViewIds);
this.viewUpdateAsyncQueue_.push(makeNoteUpdateAction(this.pluginService_, event, shownEditorViewIds));
this.lastNoteState_ = { ...event };
this.lastShownEditorViewIds_ = shownEditorViewIdsString;
}
}
public async emitActivationCheck({ noteId, parentWindowId }: EmitActivationCheckOptions) {
let filterObject: EditorActivationCheckFilterObject = {
activatedEditors: [],
effectiveNoteId: noteId,
windowId: parentWindowId,
};
filterObject = await eventManager.filterEmit('editorActivationCheck', filterObject);
logger.info('emitActivationCheck: responses:', filterObject);
for (const editor of filterObject.activatedEditors) {
const controller = this.pluginService_.pluginById(editor.pluginId).viewController(editor.viewId) as WebviewController;
if (controller.parentWindowId === parentWindowId) {
controller.setActive(editor.isActive);
}
}
}
public onEditorPluginShown(editorViewId: string) {
// Don't double-register callbacks
if (editorViewId === this.lastEditorPluginShown_) {
return;
}
this.lastEditorPluginShown_ = editorViewId;
const controller = this.pluginService_.viewControllerByViewId(editorViewId) as WebviewController;
controller?.onNoteSaveRequested(event => {
this.scheduleSaveNote_(event.noteId, event.body);
});
}
private scheduleSaveNote_(noteId: string, noteBody: string) {
this.lastNoteState_ = {
noteId,
newBody: noteBody,
};
return this.onSaveNote_({
id: noteId,
body: noteBody,
});
}
}