You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-13 00:10:37 +02:00
Plugins: Add support for editor plugins (#11296)
This commit is contained in:
@ -7,6 +7,7 @@ import JoplinViewsMenus from './JoplinViewsMenus';
|
||||
import JoplinViewsToolbarButtons from './JoplinViewsToolbarButtons';
|
||||
import JoplinViewsPanels from './JoplinViewsPanels';
|
||||
import JoplinViewsNoteList from './JoplinViewsNoteList';
|
||||
import JoplinViewsEditors from './JoplinViewsEditor';
|
||||
|
||||
/**
|
||||
* This namespace provides access to view-related services.
|
||||
@ -25,6 +26,7 @@ export default class JoplinViews {
|
||||
private menus_: JoplinViewsMenus = null;
|
||||
private toolbarButtons_: JoplinViewsToolbarButtons = null;
|
||||
private dialogs_: JoplinViewsDialogs = null;
|
||||
private editors_: JoplinViewsEditors = null;
|
||||
private noteList_: JoplinViewsNoteList = null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
private implementation_: any = null;
|
||||
@ -46,6 +48,11 @@ export default class JoplinViews {
|
||||
return this.panels_;
|
||||
}
|
||||
|
||||
public get editors() {
|
||||
if (!this.editors_) this.editors_ = new JoplinViewsEditors(this.plugin, this.store);
|
||||
return this.editors_;
|
||||
}
|
||||
|
||||
public get menuItems() {
|
||||
if (!this.menuItems_) this.menuItems_ = new JoplinViewsMenuItems(this.plugin, this.store);
|
||||
return this.menuItems_;
|
||||
|
152
packages/lib/services/plugins/api/JoplinViewsEditor.ts
Normal file
152
packages/lib/services/plugins/api/JoplinViewsEditor.ts
Normal file
@ -0,0 +1,152 @@
|
||||
/* eslint-disable multiline-comment-style */
|
||||
|
||||
import eventManager from '../../../eventManager';
|
||||
import Plugin from '../Plugin';
|
||||
import createViewHandle from '../utils/createViewHandle';
|
||||
import WebviewController, { ContainerType } from '../WebviewController';
|
||||
import { ActivationCheckCallback, EditorActivationCheckFilterObject, FilterHandler, ViewHandle, UpdateCallback } from './types';
|
||||
|
||||
/**
|
||||
* Allows creating alternative note editors. You can create a view to handle loading and saving the
|
||||
* note, and do your own rendering.
|
||||
*
|
||||
* Although it may be used to implement an alternative text editor, the more common use case may be
|
||||
* to render the note in a different, graphical way - for example displaying a graph, and
|
||||
* saving/loading the graph data in the associated note. In that case, you would detect whether the
|
||||
* current note contains graph data and, in this case, you'd display your viewer.
|
||||
*
|
||||
* Terminology: An editor is **active** when it can be used to edit the current note. Note that it
|
||||
* doesn't necessarily mean that your editor is visible - it just means that the user has the option
|
||||
* to switch to it (via the "toggle editor" button). A **visible** editor is active and is currently
|
||||
* being displayed.
|
||||
*
|
||||
* To implement an editor you need to listen to two events:
|
||||
*
|
||||
* - `onActivationCheck`: This is a way for the app to know whether your editor should be active or
|
||||
* not. Return `true` from this handler to activate your editor.
|
||||
*
|
||||
* - `onUpdate`: When this is called you should update your editor based on the current note
|
||||
* content. Call `joplin.workspace.selectedNote()` to get the current note.
|
||||
*
|
||||
* - `showEditorPlugin` and `toggleEditorPlugin` commands. Additionally you can use these commands
|
||||
* to display your editor via `joplin.commands.execute('showEditorPlugin')`. This is not always
|
||||
* necessary since the user can switch to your editor using the "toggle editor" button, however
|
||||
* you may want to programmatically display the editor in some cases - for example when creating a
|
||||
* new note specific to your editor.
|
||||
*
|
||||
* Note that only one editor view can be active at a time. This is why it is important not to
|
||||
* activate your view if it's not relevant to the current note. If more than one is active, it is
|
||||
* undefined which editor is going to be used to display the note.
|
||||
*
|
||||
* For an example of editor plugin, see the [YesYouKan
|
||||
* plugin](https://github.com/joplin/plugin-yesyoukan/blob/master/src/index.ts). In particular,
|
||||
* check the logic around `onActivationCheck` and `onUpdate` since this is the entry points for
|
||||
* using this API.
|
||||
*/
|
||||
export default class JoplinViewsEditors {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
private store: any;
|
||||
private plugin: Plugin;
|
||||
private activationCheckHandlers_: Record<string, FilterHandler<EditorActivationCheckFilterObject>> = {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
public constructor(plugin: Plugin, store: any) {
|
||||
this.store = store;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private controller(handle: ViewHandle): WebviewController {
|
||||
return this.plugin.viewController(handle) as WebviewController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new editor view
|
||||
*/
|
||||
public async create(id: string): Promise<ViewHandle> {
|
||||
const handle = createViewHandle(this.plugin, id);
|
||||
const controller = new WebviewController(handle, this.plugin.id, this.store, this.plugin.baseDir, ContainerType.Editor);
|
||||
this.plugin.addViewController(controller);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the editor HTML content
|
||||
*/
|
||||
public async setHtml(handle: ViewHandle, html: string): Promise<string> {
|
||||
return this.controller(handle).html = html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds and loads a new JS or CSS file into the panel.
|
||||
*/
|
||||
public async addScript(handle: ViewHandle, scriptPath: string): Promise<void> {
|
||||
return this.controller(handle).addScript(scriptPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* See [[JoplinViewPanels]]
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
public async onMessage(handle: ViewHandle, callback: Function): Promise<void> {
|
||||
return this.controller(handle).onMessage(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when the editor can potentially be activated - this for example when the current note
|
||||
* is changed, or when the application is opened. At that point should can check the current
|
||||
* note and decide whether your editor should be activated or not. If it should return `true`,
|
||||
* otherwise return `false`.
|
||||
*/
|
||||
public async onActivationCheck(handle: ViewHandle, callback: ActivationCheckCallback): Promise<void> {
|
||||
const handler: FilterHandler<EditorActivationCheckFilterObject> = async (object) => {
|
||||
const isActive = await callback();
|
||||
object.activatedEditors.push({
|
||||
pluginId: this.plugin.id,
|
||||
viewId: handle,
|
||||
isActive: isActive,
|
||||
});
|
||||
return object;
|
||||
};
|
||||
|
||||
this.activationCheckHandlers_[handle] = handler;
|
||||
|
||||
eventManager.filterOn('editorActivationCheck', this.activationCheckHandlers_[handle]);
|
||||
this.plugin.addOnUnloadListener(() => {
|
||||
eventManager.filterOff('editorActivationCheck', this.activationCheckHandlers_[handle]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when the editor content should be updated. This for example when the currently
|
||||
* selected note changes, or when the user makes the editor visible.
|
||||
*/
|
||||
public async onUpdate(handle: ViewHandle, callback: UpdateCallback): Promise<void> {
|
||||
this.controller(handle).onUpdate(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* See [[JoplinViewPanels]]
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
public postMessage(handle: ViewHandle, message: any): void {
|
||||
return this.controller(handle).postMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the editor is active or not.
|
||||
*/
|
||||
public async isActive(handle: ViewHandle): Promise<boolean> {
|
||||
return this.controller(handle).visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the editor is effectively visible or not. If the editor is inactive, this will
|
||||
* return `false`. If the editor is active and the user has switched to it, it will return
|
||||
* `true`. Otherwise it will return `false`.
|
||||
*/
|
||||
public async isVisible(handle: ViewHandle): Promise<boolean> {
|
||||
return this.controller(handle).isVisible();
|
||||
}
|
||||
|
||||
}
|
@ -130,4 +130,8 @@ export default class JoplinViewsPanels {
|
||||
return this.controller(handle).visible;
|
||||
}
|
||||
|
||||
public async isActive(handle: ViewHandle): Promise<boolean> {
|
||||
return this.controller(handle).isActive();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import eventManager, { EventName } from '../../../eventManager';
|
||||
import Setting from '../../../models/Setting';
|
||||
import { FolderEntity } from '../../database/types';
|
||||
import makeListener from '../utils/makeListener';
|
||||
import { Disposable, MenuItem } from './types';
|
||||
import { Disposable, EditContextMenuFilterObject, FilterHandler } from './types';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
@ -18,12 +18,6 @@ import Note from '../../../models/Note';
|
||||
*/
|
||||
import Folder from '../../../models/Folder';
|
||||
|
||||
export interface EditContextMenuFilterObject {
|
||||
items: MenuItem[];
|
||||
}
|
||||
|
||||
type FilterHandler<T> = (object: T)=> Promise<void>;
|
||||
|
||||
enum ItemChangeEventType {
|
||||
Create = 1,
|
||||
Update = 2,
|
||||
|
@ -384,6 +384,26 @@ export interface Rectangle {
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export type ActivationCheckCallback = ()=> Promise<boolean>;
|
||||
|
||||
export type UpdateCallback = ()=> Promise<void>;
|
||||
|
||||
export type VisibleHandler = ()=> Promise<void>;
|
||||
|
||||
export interface EditContextMenuFilterObject {
|
||||
items: MenuItem[];
|
||||
}
|
||||
|
||||
export interface EditorActivationCheckFilterObject {
|
||||
activatedEditors: {
|
||||
pluginId: string;
|
||||
viewId: string;
|
||||
isActive: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type FilterHandler<T> = (object: T)=> Promise<T>;
|
||||
|
||||
// =================================================================
|
||||
// Settings types
|
||||
// =================================================================
|
||||
|
Reference in New Issue
Block a user