chore(client/ts): port more files

This commit is contained in:
Elian Doran 2025-03-16 18:31:31 +02:00
parent 7f4f8bcc75
commit 4e7572cf04
No known key found for this signature in database
11 changed files with 101 additions and 26 deletions

View File

@ -177,7 +177,7 @@ export type CommandMappings = {
ExecuteCommandData<TextEditor> & { ExecuteCommandData<TextEditor> & {
callback?: GetTextEditorCallback; callback?: GetTextEditorCallback;
}; };
executeWithCodeEditor: CommandData & ExecuteCommandData<null>; executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>;
/** /**
* Called upon when attempting to retrieve the content element of a {@link NoteContext}. * 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}. * Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
@ -246,7 +246,7 @@ export type CommandMappings = {
toggleZenMode: CommandData; toggleZenMode: CommandData;
updateAttributeList: CommandData & { attributes: FAttribute[] }; updateAttributeList: CommandData & { attributes: Attribute[] };
saveAttributes: CommandData; saveAttributes: CommandData;
reloadAttributes: CommandData; reloadAttributes: CommandData;
refreshNoteList: CommandData & { noteId: string }; refreshNoteList: CommandData & { noteId: string };

View File

@ -311,7 +311,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
async getCodeEditor() { async getCodeEditor() {
return this.timeout( return this.timeout(
new Promise((resolve) => new Promise<CodeMirrorInstance>((resolve) =>
appContext.triggerCommand("executeWithCodeEditor", { appContext.triggerCommand("executeWithCodeEditor", {
resolve, resolve,
ntxId: this.ntxId ntxId: this.ntxId

View File

@ -1,7 +1,7 @@
import utils from "./utils.js"; import utils from "./utils.js";
type ElementType = HTMLElement | Document; type ElementType = HTMLElement | Document;
type Handler = (e: JQuery.TriggeredEvent<ElementType, string, ElementType, ElementType>) => void; type Handler = (e: JQuery.TriggeredEvent<ElementType | Element, string, ElementType | Element, ElementType | Element>) => void;
function removeGlobalShortcut(namespace: string) { function removeGlobalShortcut(namespace: string) {
bindGlobalShortcut("", null, namespace); bindGlobalShortcut("", null, namespace);
@ -11,7 +11,7 @@ function bindGlobalShortcut(keyboardShortcut: string, handler: Handler | null, n
bindElShortcut($(document), keyboardShortcut, handler, namespace); bindElShortcut($(document), keyboardShortcut, handler, namespace);
} }
function bindElShortcut($el: JQuery<ElementType>, keyboardShortcut: string, handler: Handler | null, namespace: string | null = null) { function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: string, handler: Handler | null, namespace: string | null = null) {
if (utils.isDesktop()) { if (utils.isDesktop()) {
keyboardShortcut = normalizeShortcut(keyboardShortcut); keyboardShortcut = normalizeShortcut(keyboardShortcut);

View File

@ -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<string, string>;
};
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: { var katex: {
renderToString(text: string, opts: { renderToString(text: string, opts: {
throwOnError: boolean throwOnError: boolean

View File

@ -42,10 +42,21 @@ const TPL = `
const MAX_DISPLAYED_NOTES = 15; const MAX_DISPLAYED_NOTES = 15;
// TODO: Deduplicate with server.
interface QuickSearchResponse {
searchResultNoteIds: string[];
error: string;
}
export default class QuickSearchWidget extends BasicWidget { export default class QuickSearchWidget extends BasicWidget {
private dropdown!: bootstrap.Dropdown;
private $searchString!: JQuery<HTMLElement>;
private $dropdownMenu!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); 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.$searchString = this.$widget.find(".search-string");
this.$dropdownMenu = this.$widget.find(".dropdown-menu"); this.$dropdownMenu = this.$widget.find(".dropdown-menu");
@ -88,7 +99,7 @@ export default class QuickSearchWidget extends BasicWidget {
} }
async search() { async search() {
const searchString = this.$searchString.val().trim(); const searchString = String(this.$searchString.val())?.trim();
if (!searchString) { if (!searchString) {
this.dropdown.hide(); this.dropdown.hide();
@ -98,10 +109,10 @@ export default class QuickSearchWidget extends BasicWidget {
this.$dropdownMenu.empty(); this.$dropdownMenu.empty();
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`); this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`); const { searchResultNoteIds, error } = await server.get<QuickSearchResponse>(`quick-search/${encodeURIComponent(searchString)}`);
if (error) { if (error) {
let tooltip = new Tooltip(this.$searchString, { let tooltip = new Tooltip(this.$searchString[0], {
trigger: "manual", trigger: "manual",
title: `Search error: ${error}`, title: `Search error: ${error}`,
placement: "right" placement: "right"
@ -164,7 +175,7 @@ export default class QuickSearchWidget extends BasicWidget {
this.dropdown.hide(); this.dropdown.hide();
await appContext.triggerCommand("searchNotes", { await appContext.triggerCommand("searchNotes", {
searchString: this.$searchString.val() searchString: String(this.$searchString.val())
}); });
} }

View File

@ -4,6 +4,7 @@ import froca from "../../services/froca.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js";
import options from "../../services/options.js"; import options from "../../services/options.js";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
import type FNote from "../../entities/fnote.js";
const TPL = ` const TPL = `
<div class="edited-notes-widget"> <div class="edited-notes-widget">
@ -22,20 +23,32 @@ const TPL = `
</div> </div>
`; `;
// TODO: Deduplicate with server.
interface EditedNotesResponse {
noteId: string;
isDeleted: boolean;
title: string;
notePath: string[];
}
export default class EditedNotesWidget extends NoteContextAwareWidget { export default class EditedNotesWidget extends NoteContextAwareWidget {
private $list!: JQuery<HTMLElement>;
private $noneFound!: JQuery<HTMLElement>;
get name() { get name() {
return "editedNotes"; return "editedNotes";
} }
isEnabled() { isEnabled() {
return super.isEnabled() && this.note.hasOwnedLabel("dateNote"); return super.isEnabled() && this.note?.hasOwnedLabel("dateNote");
} }
getTitle() { getTitle() {
return { return {
show: this.isEnabled(), show: this.isEnabled(),
// promoted attributes have priority over edited notes // 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"), title: t("edited_notes.title"),
icon: "bx bx-calendar-edit" icon: "bx bx-calendar-edit"
}; };
@ -48,8 +61,8 @@ export default class EditedNotesWidget extends NoteContextAwareWidget {
this.$noneFound = this.$widget.find(".no-edited-notes-found"); this.$noneFound = this.$widget.find(".no-edited-notes-found");
} }
async refreshWithNote(note) { async refreshWithNote(note: FNote) {
let editedNotes = await server.get(`edited-notes/${note.getLabelValue("dateNote")}`); let editedNotes = await server.get<EditedNotesResponse[]>(`edited-notes/${note.getLabelValue("dateNote")}`);
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId); editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);

View File

@ -3,6 +3,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js"; import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js";
import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js"; import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js";
import type { CommandListenerData } from "../../components/app_context.js"; import type { CommandListenerData } from "../../components/app_context.js";
import type FAttribute from "../../entities/fattribute.js";
const TPL = ` const TPL = `
<div class="attribute-list"> <div class="attribute-list">
@ -75,7 +76,8 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
} }
async updateAttributeListCommand({ attributes }: CommandListenerData<"updateAttributeList">) { 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() { focus() {

View File

@ -1,6 +1,7 @@
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import libraryLoader from "../../services/library_loader.js"; import libraryLoader from "../../services/library_loader.js";
import options from "../../services/options.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 * 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)`. * - Call `this._update(note, content)` in `#doRefresh(note)`.
*/ */
export default class AbstractCodeTypeWidget extends TypeWidget { export default class AbstractCodeTypeWidget extends TypeWidget {
protected $editor!: JQuery<HTMLElement>;
protected codeEditor!: CodeMirrorInstance;
doRender() { doRender() {
this.initialized = this.#initEditor(); this.initialized = this.#initEditor();
} }
@ -28,8 +33,14 @@ export default class AbstractCodeTypeWidget extends TypeWidget {
delete CodeMirror.keyMap.default["Alt-Right"]; delete CodeMirror.keyMap.default["Alt-Right"];
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`; 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"]); const jsMode = CodeMirror.modeInfo.find((mode) => mode.name === "JavaScript");
CodeMirror.modeInfo.find((mode) => mode.name === "SQLite").mimes = ["text/x-sqlite", "text/x-sqlite;schema=trilium"]; 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], { this.codeEditor = CodeMirror(this.$editor[0], {
value: "", value: "",
@ -73,7 +84,7 @@ export default class AbstractCodeTypeWidget extends TypeWidget {
* @param {*} note the note that was changed. * @param {*} note the note that was changed.
* @param {*} content the new content of the note. * @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) // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
// we provide fallback // we provide fallback
this.codeEditor.setValue(content || ""); this.codeEditor.setValue(content || "");

View File

@ -21,7 +21,6 @@ const TPL = `<div style="height: 100%; display: flex; flex-direction: column;">
export default class BackendLogWidget extends AbstractCodeTypeWidget { export default class BackendLogWidget extends AbstractCodeTypeWidget {
private $editor!: JQuery<HTMLElement>;
private $refreshBackendLog!: JQuery<HTMLElement>; private $refreshBackendLog!: JQuery<HTMLElement>;
doRender() { doRender() {
@ -45,7 +44,7 @@ export default class BackendLogWidget extends AbstractCodeTypeWidget {
} }
async load() { async load() {
const content = await server.get("backend-log"); const content = await server.get<string>("backend-log");
await this.initialized; await this.initialized;
this._update( this._update(

View File

@ -24,8 +24,6 @@ const TPL = `
export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
private $editor!: JQuery<HTMLElement>;
static getType() { static getType() {
return "editableCode"; return "editableCode";
} }
@ -59,7 +57,7 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
const blob = await this.note?.getBlob(); const blob = await this.note?.getBlob();
await this.spacedUpdate.allowUpdateWithoutChange(() => { await this.spacedUpdate.allowUpdateWithoutChange(() => {
this._update(note, blob?.content); this._update(note, blob?.content ?? "");
}); });
this.show(); this.show();

View File

@ -19,7 +19,6 @@ const TPL = `
</div>`; </div>`;
export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget { export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
$editor!: JQuery<HTMLElement>;
static getType() { static getType() {
return "readOnlyCode"; return "readOnlyCode";