You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Plugins: Add support for joplin.workspace.filterEditorContextMenu to allow dynamically setting editor menu items depending on context
This commit is contained in:
		| @@ -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 | ||||
| 	 */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user