From f3518cddbe0d868e9befcc23a14f741c6d817be3 Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Mon, 22 Jan 2024 07:27:42 -0800 Subject: [PATCH] Desktop: Resolves #9747: CodeMirror 6 plugin API: Support non-inline CSS assets (#9748) --- .../plugins/codemirror6/src/assets/style.css | 5 ++++ .../plugins/codemirror6/src/contentScript.ts | 29 ++++++++++++++++++- .../NoteBody/CodeMirror/v6/Editor.tsx | 6 ++++ .../editor/CodeMirror/createEditor.test.ts | 2 ++ .../CodeMirror/pluginApi/PluginLoader.ts | 26 +++++++++++++---- packages/editor/types.ts | 1 + 6 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 packages/app-cli/tests/support/plugins/codemirror6/src/assets/style.css diff --git a/packages/app-cli/tests/support/plugins/codemirror6/src/assets/style.css b/packages/app-cli/tests/support/plugins/codemirror6/src/assets/style.css new file mode 100644 index 000000000..5a1ca067d --- /dev/null +++ b/packages/app-cli/tests/support/plugins/codemirror6/src/assets/style.css @@ -0,0 +1,5 @@ + +.cm-gutter { + background-color: var(--joplin-background-color3); + color: var(--joplin-color3); +} diff --git a/packages/app-cli/tests/support/plugins/codemirror6/src/contentScript.ts b/packages/app-cli/tests/support/plugins/codemirror6/src/contentScript.ts index ec6c19671..5e61478af 100644 --- a/packages/app-cli/tests/support/plugins/codemirror6/src/contentScript.ts +++ b/packages/app-cli/tests/support/plugins/codemirror6/src/contentScript.ts @@ -4,7 +4,7 @@ // This is necessary. Having multiple copies of the CodeMirror libraries loaded can cause // the editor to not work properly. // -import { lineNumbers } from '@codemirror/view'; +import { lineNumbers, highlightActiveLineGutter, } from '@codemirror/view'; // // For the above import to work, you may also need to add @codemirror/view as a dev dependency // to package.json. (For the type information only). @@ -22,10 +22,37 @@ export default (_context: { contentScriptId: string }) => { // We add the extension to CodeMirror using a helper method: codeMirrorWrapper.addExtension([ lineNumbers(), + + // We can include multiple extensions here: + highlightActiveLineGutter(), ]); // See https://codemirror.net/ for more built-in extensions and configuration // options. }, + + // There are two main ways to style the CodeMirror editor: + // 1. With a CodeMirror6 theme extension (see https://codemirror.net/examples/styling/#themes) + // 2. With CSS assets + // + // CSS assets can be added by exporting an `assets` function: + assets: () => [ + // We can include styles by either referencing a file + { name: './assets/style.css' }, + + // or including the style sheet inline + { + inline: true, + mime: 'text/css', + text: ` + /* This CSS class is added by the highlightActiveLineGutter extension: */ + .cm-gutter .cm-activeLineGutter { + text-decoration: underline; + color: var(--joplin-color2); + background-color: var(--joplin-background-color2); + } + `, + }, + ], }; }; diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/Editor.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/Editor.tsx index c408e14f5..3eef66e50 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/Editor.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/Editor.tsx @@ -9,6 +9,7 @@ import { ContentScriptType } from '@joplin/lib/services/plugins/api/types'; import shim from '@joplin/lib/shim'; import PluginService from '@joplin/lib/services/plugins/PluginService'; import setupVim from '@joplin/editor/CodeMirror/util/setupVim'; +import { dirname } from 'path'; interface Props extends EditorProps { style: React.CSSProperties; @@ -66,6 +67,11 @@ const Editor = (props: Props, ref: ForwardedRef) => { pluginId, contentScriptId: contentScript.id, contentScriptJs: () => shim.fsDriver().readFile(contentScript.path), + loadCssAsset: (name: string) => { + const assetPath = dirname(contentScript.path); + const path = shim.fsDriver().resolveRelativePathWithinDir(assetPath, name); + return shim.fsDriver().readFile(path, 'utf8'); + }, postMessageHandler: (message: any) => { const plugin = PluginService.instance().pluginById(pluginId); return plugin.emitContentScriptMessage(contentScript.id, message); diff --git a/packages/editor/CodeMirror/createEditor.test.ts b/packages/editor/CodeMirror/createEditor.test.ts index 40db1bea9..3d5bbf08e 100644 --- a/packages/editor/CodeMirror/createEditor.test.ts +++ b/packages/editor/CodeMirror/createEditor.test.ts @@ -76,12 +76,14 @@ describe('createEditor', () => { const testPlugin1 = { pluginId: 'a.plugin.id', contentScriptId: 'a.plugin.id.contentScript', + loadCssAsset: async (_name: string) => '* { color: red; }', contentScriptJs: getContentScriptJs, postMessageHandler, }; const testPlugin2 = { pluginId: 'another.plugin.id', contentScriptId: 'another.plugin.id.contentScript', + loadCssAsset: async (_name: string) => '...', contentScriptJs: getContentScriptJs, postMessageHandler, }; diff --git a/packages/editor/CodeMirror/pluginApi/PluginLoader.ts b/packages/editor/CodeMirror/pluginApi/PluginLoader.ts index be4a9d6d4..0daa67fdf 100644 --- a/packages/editor/CodeMirror/pluginApi/PluginLoader.ts +++ b/packages/editor/CodeMirror/pluginApi/PluginLoader.ts @@ -119,7 +119,7 @@ export default class PluginLoader { }); }; - addScript(exports => { + addScript(async exports => { if (!exports?.default || !(typeof exports.default === 'function')) { throw new Error('All plugins must have a function default export'); } @@ -143,16 +143,30 @@ export default class PluginLoader { const cssStrings = []; for (const asset of loadedPlugin.assets()) { + let assetText: string = asset.text; + let assetMime: string = asset.mime; + if (!asset.inline) { - this.logMessage('Warning: The CM6 plugin API currently only supports inline CSS.'); - continue; + if (!asset.name) { + throw new Error('Non-inline asset missing required property "name"'); + } + if (assetMime !== 'text/css' && !asset.name.endsWith('.css')) { + throw new Error( + `Non-css assets are not supported by the CodeMirror 6 editor. (Asset path: ${asset.name})`, + ); + } + + assetText = await plugin.loadCssAsset(asset.name); + assetMime = 'text/css'; } - if (asset.mime !== 'text/css') { - throw new Error('Inline assets must have property "mime" set to "text/css"'); + if (assetMime !== 'text/css') { + throw new Error( + 'Plugin assets must have property "mime" set to "text/css" or have a filename ending with ".css"', + ); } - cssStrings.push(asset.text); + cssStrings.push(assetText); } addStyles(cssStrings); diff --git a/packages/editor/types.ts b/packages/editor/types.ts index 49be2fc45..7dbc8480b 100644 --- a/packages/editor/types.ts +++ b/packages/editor/types.ts @@ -68,6 +68,7 @@ export interface PluginData { pluginId: string; contentScriptId: string; contentScriptJs: ()=> Promise; + loadCssAsset: (name: string)=> Promise; postMessageHandler: (message: any)=> any; }