client: Reduce code duplication for CodeMirror

This commit is contained in:
Elian Doran
2024-10-19 23:12:33 +03:00
parent cab1d7d353
commit c7b7c68a05
3 changed files with 106 additions and 144 deletions

View File

@@ -0,0 +1,89 @@
import TypeWidget from "./type_widget.js";
import libraryLoader from "../../services/library_loader.js";
import options from "../../services/options.js";
export default class AbstractCodeTypeWidget extends TypeWidget {
doRender() {
this.initialized = this.initEditor();
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
// these conflict with backward/forward navigation shortcuts
delete CodeMirror.keyMap.default["Alt-Left"];
delete CodeMirror.keyMap.default["Alt-Right"];
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`;
CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]);
CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"];
this.codeEditor = CodeMirror(this.$editor[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
matchBrackets: true,
matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
lineNumbers: true,
// we line wrap partly also because without it horizontal scrollbar displays only when you scroll
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
...this.getExtraOpts()
});
this.onEditorInitialized();
}
getExtraOpts() {
return {};
}
onEditorInitialized() {
}
_update(note, content) {
// CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
// we provide fallback
this.codeEditor.setValue(content || "");
this.codeEditor.clearHistory();
let info = CodeMirror.findModeByMIME(note.mime);
if (!info) {
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
// To avoid inheriting a mode from a previously open code note.
info = CodeMirror.findModeByMIME("text/plain");
}
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
};
show() {
this.$widget.show();
if (this.codeEditor) { // show can be called before render
this.codeEditor.refresh();
}
}
focus() {
this.$editor.focus();
this.codeEditor.focus();
}
scrollToEnd() {
this.codeEditor.setCursor(this.codeEditor.lineCount(), 0);
this.codeEditor.focus();
}
cleanup() {
if (this.codeEditor) {
this.spacedUpdate.allowUpdateWithoutChange(() => {
this.codeEditor.setValue('');
});
}
}
}

View File

@@ -1,8 +1,7 @@
import { t } from "../../services/i18n.js";
import libraryLoader from "../../services/library_loader.js";
import TypeWidget from "./type_widget.js";
import keyboardActionService from "../../services/keyboard_actions.js";
import options from "../../services/options.js";
import AbstractCodeTypeWidget from "./code_widget_base.js";
const TPL = `
<div class="note-detail-code note-detail-printable">
@@ -21,7 +20,7 @@ const TPL = `
<div class="note-detail-code-editor"></div>
</div>`;
export default class EditableCodeTypeWidget extends TypeWidget {
export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
static getType() { return "editableCode"; }
doRender() {
@@ -30,44 +29,21 @@ export default class EditableCodeTypeWidget extends TypeWidget {
keyboardActionService.setupActionsForElement('code-detail', this.$widget, this);
super.doRender();
this.initialized = this.initEditor();
super.doRender();
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
CodeMirror.keyMap.default["Tab"] = "indentMore";
// these conflict with backward/forward navigation shortcuts
delete CodeMirror.keyMap.default["Alt-Left"];
delete CodeMirror.keyMap.default["Alt-Right"];
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`;
CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]);
CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"];
this.codeEditor = CodeMirror(this.$editor[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
matchBrackets: true,
getExtraOpts() {
return {
keyMap: options.is('vimKeymapEnabled') ? "vim": "default",
matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
lint: true,
gutters: ["CodeMirror-lint-markers"],
lineNumbers: true,
tabindex: 300,
// we line wrap partly also because without it horizontal scrollbar displays only when you scroll
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
dragDrop: false, // with true the editor inlines dropped files which is not what we expect
placeholder: t('editable_code.placeholder'),
});
};
}
onEditorInitialized() {
this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate());
}
@@ -75,57 +51,18 @@ export default class EditableCodeTypeWidget extends TypeWidget {
const blob = await this.note.getBlob();
await this.spacedUpdate.allowUpdateWithoutChange(() => {
// CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
// we provide fallback
this.codeEditor.setValue(blob.content || "");
this.codeEditor.clearHistory();
let info = CodeMirror.findModeByMIME(note.mime);
if (!info) {
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
// To avoid inheriting a mode from a previously open code note.
info = CodeMirror.findModeByMIME("text/plain");
}
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
this._update(note, blob.content);
});
this.show();
}
show() {
this.$widget.show();
if (this.codeEditor) { // show can be called before render
this.codeEditor.refresh();
}
}
getData() {
return {
content: this.codeEditor.getValue()
};
}
focus() {
this.$editor.focus();
this.codeEditor.focus();
}
scrollToEnd() {
this.codeEditor.setCursor(this.codeEditor.lineCount(), 0);
this.codeEditor.focus();
}
cleanup() {
if (this.codeEditor) {
this.spacedUpdate.allowUpdateWithoutChange(() => {
this.codeEditor.setValue('');
});
}
}
async executeWithCodeEditorEvent({resolve, ntxId}) {
if (!this.isNoteContext(ntxId)) {
return;

View File

@@ -1,6 +1,4 @@
import TypeWidget from "./type_widget.js";
import libraryLoader from "../../services/library_loader.js";
import options from "../../services/options.js";
import AbstractCodeTypeWidget from "./code_widget_base.js";
const TPL = `
<div class="note-detail-readonly-code note-detail-printable">
@@ -18,7 +16,7 @@ const TPL = `
<pre class="note-detail-readonly-code-content"></pre>
</div>`;
export default class ReadOnlyCodeTypeWidget extends TypeWidget {
export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
static getType() { return "readOnlyCode"; }
doRender() {
@@ -26,35 +24,6 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget {
this.$editor = this.$widget.find('.note-detail-readonly-code-content');
super.doRender();
this.initialized = this.initEditor();
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
// these conflict with backward/forward navigation shortcuts
delete CodeMirror.keyMap.default["Alt-Left"];
delete CodeMirror.keyMap.default["Alt-Right"];
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`;
CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]);
CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"];
this.codeEditor = CodeMirror(this.$editor[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
matchBrackets: true,
matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
gutters: ["CodeMirror-lint-markers"],
lineNumbers: true,
// we line wrap partly also because without it horizontal scrollbar displays only when you scroll
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
readOnly: true
});
}
async doRefresh(note) {
@@ -64,47 +33,14 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget {
content = this.format(content);
}
// CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
// we provide fallback
this.codeEditor.setValue(content || "");
this.codeEditor.clearHistory();
let info = CodeMirror.findModeByMIME(note.mime);
if (!info) {
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
// To avoid inheriting a mode from a previously open code note.
info = CodeMirror.findModeByMIME("text/plain");
}
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
this._update(note, content);
this.show();
}
show() {
this.$widget.show();
if (this.codeEditor) { // show can be called before render
this.codeEditor.refresh();
}
}
focus() {
this.$editor.focus();
this.codeEditor.focus();
}
scrollToEnd() {
this.codeEditor.setCursor(this.codeEditor.lineCount(), 0);
this.codeEditor.focus();
}
cleanup() {
if (this.codeEditor) {
this.spacedUpdate.allowUpdateWithoutChange(() => {
this.codeEditor.setValue('');
});
}
getExtraOpts() {
return {
readOnly: true
};
}
async executeWithContentElementEvent({resolve, ntxId}) {