mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Desktop: Improve beta editor support for the Rich Markdown plugin (#9935)
This commit is contained in:
parent
17a8ce5010
commit
c35085d1d5
@ -604,6 +604,7 @@ packages/default-plugins/utils/getCurrentCommitHash.js
|
||||
packages/default-plugins/utils/getPathToPatchFileFor.js
|
||||
packages/default-plugins/utils/readRepositoryJson.js
|
||||
packages/default-plugins/utils/waitForCliInput.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5BuiltInOptions.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/Decorator.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -584,6 +584,7 @@ packages/default-plugins/utils/getCurrentCommitHash.js
|
||||
packages/default-plugins/utils/getPathToPatchFileFor.js
|
||||
packages/default-plugins/utils/readRepositoryJson.js
|
||||
packages/default-plugins/utils/waitForCliInput.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5BuiltInOptions.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
|
||||
packages/editor/CodeMirror/CodeMirror5Emulation/Decorator.js
|
||||
|
@ -201,8 +201,10 @@ class Application extends BaseApplication {
|
||||
|
||||
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
|
||||
// https://github.com/laurent22/joplin/issues/155
|
||||
//
|
||||
// Note: Be careful about the specificity here. Incorrect specificity can break monospaced fonts in tables.
|
||||
|
||||
const css = `.CodeMirror *, .cm-editor .cm-content { font-family: ${fontFamilies.join(', ')} !important; }`;
|
||||
const css = `.CodeMirror5 *, .cm-editor .cm-content { font-family: ${fontFamilies.join(', ')} !important; }`;
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.type = 'text/css';
|
||||
styleTag.appendChild(document.createTextNode(css));
|
||||
|
@ -283,7 +283,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
}
|
||||
}, [pluginOptions, editor]);
|
||||
|
||||
return <div className='codeMirrorEditor' style={props.style} ref={editorParent} />;
|
||||
return <div className='codeMirrorEditor CodeMirror5' style={props.style} ref={editorParent} />;
|
||||
}
|
||||
|
||||
export default forwardRef(Editor);
|
||||
|
@ -98,6 +98,12 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
const editor = createEditor(editorContainerRef.current, editorProps);
|
||||
editor.addStyles({
|
||||
'.cm-scroller': { overflow: 'auto' },
|
||||
'&.CodeMirror': {
|
||||
height: 'unset',
|
||||
background: 'unset',
|
||||
overflow: 'unset',
|
||||
direction: 'unset',
|
||||
},
|
||||
});
|
||||
setEditor(editor);
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
import { Compartment, Extension, RangeSetBuilder, StateEffect } from '@codemirror/state';
|
||||
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
||||
|
||||
const activeLineDecoration = Decoration.line({ class: 'CodeMirror-activeline CodeMirror-activeline-background' });
|
||||
|
||||
const optionToExtension: Record<string, Extension> = {
|
||||
'styleActiveLine': [
|
||||
ViewPlugin.fromClass(class {
|
||||
public decorations: DecorationSet;
|
||||
|
||||
public constructor(view: EditorView) {
|
||||
this.updateDecorations(view);
|
||||
}
|
||||
|
||||
public update(update: ViewUpdate) {
|
||||
this.updateDecorations(update.view);
|
||||
}
|
||||
|
||||
private updateDecorations(view: EditorView) {
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
let lastLine = -1;
|
||||
|
||||
for (const selection of view.state.selection.ranges) {
|
||||
const startLine = selection.from;
|
||||
const line = view.state.doc.lineAt(startLine);
|
||||
|
||||
if (line.number !== lastLine) {
|
||||
builder.add(line.from, line.from, activeLineDecoration);
|
||||
}
|
||||
|
||||
lastLine = line.number;
|
||||
}
|
||||
|
||||
this.decorations = builder.finish();
|
||||
}
|
||||
}, {
|
||||
decorations: plugin => plugin.decorations,
|
||||
}),
|
||||
EditorView.baseTheme({
|
||||
'&dark .CodeMirror-activeline-background': {
|
||||
background: '#3304',
|
||||
color: 'white',
|
||||
},
|
||||
'&light .CodeMirror-activeline-background': {
|
||||
background: '#7ff4',
|
||||
color: 'black',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
// Maps several CM5 options to CM6 extensions
|
||||
export default class CodeMirror5BuiltInOptions {
|
||||
private activeOptions: string[] = [];
|
||||
private extensionCompartment: Compartment = new Compartment();
|
||||
|
||||
public constructor(private editor: EditorView) {
|
||||
editor.dispatch({
|
||||
effects: StateEffect.appendConfig.of(this.extensionCompartment.of([])),
|
||||
});
|
||||
}
|
||||
|
||||
private updateExtensions() {
|
||||
const extensions = this.activeOptions.map(option => optionToExtension[option]);
|
||||
this.editor.dispatch({
|
||||
effects: this.extensionCompartment.reconfigure(extensions),
|
||||
});
|
||||
}
|
||||
|
||||
public supportsOption(option: string) {
|
||||
return optionToExtension.hasOwnProperty(option);
|
||||
}
|
||||
|
||||
public setOption(optionName: string, value: boolean) {
|
||||
this.activeOptions = this.activeOptions.filter(other => other !== optionName);
|
||||
|
||||
if (value) {
|
||||
this.activeOptions.push(optionName);
|
||||
}
|
||||
|
||||
this.updateExtensions();
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import { StateEffect } from '@codemirror/state';
|
||||
import { StreamParser } from '@codemirror/language';
|
||||
import Decorator, { LineWidgetOptions, MarkTextOptions } from './Decorator';
|
||||
import insertLineAfter from '../editorCommands/insertLineAfter';
|
||||
import CodeMirror5BuiltInOptions from './CodeMirror5BuiltInOptions';
|
||||
const { pregQuote } = require('@joplin/lib/string-utils-common');
|
||||
|
||||
|
||||
@ -52,6 +53,7 @@ export default class CodeMirror5Emulation extends BaseCodeMirror5Emulation {
|
||||
private _options: Record<string, CodeMirror5OptionRecord> = Object.create(null);
|
||||
private _decorator: Decorator;
|
||||
private _decoratorExtension: Extension;
|
||||
private _builtInOptions: CodeMirror5BuiltInOptions;
|
||||
|
||||
// Used by some plugins to store state.
|
||||
public state: Record<string, any> = Object.create(null);
|
||||
@ -70,6 +72,7 @@ export default class CodeMirror5Emulation extends BaseCodeMirror5Emulation {
|
||||
const { decorator, extension: decoratorExtension } = Decorator.create(editor);
|
||||
this._decorator = decorator;
|
||||
this._decoratorExtension = decoratorExtension;
|
||||
this._builtInOptions = new CodeMirror5BuiltInOptions(editor);
|
||||
|
||||
editor.dispatch({
|
||||
effects: StateEffect.appendConfig.of(this.makeCM6Extensions()),
|
||||
@ -129,10 +132,8 @@ export default class CodeMirror5Emulation extends BaseCodeMirror5Emulation {
|
||||
return { dom };
|
||||
}),
|
||||
|
||||
// Note: We can allow legacy CM5 CSS to apply to the editor
|
||||
// with a line similar to the following:
|
||||
// EditorView.editorAttributes.of({ class: 'CodeMirror' }),
|
||||
// Many of these styles, however, don't work well with CodeMirror 6.
|
||||
// Allows legacy CM5 CSS to apply to the editor:
|
||||
EditorView.editorAttributes.of({ class: 'CodeMirror' }),
|
||||
];
|
||||
}
|
||||
|
||||
@ -316,6 +317,8 @@ export default class CodeMirror5Emulation extends BaseCodeMirror5Emulation {
|
||||
const oldValue = this._options[name].value;
|
||||
this._options[name].value = value;
|
||||
this._options[name].onUpdate(this, value, oldValue);
|
||||
} else if (this._builtInOptions.supportsOption(name)) {
|
||||
this._builtInOptions.setOption(name, value);
|
||||
} else {
|
||||
super.setOption(name, value);
|
||||
}
|
||||
@ -329,6 +332,20 @@ export default class CodeMirror5Emulation extends BaseCodeMirror5Emulation {
|
||||
}
|
||||
}
|
||||
|
||||
public override coordsChar(coords: { left: number; top: number }, mode?: 'div' | 'local'): DocumentPosition {
|
||||
// codemirror-vim's API only supports "div" mode. Thus, we convert
|
||||
// local to div:
|
||||
if (mode !== 'div') {
|
||||
const bbox = this.editor.contentDOM.getBoundingClientRect();
|
||||
coords = {
|
||||
left: coords.left - bbox.left,
|
||||
top: coords.top - bbox.top,
|
||||
};
|
||||
}
|
||||
|
||||
return super.coordsChar(coords, 'div');
|
||||
}
|
||||
|
||||
// codemirror-vim's API doesn't match the API docs here -- it expects addOverlay
|
||||
// to return a SearchQuery. As such, this override returns "any".
|
||||
public override addOverlay<State>(modeObject: OverlayType<State>): any {
|
||||
@ -353,7 +370,26 @@ export default class CodeMirror5Emulation extends BaseCodeMirror5Emulation {
|
||||
}
|
||||
|
||||
public addLineWidget(lineNumber: number, node: HTMLElement, options: LineWidgetOptions) {
|
||||
this._decorator.addLineWidget(lineNumber, node, options);
|
||||
return this._decorator.addLineWidget(lineNumber, node, options);
|
||||
}
|
||||
|
||||
public addWidget(pos: DocumentPosition, node: HTMLElement) {
|
||||
if (node.parentElement) {
|
||||
node.remove();
|
||||
}
|
||||
|
||||
const loc = posFromDocumentPosition(this.editor.state.doc, pos);
|
||||
const screenCoords = this.editor.coordsAtPos(loc);
|
||||
const bbox = this.editor.contentDOM.getBoundingClientRect();
|
||||
|
||||
node.style.position = 'absolute';
|
||||
|
||||
const left = screenCoords.left - bbox.left;
|
||||
node.style.left = `${left}px`;
|
||||
node.style.maxWidth = `${bbox.width - left}px`;
|
||||
node.style.top = `${screenCoords.top + this.editor.scrollDOM.scrollTop}px`;
|
||||
|
||||
this.editor.scrollDOM.appendChild(node);
|
||||
}
|
||||
|
||||
public markText(from: DocumentPosition, to: DocumentPosition, options?: MarkTextOptions) {
|
||||
|
@ -68,6 +68,9 @@ class WidgetDecorationWrapper extends WidgetType {
|
||||
container.classList.add(this.options.className);
|
||||
}
|
||||
|
||||
// Applies margins and related CSS:
|
||||
container.classList.add('cm-line');
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
@ -49,27 +49,27 @@ const blockQuoteDecoration = Decoration.line({
|
||||
});
|
||||
|
||||
const header1LineDecoration = Decoration.line({
|
||||
attributes: { class: 'cm-h1 cm-headerLine' },
|
||||
attributes: { class: 'cm-h1 cm-headerLine cm-header' },
|
||||
});
|
||||
|
||||
const header2LineDecoration = Decoration.line({
|
||||
attributes: { class: 'cm-h2 cm-headerLine' },
|
||||
attributes: { class: 'cm-h2 cm-headerLine cm-header' },
|
||||
});
|
||||
|
||||
const header3LineDecoration = Decoration.line({
|
||||
attributes: { class: 'cm-h3 cm-headerLine' },
|
||||
attributes: { class: 'cm-h3 cm-headerLine cm-header' },
|
||||
});
|
||||
|
||||
const header4LineDecoration = Decoration.line({
|
||||
attributes: { class: 'cm-h4 cm-headerLine' },
|
||||
attributes: { class: 'cm-h4 cm-headerLine cm-header' },
|
||||
});
|
||||
|
||||
const header5LineDecoration = Decoration.line({
|
||||
attributes: { class: 'cm-h5 cm-headerLine' },
|
||||
attributes: { class: 'cm-h5 cm-headerLine cm-header' },
|
||||
});
|
||||
|
||||
const header6LineDecoration = Decoration.line({
|
||||
attributes: { class: 'cm-h6 cm-headerLine' },
|
||||
attributes: { class: 'cm-h6 cm-headerLine cm-header' },
|
||||
});
|
||||
|
||||
const tableHeaderDecoration = Decoration.line({
|
||||
|
@ -132,7 +132,7 @@ export default class PluginLoader {
|
||||
pluginId: plugin.pluginId,
|
||||
contentScriptId: plugin.contentScriptId,
|
||||
};
|
||||
const loadedPlugin = exports.default(context);
|
||||
const loadedPlugin = exports.default(context) ?? {};
|
||||
|
||||
loadedPlugin.plugin?.(this.editor);
|
||||
|
||||
|
6
packages/editor/CodeMirror/theme.ts
vendored
6
packages/editor/CodeMirror/theme.ts
vendored
@ -85,10 +85,12 @@ const createTheme = (theme: EditorTheme): Extension[] => {
|
||||
};
|
||||
|
||||
const codeMirrorTheme = EditorView.theme({
|
||||
'&': baseGlobalStyle,
|
||||
// Include &.CodeMirror to handle the case where additional CodeMirror 5 styles
|
||||
// need to be overridden.
|
||||
'&, &.CodeMirror': baseGlobalStyle,
|
||||
|
||||
// These must be !important or more specific than CodeMirror's built-ins
|
||||
'.cm-content': {
|
||||
'& .cm-content': {
|
||||
fontFamily: theme.fontFamily,
|
||||
...baseContentStyle,
|
||||
paddingBottom: theme.isDesktop ? '400px' : undefined,
|
||||
|
@ -91,4 +91,5 @@ activatable
|
||||
titlewrapper
|
||||
notyf
|
||||
Notyf
|
||||
activeline
|
||||
Prec
|
Loading…
Reference in New Issue
Block a user