diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index 61e3ecd2e..4eb8bdb6a 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -177,7 +177,7 @@ export type CommandMappings = { ExecuteCommandData & { callback?: GetTextEditorCallback; }; - executeWithCodeEditor: CommandData & ExecuteCommandData; + executeWithCodeEditor: CommandData & ExecuteCommandData; /** * Called upon when attempting to retrieve the content element of a {@link NoteContext}. * Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}. @@ -246,7 +246,7 @@ export type CommandMappings = { toggleZenMode: CommandData; - updateAttributeList: CommandData & { attributes: FAttribute[] }; + updateAttributeList: CommandData & { attributes: Attribute[] }; saveAttributes: CommandData; reloadAttributes: CommandData; refreshNoteList: CommandData & { noteId: string }; diff --git a/src/public/app/components/note_context.ts b/src/public/app/components/note_context.ts index 0b4798cea..7d9f3d246 100644 --- a/src/public/app/components/note_context.ts +++ b/src/public/app/components/note_context.ts @@ -311,7 +311,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> async getCodeEditor() { return this.timeout( - new Promise((resolve) => + new Promise((resolve) => appContext.triggerCommand("executeWithCodeEditor", { resolve, ntxId: this.ntxId diff --git a/src/public/app/services/shortcuts.ts b/src/public/app/services/shortcuts.ts index 65da81a0d..d19e434d1 100644 --- a/src/public/app/services/shortcuts.ts +++ b/src/public/app/services/shortcuts.ts @@ -1,7 +1,7 @@ import utils from "./utils.js"; type ElementType = HTMLElement | Document; -type Handler = (e: JQuery.TriggeredEvent) => void; +type Handler = (e: JQuery.TriggeredEvent) => void; function removeGlobalShortcut(namespace: string) { bindGlobalShortcut("", null, namespace); @@ -11,7 +11,7 @@ function bindGlobalShortcut(keyboardShortcut: string, handler: Handler | null, n bindElShortcut($(document), keyboardShortcut, handler, namespace); } -function bindElShortcut($el: JQuery, keyboardShortcut: string, handler: Handler | null, namespace: string | null = null) { +function bindElShortcut($el: JQuery, keyboardShortcut: string, handler: Handler | null, namespace: string | null = null) { if (utils.isDesktop()) { keyboardShortcut = normalizeShortcut(keyboardShortcut); diff --git a/src/public/app/types.d.ts b/src/public/app/types.d.ts index beea7477f..0252c9d37 100644 --- a/src/public/app/types.d.ts +++ b/src/public/app/types.d.ts @@ -184,6 +184,48 @@ declare global { } }; + var CodeMirror: { + (el: HTMLElement, opts: { + value: string; + viewportMargin: number; + indentUnit: number; + matchBrackets: boolean; + matchTags: { bothTags: boolean }; + highlightSelectionMatches: { + showToken: boolean; + annotateScrollbar: boolean; + }; + lineNumbers: boolean; + lineWrapping: boolean; + }): CodeMirrorInstance; + keyMap: { + default: Record; + }; + modeURL: string; + modeInfo: ModeInfo[]; + findModeByMIME(mime: string): ModeInfo; + autoLoadMode(instance: CodeMirrorInstance, mode: string) + } + + interface ModeInfo { + name: string; + mode: string; + mime: string; + mimes: string[]; + } + + interface CodeMirrorInstance { + getValue(): string; + setValue(val: string); + clearHistory(); + setOption(name: string, value: string); + refresh(); + focus(); + setCursor(line: number, col: number); + lineCount(): number; + on(event: string, callback: () => void); + } + var katex: { renderToString(text: string, opts: { throwOnError: boolean diff --git a/src/public/app/widgets/quick_search.js b/src/public/app/widgets/quick_search.ts similarity index 90% rename from src/public/app/widgets/quick_search.js rename to src/public/app/widgets/quick_search.ts index 2f456d355..90ed9325e 100644 --- a/src/public/app/widgets/quick_search.js +++ b/src/public/app/widgets/quick_search.ts @@ -42,10 +42,21 @@ const TPL = ` const MAX_DISPLAYED_NOTES = 15; +// TODO: Deduplicate with server. +interface QuickSearchResponse { + searchResultNoteIds: string[]; + error: string; +} + export default class QuickSearchWidget extends BasicWidget { + + private dropdown!: bootstrap.Dropdown; + private $searchString!: JQuery; + private $dropdownMenu!: JQuery; + doRender() { this.$widget = $(TPL); - this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); + this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]); this.$searchString = this.$widget.find(".search-string"); this.$dropdownMenu = this.$widget.find(".dropdown-menu"); @@ -88,7 +99,7 @@ export default class QuickSearchWidget extends BasicWidget { } async search() { - const searchString = this.$searchString.val().trim(); + const searchString = String(this.$searchString.val())?.trim(); if (!searchString) { this.dropdown.hide(); @@ -98,10 +109,10 @@ export default class QuickSearchWidget extends BasicWidget { this.$dropdownMenu.empty(); this.$dropdownMenu.append(`${t("quick-search.searching")}`); - const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`); + const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`); if (error) { - let tooltip = new Tooltip(this.$searchString, { + let tooltip = new Tooltip(this.$searchString[0], { trigger: "manual", title: `Search error: ${error}`, placement: "right" @@ -164,7 +175,7 @@ export default class QuickSearchWidget extends BasicWidget { this.dropdown.hide(); await appContext.triggerCommand("searchNotes", { - searchString: this.$searchString.val() + searchString: String(this.$searchString.val()) }); } diff --git a/src/public/app/widgets/ribbon_widgets/edited_notes.js b/src/public/app/widgets/ribbon_widgets/edited_notes.ts similarity index 77% rename from src/public/app/widgets/ribbon_widgets/edited_notes.js rename to src/public/app/widgets/ribbon_widgets/edited_notes.ts index 437e73491..332d52fb7 100644 --- a/src/public/app/widgets/ribbon_widgets/edited_notes.js +++ b/src/public/app/widgets/ribbon_widgets/edited_notes.ts @@ -4,6 +4,7 @@ import froca from "../../services/froca.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js"; import options from "../../services/options.js"; import { t } from "../../services/i18n.js"; +import type FNote from "../../entities/fnote.js"; const TPL = `
@@ -15,27 +16,39 @@ const TPL = ` overflow: auto; } - +
${t("edited_notes.no_edited_notes_found")}
- +
`; +// TODO: Deduplicate with server. +interface EditedNotesResponse { + noteId: string; + isDeleted: boolean; + title: string; + notePath: string[]; +} + export default class EditedNotesWidget extends NoteContextAwareWidget { + + private $list!: JQuery; + private $noneFound!: JQuery; + get name() { return "editedNotes"; } isEnabled() { - return super.isEnabled() && this.note.hasOwnedLabel("dateNote"); + return super.isEnabled() && this.note?.hasOwnedLabel("dateNote"); } getTitle() { return { show: this.isEnabled(), // promoted attributes have priority over edited notes - activate: (this.note.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon"), + activate: (this.note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon"), title: t("edited_notes.title"), icon: "bx bx-calendar-edit" }; @@ -48,8 +61,8 @@ export default class EditedNotesWidget extends NoteContextAwareWidget { this.$noneFound = this.$widget.find(".no-edited-notes-found"); } - async refreshWithNote(note) { - let editedNotes = await server.get(`edited-notes/${note.getLabelValue("dateNote")}`); + async refreshWithNote(note: FNote) { + let editedNotes = await server.get(`edited-notes/${note.getLabelValue("dateNote")}`); editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId); diff --git a/src/public/app/widgets/ribbon_widgets/owned_attribute_list.ts b/src/public/app/widgets/ribbon_widgets/owned_attribute_list.ts index 0d7794b39..af0ac5bb2 100644 --- a/src/public/app/widgets/ribbon_widgets/owned_attribute_list.ts +++ b/src/public/app/widgets/ribbon_widgets/owned_attribute_list.ts @@ -3,6 +3,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js"; import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js"; import type { CommandListenerData } from "../../components/app_context.js"; +import type FAttribute from "../../entities/fattribute.js"; const TPL = `
@@ -75,7 +76,8 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget { } async updateAttributeListCommand({ attributes }: CommandListenerData<"updateAttributeList">) { - await this.attributeEditorWidget.updateAttributeList(attributes); + // TODO: See why we need FAttribute[] and Attribute[] + await this.attributeEditorWidget.updateAttributeList(attributes as FAttribute[]); } focus() { diff --git a/src/public/app/widgets/type_widgets/abstract_code_type_widget.js b/src/public/app/widgets/type_widgets/abstract_code_type_widget.ts similarity index 86% rename from src/public/app/widgets/type_widgets/abstract_code_type_widget.js rename to src/public/app/widgets/type_widgets/abstract_code_type_widget.ts index 8baae266a..532951d5c 100644 --- a/src/public/app/widgets/type_widgets/abstract_code_type_widget.js +++ b/src/public/app/widgets/type_widgets/abstract_code_type_widget.ts @@ -1,6 +1,7 @@ import TypeWidget from "./type_widget.js"; import libraryLoader from "../../services/library_loader.js"; import options from "../../services/options.js"; +import type FNote from "../../entities/fnote.js"; /** * An abstract {@link TypeWidget} which implements the CodeMirror editor, meant to be used as a parent for @@ -16,6 +17,10 @@ import options from "../../services/options.js"; * - Call `this._update(note, content)` in `#doRefresh(note)`. */ export default class AbstractCodeTypeWidget extends TypeWidget { + + protected $editor!: JQuery; + protected codeEditor!: CodeMirrorInstance; + doRender() { this.initialized = this.#initEditor(); } @@ -28,8 +33,14 @@ export default class AbstractCodeTypeWidget extends TypeWidget { 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"]; + const jsMode = CodeMirror.modeInfo.find((mode) => mode.name === "JavaScript"); + if (jsMode) { + jsMode.mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]); + } + const sqlMode = CodeMirror.modeInfo.find((mode) => mode.name === "SQLite"); + if (sqlMode) { + sqlMode.mimes = ["text/x-sqlite", "text/x-sqlite;schema=trilium"]; + } this.codeEditor = CodeMirror(this.$editor[0], { value: "", @@ -73,7 +84,7 @@ export default class AbstractCodeTypeWidget extends TypeWidget { * @param {*} note the note that was changed. * @param {*} content the new content of the note. */ - _update(note, content) { + _update(note: { mime: string }, content: string) { // 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 || ""); diff --git a/src/public/app/widgets/type_widgets/content/backend_log.ts b/src/public/app/widgets/type_widgets/content/backend_log.ts index e3f3827a3..03699d873 100644 --- a/src/public/app/widgets/type_widgets/content/backend_log.ts +++ b/src/public/app/widgets/type_widgets/content/backend_log.ts @@ -21,7 +21,6 @@ const TPL = `
export default class BackendLogWidget extends AbstractCodeTypeWidget { - private $editor!: JQuery; private $refreshBackendLog!: JQuery; doRender() { @@ -45,7 +44,7 @@ export default class BackendLogWidget extends AbstractCodeTypeWidget { } async load() { - const content = await server.get("backend-log"); + const content = await server.get("backend-log"); await this.initialized; this._update( diff --git a/src/public/app/widgets/type_widgets/editable_code.ts b/src/public/app/widgets/type_widgets/editable_code.ts index 389eb7ae3..06c3ece64 100644 --- a/src/public/app/widgets/type_widgets/editable_code.ts +++ b/src/public/app/widgets/type_widgets/editable_code.ts @@ -24,8 +24,6 @@ const TPL = ` export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { - private $editor!: JQuery; - static getType() { return "editableCode"; } @@ -59,7 +57,7 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { const blob = await this.note?.getBlob(); await this.spacedUpdate.allowUpdateWithoutChange(() => { - this._update(note, blob?.content); + this._update(note, blob?.content ?? ""); }); this.show(); diff --git a/src/public/app/widgets/type_widgets/read_only_code.ts b/src/public/app/widgets/type_widgets/read_only_code.ts index 4d7ce3ab0..e4a4bc447 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.ts +++ b/src/public/app/widgets/type_widgets/read_only_code.ts @@ -19,7 +19,6 @@ const TPL = `
`; export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget { - $editor!: JQuery; static getType() { return "readOnlyCode";