1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Plugins: Added a way to execute commands from Markdown-it content scripts

This commit is contained in:
Laurent Cozic 2020-12-11 12:00:24 +00:00
parent bc76e4a918
commit 65cc6853bd
7 changed files with 136 additions and 24 deletions

View File

@ -3,6 +3,22 @@ import { ContentScriptType } from 'api/types';
joplin.plugins.register({ joplin.plugins.register({
onStart: async function() { onStart: async function() {
await joplin.commands.register({
name: 'testCommand',
label: 'My Test Command',
execute: async (...args) => {
alert('Got command "testCommand" with args: ' + JSON.stringify(args));
},
});
await joplin.commands.register({
name: 'testCommandNoArgs',
label: 'My Test Command (no args)',
execute: async () => {
alert('Got command "testCommandNoArgs"');
},
});
await joplin.plugins.registerContentScript( await joplin.plugins.registerContentScript(
ContentScriptType.MarkdownItPlugin, ContentScriptType.MarkdownItPlugin,
'justtesting', 'justtesting',

View File

@ -1,4 +1,9 @@
.just-testing { .just-testing {
background-color: red; background-color: rgb(202, 255, 255);
color: yellow; color: rgb(82, 0, 78);
}
.just-testing a {
background-color: rgb(202, 255, 255);
color: rgb(10, 0, 104);
} }

View File

@ -6,7 +6,13 @@ function plugin(markdownIt, _options) {
markdownIt.renderer.rules.fence = function(tokens, idx, options, env, self) { markdownIt.renderer.rules.fence = function(tokens, idx, options, env, self) {
const token = tokens[idx]; const token = tokens[idx];
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self); if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
return `<div class="just-testing">JUST TESTING: ${token.content}</div>`; return `
<div class="just-testing">
<p>JUST TESTING: ${token.content}</p>
<p><a href="#" onclick="webviewApi.executeCommand('testCommand', 'one', 'two'); return false;">Click to send "testCommand" to plugin</a></p>
<p><a href="#" onclick="webviewApi.executeCommand('testCommandNoArgs'); return false;">Click to send "testCommandNoArgs" to plugin</a></p>
</div>
`;
}; };
} }

View File

@ -3,6 +3,7 @@ import { FormNote } from './types';
import contextMenu from './contextMenu'; import contextMenu from './contextMenu';
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'; import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import CommandService from '@joplin/lib/services/CommandService';
const BaseItem = require('@joplin/lib/models/BaseItem'); const BaseItem = require('@joplin/lib/models/BaseItem');
const BaseModel = require('@joplin/lib/BaseModel').default; const BaseModel = require('@joplin/lib/BaseModel').default;
const Resource = require('@joplin/lib/models/Resource.js'); const Resource = require('@joplin/lib/models/Resource.js');
@ -90,6 +91,10 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
} }
} else if (msg.indexOf('#') === 0) { } else if (msg.indexOf('#') === 0) {
// This is an internal anchor, which is handled by the WebView so skip this case // This is an internal anchor, which is handled by the WebView so skip this case
} else if (msg === 'contentScriptExecuteCommand') {
const commandName = arg0.name;
const commandArgs = arg0.args || [];
void CommandService.instance().execute(commandName, ...commandArgs);
} else { } else {
bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg)); bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
} }

View File

@ -49,10 +49,24 @@
<script src="./lib.js"></script> <script src="./lib.js"></script>
<script> <script>
// This is function used internally to send message from the webview to
// the host.
const ipcProxySendToHost = (methodName, arg) => { const ipcProxySendToHost = (methodName, arg) => {
window.postMessage({ target: 'main', name: methodName, args: [ arg ] }, '*'); window.postMessage({ target: 'main', name: methodName, args: [ arg ] }, '*');
} }
// This function is reserved for plugin, currently only to allow
// executing a command, but more features could be added to the object
// later on.
const webviewApi = {
executeCommand: function (commandName, ...args) {
return ipcProxySendToHost('contentScriptExecuteCommand', {
name: commandName,
args: args,
});
}
}
let pluginAssetsAdded_ = {}; let pluginAssetsAdded_ = {};
try { try {

View File

@ -51,14 +51,20 @@ export default class JoplinPlugins {
} }
/** /**
* Registers a new content script. Unlike regular plugin code, which runs in a separate process, content scripts run within the main process code * Registers a new content script. Unlike regular plugin code, which
* and thus allow improved performances and more customisations in specific cases. It can be used for example to load a Markdown or editor plugin. * runs in a separate process, content scripts run within the main
* process code and thus allow improved performances and more
* customisations in specific cases. It can be used for example to load
* a Markdown or editor plugin.
* *
* Note that registering a content script in itself will do nothing - it will only be loaded in specific cases by the relevant app modules * Note that registering a content script in itself will do nothing -
* (eg. the Markdown renderer or the code editor). So it is not a way to inject and run arbitrary code in the app, which for safety and performance reasons is not supported. * it will only be loaded in specific cases by the relevant app modules
* (eg. the Markdown renderer or the code editor). So it is not a way
* to inject and run arbitrary code in the app, which for safety and
* performance reasons is not supported.
* *
* [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script) * * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script) * * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
* *
* @param type Defines how the script will be used. See the type definition for more information about each supported type. * @param type Defines how the script will be used. See the type definition for more information about each supported type.
* @param id A unique ID for the content script. * @param id A unique ID for the content script.

View File

@ -334,7 +334,8 @@ export type Path = string[];
export enum ContentScriptType { export enum ContentScriptType {
/** /**
* Registers a new Markdown-It plugin, which should follow the template below. * Registers a new Markdown-It plugin, which should follow the template
* below.
* *
* ```javascript * ```javascript
* module.exports = { * module.exports = {
@ -350,14 +351,49 @@ export enum ContentScriptType {
* } * }
* } * }
* ``` * ```
* See [the
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* for a simple Markdown-it plugin example.
* *
* - The `context` argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate. * ## Exported members
* *
* - The **required** `plugin` key is the actual Markdown-It plugin - check the [official doc](https://github.com/markdown-it/markdown-it) for more information. The `options` parameter is of type [RuleOptions](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml.ts), which contains a number of options, mostly useful for Joplin's internal code. * - The `context` argument is currently unused but could be used later
* on to provide access to your own plugin so that the content script
* and plugin can communicate.
* *
* - Using the **optional** `assets` key you may specify assets such as JS or CSS that should be loaded in the rendered HTML document. Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts) to see how the data should be structured. * - The **required** `plugin` key is the actual Markdown-It plugin -
* check the [official
* doc](https://github.com/markdown-it/markdown-it) for more
* information. The `options` parameter is of type
* [RuleOptions](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml.ts),
* which contains a number of options, mostly useful for Joplin's
* internal code.
* *
* To include a regular Markdown-It plugin, that doesn't make use of any Joplin-specific features, you would simply create a file such as this: * - Using the **optional** `assets` key you may specify assets such as
* JS or CSS that should be loaded in the rendered HTML document.
* Check for example the Joplin [Mermaid
* plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
* to see how the data should be structured.
*
* ## Passing messages from the content script to your plugin
*
* The application provides the following function to allow executing
* commands from the rendered HTML code:
*
* `webviewApi.executeCommand(commandName, ...args)`
*
* So you can use this mechanism to pass messages from the note viewer
* to your own plugin. To do so you would define a command, using
* `joplin.commands.register`, then you would call this command using
* the `webviewApi` object. See again [the
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* to see how this can be done.
*
* ## Registering an existing Markdown-it plugin
*
* To include a regular Markdown-It plugin, that doesn't make use of
* any Joplin-specific features, you would simply create a file such as
* this:
* *
* ```javascript * ```javascript
* module.exports = { * module.exports = {
@ -371,7 +407,8 @@ export enum ContentScriptType {
*/ */
MarkdownItPlugin = 'markdownItPlugin', MarkdownItPlugin = 'markdownItPlugin',
/** /**
* Registers a new CodeMirror plugin, which should follow the template below. * Registers a new CodeMirror plugin, which should follow the template
* below.
* *
* ```javascript * ```javascript
* module.exports = { * module.exports = {
@ -382,8 +419,8 @@ export enum ContentScriptType {
* }, * },
* codeMirrorResources: [], * codeMirrorResources: [],
* codeMirrorOptions: { * codeMirrorOptions: {
* // ... * // ...
* }, * },
* assets: { * assets: {
* // ... * // ...
* }, * },
@ -392,19 +429,42 @@ export enum ContentScriptType {
* } * }
* ``` * ```
* *
* - The `context` argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate. * - The `context` argument is currently unused but could be used later
* on to provide access to your own plugin so that the content script
* and plugin can communicate.
* *
* - The `plugin` key is your CodeMirror plugin. This is where you can register new commands with CodeMirror or interact with the CodeMirror instance as needed. * - The `plugin` key is your CodeMirror plugin. This is where you can
* register new commands with CodeMirror or interact with the
* CodeMirror instance as needed.
* *
* - The `codeMirrorResources` key is an array of CodeMirror resources that will be loaded and attached to the CodeMirror module. These are made up of addons, keymaps, and modes. For example, for a plugin that want's to enable clojure highlighting in code blocks. `codeMirrorResources` would be set to `['mode/clojure/clojure']`. * - The `codeMirrorResources` key is an array of CodeMirror resources
* that will be loaded and attached to the CodeMirror module. These
* are made up of addons, keymaps, and modes. For example, for a
* plugin that want's to enable clojure highlighting in code blocks.
* `codeMirrorResources` would be set to `['mode/clojure/clojure']`.
* *
* - The `codeMirrorOptions` key contains all the [CodeMirror](https://codemirror.net/doc/manual.html#config) options that will be set or changed by this plugin. New options can alse be declared via [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption), and then have their value set here. For example, a plugin that enables line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`. * - The `codeMirrorOptions` key contains all the
* [CodeMirror](https://codemirror.net/doc/manual.html#config)
* options that will be set or changed by this plugin. New options
* can alse be declared via
* [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption),
* and then have their value set here. For example, a plugin that
* enables line numbers would set `codeMirrorOptions` to
* `{'lineNumbers': true}`.
* *
* - Using the **optional** `assets` key you may specify **only** CSS assets that should be loaded in the rendered HTML document. Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts) to see how the data should be structured. * - Using the **optional** `assets` key you may specify **only** CSS
* assets that should be loaded in the rendered HTML document. Check
* for example the Joplin [Mermaid
* plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
* to see how the data should be structured.
* *
* One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys must be provided for the plugin to be valid. Having multiple or all provided is also okay. * One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions`
* keys must be provided for the plugin to be valid. Having multiple or
* all provided is also okay.
* *
* See the [demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script) for an example of all these keys being used in one plugin. * See the [demo
* plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
* for an example of all these keys being used in one plugin.
*/ */
CodeMirrorPlugin = 'codeMirrorPlugin', CodeMirrorPlugin = 'codeMirrorPlugin',
} }