1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-18 09:35:20 +02:00
joplin/packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.ts

142 lines
4.3 KiB
TypeScript
Raw Normal View History

/* eslint-disable import/prefer-default-export */
// This contains the CodeMirror instance, which needs to be built into a bundle
// using `npm run buildInjectedJs`. This bundle is then loaded from
// NoteEditor.tsx into the webview.
//
// In general, since this file is harder to debug due to the intermediate built
// step, it's better to keep it as light as possible - it shoud just be a light
// wrapper to access CodeMirror functionalities. Anything else should be done
// from NoteEditor.tsx.
import createTheme from './theme';
import decoratorExtension from './decoratorExtension';
import { EditorState } from '@codemirror/state';
import { markdown } from '@codemirror/lang-markdown';
import { highlightSelectionMatches, search } from '@codemirror/search';
import { EditorView, drawSelection, highlightSpecialChars, ViewUpdate } from '@codemirror/view';
import { undo, redo, history, undoDepth, redoDepth } from '@codemirror/commands';
import { keymap } from '@codemirror/view';
import { indentOnInput } from '@codemirror/language';
import { searchKeymap } from '@codemirror/search';
import { historyKeymap, defaultKeymap } from '@codemirror/commands';
import { MarkdownMathExtension } from './markdownMathParser';
import { GFM as GitHubFlavoredMarkdownExtension } from '@lezer/markdown';
import syntaxHighlightingLanguages from './syntaxHighlightingLanguages';
interface CodeMirrorResult {
editor: EditorView;
undo: Function;
redo: Function;
select(anchor: number, head: number): void;
scrollSelectionIntoView(): void;
insertText(text: string): void;
}
function postMessage(name: string, data: any) {
(window as any).ReactNativeWebView.postMessage(JSON.stringify({
data,
name,
}));
}
function logMessage(...msg: any[]) {
postMessage('onLog', { value: msg });
}
export function initCodeMirror(parentElement: any, initialText: string, theme: any): CodeMirrorResult {
logMessage('Initializing CodeMirror...');
let schedulePostUndoRedoDepthChangeId_: any = 0;
function schedulePostUndoRedoDepthChange(editor: EditorView, doItNow: boolean = false) {
if (schedulePostUndoRedoDepthChangeId_) {
if (doItNow) {
clearTimeout(schedulePostUndoRedoDepthChangeId_);
} else {
return;
}
}
schedulePostUndoRedoDepthChangeId_ = setTimeout(() => {
schedulePostUndoRedoDepthChangeId_ = null;
postMessage('onUndoRedoDepthChange', {
undoDepth: undoDepth(editor.state),
redoDepth: redoDepth(editor.state),
});
}, doItNow ? 0 : 1000);
}
const editor = new EditorView({
state: EditorState.create({
// See https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
// for a sample configuration.
extensions: [
markdown({
extensions: [
MarkdownMathExtension,
GitHubFlavoredMarkdownExtension,
],
codeLanguages: syntaxHighlightingLanguages,
}),
...createTheme(theme),
history(),
search(),
drawSelection(),
highlightSpecialChars(),
highlightSelectionMatches(),
indentOnInput(),
decoratorExtension,
EditorView.lineWrapping,
EditorView.contentAttributes.of({ autocapitalize: 'sentence' }),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (viewUpdate.docChanged) {
postMessage('onChange', { value: editor.state.doc.toString() });
schedulePostUndoRedoDepthChange(editor);
}
if (!viewUpdate.state.selection.eq(viewUpdate.startState.selection)) {
const mainRange = viewUpdate.state.selection.main;
const selStart = mainRange.from;
const selEnd = mainRange.to;
postMessage('onSelectionChange', { selection: { start: selStart, end: selEnd } });
}
}),
keymap.of([
...defaultKeymap, ...historyKeymap, ...searchKeymap,
]),
],
doc: initialText,
}),
parent: parentElement,
});
return {
editor,
undo: () => {
undo(editor);
schedulePostUndoRedoDepthChange(editor, true);
},
redo: () => {
redo(editor);
schedulePostUndoRedoDepthChange(editor, true);
},
select: (anchor: number, head: number) => {
editor.dispatch(editor.state.update({
selection: { anchor, head },
scrollIntoView: true,
}));
},
scrollSelectionIntoView: () => {
editor.dispatch(editor.state.update({
scrollIntoView: true,
}));
},
insertText: (text: string) => {
editor.dispatch(editor.state.replaceSelection(text));
},
};
}