From c7b7c68a05c9e3702665dc3fa9fa4c598219d470 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 19 Oct 2024 23:12:33 +0300 Subject: [PATCH] client: Reduce code duplication for CodeMirror --- .../widgets/type_widgets/code_widget_base.js | 89 +++++++++++++++++++ .../app/widgets/type_widgets/editable_code.js | 83 +++-------------- .../widgets/type_widgets/read_only_code.js | 78 ++-------------- 3 files changed, 106 insertions(+), 144 deletions(-) create mode 100644 src/public/app/widgets/type_widgets/code_widget_base.js diff --git a/src/public/app/widgets/type_widgets/code_widget_base.js b/src/public/app/widgets/type_widgets/code_widget_base.js new file mode 100644 index 000000000..5153c1b60 --- /dev/null +++ b/src/public/app/widgets/type_widgets/code_widget_base.js @@ -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(''); + }); + } + } + +} \ No newline at end of file diff --git a/src/public/app/widgets/type_widgets/editable_code.js b/src/public/app/widgets/type_widgets/editable_code.js index c586830e8..4a70f3962 100644 --- a/src/public/app/widgets/type_widgets/editable_code.js +++ b/src/public/app/widgets/type_widgets/editable_code.js @@ -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 = `
@@ -21,7 +20,7 @@ const TPL = `
`; -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; diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index 040996ddb..cde96eaa8 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -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 = `
@@ -18,7 +16,7 @@ const TPL = `

 
`; -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}) {