1
0
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:
Henry Heino 2024-03-09 02:48:22 -08:00 committed by GitHub
parent 17a8ce5010
commit c35085d1d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 152 additions and 17 deletions

View File

@ -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
View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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({

View File

@ -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);

View File

@ -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,

View File

@ -91,4 +91,5 @@ activatable
titlewrapper
notyf
Notyf
activeline
Prec