Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desktop: Improve beta editor support for the Rich Markdown plugin #9935

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,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
Expand Down
4 changes: 3 additions & 1 deletion packages/app-desktop/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
Comment on lines +101 to +106
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the .CodeMirror class to the editor makes it easier to migrate styles originally intended for CodeMirror 5. It does, however, mean that several CodeMirror 5 styles need to be overridden.

});
setEditor(editor);

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');


Expand Down Expand Up @@ -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);
Expand All @@ -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()),
Expand Down Expand Up @@ -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' }),
];
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions packages/editor/CodeMirror/CodeMirror5Emulation/Decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
12 changes: 6 additions & 6 deletions packages/editor/CodeMirror/markdown/decoratorExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/CodeMirror/pluginApi/PluginLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
6 changes: 4 additions & 2 deletions packages/editor/CodeMirror/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion packages/tools/cspell/dictionary4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@ activatable
titlewrapper
notyf
Notyf
Prec
activeline
Prec
Loading