Merge pull request #2029 from TriliumNext/refactor/typecheck_errors

Solve typecheck errors
This commit is contained in:
Elian Doran 2025-05-29 10:21:53 +03:00 committed by GitHub
commit 63ea9104c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
86 changed files with 345 additions and 1003 deletions

View File

@ -39,7 +39,7 @@ jobs:
- uses: nrwl/nx-set-shas@v4 - uses: nrwl/nx-set-shas@v4
- name: Check affected - name: Check affected
run: pnpm nx affected -t build rebuild-deps run: pnpm nx affected -t typecheck build rebuild-deps
report-electron-size: report-electron-size:
name: Report Electron size name: Report Electron size

View File

@ -26,5 +26,7 @@
".github/workflows/nightly.yml" ".github/workflows/nightly.yml"
], ],
"typescript.validate.enable": true, "typescript.validate.enable": true,
"typescript.tsserver.experimental.enableProjectDiagnostics": true "typescript.tsserver.experimental.enableProjectDiagnostics": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
} }

View File

@ -27,6 +27,7 @@ import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.j
import type { NativeImage, TouchBar } from "electron"; import type { NativeImage, TouchBar } from "electron";
import TouchBarComponent from "./touch_bar.js"; import TouchBarComponent from "./touch_bar.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5"; import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget; getRootWidget: (appContext: AppContext) => RootWidget;
@ -191,7 +192,7 @@ export type CommandMappings = {
ExecuteCommandData<CKTextEditor> & { ExecuteCommandData<CKTextEditor> & {
callback?: GetTextEditorCallback; callback?: GetTextEditorCallback;
}; };
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>; executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirror>;
/** /**
* 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}.

View File

@ -54,7 +54,7 @@ export default class TouchBarComponent extends Component {
#refreshTouchBar() { #refreshTouchBar() {
const { TouchBar } = this.remote; const { TouchBar } = this.remote;
const parentComponent = this.lastFocusedComponent; const parentComponent = this.lastFocusedComponent;
let touchBar = null; let touchBar: Electron.CrossProcessExports.TouchBar | null = null;
if (this.$activeModal?.length) { if (this.$activeModal?.length) {
touchBar = this.#buildModalTouchBar(); touchBar = this.#buildModalTouchBar();

View File

@ -789,7 +789,7 @@ class FNote {
*/ */
async getRelationTargets(name: string) { async getRelationTargets(name: string) {
const relations = this.getRelations(name); const relations = this.getRelations(name);
const targets = []; const targets: (FNote | null)[] = [];
for (const relation of relations) { for (const relation of relations) {
targets.push(await this.froca.getNote(relation.value)); targets.push(await this.froca.getNote(relation.value));

View File

@ -80,7 +80,7 @@ async function copy(branchIds: string[]) {
if (utils.isElectron()) { if (utils.isElectron()) {
// https://github.com/zadam/trilium/issues/2401 // https://github.com/zadam/trilium/issues/2401
const { clipboard } = require("electron"); const { clipboard } = require("electron");
const links = []; const links: string[] = [];
for (const branch of froca.getBranches(clipboardBranchIds)) { for (const branch of froca.getBranches(clipboardBranchIds)) {
const $link = await linkService.createLink(`${branch.parentNoteId}/${branch.noteId}`, { referenceLink: true }); const $link = await linkService.createLink(`${branch.parentNoteId}/${branch.noteId}`, { referenceLink: true });

View File

@ -50,7 +50,7 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
// To this we count: standard parent-child relationships and template/inherit relations (attribute inheritance follows them). // To this we count: standard parent-child relationships and template/inherit relations (attribute inheritance follows them).
// Here we watch for changes which might violate this principle - e.g., an introduction of a new "inherit" relation might // Here we watch for changes which might violate this principle - e.g., an introduction of a new "inherit" relation might
// mean we need to load the target of the relation (and then perhaps transitively the whole note path of this target). // mean we need to load the target of the relation (and then perhaps transitively the whole note path of this target).
const missingNoteIds = []; const missingNoteIds: string[] = [];
for (const { entityName, entity } of entityChanges) { for (const { entityName, entity } of entityChanges) {
if (!entity) { if (!entity) {

View File

@ -215,9 +215,9 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
const viewScope: ViewScope = { const viewScope: ViewScope = {
viewMode: "default" viewMode: "default"
}; };
let ntxId = null; let ntxId: string | null = null;
let hoistedNoteId = null; let hoistedNoteId: string | null = null;
let searchString = null; let searchString: string | null = null;
if (paramString) { if (paramString) {
for (const pair of paramString.split("&")) { for (const pair of paramString.split("&")) {

View File

@ -1,7 +1,7 @@
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url"; type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
type Multiplicity = "single" | "multi"; type Multiplicity = "single" | "multi";
interface DefinitionObject { export interface DefinitionObject {
isPromoted?: boolean; isPromoted?: boolean;
labelType?: LabelType; labelType?: LabelType;
multiplicity?: Multiplicity; multiplicity?: Multiplicity;

View File

@ -97,7 +97,7 @@ export function loadHighlightingTheme(themeName: string) {
*/ */
export function isSyntaxHighlightEnabled() { export function isSyntaxHighlightEnabled() {
const theme = options.get("codeBlockTheme"); const theme = options.get("codeBlockTheme");
return theme && theme !== "none"; return !!theme && theme !== "none";
} }
/** /**

View File

@ -34,8 +34,8 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root
path.push("root"); path.push("root");
} }
const effectivePathSegments = []; const effectivePathSegments: string[] = [];
let childNoteId = null; let childNoteId: string | null = null;
let i = 0; let i = 0;
while (true) { while (true) {
@ -197,7 +197,7 @@ function getNotePath(node: Fancytree.FancytreeNode) {
return ""; return "";
} }
const path = []; const path: string[] = [];
while (node) { while (node) {
if (node.data.noteId) { if (node.data.noteId) {
@ -236,7 +236,7 @@ async function getNoteTitle(noteId: string, parentNoteId: string | null = null)
} }
async function getNotePathTitleComponents(notePath: string) { async function getNotePathTitleComponents(notePath: string) {
const titleComponents = []; const titleComponents: string[] = [];
if (notePath.startsWith("root/")) { if (notePath.startsWith("root/")) {
notePath = notePath.substr(5); notePath = notePath.substr(5);

View File

@ -51,7 +51,7 @@ function getMonthsInDateRange(startDate: string, endDate: string) {
const end = endDate.split("-"); const end = endDate.split("-");
const startYear = parseInt(start[0]); const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]); const endYear = parseInt(end[0]);
const dates = []; const dates: string[] = [];
for (let i = startYear; i <= endYear; i++) { for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1; const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
@ -84,7 +84,7 @@ function formatTimeInterval(ms: number) {
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24); const days = Math.floor(hours / 24);
const plural = (count: number, name: string) => `${count} ${name}${count > 1 ? "s" : ""}`; const plural = (count: number, name: string) => `${count} ${name}${count > 1 ? "s" : ""}`;
const segments = []; const segments: string[] = [];
if (days > 0) { if (days > 0) {
segments.push(plural(days, "day")); segments.push(plural(days, "day"));
@ -149,7 +149,7 @@ function isMac() {
export const hasTouchBar = (isMac() && isElectron()); export const hasTouchBar = (isMac() && isElectron());
function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement>) { function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement> | JQueryEventObject) {
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
} }

View File

@ -28,7 +28,7 @@ interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
* ]); * ]);
*/ */
export function buildNotes(notes: NoteDefinition[]) { export function buildNotes(notes: NoteDefinition[]) {
const ids = []; const ids: string[] = [];
for (const noteDef of notes) { for (const noteDef of notes) {
ids.push(buildNote(noteDef).noteId); ids.push(buildNote(noteDef).noteId);

View File

@ -718,7 +718,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
} }
buildDefinitionValue() { buildDefinitionValue() {
const props = []; const props: string[] = [];
if (this.$inputPromoted.is(":checked")) { if (this.$inputPromoted.is(":checked")) {
props.push("promoted"); props.push("promoted");
@ -728,10 +728,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
} }
} }
props.push(this.$inputMultiplicity.val()); props.push(this.$inputMultiplicity.val() as string);
if (this.attrType === "label-definition") { if (this.attrType === "label-definition") {
props.push(this.$inputLabelType.val()); props.push(this.$inputLabelType.val() as string);
if (this.$inputLabelType.val() === "number" && this.$inputNumberPrecision.val() !== "") { if (this.$inputLabelType.val() === "number" && this.$inputNumberPrecision.val() !== "") {
props.push(`precision=${this.$inputNumberPrecision.val()}`); props.push(`precision=${this.$inputNumberPrecision.val()}`);

View File

@ -29,7 +29,7 @@ export default class LauncherContainer extends FlexContainer<LauncherWidget> {
return; return;
} }
const newChildren = []; const newChildren: LauncherWidget[] = [];
for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) { for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) {
try { try {

View File

@ -217,7 +217,7 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
} }
refreshNotShown(data: NoteSwitchedContext | EventData<"activeContextChanged">) { refreshNotShown(data: NoteSwitchedContext | EventData<"activeContextChanged">) {
const promises = []; const promises: (Promise<unknown> | null | undefined)[] = [];
for (const subContext of data.noteContext.getMainContext().getSubContexts()) { for (const subContext of data.noteContext.getMainContext().getSubContexts()) {
if (!subContext.ntxId) { if (!subContext.ntxId) {

View File

@ -2,16 +2,6 @@ import type { FindAndReplaceState, FindCommandResult } from "@triliumnext/ckedit
import type { FindResult } from "./find.js"; import type { FindResult } from "./find.js";
import type FindWidget from "./find.js"; import type FindWidget from "./find.js";
// TODO: Deduplicate.
interface Match {
className: string;
clear(): void;
find(): {
from: number;
to: number;
};
}
export default class FindInText { export default class FindInText {
private parent: FindWidget; private parent: FindWidget;
@ -35,7 +25,7 @@ export default class FindInText {
} }
const model = textEditor.model; const model = textEditor.model;
let findResult = null; let findResult: FindCommandResult | null = null;
let totalFound = 0; let totalFound = 0;
let currentFound = -1; let currentFound = -1;

View File

@ -243,7 +243,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
// Used to determine if a string is only a formula // Used to determine if a string is only a formula
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/; const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
for (let match = null, hltIndex = 0; (match = combinedRegex.exec(content)) !== null; hltIndex++) { for (let match: RegExpMatchArray | null = null, hltIndex = 0; (match = combinedRegex.exec(content)) !== null; hltIndex++) {
const subHtml = match[0]; const subHtml = match[0];
const startIndex = match.index; const startIndex = match.index;
const endIndex = combinedRegex.lastIndex; const endIndex = combinedRegex.lastIndex;
@ -324,8 +324,9 @@ export default class HighlightsListWidget extends RightPanelWidget {
}); });
} else { } else {
const textEditor = await this.noteContext.getTextEditor(); const textEditor = await this.noteContext.getTextEditor();
if (textEditor) { const el = textEditor?.editing.view.domRoots.values().next().value;
targetElement = $(textEditor.editing.view.domRoots.values().next().value) if (el) {
targetElement = $(el)
.find(findSubStr) .find(findSubStr)
.filter(function () { .filter(function () {
// When finding span[style*="color"] but not looking for span[style*="background-color"], // When finding span[style*="color"] but not looking for span[style*="background-color"],

View File

@ -350,7 +350,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}, },
scrollParent: this.$tree, scrollParent: this.$tree,
minExpandLevel: 2, // root can't be collapsed minExpandLevel: 2, // root can't be collapsed
click: (event: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, data): boolean => { click: (event, data): boolean => {
this.activityDetected(); this.activityDetected();
const targetType = data.targetType; const targetType = data.targetType;
@ -745,7 +745,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
prepareChildren(parentNote: FNote) { prepareChildren(parentNote: FNote) {
utils.assertArguments(parentNote); utils.assertArguments(parentNote);
const noteList = []; const noteList: Node[] = [];
const hideArchivedNotes = this.hideArchivedNotes; const hideArchivedNotes = this.hideArchivedNotes;
@ -839,7 +839,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
getExtraClasses(note: FNote) { getExtraClasses(note: FNote) {
utils.assertArguments(note); utils.assertArguments(note);
const extraClasses = []; const extraClasses: string[] = [];
if (note.isProtected) { if (note.isProtected) {
extraClasses.push("protected"); extraClasses.push("protected");
@ -1265,8 +1265,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const allBranchesDeleted = branchRows.every((branchRow) => !!branchRow.isDeleted); const allBranchesDeleted = branchRows.every((branchRow) => !!branchRow.isDeleted);
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded. // activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
let movedActiveNode = null; let movedActiveNode: Fancytree.FancytreeNode | null = null;
let parentsOfAddedNodes = []; let parentsOfAddedNodes: Fancytree.FancytreeNode[] = [];
for (const branchRow of branchRows) { for (const branchRow of branchRows) {
if (branchRow.noteId) { if (branchRow.noteId) {

View File

@ -85,7 +85,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
this.$notePathIntro.text(t("note_paths.intro_not_placed")); this.$notePathIntro.text(t("note_paths.intro_not_placed"));
} }
const renderedPaths = []; const renderedPaths: JQuery<HTMLElement>[] = [];
for (const notePathRecord of sortedNotePaths) { for (const notePathRecord of sortedNotePaths) {
const notePath = notePathRecord.notePath; const notePath = notePathRecord.notePath;
@ -115,7 +115,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
} }
} }
const icons = []; const icons: string[] = [];
if (this.notePath === notePath.join("/")) { if (this.notePath === notePath.join("/")) {
$pathItem.addClass("path-current"); $pathItem.addClass("path-current");

View File

@ -122,7 +122,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
return; return;
} }
const $cells = []; const $cells: JQuery<HTMLElement>[] = [];
for (const definitionAttr of promotedDefAttrs) { for (const definitionAttr of promotedDefAttrs) {
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation"; const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";

View File

@ -304,7 +304,7 @@ export default class TocWidget extends RightPanelWidget {
const validHeadingKeys = new Set<string>(); // Used to clean up obsolete entries in tocCollapsedHeadings const validHeadingKeys = new Set<string>(); // Used to clean up obsolete entries in tocCollapsedHeadings
let headingCount = 0; let headingCount = 0;
for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) { for (let m: RegExpMatchArray | null = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) {
// //
// Nest/unnest whatever necessary number of ordered lists // Nest/unnest whatever necessary number of ordered lists
// //
@ -394,12 +394,14 @@ export default class TocWidget extends RightPanelWidget {
const isDocNote = this.note.type === "doc"; const isDocNote = this.note.type === "doc";
const isReadOnly = await this.noteContext.isReadOnly(); const isReadOnly = await this.noteContext.isReadOnly();
let $container; let $container: JQuery<HTMLElement> | null = null;
if (isReadOnly || isDocNote) { if (isReadOnly || isDocNote) {
$container = await this.noteContext.getContentElement(); $container = await this.noteContext.getContentElement();
} else { } else {
const textEditor = await this.noteContext.getTextEditor(); const textEditor = await this.noteContext.getTextEditor();
$container = $(textEditor.sourceElement); if (textEditor?.sourceElement) {
$container = $(textEditor.sourceElement);
}
} }
const headingElement = $container?.find(":header:not(section.include-note :header)")?.[headingIndex]; const headingElement = $container?.find(":header:not(section.include-note :header)")?.[headingIndex];

View File

@ -138,8 +138,8 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
*/ */
async #setupPanZoom(preservePanZoom: boolean) { async #setupPanZoom(preservePanZoom: boolean) {
// Clean up // Clean up
let pan = null; let pan: SvgPanZoom.Point | null = null;
let zoom = null; let zoom: number | null = null;
if (preservePanZoom && this.zoomInstance) { if (preservePanZoom && this.zoomInstance) {
// Store pan and zoom for same note, when the user is editing the note. // Store pan and zoom for same note, when the user is editing the note.
pan = this.zoomInstance.getPan(); pan = this.zoomInstance.getPan();

View File

@ -6,7 +6,7 @@ type ToolbarConfig = string | "|" | { items: ToolbarConfig[] };
describe("CKEditor config", () => { describe("CKEditor config", () => {
it("has same toolbar items for fixed and floating", () => { it("has same toolbar items for fixed and floating", () => {
function traverseItems(config: ToolbarConfig): string[] { function traverseItems(config: ToolbarConfig): string[] {
const result = []; const result: (string | string[])[] = [];
if (typeof config === "object") { if (typeof config === "object") {
for (const item of config.items) { for (const item of config.items) {
result.push(traverseItems(item)); result.push(traverseItems(item));

View File

@ -111,7 +111,7 @@ export function buildConfig(): EditorConfig {
}, },
mapLanguageName: getHighlightJsNameForMime, mapLanguageName: getHighlightJsNameForMime,
defaultMimeType: MIME_TYPE_AUTO, defaultMimeType: MIME_TYPE_AUTO,
enabled: isSyntaxHighlightEnabled enabled: isSyntaxHighlightEnabled()
}, },
clipboard: { clipboard: {
copy: copyText copy: copyText
@ -134,7 +134,7 @@ export function buildToolbarConfig(isClassicToolbar: boolean) {
export function buildMobileToolbar() { export function buildMobileToolbar() {
const classicConfig = buildClassicToolbar(false); const classicConfig = buildClassicToolbar(false);
const items = []; const items: string[] = [];
for (const item of classicConfig.toolbar.items) { for (const item of classicConfig.toolbar.items) {
if (typeof item === "object" && "items" in item) { if (typeof item === "object" && "items" in item) {

View File

@ -589,7 +589,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
backgroundColor: buildSelectedBackgroundColor(editor.commands.get(command)?.value as boolean) backgroundColor: buildSelectedBackgroundColor(editor.commands.get(command)?.value as boolean)
}); });
let headingSelectedIndex = undefined; let headingSelectedIndex: number | undefined = undefined;
const headingCommand = editor.commands.get("heading"); const headingCommand = editor.commands.get("heading");
const paragraphCommand = editor.commands.get("paragraph"); const paragraphCommand = editor.commands.get("paragraph");
if (paragraphCommand?.value) { if (paragraphCommand?.value) {

View File

@ -1,43 +0,0 @@
import { describe, expect, it } from "vitest";
import { trimIndentation } from "@triliumnext/commons";
import { validateMermaid } from "./mermaid.js";
describe("Mermaid linter", () => {
(global as any).CodeMirror = {
Pos(line: number, col: number) {
return { line, col };
}
};
it("reports correctly bad diagram type", async () => {
const input = trimIndentation`\
stateDiagram-v23
[*] -> Still
`;
const result = await validateMermaid(input);
expect(result).toMatchObject([{
message: "Expecting 'SPACE', 'NL', 'SD', got 'ID'",
from: { line: 0, col: 0 },
to: { line: 0, col: 1 }
}]);
});
it("reports correctly basic arrow missing in diagram", async () => {
const input = trimIndentation`\
xychart-beta horizontal
title "Percentage usge"
x-axis [data, sys, usr, var]
y-axis 0--->100
bar [20, 70, 0, 0]
`;
const result = await validateMermaid(input);
expect(result).toMatchObject([{
message: "Expecting 'ARROW_DELIMITER', got 'MINUS'",
from: { line: 3, col: 8 },
to: { line: 3, col: 9 }
}]);
});
});

View File

@ -1,58 +0,0 @@
import mermaid from "mermaid";
interface MermaidParseError extends Error {
hash: {
text: string;
token: string;
line: number;
loc: {
first_line: number;
first_column: number;
last_line: number;
last_column: number;
};
expected: string[]
}
}
export default function registerErrorReporter() {
CodeMirror.registerHelper("lint", null, validateMermaid);
}
export async function validateMermaid(text: string) {
if (!text.trim()) {
return [];
}
try {
await mermaid.parse(text);
} catch (e: unknown) {
console.warn("Got validation error", JSON.stringify(e));
const mermaidError = (e as MermaidParseError);
const loc = mermaidError.hash.loc;
let firstCol = loc.first_column + 1;
let lastCol = loc.last_column + 1;
if (firstCol === 1 && lastCol === 1) {
firstCol = 0;
}
let messageLines = mermaidError.message.split("\n");
if (messageLines.length >= 4) {
messageLines = messageLines.slice(3);
}
return [
{
message: messageLines.join("\n"),
severity: "error",
from: CodeMirror.Pos(loc.first_line - 1, firstCol),
to: CodeMirror.Pos(loc.last_line - 1, lastCol)
}
];
}
return [];
}

View File

@ -207,8 +207,8 @@ export default class MindMapWidget extends TypeWidget {
await this.#initLibrary(content?.direction); await this.#initLibrary(content?.direction);
} }
this.mind.refresh(content ?? this.MindElixir.new(NEW_TOPIC_NAME)); this.mind!.refresh(content ?? this.MindElixir.new(NEW_TOPIC_NAME));
this.mind.toCenter(); this.mind!.toCenter();
} }
async #initLibrary(direction?: number) { async #initLibrary(direction?: number) {
@ -259,7 +259,7 @@ export default class MindMapWidget extends TypeWidget {
} }
async renderSvg() { async renderSvg() {
return await this.mind.exportSvg().text(); return await this.mind!.exportSvg().text();
} }
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {

View File

@ -198,7 +198,7 @@ export default class AiSettingsWidget extends OptionsWidget {
const providerPrecedence = (this.$widget.find('.ai-provider-precedence').val() as string || '').split(','); const providerPrecedence = (this.$widget.find('.ai-provider-precedence').val() as string || '').split(',');
// Check for OpenAI configuration if it's in the precedence list // Check for OpenAI configuration if it's in the precedence list
const openaiWarnings = []; const openaiWarnings: string[] = [];
if (providerPrecedence.includes('openai')) { if (providerPrecedence.includes('openai')) {
const openaiApiKey = this.$widget.find('.openai-api-key').val(); const openaiApiKey = this.$widget.find('.openai-api-key').val();
if (!openaiApiKey) { if (!openaiApiKey) {
@ -207,7 +207,7 @@ export default class AiSettingsWidget extends OptionsWidget {
} }
// Check for Anthropic configuration if it's in the precedence list // Check for Anthropic configuration if it's in the precedence list
const anthropicWarnings = []; const anthropicWarnings: string[] = [];
if (providerPrecedence.includes('anthropic')) { if (providerPrecedence.includes('anthropic')) {
const anthropicApiKey = this.$widget.find('.anthropic-api-key').val(); const anthropicApiKey = this.$widget.find('.anthropic-api-key').val();
if (!anthropicApiKey) { if (!anthropicApiKey) {
@ -216,7 +216,7 @@ export default class AiSettingsWidget extends OptionsWidget {
} }
// Check for Voyage configuration if it's in the precedence list // Check for Voyage configuration if it's in the precedence list
const voyageWarnings = []; const voyageWarnings: string[] = [];
if (providerPrecedence.includes('voyage')) { if (providerPrecedence.includes('voyage')) {
const voyageApiKey = this.$widget.find('.voyage-api-key').val(); const voyageApiKey = this.$widget.find('.voyage-api-key').val();
if (!voyageApiKey) { if (!voyageApiKey) {
@ -225,7 +225,7 @@ export default class AiSettingsWidget extends OptionsWidget {
} }
// Check for Ollama configuration if it's in the precedence list // Check for Ollama configuration if it's in the precedence list
const ollamaWarnings = []; const ollamaWarnings: string[] = [];
if (providerPrecedence.includes('ollama')) { if (providerPrecedence.includes('ollama')) {
const ollamaBaseUrl = this.$widget.find('.ollama-base-url').val(); const ollamaBaseUrl = this.$widget.find('.ollama-base-url').val();
if (!ollamaBaseUrl) { if (!ollamaBaseUrl) {
@ -234,7 +234,7 @@ export default class AiSettingsWidget extends OptionsWidget {
} }
// Similar checks for embeddings // Similar checks for embeddings
const embeddingWarnings = []; const embeddingWarnings: string[] = [];
const embeddingsEnabled = this.$widget.find('.enable-automatic-indexing').prop('checked'); const embeddingsEnabled = this.$widget.find('.enable-automatic-indexing').prop('checked');
if (embeddingsEnabled) { if (embeddingsEnabled) {

View File

@ -83,6 +83,13 @@ interface CreateChildResponse {
}; };
} }
interface Event {
startDate: string,
endDate?: string | null,
startTime?: string | null,
endTime?: string | null
}
const CALENDAR_VIEWS = [ const CALENDAR_VIEWS = [
"timeGridWeek", "timeGridWeek",
"dayGridMonth", "dayGridMonth",
@ -325,8 +332,8 @@ export default class CalendarView extends ViewMode {
} }
#parseStartEndTimeFromEvent(e: DateSelectArg | EventImpl) { #parseStartEndTimeFromEvent(e: DateSelectArg | EventImpl) {
let startTime = null; let startTime: string | undefined | null = null;
let endTime = null; let endTime: string | undefined | null = null;
if (!e.allDay) { if (!e.allDay) {
startTime = CalendarView.#formatTimeToLocalISO(e.start); startTime = CalendarView.#formatTimeToLocalISO(e.start);
endTime = CalendarView.#formatTimeToLocalISO(e.end); endTime = CalendarView.#formatTimeToLocalISO(e.end);
@ -391,7 +398,7 @@ export default class CalendarView extends ViewMode {
} }
async #buildEventsForCalendar(e: EventSourceFuncArg) { async #buildEventsForCalendar(e: EventSourceFuncArg) {
const events = []; const events: EventInput[] = [];
// Gather all the required date note IDs. // Gather all the required date note IDs.
const dateRange = utils.getMonthsInDateRange(e.startStr, e.endStr); const dateRange = utils.getMonthsInDateRange(e.startStr, e.endStr);
@ -483,12 +490,7 @@ export default class CalendarView extends ViewMode {
return note.getLabelValue(defaultLabelName); return note.getLabelValue(defaultLabelName);
} }
static async buildEvent(note: FNote, { startDate, endDate, startTime, endTime }: { static async buildEvent(note: FNote, { startDate, endDate, startTime, endTime }: Event) {
startDate: string,
endDate?: string | null,
startTime?: string | null,
endTime?: string | null
}) {
const customTitleAttributeName = note.getLabelValue("calendar:title"); const customTitleAttributeName = note.getLabelValue("calendar:title");
const titles = await CalendarView.#parseCustomTitle(customTitleAttributeName, note); const titles = await CalendarView.#parseCustomTitle(customTitleAttributeName, note);
const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color"); const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color");
@ -553,7 +555,7 @@ export default class CalendarView extends ViewMode {
if (relations.length > 0) { if (relations.length > 0) {
const noteIds = relations.map((r) => r.targetNoteId); const noteIds = relations.map((r) => r.targetNoteId);
const notesFromRelation = await froca.getNotes(noteIds); const notesFromRelation = await froca.getNotes(noteIds);
const titles = []; const titles: string[][] = [];
for (const targetNote of notesFromRelation) { for (const targetNote of notesFromRelation) {
const targetCustomTitleValue = targetNote.getAttributeValue("label", "calendar:title"); const targetCustomTitleValue = targetNote.getAttributeValue("label", "calendar:title");
@ -631,7 +633,7 @@ export default class CalendarView extends ViewMode {
// Icon button. // Icon button.
const iconEl = subItem.querySelector("span.fc-icon"); const iconEl = subItem.querySelector("span.fc-icon");
let icon = null; let icon: string | null = null;
if (iconEl?.classList.contains("fc-icon-chevron-left")) { if (iconEl?.classList.contains("fc-icon-chevron-left")) {
icon = "NSImageNameTouchBarGoBackTemplate"; icon = "NSImageNameTouchBarGoBackTemplate";
mode = "buttons"; mode = "buttons";

View File

@ -6,8 +6,7 @@
"node" "node"
], ],
"rootDir": "src", "rootDir": "src",
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
"verbatimModuleSyntax": false
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts"

View File

@ -5,7 +5,6 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"target": "ES2020", "target": "ES2020",
"outDir": "dist", "outDir": "dist",
"strict": false,
"types": [ "types": [
"node", "node",
"express" "express"

View File

@ -3,8 +3,7 @@
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"outDir": "out-tsc/playwright", "outDir": "out-tsc/playwright",
"sourceMap": false, "sourceMap": false
"verbatimModuleSyntax": false
}, },
"include": [ "include": [
"**/*.ts", "**/*.ts",

View File

@ -251,7 +251,7 @@ export default class Becca {
getAllNoteSet() { getAllNoteSet() {
// caching this since it takes 10s of milliseconds to fill this initial NoteSet for many notes // caching this since it takes 10s of milliseconds to fill this initial NoteSet for many notes
if (!this.allNoteSetCache) { if (!this.allNoteSetCache) {
const allNotes = []; const allNotes: BNote[] = [];
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];

View File

@ -76,7 +76,7 @@ function getNoteTitleArrayForPath(notePathArray: string[]) {
return [getNoteTitle(notePathArray[0])]; return [getNoteTitle(notePathArray[0])];
} }
const titles = []; const titles: string[] = [];
let parentNoteId = "root"; let parentNoteId = "root";
let hoistedNotePassed = false; let hoistedNotePassed = false;

View File

@ -388,7 +388,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
} }
} }
const templateAttributes = []; const templateAttributes: BAttribute[] = [];
for (const ownedAttr of parentAttributes) { for (const ownedAttr of parentAttributes) {
// parentAttributes so we process also inherited templates // parentAttributes so we process also inherited templates

View File

@ -254,7 +254,7 @@ function hasConnectingRelation(sourceNote: BNote, targetNote: BNote) {
} }
async function findSimilarNotes(noteId: string): Promise<SimilarNote[] | undefined> { async function findSimilarNotes(noteId: string): Promise<SimilarNote[] | undefined> {
const results = []; const results: SimilarNote[] = [];
let i = 0; let i = 0;
const baseNote = becca.notes[noteId]; const baseNote = becca.notes[noteId];

View File

@ -12,6 +12,11 @@ interface Backlink {
excerpts?: string[]; excerpts?: string[];
} }
interface TreeLink {
sourceNoteId: string;
targetNoteId: string;
}
function buildDescendantCountMap(noteIdsToCount: string[]) { function buildDescendantCountMap(noteIdsToCount: string[]) {
if (!Array.isArray(noteIdsToCount)) { if (!Array.isArray(noteIdsToCount)) {
throw new Error("noteIdsToCount: type error"); throw new Error("noteIdsToCount: type error");
@ -50,7 +55,7 @@ function getNeighbors(note: BNote, depth: number): string[] {
return []; return [];
} }
const retNoteIds = []; const retNoteIds: string[] = [];
function isIgnoredRelation(relation: BAttribute) { function isIgnoredRelation(relation: BAttribute) {
return ["relationMapLink", "template", "inherit", "image", "ancestor"].includes(relation.name); return ["relationMapLink", "template", "inherit", "image", "ancestor"].includes(relation.name);
@ -196,7 +201,7 @@ function getTreeMap(req: Request) {
const noteIds = new Set<string>(); const noteIds = new Set<string>();
notes.forEach(([noteId]) => noteId && noteIds.add(noteId)); notes.forEach(([noteId]) => noteId && noteIds.add(noteId));
const links = []; const links: TreeLink[] = [];
for (const { parentNoteId, childNoteId } of subtree.relationships) { for (const { parentNoteId, childNoteId } of subtree.relationships) {
if (!noteIds.has(parentNoteId) || !noteIds.has(childNoteId)) { if (!noteIds.has(parentNoteId) || !noteIds.has(childNoteId)) {
@ -246,7 +251,7 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
const html = sourceNote.getContent(); const html = sourceNote.getContent();
const document = new JSDOM(html).window.document; const document = new JSDOM(html).window.document;
const excerpts = []; const excerpts: string[] = [];
removeImages(document); removeImages(document);

View File

@ -9,6 +9,12 @@ import { changeLanguage, getLocales } from "../../services/i18n.js";
import type { OptionNames } from "@triliumnext/commons"; import type { OptionNames } from "@triliumnext/commons";
import config from "../../services/config.js"; import config from "../../services/config.js";
interface UserTheme {
val: string; // value of the theme, used in the URL
title: string; // title of the theme, displayed in the UI
noteId: string; // ID of the note containing the theme
}
// options allowed to be updated directly in the Options dialog // options allowed to be updated directly in the Options dialog
const ALLOWED_OPTIONS = new Set<OptionNames>([ const ALLOWED_OPTIONS = new Set<OptionNames>([
"eraseEntitiesAfterTimeInSeconds", "eraseEntitiesAfterTimeInSeconds",
@ -177,7 +183,7 @@ function update(name: string, value: string) {
function getUserThemes() { function getUserThemes() {
const notes = searchService.searchNotes("#appTheme", { ignoreHoistedNote: true }); const notes = searchService.searchNotes("#appTheme", { ignoreHoistedNote: true });
const ret = []; const ret: UserTheme[] = [];
for (const note of notes) { for (const note of notes) {
let value = note.getOwnedLabelValue("appTheme"); let value = note.getOwnedLabelValue("appTheme");

View File

@ -21,7 +21,7 @@ interface RecentChangeRow {
function getRecentChanges(req: Request) { function getRecentChanges(req: Request) {
const { ancestorNoteId } = req.params; const { ancestorNoteId } = req.params;
let recentChanges = []; let recentChanges: RecentChangeRow[] = [];
const revisionRows = sql.getRows<RecentChangeRow>(` const revisionRows = sql.getRows<RecentChangeRow>(`
SELECT SELECT

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
import scriptService from "../../services/script.js"; import scriptService, { type Bundle } from "../../services/script.js";
import attributeService from "../../services/attributes.js"; import attributeService from "../../services/attributes.js";
import becca from "../../becca/becca.js"; import becca from "../../becca/becca.js";
import syncService from "../../services/sync.js"; import syncService from "../../services/sync.js";
@ -54,7 +54,7 @@ function run(req: Request) {
function getBundlesWithLabel(label: string, value?: string) { function getBundlesWithLabel(label: string, value?: string) {
const notes = attributeService.getNotesWithLabel(label, value); const notes = attributeService.getNotesWithLabel(label, value);
const bundles = []; const bundles: Bundle[] = [];
for (const note of notes) { for (const note of notes) {
const bundle = scriptService.getScriptBundleForFrontend(note); const bundle = scriptService.getScriptBundleForFrontend(note);
@ -97,7 +97,7 @@ function getRelationBundles(req: Request) {
const targetNoteIds = filtered.map((relation) => relation.value); const targetNoteIds = filtered.map((relation) => relation.value);
const uniqueNoteIds = Array.from(new Set(targetNoteIds)); const uniqueNoteIds = Array.from(new Set(targetNoteIds));
const bundles = []; const bundles: Bundle[] = [];
for (const noteId of uniqueNoteIds) { for (const noteId of uniqueNoteIds) {
const note = becca.getNoteOrThrow(noteId); const note = becca.getNoteOrThrow(noteId);

View File

@ -6,9 +6,14 @@ import type { Request } from "express";
import ValidationError from "../../errors/validation_error.js"; import ValidationError from "../../errors/validation_error.js";
import { safeExtractMessageAndStackFromError } from "../../services/utils.js"; import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
interface Table {
name: string;
columns: unknown[];
}
function getSchema() { function getSchema() {
const tableNames = sql.getColumn(/*sql*/`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`); const tableNames = sql.getColumn<string>(/*sql*/`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
const tables = []; const tables: Table[] = [];
for (const tableName of tableNames) { for (const tableName of tableNames) {
tables.push({ tables.push({
@ -31,7 +36,7 @@ function execute(req: Request) {
const queries = content.split("\n---"); const queries = content.split("\n---");
try { try {
const results = []; const results: unknown[] = [];
for (let query of queries) { for (let query of queries) {
query = query.trim(); query = query.trim();

View File

@ -5,6 +5,7 @@ import log from "../../services/log.js";
import NotFoundError from "../../errors/not_found_error.js"; import NotFoundError from "../../errors/not_found_error.js";
import type { Request } from "express"; import type { Request } from "express";
import type BNote from "../../becca/entities/bnote.js"; import type BNote from "../../becca/entities/bnote.js";
import type { AttributeRow, BranchRow, NoteRow } from "@triliumnext/commons";
function getNotesAndBranchesAndAttributes(_noteIds: string[] | Set<string>) { function getNotesAndBranchesAndAttributes(_noteIds: string[] | Set<string>) {
const noteIds = new Set(_noteIds); const noteIds = new Set(_noteIds);
@ -53,7 +54,7 @@ function getNotesAndBranchesAndAttributes(_noteIds: string[] | Set<string>) {
collectEntityIds(note); collectEntityIds(note);
} }
const notes = []; const notes: NoteRow[] = [];
for (const noteId of collectedNoteIds) { for (const noteId of collectedNoteIds) {
const note = becca.notes[noteId]; const note = becca.notes[noteId];
@ -68,7 +69,7 @@ function getNotesAndBranchesAndAttributes(_noteIds: string[] | Set<string>) {
}); });
} }
const branches = []; const branches: BranchRow[] = [];
if (noteIds.has("root")) { if (noteIds.has("root")) {
branches.push({ branches.push({
@ -99,7 +100,7 @@ function getNotesAndBranchesAndAttributes(_noteIds: string[] | Set<string>) {
}); });
} }
const attributes = []; const attributes: AttributeRow[] = [];
for (const attributeId of collectedAttributeIds) { for (const attributeId of collectedAttributeIds) {
const attribute = becca.attributes[attributeId]; const attribute = becca.attributes[attributeId];

View File

@ -7,6 +7,11 @@ import eraseService from "./erase.js";
type SectorHash = Record<string, string>; type SectorHash = Record<string, string>;
interface FailedCheck {
entityName: string;
sector: string[1];
}
function getEntityHashes() { function getEntityHashes() {
// blob erasure is not synced, we should check before each sync if there's some blob to erase // blob erasure is not synced, we should check before each sync if there's some blob to erase
eraseService.eraseUnusedBlobs(); eraseService.eraseUnusedBlobs();
@ -56,7 +61,7 @@ function getEntityHashes() {
function checkContentHashes(otherHashes: Record<string, SectorHash>) { function checkContentHashes(otherHashes: Record<string, SectorHash>) {
const entityHashes = getEntityHashes(); const entityHashes = getEntityHashes();
const failedChecks = []; const failedChecks: FailedCheck[] = [];
for (const entityName in entityHashes) { for (const entityName in entityHashes) {
const thisSectorHashes: SectorHash = entityHashes[entityName] || {}; const thisSectorHashes: SectorHash = entityHashes[entityName] || {};

View File

@ -51,7 +51,7 @@ export function getLocales(): Locale[] {
} }
function getCurrentLanguage(): LOCALE_IDS { function getCurrentLanguage(): LOCALE_IDS {
let language: string; let language: string | null = null;
if (sql_init.isDbInitialized()) { if (sql_init.isDbInitialized()) {
language = options.getOptionOrNull("locale"); language = options.getOptionOrNull("locale");
} }

View File

@ -84,7 +84,7 @@ async function importOpml(taskContext: TaskContext, fileBuffer: string | Buffer,
} }
const outlines = xml.opml.body[0].outline || []; const outlines = xml.opml.body[0].outline || [];
let returnNote = null; let returnNote: BNote | null = null;
for (const outline of outlines) { for (const outline of outlines) {
const note = importOutline(outline, parentNote.noteId); const note = importOutline(outline, parentNote.noteId);

View File

@ -9,7 +9,7 @@ export interface Message {
content: string; content: string;
name?: string; name?: string;
tool_call_id?: string; tool_call_id?: string;
tool_calls?: ToolCall[]; tool_calls?: ToolCall[] | null;
sessionId?: string; // Optional session ID for WebSocket communication sessionId?: string; // Optional session ID for WebSocket communication
} }
@ -210,7 +210,7 @@ export interface ChatResponse {
stream?: (callback: (chunk: StreamChunk) => Promise<void> | void) => Promise<string>; stream?: (callback: (chunk: StreamChunk) => Promise<void> | void) => Promise<string>;
/** Tool calls from the LLM (if tools were used and the model supports them) */ /** Tool calls from the LLM (if tools were used and the model supports them) */
tool_calls?: ToolCall[]; tool_calls?: ToolCall[] | null;
} }
export interface AIService { export interface AIService {

View File

@ -160,7 +160,7 @@ function extractJsStructure(content: string): string {
} }
// Look for class declarations // Look for class declarations
const classes = []; const classes: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('class ') || line.includes(' class ')) { if (line.startsWith('class ') || line.includes(' class ')) {
@ -173,7 +173,7 @@ function extractJsStructure(content: string): string {
} }
// Look for function declarations // Look for function declarations
const functions = []; const functions: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('function ') || if (line.startsWith('function ') ||
@ -212,7 +212,7 @@ function extractPythonStructure(content: string): string {
} }
// Look for class declarations // Look for class declarations
const classes = []; const classes: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('class ')) { if (line.startsWith('class ')) {
@ -225,7 +225,7 @@ function extractPythonStructure(content: string): string {
} }
// Look for function declarations // Look for function declarations
const functions = []; const functions: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('def ')) { if (line.startsWith('def ')) {
@ -263,7 +263,7 @@ function extractClassBasedStructure(content: string): string {
} }
// Look for class declarations // Look for class declarations
const classes = []; const classes: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.match(/^(public|private|protected)?\s*(class|interface|enum)\s+\w+/)) { if (line.match(/^(public|private|protected)?\s*(class|interface|enum)\s+\w+/)) {
@ -276,7 +276,7 @@ function extractClassBasedStructure(content: string): string {
} }
// Look for method declarations // Look for method declarations
const methods = []; const methods: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.match(/^(public|private|protected)?\s*(static)?\s*[\w<>[\]]+\s+\w+\s*\(/)) { if (line.match(/^(public|private|protected)?\s*(static)?\s*[\w<>[\]]+\s+\w+\s*\(/)) {
@ -319,7 +319,7 @@ function extractGoStructure(content: string): string {
} }
// Look for type declarations (structs, interfaces) // Look for type declarations (structs, interfaces)
const types = []; const types: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('type ') && (line.includes(' struct ') || line.includes(' interface '))) { if (line.startsWith('type ') && (line.includes(' struct ') || line.includes(' interface '))) {
@ -332,7 +332,7 @@ function extractGoStructure(content: string): string {
} }
// Look for function declarations // Look for function declarations
const functions = []; const functions: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('func ')) { if (line.startsWith('func ')) {
@ -366,7 +366,7 @@ function extractRustStructure(content: string): string {
} }
// Look for struct/enum/trait declarations // Look for struct/enum/trait declarations
const types = []; const types: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('struct ') || line.startsWith('enum ') || line.startsWith('trait ')) { if (line.startsWith('struct ') || line.startsWith('enum ') || line.startsWith('trait ')) {
@ -379,8 +379,8 @@ function extractRustStructure(content: string): string {
} }
// Look for function/impl declarations // Look for function/impl declarations
const functions = []; const functions: string[] = [];
const impls = []; const impls: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();

View File

@ -198,7 +198,7 @@ export async function semanticChunking(
// Try to split on headers first // Try to split on headers first
const headerPattern = /#{1,6}\s+.+|<h[1-6][^>]*>.*?<\/h[1-6]>/g; const headerPattern = /#{1,6}\s+.+|<h[1-6][^>]*>.*?<\/h[1-6]>/g;
const sections = []; const sections: string[] = [];
let lastIndex = 0; let lastIndex = 0;
let match; let match;

View File

@ -439,7 +439,7 @@ export class QueryDecompositionTool {
// If no pattern match, try to extract noun phrases // If no pattern match, try to extract noun phrases
const words = query.split(/\s+/); const words = query.split(/\s+/);
const potentialEntities = []; const potentialEntities: string[] = [];
let currentPhrase = ''; let currentPhrase = '';
for (const word of words) { for (const word of words) {

View File

@ -246,7 +246,7 @@ export abstract class BaseEmbeddingProvider {
*/ */
protected generateNoteContextText(context: NoteEmbeddingContext): string { protected generateNoteContextText(context: NoteEmbeddingContext): string {
// Build a relationship-focused summary first // Build a relationship-focused summary first
const relationshipSummary = []; const relationshipSummary: string[] = [];
// Summarize the note's place in the hierarchy // Summarize the note's place in the hierarchy
if (context.parentTitles.length > 0) { if (context.parentTitles.length > 0) {

View File

@ -22,20 +22,24 @@ export interface NoteEmbeddingContext {
title: string; title: string;
mime: string; mime: string;
}[]; }[];
backlinks?: { backlinks?: Backlink[];
sourceNoteId: string; relatedNotes?: RelatedNote[];
sourceTitle: string;
relationName: string;
}[];
relatedNotes?: {
targetNoteId: string;
targetTitle: string;
relationName: string;
}[];
labelValues?: Record<string, string>; labelValues?: Record<string, string>;
templateTitles?: string[]; templateTitles?: string[];
} }
export interface Backlink {
sourceNoteId: string;
sourceTitle: string;
relationName: string;
}
export interface RelatedNote {
targetNoteId: string;
targetTitle: string;
relationName: string;
}
/** /**
* Information about an embedding model's capabilities * Information about an embedding model's capabilities
*/ */

View File

@ -13,6 +13,21 @@ import indexService from '../index_service.js';
// Track which notes are currently being processed // Track which notes are currently being processed
const notesInProcess = new Set<string>(); const notesInProcess = new Set<string>();
interface FailedItemRow {
noteId: string;
operation: string;
attempts: number;
lastAttempt: string;
error: string | null;
failed: number;
}
interface FailedItemWithTitle extends FailedItemRow {
title?: string;
failureType: 'chunks' | 'full';
isPermanent: boolean;
}
/** /**
* Queues a note for embedding update * Queues a note for embedding update
*/ */
@ -77,17 +92,17 @@ export async function queueNoteForEmbedding(noteId: string, operation = 'UPDATE'
*/ */
export async function getFailedEmbeddingNotes(limit: number = 100): Promise<any[]> { export async function getFailedEmbeddingNotes(limit: number = 100): Promise<any[]> {
// Get notes with failed embedding attempts or permanently failed flag // Get notes with failed embedding attempts or permanently failed flag
const failedQueueItems = await sql.getRows(` const failedQueueItems = sql.getRows<FailedItemRow>(`
SELECT noteId, operation, attempts, lastAttempt, error, failed SELECT noteId, operation, attempts, lastAttempt, error, failed
FROM embedding_queue FROM embedding_queue
WHERE attempts > 0 OR failed = 1 WHERE attempts > 0 OR failed = 1
ORDER BY failed DESC, attempts DESC, lastAttempt DESC ORDER BY failed DESC, attempts DESC, lastAttempt DESC
LIMIT ?`, LIMIT ?`,
[limit] [limit]
) as {noteId: string, operation: string, attempts: number, lastAttempt: string, error: string, failed: number}[]; );
// Add titles to the failed notes // Add titles to the failed notes
const failedNotesWithTitles = []; const failedNotesWithTitles: FailedItemWithTitle[] = [];
for (const item of failedQueueItems) { for (const item of failedQueueItems) {
const note = becca.getNote(item.noteId); const note = becca.getNote(item.noteId);
if (note) { if (note) {

View File

@ -8,6 +8,14 @@ import entityChangesService from "../../../services/entity_changes.js";
import type { EntityChange } from "../../../services/entity_changes_interface.js"; import type { EntityChange } from "../../../services/entity_changes_interface.js";
import { EMBEDDING_CONSTANTS } from "../constants/embedding_constants.js"; import { EMBEDDING_CONSTANTS } from "../constants/embedding_constants.js";
import { SEARCH_CONSTANTS } from '../constants/search_constants.js'; import { SEARCH_CONSTANTS } from '../constants/search_constants.js';
interface Similarity {
noteId: string;
similarity: number;
contentType: string;
bonuses?: Record<string, number>; // Optional for debugging
}
/** /**
* Creates or updates an embedding for a note * Creates or updates an embedding for a note
*/ */
@ -434,7 +442,7 @@ async function processEmbeddings(queryEmbedding: Float32Array, embeddings: any[]
return { bonuses, totalBonus }; return { bonuses, totalBonus };
} }
const similarities = []; const similarities: Similarity[] = [];
try { try {
// Try to extract the original query text if it was added to the metadata // Try to extract the original query text if it was added to the metadata

View File

@ -95,7 +95,7 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
const providerPrecedence = await options.getOption('aiProviderPrecedence'); const providerPrecedence = await options.getOption('aiProviderPrecedence');
if (providerPrecedence) { if (providerPrecedence) {
// Parse provider precedence list // Parse provider precedence list
let providers = []; let providers: string[] = [];
if (providerPrecedence.includes(',')) { if (providerPrecedence.includes(',')) {
providers = providerPrecedence.split(',').map(p => p.trim()); providers = providerPrecedence.split(',').map(p => p.trim());
} else if (providerPrecedence.startsWith('[') && providerPrecedence.endsWith(']')) { } else if (providerPrecedence.startsWith('[') && providerPrecedence.endsWith(']')) {

View File

@ -7,6 +7,21 @@ import { getAnthropicOptions } from './providers.js';
import log from '../../log.js'; import log from '../../log.js';
import Anthropic from '@anthropic-ai/sdk'; import Anthropic from '@anthropic-ai/sdk';
import { SEARCH_CONSTANTS } from '../constants/search_constants.js'; import { SEARCH_CONSTANTS } from '../constants/search_constants.js';
import type { ToolCall } from '../tools/tool_interfaces.js';
interface AnthropicMessage extends Omit<Message, "content"> {
content: MessageContent[] | string;
}
interface MessageContent {
type: "text" | "tool_use" | "tool_result";
text?: string;
id?: string;
name?: string;
content?: string;
tool_use_id?: string;
input?: string | Record<string, unknown>;
}
export class AnthropicService extends BaseAIService { export class AnthropicService extends BaseAIService {
private client: any = null; private client: any = null;
@ -130,7 +145,7 @@ export class AnthropicService extends BaseAIService {
.join(''); .join('');
// Process tool calls if any are present in the response // Process tool calls if any are present in the response
let toolCalls = null; let toolCalls: ToolCall[] | null = null;
if (response.content) { if (response.content) {
const toolBlocks = response.content.filter((block: any) => const toolBlocks = response.content.filter((block: any) =>
block.type === 'tool_use' || block.type === 'tool_use' ||
@ -157,7 +172,7 @@ export class AnthropicService extends BaseAIService {
return null; return null;
}).filter(Boolean); }).filter(Boolean);
log.info(`Extracted ${toolCalls.length} tool calls from Anthropic response`); log.info(`Extracted ${toolCalls?.length} tool calls from Anthropic response`);
} }
} }
@ -406,8 +421,8 @@ export class AnthropicService extends BaseAIService {
/** /**
* Format messages for the Anthropic API * Format messages for the Anthropic API
*/ */
private formatMessages(messages: Message[]): any[] { private formatMessages(messages: Message[]): AnthropicMessage[] {
const anthropicMessages: any[] = []; const anthropicMessages: AnthropicMessage[] = [];
// Process each message // Process each message
for (const msg of messages) { for (const msg of messages) {
@ -424,7 +439,7 @@ export class AnthropicService extends BaseAIService {
// Assistant messages need special handling for tool_calls // Assistant messages need special handling for tool_calls
if (msg.tool_calls && msg.tool_calls.length > 0) { if (msg.tool_calls && msg.tool_calls.length > 0) {
// Create content blocks array for tool calls // Create content blocks array for tool calls
const content = []; const content: MessageContent[] = [];
// Add text content if present // Add text content if present
if (msg.content) { if (msg.content) {

View File

@ -181,7 +181,7 @@ export async function updateEmbeddingProviderConfig(
} }
// Build update query parts // Build update query parts
const updates = []; const updates: string[] = [];
const params: any[] = []; const params: any[] = [];
if (priority !== undefined) { if (priority !== undefined) {

View File

@ -8,6 +8,26 @@ import type { Tool, ToolHandler } from './tool_interfaces.js';
import log from '../../log.js'; import log from '../../log.js';
import becca from '../../../becca/becca.js'; import becca from '../../../becca/becca.js';
interface CodeBlock {
code: string;
language?: string;
}
interface Heading {
text: string;
level: number; // 1 for H1, 2 for H2, etc.
}
interface List {
type: "unordered" | "ordered";
items: string[];
}
interface Table {
headers: string[];
rows: string[][];
}
/** /**
* Definition of the content extraction tool * Definition of the content extraction tool
*/ */
@ -137,8 +157,8 @@ export class ContentExtractionTool implements ToolHandler {
/** /**
* Extract lists from HTML content * Extract lists from HTML content
*/ */
private extractLists(content: string): Array<{ type: string, items: string[] }> { private extractLists(content: string): List[] {
const lists = []; const lists: List[] = [];
// Extract unordered lists // Extract unordered lists
const ulRegex = /<ul[^>]*>([\s\S]*?)<\/ul>/gi; const ulRegex = /<ul[^>]*>([\s\S]*?)<\/ul>/gi;
@ -179,7 +199,7 @@ export class ContentExtractionTool implements ToolHandler {
* Extract list items from list content * Extract list items from list content
*/ */
private extractListItems(listContent: string): string[] { private extractListItems(listContent: string): string[] {
const items = []; const items: string[] = [];
const itemRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi; const itemRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi;
let itemMatch; let itemMatch;
@ -196,15 +216,15 @@ export class ContentExtractionTool implements ToolHandler {
/** /**
* Extract tables from HTML content * Extract tables from HTML content
*/ */
private extractTables(content: string): Array<{ headers: string[], rows: string[][] }> { private extractTables(content: string): Table[] {
const tables = []; const tables: Table[] = [];
const tableRegex = /<table[^>]*>([\s\S]*?)<\/table>/gi; const tableRegex = /<table[^>]*>([\s\S]*?)<\/table>/gi;
let tableMatch; let tableMatch: RegExpExecArray | null;
while ((tableMatch = tableRegex.exec(content)) !== null) { while ((tableMatch = tableRegex.exec(content)) !== null) {
const tableContent = tableMatch[1]; const tableContent = tableMatch[1];
const headers = []; const headers: string[] = [];
const rows = []; const rows: string[][] = [];
// Extract table headers // Extract table headers
const headerRegex = /<th[^>]*>([\s\S]*?)<\/th>/gi; const headerRegex = /<th[^>]*>([\s\S]*?)<\/th>/gi;
@ -218,7 +238,7 @@ export class ContentExtractionTool implements ToolHandler {
let rowMatch; let rowMatch;
while ((rowMatch = rowRegex.exec(tableContent)) !== null) { while ((rowMatch = rowRegex.exec(tableContent)) !== null) {
const rowContent = rowMatch[1]; const rowContent = rowMatch[1];
const cells = []; const cells: string[] = [];
const cellRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi; const cellRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi;
let cellMatch; let cellMatch;
@ -246,7 +266,7 @@ export class ContentExtractionTool implements ToolHandler {
* Extract headings from HTML content * Extract headings from HTML content
*/ */
private extractHeadings(content: string): Array<{ level: number, text: string }> { private extractHeadings(content: string): Array<{ level: number, text: string }> {
const headings = []; const headings: Heading[] = [];
for (let i = 1; i <= 6; i++) { for (let i = 1; i <= 6; i++) {
const headingRegex = new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\/h${i}>`, 'gi'); const headingRegex = new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\/h${i}>`, 'gi');
@ -270,7 +290,7 @@ export class ContentExtractionTool implements ToolHandler {
* Extract code blocks from HTML content * Extract code blocks from HTML content
*/ */
private extractCodeBlocks(content: string): Array<{ language?: string, code: string }> { private extractCodeBlocks(content: string): Array<{ language?: string, code: string }> {
const codeBlocks = []; const codeBlocks: CodeBlock[] = [];
// Look for <pre> and <code> blocks // Look for <pre> and <code> blocks
const preRegex = /<pre[^>]*>([\s\S]*?)<\/pre>/gi; const preRegex = /<pre[^>]*>([\s\S]*?)<\/pre>/gi;

View File

@ -9,6 +9,7 @@ import log from '../../log.js';
import becca from '../../../becca/becca.js'; import becca from '../../../becca/becca.js';
import notes from '../../notes.js'; import notes from '../../notes.js';
import attributes from '../../attributes.js'; import attributes from '../../attributes.js';
import type { BNote } from '../../backend_script_entrypoint.js';
/** /**
* Definition of the note creation tool * Definition of the note creation tool
@ -89,7 +90,7 @@ export class NoteCreationTool implements ToolHandler {
log.info(`Executing create_note tool - Title: "${title}", Type: ${type}, ParentNoteId: ${parentNoteId || 'root'}`); log.info(`Executing create_note tool - Title: "${title}", Type: ${type}, ParentNoteId: ${parentNoteId || 'root'}`);
// Validate parent note exists if specified // Validate parent note exists if specified
let parent = null; let parent: BNote | null = null;
if (parentNoteId) { if (parentNoteId) {
parent = becca.notes[parentNoteId]; parent = becca.notes[parentNoteId];
if (!parent) { if (!parent) {

View File

@ -10,6 +10,14 @@ import becca from '../../../becca/becca.js';
import attributes from '../../attributes.js'; import attributes from '../../attributes.js';
import aiServiceManager from '../ai_service_manager.js'; import aiServiceManager from '../ai_service_manager.js';
import { SEARCH_CONSTANTS } from '../constants/search_constants.js'; import { SEARCH_CONSTANTS } from '../constants/search_constants.js';
import type { Backlink, RelatedNote } from '../embeddings/embeddings_interface.js';
interface Suggestion {
targetNoteId: string;
targetTitle: string;
similarity: number;
suggestedRelation: string;
}
/** /**
* Definition of the relationship tool * Definition of the relationship tool
@ -180,7 +188,7 @@ export class RelationshipTool implements ToolHandler {
.filter((attr: any) => attr.type === 'relation') .filter((attr: any) => attr.type === 'relation')
.slice(0, limit); .slice(0, limit);
const outgoingRelations = []; const outgoingRelations: RelatedNote[] = [];
for (const attr of outgoingAttributes) { for (const attr of outgoingAttributes) {
const targetNote = becca.notes[attr.value]; const targetNote = becca.notes[attr.value];
@ -196,7 +204,7 @@ export class RelationshipTool implements ToolHandler {
// Get incoming relationships (where this note is the target) // Get incoming relationships (where this note is the target)
// Since becca.findNotesWithRelation doesn't exist, use attributes to find notes with relation // Since becca.findNotesWithRelation doesn't exist, use attributes to find notes with relation
const incomingRelations = []; const incomingRelations: Backlink[] = [];
// Find all attributes of type relation that point to this note // Find all attributes of type relation that point to this note
const relationAttributes = sourceNote.getTargetRelations(); const relationAttributes = sourceNote.getTargetRelations();
@ -321,7 +329,7 @@ export class RelationshipTool implements ToolHandler {
const sourceContent = await sourceNote.getContent(); const sourceContent = await sourceNote.getContent();
// Prepare suggestions // Prepare suggestions
const suggestions = []; const suggestions: Suggestion[] = [];
for (const relatedNote of relatedResult.relatedNotes) { for (const relatedNote of relatedResult.relatedNotes) {
try { try {

View File

@ -755,7 +755,7 @@ function updateNoteData(noteId: string, content: string, attachments: Attachment
function undeleteNote(noteId: string, taskContext: TaskContext) { function undeleteNote(noteId: string, taskContext: TaskContext) {
const noteRow = sql.getRow<NoteRow>("SELECT * FROM notes WHERE noteId = ?", [noteId]); const noteRow = sql.getRow<NoteRow>("SELECT * FROM notes WHERE noteId = ?", [noteId]);
if (!noteRow.isDeleted) { if (!noteRow.isDeleted || !noteRow.deleteId) {
log.error(`Note '${noteId}' is not deleted and thus cannot be undeleted.`); log.error(`Note '${noteId}' is not deleted and thus cannot be undeleted.`);
return; return;
} }

View File

@ -118,7 +118,7 @@ function getParams(params?: ScriptParams) {
} }
function getScriptBundleForFrontend(note: BNote, script?: string, params?: ScriptParams) { function getScriptBundleForFrontend(note: BNote, script?: string, params?: ScriptParams) {
let overrideContent = null; let overrideContent: string | null = null;
if (script) { if (script) {
overrideContent = `return (${script}\r\n)(${getParams(params)})`; overrideContent = `return (${script}\r\n)(${getParams(params)})`;
@ -170,7 +170,7 @@ function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: string |
includedNoteIds.push(note.noteId); includedNoteIds.push(note.noteId);
const modules = []; const modules: BNote[] = [];
for (const child of note.getChildNotes()) { for (const child of note.getChildNotes()) {
const childBundle = getScriptBundle(child, false, scriptEnv, includedNoteIds); const childBundle = getScriptBundle(child, false, scriptEnv, includedNoteIds);

View File

@ -52,7 +52,7 @@ class NoteFlatTextExp extends Expression {
return; return;
} }
const foundAttrTokens = []; const foundAttrTokens: string[] = [];
for (const token of remainingTokens) { for (const token of remainingTokens) {
if (note.type.includes(token) || note.mime.includes(token)) { if (note.type.includes(token) || note.mime.includes(token)) {
@ -73,7 +73,7 @@ class NoteFlatTextExp extends Expression {
for (const parentNote of note.parents) { for (const parentNote of note.parents) {
const title = normalize(beccaService.getNoteTitle(note.noteId, parentNote.noteId)); const title = normalize(beccaService.getNoteTitle(note.noteId, parentNote.noteId));
const foundTokens = foundAttrTokens.slice(); const foundTokens: string[] = foundAttrTokens.slice();
for (const token of remainingTokens) { for (const token of remainingTokens) {
if (title.includes(token)) { if (title.includes(token)) {
@ -100,7 +100,7 @@ class NoteFlatTextExp extends Expression {
continue; continue;
} }
const foundAttrTokens = []; const foundAttrTokens: string[] = [];
for (const token of this.tokens) { for (const token of this.tokens) {
if (note.type.includes(token) || note.mime.includes(token)) { if (note.type.includes(token) || note.mime.includes(token)) {
@ -153,7 +153,7 @@ class NoteFlatTextExp extends Expression {
* Returns noteIds which have at least one matching tokens * Returns noteIds which have at least one matching tokens
*/ */
getCandidateNotes(noteSet: NoteSet): BNote[] { getCandidateNotes(noteSet: NoteSet): BNote[] {
const candidateNotes = []; const candidateNotes: BNote[] = [];
for (const note of noteSet.notes) { for (const note of noteSet.notes) {
for (const token of this.tokens) { for (const token of this.tokens) {

View File

@ -294,11 +294,11 @@ function getExpression(tokens: TokenData[], searchContext: SearchContext, level
valueExtractor: ValueExtractor; valueExtractor: ValueExtractor;
direction: string; direction: string;
}[] = []; }[] = [];
let limit; let limit: number | undefined = undefined;
if (tokens[i].token === "orderby") { if (tokens[i].token === "orderby") {
do { do {
const propertyPath = []; const propertyPath: string[] = [];
let direction = "asc"; let direction = "asc";
do { do {

View File

@ -36,7 +36,7 @@ function searchFromNote(note: BNote): SearchNoteResult {
const searchScript = note.getRelationValue("searchScript"); const searchScript = note.getRelationValue("searchScript");
const searchString = note.getLabelValue("searchString") || ""; const searchString = note.getLabelValue("searchString") || "";
let error = null; let error: string | null = null;
if (searchScript) { if (searchScript) {
searchResultNoteIds = searchFromRelation(note, "searchScript"); searchResultNoteIds = searchFromRelation(note, "searchScript");

View File

@ -43,7 +43,7 @@ export default class Shaca {
} }
getNotes(noteIds: string[], ignoreMissing = false) { getNotes(noteIds: string[], ignoreMissing = false) {
const filteredNotes = []; const filteredNotes: SNote[] = [];
for (const noteId of noteIds) { for (const noteId of noteIds) {
const note = this.notes[noteId]; const note = this.notes[noteId];

View File

@ -5,7 +5,6 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"target": "ES2020", "target": "ES2020",
"outDir": "dist", "outDir": "dist",
"strict": false,
"types": [ "types": [
"node", "node",
"express" "express"
@ -14,7 +13,8 @@
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo" "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts",
"package.json"
], ],
"exclude": [ "exclude": [
"eslint.config.js", "eslint.config.js",

View File

@ -1,6 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2020",
"outDir": "./out-tsc/vitest", "outDir": "./out-tsc/vitest",
"types": [ "types": [
"vitest/globals", "vitest/globals",
@ -24,6 +27,7 @@
"src/**/*.test.jsx", "src/**/*.test.jsx",
"src/**/*.spec.jsx", "src/**/*.spec.jsx",
"src/**/*.d.ts", "src/**/*.d.ts",
"src/**/*.ts" "src/**/*.ts",
"package.json"
] ]
} }

10
nx.json
View File

@ -62,16 +62,6 @@
} }
], ],
"targetDefaults": { "targetDefaults": {
"@nx/js:swc": {
"cache": true,
"dependsOn": [
"^build"
],
"inputs": [
"production",
"^production"
]
},
"test": { "test": {
"dependsOn": [ "dependsOn": [
"^build" "^build"

View File

@ -38,10 +38,6 @@
"@nx/vite": "21.1.2", "@nx/vite": "21.1.2",
"@nx/web": "21.1.2", "@nx/web": "21.1.2",
"@playwright/test": "^1.36.0", "@playwright/test": "^1.36.0",
"@swc-node/register": "~1.10.0",
"@swc/cli": "~0.7.0",
"@swc/core": "~1.11.0",
"@swc/helpers": "~0.5.11",
"@triliumnext/server": "workspace:*", "@triliumnext/server": "workspace:*",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/node": "22.15.24", "@types/node": "22.15.24",
@ -59,7 +55,6 @@
"jsonc-eslint-parser": "^2.1.0", "jsonc-eslint-parser": "^2.1.0",
"nx": "21.1.2", "nx": "21.1.2",
"react-refresh": "^0.17.0", "react-refresh": "^0.17.0",
"swc-loader": "0.2.6",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"tsx": "4.19.4", "tsx": "4.19.4",
"typescript": "~5.8.0", "typescript": "~5.8.0",

View File

@ -22,28 +22,3 @@ declare global {
importMarkdownInline(): void; importMarkdownInline(): void;
} }
} }
declare module "ckeditor5" {
interface Editor {
getSelectedHtml(): string;
removeSelection(): Promise<void>;
}
interface EditorConfig {
syntaxHighlighting?: {
loadHighlightJs: () => Promise<any>;
mapLanguageName(mimeType: string): string;
defaultMimeType: string;
enabled: boolean;
},
moveBlockUp?: {
keystroke: string[];
},
moveBlockDown?: {
keystroke: string[];
},
clipboard?: {
copy(text: string): void;
}
}
}

View File

@ -5,6 +5,10 @@ import { BalloonEditor, DecoupledEditor, FindAndReplaceEditing, FindCommand } fr
export { EditorWatchdog } from "ckeditor5"; export { EditorWatchdog } from "ckeditor5";
export type { EditorConfig, MentionFeed, MentionFeedObjectItem, Node, Position, Element, WatchdogConfig } from "ckeditor5"; export type { EditorConfig, MentionFeed, MentionFeedObjectItem, Node, Position, Element, WatchdogConfig } from "ckeditor5";
// Import with sideffects to ensure that type augmentations are present.
import "@triliumnext/ckeditor5-math";
import "@triliumnext/ckeditor5-mermaid";
/** /**
* Short-hand for the CKEditor classes supported by Trilium for text editing. * Short-hand for the CKEditor classes supported by Trilium for text editing.
* Specialized editors such as the {@link AttributeEditor} are not included. * Specialized editors such as the {@link AttributeEditor} are not included.
@ -43,3 +47,28 @@ export class PopupEditor extends BalloonEditor {
return POPUP_EDITOR_PLUGINS; return POPUP_EDITOR_PLUGINS;
} }
} }
declare module "ckeditor5" {
interface Editor {
getSelectedHtml(): string;
removeSelection(): Promise<void>;
}
interface EditorConfig {
syntaxHighlighting?: {
loadHighlightJs: () => Promise<any>;
mapLanguageName(mimeType: string): string;
defaultMimeType: string;
enabled: boolean;
},
moveBlockUp?: {
keystroke: string[];
},
moveBlockDown?: {
keystroke: string[];
},
clipboard?: {
copy(text: string): void;
}
}
}

View File

@ -1,7 +1,12 @@
import type { Element, Writer } from "ckeditor5"; import type { Element, Position, Writer } from "ckeditor5";
import type { Node, Editor } from "ckeditor5"; import type { Node, Editor } from "ckeditor5";
import { Plugin } from "ckeditor5"; import { Plugin } from "ckeditor5";
interface SpanStackEntry {
className: string;
posStart: Position;
}
/* /*
* This code is an adaptation of https://github.com/antoniotejada/Trilium-SyntaxHighlightWidget with additional improvements, such as: * This code is an adaptation of https://github.com/antoniotejada/Trilium-SyntaxHighlightWidget with additional improvements, such as:
* *
@ -234,10 +239,10 @@ export default class SyntaxHighlighting extends Plugin {
let iHtml = 0; let iHtml = 0;
let html = highlightRes.value; let html = highlightRes.value;
let spanStack = []; let spanStack: SpanStackEntry[] = [];
let iChild = -1; let iChild = -1;
let childText = ""; let childText = "";
let child = null; let child: Node | null = null;
let iChildText = 0; let iChildText = 0;
while (iHtml < html.length) { while (iHtml < html.length) {

View File

@ -1,5 +1,5 @@
import { indentLess, indentMore } from "@codemirror/commands"; import { indentLess, indentMore } from "@codemirror/commands";
import { EditorSelection, EditorState, type ChangeSpec } from "@codemirror/state"; import { EditorSelection, EditorState, SelectionRange, type ChangeSpec } from "@codemirror/state";
import type { KeyBinding } from "@codemirror/view"; import type { KeyBinding } from "@codemirror/view";
/** /**
@ -19,8 +19,8 @@ const smartIndentWithTab: KeyBinding[] = [
} }
const { selection } = state; const { selection } = state;
const changes = []; const changes: ChangeSpec[] = [];
const newSelections = []; const newSelections: SelectionRange[] = [];
// Step 1: Handle non-empty selections → replace with tab // Step 1: Handle non-empty selections → replace with tab
if (selection.ranges.some(range => !range.empty)) { if (selection.ranges.some(range => !range.empty)) {

View File

@ -23,6 +23,6 @@
"src/**/*.spec.js", "src/**/*.spec.js",
"src/**/*.test.jsx", "src/**/*.test.jsx",
"src/**/*.spec.jsx", "src/**/*.spec.jsx",
"src/**/*.d.ts" "src/**/*.ts"
] ]
} }

View File

@ -1,22 +0,0 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "es6"
},
"sourceMaps": true,
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$"]
}

View File

@ -4,16 +4,16 @@
"description": "Shared library between the clients (e.g. browser, Electron) and the server, mostly for type definitions and utility methods.", "description": "Shared library between the clients (e.g. browser, Electron) and the server, mostly for type definitions and utility methods.",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/main.js",
"module": "./dist/index.js", "module": "./dist/main.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"exports": { "exports": {
"./package.json": "./package.json", "./package.json": "./package.json",
".": { ".": {
"development": "./src/index.ts", "development": "./src/index.ts",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js", "import": "./dist/main.js",
"default": "./dist/index.js" "default": "./dist/main.js"
} }
}, },
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
@ -27,21 +27,31 @@
"sourceRoot": "packages/commons/src", "sourceRoot": "packages/commons/src",
"targets": { "targets": {
"build": { "build": {
"executor": "@nx/js:swc", "executor": "@nx/esbuild:esbuild",
"outputs": [ "outputs": [
"{options.outputPath}" "{options.outputPath}"
], ],
"defaultConfiguration": "production",
"options": { "options": {
"outputPath": "packages/commons/dist",
"main": "packages/commons/src/index.ts", "main": "packages/commons/src/index.ts",
"outputPath": "packages/commons/dist",
"outputFileName": "main.js",
"tsConfig": "packages/commons/tsconfig.lib.json", "tsConfig": "packages/commons/tsconfig.lib.json",
"skipTypeCheck": true, "platform": "node",
"stripLeadingPaths": true "format": [
"esm"
],
"declarationRootDir": "packages/commons/src"
},
"configurations": {
"development": {
"minify": false
},
"production": {
"minify": true
}
} }
} }
} }
},
"dependencies": {
"@swc/helpers": "~0.5.11"
} }
} }

View File

@ -126,17 +126,17 @@ export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number];
export interface NoteRow { export interface NoteRow {
noteId: string; noteId: string;
deleteId: string; deleteId?: string;
title: string; title: string;
type: NoteType; type: NoteType;
mime: string; mime: string;
isProtected: boolean; isProtected?: boolean;
isDeleted: boolean; isDeleted?: boolean;
blobId: string; blobId?: string;
dateCreated: string; dateCreated?: string;
dateModified: string; dateModified?: string;
utcDateCreated: string; utcDateCreated?: string;
utcDateModified: string; utcDateModified?: string;
content?: string | Buffer; content?: string | Buffer;
} }

View File

@ -44,7 +44,7 @@ export function trimIndentation(strings: TemplateStringsArray, ...values: any[])
// Trim the indentation of the first line in all the lines. // Trim the indentation of the first line in all the lines.
const lines = str.split("\n"); const lines = str.split("\n");
const output = []; const output: string[] = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
let numSpacesLine = 0; let numSpacesLine = 0;
while (str.charAt(numSpacesLine) == " " && numSpacesLine < str.length) { while (str.charAt(numSpacesLine) == " " && numSpacesLine < str.length) {

View File

@ -56,9 +56,6 @@
} }
} }
}, },
"dependencies": {
"@swc/helpers": "~0.5.11"
},
"devDependencies": { "devDependencies": {
"turndown": "7.2.0", "turndown": "7.2.0",
"turndown-attendant": "0.0.3" "turndown-attendant": "0.0.3"

637
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ ignoredBuiltDependencies:
onlyBuiltDependencies: onlyBuiltDependencies:
- '@parcel/watcher' - '@parcel/watcher'
- '@scarf/scarf' - '@scarf/scarf'
- '@swc/core'
- bufferutil - bufferutil
- core-js-pure - core-js-pure
- esbuild - esbuild

View File

@ -18,7 +18,7 @@
"strict": true, "strict": true,
"target": "es2022", "target": "es2022",
"customConditions": ["development"], "customConditions": ["development"],
"verbatimModuleSyntax": true, "verbatimModuleSyntax": false, // TODO: Re-enable it when migrating back to ESM.
"resolveJsonModule": true, "resolveJsonModule": true,
"downlevelIteration": true, "downlevelIteration": true,
"esModuleInterop": true, "esModuleInterop": true,