1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-06 09:19:22 +02:00

Plugins: Add support for external CodeMirror plugins (#4015)

This commit is contained in:
Caleb John
2020-11-17 11:28:32 -07:00
committed by GitHub
parent 4e08adb76f
commit 3e00f6078d
38 changed files with 5732 additions and 50 deletions

View File

@@ -637,6 +637,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
readOnly={props.visiblePanes.indexOf('editor') < 0}
autoMatchBraces={Setting.value('editor.autoMatchingBraces')}
keyMap={props.keyboardMode}
plugins={props.plugins}
onChange={codeMirror_change}
onScroll={editor_scroll}
onEditorContextMenu={onEditorContextMenu}

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import * as CodeMirror from 'codemirror';
@@ -18,6 +19,7 @@ import useLineSorting from './utils/useLineSorting';
import useEditorSearch from './utils/useEditorSearch';
import useJoplinMode from './utils/useJoplinMode';
import useKeymap from './utils/useKeymap';
import useExternalPlugins from './utils/useExternalPlugins';
import 'codemirror/keymap/emacs';
import 'codemirror/keymap/vim';
@@ -75,18 +77,19 @@ for (let i = 0; i < topLanguages.length; i++) {
}
export interface EditorProps {
value: string;
searchMarkers: any;
mode: string;
style: any;
codeMirrorTheme: any;
readOnly: boolean;
autoMatchBraces: boolean;
keyMap: string;
onChange: any;
onScroll: any;
onEditorContextMenu: any;
onEditorPaste: any;
value: string,
searchMarkers: any,
mode: string,
style: any,
codeMirrorTheme: any,
readOnly: boolean,
autoMatchBraces: boolean,
keyMap: string,
plugins: PluginStates,
onChange: any,
onScroll: any,
onEditorContextMenu: any,
onEditorPaste: any,
}
function Editor(props: EditorProps, ref: any) {
@@ -102,6 +105,7 @@ function Editor(props: EditorProps, ref: any) {
useEditorSearch(CodeMirror);
useJoplinMode(CodeMirror);
useKeymap(CodeMirror);
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
useImperativeHandle(ref, () => {
return editor;
@@ -238,6 +242,14 @@ function Editor(props: EditorProps, ref: any) {
}
}, [props.keyMap]);
useEffect(() => {
if (editor) {
for (const option in pluginOptions) {
editor.setOption(option, pluginOptions[option]);
}
}
}, [pluginOptions, editor]);
return <div style={props.style} ref={editorParent} />;
}

View File

@@ -0,0 +1,90 @@
import { useEffect, useState } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { contentScriptsToCodeMirrorPlugin } from '@joplin/lib/services/plugins/utils/loadContentScripts';
import { extname } from 'path';
import shim from '@joplin/lib/shim';
import uuid from '@joplin/lib/uuid';
const { reg } = require('@joplin/lib/registry.js');
export default function useExternalPlugins(CodeMirror: any, plugins: PluginStates) {
const [options, setOptions] = useState({});
useEffect(() => {
let newOptions = {};
const contentScripts = contentScriptsToCodeMirrorPlugin(plugins);
for (const contentScript of contentScripts) {
try {
const mod = contentScript.module;
if (mod.codeMirrorResources) {
for (const asset of mod.codeMirrorResources) {
try {
require(`codemirror/${asset}`);
} catch (error) {
error.message = `${asset} is not a valid CodeMirror asset, keymap or mode. You can find a list of valid assets here: https://codemirror.net/doc/manual.html#addons`;
throw error;
}
}
}
if (mod.codeMirrorOptions) {
newOptions = Object.assign({}, newOptions, mod.codeMirrorOptions);
}
if (mod.assets) {
const cssStrings = [];
for (const asset of mod.assets()) {
let mime = asset.mime;
if (!mime && asset.inline) throw new Error('Mime type is required for inline assets');
if (!mime && asset.name) {
const ext = extname(asset.name).toLowerCase();
if (ext === '.css') mime = 'text/css';
}
if (mime !== 'text/css') throw new Error('Only css assets are supported for CodeMirror plugins');
if (asset.inline) {
cssStrings.push(asset.text);
} else {
addScript(shim.fsDriver().resolveRelativePathWithinDir(contentScript.assetPath, asset.name), contentScript.id);
}
}
if (cssStrings.length > 0) {
addInlineCss(cssStrings, contentScript.id);
}
}
if (mod.plugin) {
mod.plugin(CodeMirror);
}
} catch (error) {
reg.logger().error(error.toString());
}
}
setOptions(newOptions);
}, [plugins]);
function addInlineCss(cssStrings: string[], id: string) {
const element = document.createElement('style');
element.setAttribute('id', `content-script-${id}-inline-${uuid.createNano()}`);
document.head.appendChild(element);
element.appendChild(document.createTextNode(cssStrings.join('\n')));
}
function addScript(path: string, id: string) {
const element = document.createElement('link');
element.setAttribute('id', `content-script-${id}-link-${uuid.createNano()}`);
element.setAttribute('rel', 'stylesheet');
element.setAttribute('href', path);
document.head.appendChild(element);
}
return options;
}

View File

@@ -1,5 +1,5 @@
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import contentScriptsToRendererRules from '@joplin/lib/services/plugins/utils/contentScriptsToRendererRules';
import { contentScriptsToRendererRules } from '@joplin/lib/services/plugins/utils/loadContentScripts';
import { useCallback, useMemo } from 'react';
import { ResourceInfos } from './types';
import markupLanguageUtils from '@joplin/lib/markupLanguageUtils';