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

Desktop: Fix issue that was preventing editor context menu from being refreshed (#4303)

This commit is contained in:
Caleb John 2021-01-08 09:35:23 -07:00 committed by GitHub
parent 4a0fb124a7
commit c484c88715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 202 additions and 82 deletions

View File

@ -1,3 +1,6 @@
dist/
node_modules/
publish/
dist/*
*.jpl

View File

@ -6,3 +6,4 @@
/dist
tsconfig.json
webpack.config.js

View File

@ -49,11 +49,11 @@ In general all this is done automatically by the plugin generator, which will se
## Updating the plugin framework
To update the plugin framework, run `yo joplin --update`
To update the plugin framework, run `npm run update`.
Keep in mind that doing so will overwrite all the framework-related files **outside of the "src/" directory** (your source code will not be touched). So if you have modified any of the framework-related files, such as package.json or .gitignore, make sure your code is under version control so that you can check the diff and re-apply your changes.
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
For that reason, it's generally best not to change any of the framework files or to do so in a way that minimises the number of changes. For example, if you want to modify the Webpack config, create a new separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## Content scripts

View File

@ -21,7 +21,7 @@ import { Command } from './types';
* and look at the `execute()` command.
*/
export default class JoplinCommands {
/**
/**
* <span class="platform-desktop">desktop</span> Executes the given
* command.
*
@ -40,8 +40,8 @@ export default class JoplinCommands {
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
* ```
*/
execute(commandName: string, ...args: any[]): Promise<any | void>;
/**
execute(commandName: string, ...args: any[]): Promise<any | void>;
/**
* <span class="platform-desktop">desktop</span> Registers a new command.
*
* ```typescript
@ -57,5 +57,5 @@ export default class JoplinCommands {
* });
* ```
*/
register(command: Command): Promise<void>;
register(command: Command): Promise<void>;
}

View File

@ -5,6 +5,6 @@
* so for now disable filters.
*/
export default class JoplinFilters {
on(name: string, callback: Function): Promise<void>;
off(name: string, callback: Function): Promise<void>;
on(name: string, callback: Function): Promise<void>;
off(name: string, callback: Function): Promise<void>;
}

View File

@ -12,6 +12,6 @@ import { ExportModule, ImportModule } from './types';
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
*/
export default class JoplinInterop {
registerExportModule(module: ExportModule): Promise<void>;
registerImportModule(module: ImportModule): Promise<void>;
registerExportModule(module: ExportModule): Promise<void>;
registerImportModule(module: ImportModule): Promise<void>;
}

View File

@ -3,7 +3,7 @@ import { Disposable } from './types';
declare enum ItemChangeEventType {
Create = 1,
Update = 2,
Delete = 3
Delete = 3,
}
interface ItemChangeEvent {
id: string;
@ -12,8 +12,8 @@ interface ItemChangeEvent {
interface SyncStartEvent {
withErrors: boolean;
}
declare type ItemChangeHandler = (event: ItemChangeEvent) => void;
declare type SyncStartHandler = (event: SyncStartEvent) => void;
declare type ItemChangeHandler = (event: ItemChangeEvent)=> void;
declare type SyncStartHandler = (event: SyncStartEvent)=> void;
/**
* The workspace service provides access to all the parts of Joplin that
* are being worked on - i.e. the currently selected notes or notebooks as

View File

@ -701,14 +701,39 @@
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
"chokidar": {
@ -3442,12 +3467,20 @@
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
"has-flag": "^4.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
}
}
},
"tapable": {
@ -3749,6 +3782,17 @@
"semver": "^6.0.0"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
@ -3768,6 +3812,15 @@
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
@ -4395,6 +4448,28 @@
"yargs": "^13.3.2"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",

View File

@ -4,9 +4,12 @@
"description": "",
"scripts": {
"dist": "webpack",
"prepare": "npm run dist"
"prepare": "npm run dist",
"update": "npm install -g generator-joplin && yo joplin --update"
},
"keywords": ["joplin-plugin"],
"keywords": [
"joplin-plugin"
],
"license": "MIT",
"devDependencies": {
"@types/node": "^14.0.14",
@ -21,4 +24,4 @@
"webpack-cli": "^3.3.11",
"chalk": "^4.1.0"
}
}
}

View File

@ -1,5 +1,6 @@
import joplin from 'api';
import { ContentScriptType } from 'api/types';
import { MenuItemLocation } from 'api/types';
joplin.plugins.register({
onStart: async function() {
@ -9,5 +10,15 @@ joplin.plugins.register({
'matchHighlighter',
'./joplinMatchHighlighter.js'
);
await joplin.commands.register({
name: 'editor.printSomething',
label: 'Print some random string',
execute: async () => {
alert('mathMode.printSomething not implemented by Editor yet');
},
});
await joplin.views.menuItems.create('printSomethingButton', 'editor.printSomething', MenuItemLocation.Tools, { accelerator: 'Ctrl+Alt+Shift+U' });
},
});

View File

@ -6,5 +6,8 @@
"version": "1.0.0",
"author": "CalebJohn",
"app_min_version": "1.4",
"homepage_url": "inmoth.ca"
"homepage_url": "joplinapp.org",
"content_scripts": [
"joplinMatchHighlighter"
]
}

View File

@ -1,3 +1,11 @@
// -----------------------------------------------------------------------------
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
// is recommended not to edit this file as it would be overwritten when updating
// the plugin framework. If you do make some changes, consider using an external
// JS file and requiring it here to minimize the changes. That way when you
// update, you can easily restore the functionality you've added.
// -----------------------------------------------------------------------------
const path = require('path');
const crypto = require('crypto');
const fs = require('fs-extra');

View File

@ -690,7 +690,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
return () => {
bridge().window().webContents.off('context-menu', onContextMenu);
};
}, []);
}, [props.plugins]);
function renderEditor() {

View File

@ -26,10 +26,6 @@ export default function useKeymap(CodeMirror: any) {
CodeMirror.Vim.mapCommand('o', 'action', 'insertListElement', { after: true }, { context: 'normal', isEdit: true, interlaceInsertRepeat: true });
}
function isEditorCommand(command: string) {
return command.startsWith('editor.');
}
// Converts a command of the form editor.command to just command
function editorCommandToCodeMirror(command: String) {
return command.slice(7); // 7 is the length of editor.
@ -38,10 +34,24 @@ export default function useKeymap(CodeMirror: any) {
// CodeMirror and Electron register accelerators slightly different
// CodeMirror requires a - between keys while Electron want's a +
// CodeMirror doesn't recognize Option (it uses Alt instead)
// This function uses simple regex to translate the Electron
// accelerator to a CodeMirror accelerator
// CodeMirror requires Shift to be first
function normalizeAccelerator(accelerator: String) {
return accelerator.replace(/\+/g, '-').replace('Option', 'Alt');
const command = accelerator.replace(/\+/g, '-').replace('Option', 'Alt');
// From here is taken out of codemirror/lib/codemirror.js
const parts = command.split(/-(?!$)/);
let name = parts[parts.length - 1];
let alt, ctrl, shift, cmd;
for (let i = 0; i < parts.length - 1; i++) {
const mod = parts[i];
if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; } else if (/^a(lt)?$/i.test(mod)) { alt = true; } else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } else if (/^s(hift)?$/i.test(mod)) { shift = true; } else { throw new Error(`Unrecognized modifier name: ${mod}`); }
}
if (alt) { name = `Alt-${name}`; }
if (ctrl) { name = `Ctrl-${name}`; }
if (cmd) { name = `Cmd-${name}`; }
if (shift) { name = `Shift-${name}`; }
return name;
// End of code taken from codemirror/lib/codemirror.js
}
// Because there is sometimes a clash between these keybindings and the Joplin window ones
@ -53,21 +63,17 @@ export default function useKeymap(CodeMirror: any) {
if (!key.command || !key.accelerator) return;
let command = '';
if (isEditorCommand(key.command)) {
command = editorCommandToCodeMirror(key.command);
} else {
// We need to register Joplin commands with codemirror
command = `joplin${key.command}`;
// Not all commands are registered with the command service
// (for example, the Quit command)
// This check will ensure that codemirror only takesover the commands that are
// see gui/KeymapConfig/getLabel.ts for more information
const commandNames = CommandService.instance().commandNames();
if (commandNames.includes(key.command)) {
CodeMirror.commands[command] = () => {
void CommandService.instance().execute(key.command);
};
}
// We need to register Joplin commands with codemirror
command = `joplin${key.command}`;
// Not all commands are registered with the command service
// (for example, the Quit command)
// This check will ensure that codemirror only takesover the commands that are
// see gui/KeymapConfig/getLabel.ts for more information
const commandNames = CommandService.instance().commandNames();
if (commandNames.includes(key.command)) {
CodeMirror.commands[command] = () => {
void CommandService.instance().execute(key.command);
};
}
// CodeMirror and Electron have slightly different formats for defining accelerators
@ -83,8 +89,9 @@ export default function useKeymap(CodeMirror: any) {
keymapItems.forEach((key) => { registerJoplinCommand(key); });
}
CodeMirror.defineExtension('supportsCommand', function(cmd: EditorCommand) {
return isEditorCommand(cmd.name) && editorCommandToCodeMirror(cmd.name) in CodeMirror.commands;
return CommandService.isEditorCommand(cmd.name) && editorCommandToCodeMirror(cmd.name) in CodeMirror.commands;
});
// Used when an editor command is executed using the CommandService.instance().execute

View File

@ -233,6 +233,7 @@ function NoteEditor(props: NoteEditorProps) {
useWindowCommandHandler({
dispatch: props.dispatch,
plugins: props.plugins,
formNote,
setShowLocalSearch,
noteSearchBarRef,

View File

@ -1,6 +1,5 @@
import { useEffect } from 'react';
import { FormNote, ScrollOptionTypes } from './types';
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
const time = require('@joplin/lib/time').default;
const { reg } = require('@joplin/lib/registry.js');
@ -20,6 +19,7 @@ interface HookDependencies {
titleInputRef: any;
saveNoteAndWait: Function;
setFormNote: Function;
plugins: any;
}
function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, setFormNote: Function): CommandRuntime {
@ -61,10 +61,10 @@ function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, s
}
export default function useWindowCommandHandler(dependencies: HookDependencies) {
const { setShowLocalSearch, noteSearchBarRef, editorRef, titleInputRef, setFormNote } = dependencies;
const { setShowLocalSearch, noteSearchBarRef, editorRef, titleInputRef, setFormNote, plugins } = dependencies;
useEffect(() => {
for (const declaration of editorCommandDeclarations) {
for (const declaration of CommandService.instance().editorCommandDeclarations()) {
CommandService.instance().registerRuntime(declaration.name, editorCommandRuntime(declaration, editorRef, setFormNote));
}
@ -80,7 +80,7 @@ export default function useWindowCommandHandler(dependencies: HookDependencies)
}
return () => {
for (const declaration of editorCommandDeclarations) {
for (const declaration of CommandService.instance().editorCommandDeclarations()) {
CommandService.instance().unregisterRuntime(declaration.name);
}
@ -88,5 +88,5 @@ export default function useWindowCommandHandler(dependencies: HookDependencies)
CommandService.instance().unregisterRuntime(command.declaration.name);
}
};
}, [editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef]);
}, [editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef, plugins]);
}

View File

@ -310,4 +310,35 @@ export default class CommandService extends BaseService {
return !!command;
}
public static isEditorCommand(commandName: string) {
return (commandName.indexOf('editor.') === 0 ||
// These commands are grandfathered in, but in the future
// all editor commands should start with "editor."
commandName === 'textCopy' ||
commandName === 'textCut' ||
commandName === 'textPaste' ||
commandName === 'textSelectAll' ||
commandName === 'textBold' ||
commandName === 'textItalic' ||
commandName === 'textLink' ||
commandName === 'textCode' ||
commandName === 'attachFile' ||
commandName === 'textNumberedList' ||
commandName === 'textBulletedList' ||
commandName === 'textCheckbox' ||
commandName === 'textHeading' ||
commandName === 'textHorizontalRule' ||
commandName === 'insertDateTime'
);
}
public editorCommandDeclarations(): CommandDeclaration[] {
const output = [];
for (const name in this.commands_) {
if (CommandService.isEditorCommand(name)) { output.push(this.commands_[name].declaration); }
}
return output;
}
}

View File

@ -33,30 +33,6 @@ export default class ToolbarButtonUtils {
return this.service_;
}
// Editor commands will focus the editor after they're executed
private isEditorCommand(commandName: string) {
return (commandName.indexOf('editor.') === 0 ||
// These commands are grandfathered in, but in the future
// all editor commands should start with "editor."
// WARNING: Some commands such as textLink are not defined here
// because they are more complex and handle focus manually
commandName === 'textCopy' ||
commandName === 'textCut' ||
commandName === 'textPaste' ||
commandName === 'textSelectAll' ||
commandName === 'textBold' ||
commandName === 'textItalic' ||
commandName === 'textCode' ||
commandName === 'attachFile' ||
commandName === 'textNumberedList' ||
commandName === 'textBulletedList' ||
commandName === 'textCheckbox' ||
commandName === 'textHeading' ||
commandName === 'textHorizontalRule' ||
commandName === 'insertDateTime'
);
}
private commandToToolbarButton(commandName: string, whenClauseContext: any): ToolbarButtonInfo {
const newEnabled = this.service.isEnabled(commandName, whenClauseContext);
const newTitle = this.service.title(commandName);
@ -78,7 +54,8 @@ export default class ToolbarButtonUtils {
enabled: newEnabled,
onClick: async () => {
void this.service.execute(commandName);
if (this.isEditorCommand(commandName)) {
// WARNING: textLink is a special case because it handles it's own focus
if (CommandService.isEditorCommand(commandName) && commandName !== 'textLink') {
void this.service.execute('editor.focus');
}
},