1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Plugins: Add support for joplin.workspace.filterEditorContextMenu to allow dynamically setting editor menu items depending on context

This commit is contained in:
Laurent Cozic 2021-12-27 17:37:50 +01:00
parent 941445a238
commit 960863fb75
5 changed files with 63 additions and 15 deletions

View File

@ -36,6 +36,8 @@ const MenuItem = bridge().MenuItem;
import { reg } from '@joplin/lib/registry';
import ErrorBoundary from '../../../ErrorBoundary';
import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
import eventManager from '@joplin/lib/eventManager';
import { EditContextMenuFilterObject } from '@joplin/lib/services/plugins/api/JoplinWorkspace';
const menuUtils = new MenuUtils(CommandService.instance());
@ -733,7 +735,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y;
}
function onContextMenu(_event: any, params: any) {
async function onContextMenu(_event: any, params: any) {
if (!pointerInsideEditor(params.x, params.y)) return;
const menu = new Menu();
@ -787,6 +789,23 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
editorRef.current.alignSelection(params);
}
let filterObject: EditContextMenuFilterObject = {
items: [],
};
filterObject = await eventManager.filterEmit('editorContextMenu', filterObject);
for (const item of filterObject.items) {
menu.append(new MenuItem({
label: item.label,
click: async () => {
const args = item.commandArgs || [];
void CommandService.instance().execute(item.commandName, ...args);
},
type: item.type,
}));
}
menuUtils.pluginContextMenuItems(props.plugins, MenuItemLocation.EditorContextMenu).forEach((item: any) => {
menu.append(new MenuItem(item));
});

View File

@ -4,5 +4,5 @@
# It could be used to develop plugins too.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/toc"
yarn i --prefix="$PLUGIN_PATH" && yarn start --dev-plugins "$PLUGIN_PATH"
PLUGIN_PATH=~/src/joplin-rich-markdown
npm install --prefix="$PLUGIN_PATH" && yarn start --dev-plugins "$PLUGIN_PATH"

View File

@ -1,3 +1,5 @@
const fastDeepEqual = require('fast-deep-equal');
const events = require('events');
export class EventManager {
@ -43,21 +45,22 @@ export class EventManager {
return this.removeListener(`filter:${filterName}`, callback);
}
filterEmit(filterName: string, object: any) {
// We freeze the object we pass to the listeners so that they
// don't modify it directly. Instead they must return a
// modified copy (or the input itself).
let output = Object.freeze(object);
public async filterEmit(filterName: string, object: any) {
let output = object;
const listeners = this.emitter_.listeners(`filter:${filterName}`);
for (const listener of listeners) {
const newOutput = listener(output);
// When we pass the object to the plugin, it is always going to be
// modified since it is serialized/unserialized. So we need to use a
// deep equality check to see if it's been changed. Normally the
// filter objects should be relatively small so there shouldn't be
// much of a performance hit.
const newOutput = await listener(output);
if (newOutput === undefined) {
throw new Error(`Filter "${filterName}": Filter must return a value or the unmodified input. Returning nothing or "undefined" is not supported.`);
}
// Plugin didn't return anything - so we leave the object as it is.
if (newOutput === undefined) continue;
if (newOutput !== output) {
output = Object.freeze(newOutput);
if (!fastDeepEqual(newOutput, output)) {
output = newOutput;
}
}

View File

@ -3,7 +3,7 @@ import eventManager from '../../../eventManager';
import Setting from '../../../models/Setting';
import { FolderEntity } from '../../database/types';
import makeListener from '../utils/makeListener';
import { Disposable } from './types';
import { Disposable, MenuItem } from './types';
/**
* @ignore
@ -15,6 +15,12 @@ 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,
@ -113,6 +119,14 @@ export default class JoplinWorkspace {
return makeListener(eventManager, 'syncComplete', callback);
}
/**
* Called just before the editor context menu is about to open. Allows
* adding items to it.
*/
public filterEditorContextMenu(handler: FilterHandler<EditContextMenuFilterObject>) {
eventManager.filterOn('editorContextMenu', handler);
}
/**
* Gets the currently selected note
*/
@ -139,4 +153,5 @@ export default class JoplinWorkspace {
public async selectedNoteIds(): Promise<string[]> {
return this.store.getState().selectedNoteIds.slice();
}
}

View File

@ -269,6 +269,17 @@ export interface MenuItem {
*/
commandName?: string;
/**
* Arguments that should be passed to the command. They will be as rest
* parameters.
*/
commandArgs?: any[];
/**
* Set to "separator" to create a divider line
*/
type?: string;
/**
* Accelerator associated with the menu item
*/