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:
parent
941445a238
commit
960863fb75
@ -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));
|
||||
});
|
||||
|
@ -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"
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user