Merge branch 'develop' into calendar

This commit is contained in:
Jin 2025-04-02 23:28:15 +02:00
commit bf70b949a6
73 changed files with 392 additions and 402 deletions

View File

@ -1,44 +0,0 @@
{
"typescript": {
"indentWidth": 4,
"quoteStyle": "preferDouble",
"semiColons": "prefer",
"quoteProps": "asNeeded",
"newLineKind": "lf",
"lineWidth": 200,
"trailingCommas": "never",
"arrayPattern.spaceAround": true,
"arrayExpression.spaceAround": true
},
"json": {
},
"markdown": {
},
"dockerfile": {
},
"malva": {
},
"markup": {
},
"yaml": {
},
"excludes": [
"**/node_modules",
"**/*-lock.json",
"*.html",
"*.md",
"*.yml",
"libraries/*",
"docs/*",
"src/public/app/doc_notes"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.94.0.wasm",
"https://plugins.dprint.dev/json-0.20.0.wasm",
"https://plugins.dprint.dev/markdown-0.18.0.wasm",
"https://plugins.dprint.dev/dockerfile-0.3.2.wasm",
"https://plugins.dprint.dev/g-plane/malva-v0.11.1.wasm",
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.19.0.wasm",
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.0.wasm"
]
}

View File

@ -4,6 +4,7 @@
"editorconfig.editorconfig",
"vitest.explorer",
"ms-playwright.playwright",
"tobermory.es6-string-html"
"tobermory.es6-string-html",
"dbaeumer.vscode-eslint"
]
}

View File

@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.92.4",
"appVersion": "0.92.5-beta",
"files": [
{
"isClone": false,
@ -1433,28 +1433,28 @@
{
"type": "relation",
"name": "internalLink",
"value": "BlN9DFI679QC",
"value": "vZWERwf8U3nx",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "vZWERwf8U3nx",
"value": "4FahAwuGTAwC",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "4FahAwuGTAwC",
"value": "0vhv7lsOLy82",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "0vhv7lsOLy82",
"value": "BlN9DFI679QC",
"isInheritable": false,
"position": 40
},
@ -3058,6 +3058,20 @@
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "QxEyIjRBizuC",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "UYuUB1ZekNQU",
"isInheritable": false,
"position": 70
},
{
"type": "label",
"name": "shareAlias",
@ -3085,20 +3099,6 @@
"value": "",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "UYuUB1ZekNQU",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "QxEyIjRBizuC",
"isInheritable": false,
"position": 70
}
],
"format": "markdown",
@ -3231,14 +3231,14 @@
{
"type": "relation",
"name": "internalLink",
"value": "QxEyIjRBizuC",
"value": "6f9hih2hXXZk",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "6f9hih2hXXZk",
"value": "QxEyIjRBizuC",
"isInheritable": false,
"position": 30
},
@ -3324,44 +3324,44 @@
{
"type": "relation",
"name": "internalLink",
"value": "6f9hih2hXXZk",
"value": "QxEyIjRBizuC",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "4TIF1oA4VQRO",
"value": "6f9hih2hXXZk",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "nRhnJkTT8cPs",
"value": "4TIF1oA4VQRO",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "s8alTXmpFR61",
"value": "nRhnJkTT8cPs",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "s8alTXmpFR61",
"isInheritable": false,
"position": 50
},
{
"type": "label",
"name": "iconClass",
"value": "bx bx-code",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "QxEyIjRBizuC",
"isInheritable": false,
"position": 60
}
],
"format": "markdown",
@ -4091,48 +4091,62 @@
"type": "text",
"mime": "text/markdown",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "zEY4DaJG4YT5",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "OFXdgB2nNk1F",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "wX4HbRucYSDD",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "imageLink",
"value": "EH6qNioOHeyT",
"isInheritable": false,
"position": 50
"position": 10
},
{
"type": "relation",
"name": "imageLink",
"value": "xeZPrfi77XPu",
"isInheritable": false,
"position": 60
"position": 20
},
{
"type": "relation",
"name": "imageLink",
"value": "N98UhifxrVpZ",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "BFs8mudNFgCS",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "zEY4DaJG4YT5",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "OFXdgB2nNk1F",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "wX4HbRucYSDD",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "BCkXAVs63Ttv",
"isInheritable": false,
"position": 80
},
{
"type": "label",
"name": "shareAlias",
@ -4146,20 +4160,6 @@
"value": "bx bxs-network-chart",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "BFs8mudNFgCS",
"isInheritable": false,
"position": 80
},
{
"type": "relation",
"name": "internalLink",
"value": "BCkXAVs63Ttv",
"isInheritable": false,
"position": 90
}
],
"format": "markdown",
@ -4190,19 +4190,19 @@
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "BCkXAVs63Ttv",
"isInheritable": false,
"position": 10
},
{
"type": "label",
"name": "iconClass",
"value": "bx bxs-network-chart",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "BCkXAVs63Ttv",
"isInheritable": false,
"position": 20
}
],
"format": "markdown",
@ -5916,23 +5916,23 @@
{
"type": "relation",
"name": "internalLink",
"value": "iRwzGnHPzonm",
"value": "bdUJEHsAPYQR",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "iRwzGnHPzonm",
"isInheritable": false,
"position": 30
},
{
"type": "label",
"name": "shareAlias",
"value": "note-map",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "bdUJEHsAPYQR",
"isInheritable": false,
"position": 40
}
],
"format": "markdown",
@ -6887,21 +6887,21 @@
{
"type": "relation",
"name": "internalLink",
"value": "KSZ04uQ2D1St",
"value": "_optionsTextNotes",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "_optionsTextNotes",
"value": "_optionsCodeNotes",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "_optionsCodeNotes",
"value": "KSZ04uQ2D1St",
"isInheritable": false,
"position": 30
},
@ -6929,21 +6929,21 @@
{
"type": "relation",
"name": "internalLink",
"value": "gBbsAeiuUxI5",
"value": "H0mM1lTxF9JI",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "N4IDkixaDG9C",
"value": "gBbsAeiuUxI5",
"isInheritable": false,
"position": 80
},
{
"type": "relation",
"name": "internalLink",
"value": "H0mM1lTxF9JI",
"value": "N4IDkixaDG9C",
"isInheritable": false,
"position": 90
},

View File

@ -35,7 +35,7 @@ Basic Auth is meant to be used with tools which support only basic auth.
It is possible to write simple Bash scripts to interact with Trilium. As an example, here's how to obtain the HTML content of a note:
```sh
```
#!/usr/bin/env bash
# Configuration

View File

@ -15,7 +15,7 @@ Note that some information is also stored as [Attachments](../Attachments). For
Here's part of the HTML representation of this note, as it's stored in the database (but prettified).
```html
```
<h2>
Understanding the source code of the different notes
</h2>

View File

@ -21,7 +21,7 @@ Trilium makes use of such features:
* The math feature is added by a version of [isaul32/ckeditor5-math: Math feature for CKEditor 5.](https://github.com/isaul32/ckeditor5-math) modified by us to fit our needs.
* We also make use of modified upstream plugins such as [ckeditor/ckeditor5-mermaid](https://github.com/ckeditor/ckeditor5-mermaid) to allow inline Mermaid code.
* [mlewand/ckeditor5-keyboard-marker: Plugin adds support for the keyboard input element (<kbd>) to CKEditor 5.](https://github.com/mlewand/ckeditor5-keyboard-marker)
* [mlewand/ckeditor5-keyboard-marker: Plugin adds support for the keyboard input element (`<kbd>`) to CKEditor 5.](https://github.com/mlewand/ckeditor5-keyboard-marker)
* A modified version of [ThomasAitken/ckeditor5-footnotes: Footnotes plugin for CKEditor5](https://github.com/ThomasAitken/ckeditor5-footnotes) to allow footnotes.
Apart from that, Trilium also has its own set of specific plugins such as:

View File

@ -25,3 +25,40 @@ test("Complete help in search", async ({ page, context }) => {
const popup = await popupPromise;
expect(popup.url()).toBe("https://triliumnext.github.io/Docs/Wiki/search.html");
});
test("In-app-help works in English", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.currentNoteSplit.press("F1");
const title = "User Guide";
await expect(app.noteTreeHoistedNote).toContainText(title);
await expect(app.currentNoteSplitTitle).toHaveValue(title);
app.noteTree.getByText("Troubleshooting").click();
await expect(app.currentNoteSplitTitle).toHaveValue("Troubleshooting");
await app.currentNoteSplitContent.locator("p").first().waitFor({ state: "visible" });
expect(await app.currentNoteSplitContent.locator("p").count()).toBeGreaterThan(10);
});
test("In-app-help works in other languages", async ({ page, context }) => {
const app = new App(page, context);
try {
await app.goto();
await app.setOption("locale", "cn");
await app.goto();
await app.currentNoteSplit.press("F1");
const title = "用户指南";
await expect(app.noteTreeHoistedNote).toContainText(title);
await expect(app.currentNoteSplitTitle).toHaveValue(title);
app.noteTree.getByText("Troubleshooting").click();
await expect(app.currentNoteSplitTitle).toHaveValue("Troubleshooting");
await app.currentNoteSplitContent.locator("p").first().waitFor({ state: "visible" });
expect(await app.currentNoteSplitContent.locator("p").count()).toBeGreaterThan(10);
} finally {
// Ensure English is set after each locale change to avoid any leaks to other tests.
await app.setOption("locale", "en");
}
});

View File

@ -57,3 +57,46 @@ test("Can drag tab to new window", async ({ page, context }) => {
const popupApp = new App(popup, context);
await expect(popupApp.getActiveTab()).toHaveText(NOTE_TITLE);
});
test("Tabs are restored in right order", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
// Open three tabs.
await app.closeAllTabs();
await app.goToNoteInNewTab("Code notes");
await app.addNewTab();
await app.goToNoteInNewTab("Text notes");
await app.addNewTab();
await app.goToNoteInNewTab("Mermaid");
// Select the mid one.
await app.getTab(1).click();
// Refresh the page and check the order.
await app.goto( { preserveTabs: true });
await expect(app.getTab(0)).toContainText("Code notes");
await expect(app.getTab(1)).toContainText("Text notes");
await expect(app.getTab(2)).toContainText("Mermaid");
// Check the note tree has the right active node.
await expect(app.noteTreeActiveNote).toContainText("Text notes");
});
test("Empty tabs are cleared out", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
// Open three tabs.
await app.closeAllTabs();
await app.addNewTab();
await app.goToNoteInNewTab("Code notes");
await app.addNewTab();
await app.addNewTab();
// Refresh the page and check the order.
await app.goto({ preserveTabs: true });
// Expect no empty tabs.
expect(await app.tabBar.locator(".note-tab-wrapper").count()).toBe(1);
});

View File

@ -4,6 +4,7 @@ import type { BrowserContext } from "@playwright/test";
interface GotoOpts {
url?: string;
isMobile?: boolean;
preserveTabs?: boolean;
}
const BASE_URL = "http://127.0.0.1:8082";
@ -14,8 +15,12 @@ export default class App {
readonly tabBar: Locator;
readonly noteTree: Locator;
readonly noteTreeActiveNote: Locator;
readonly noteTreeHoistedNote: Locator;
readonly launcherBar: Locator;
readonly currentNoteSplit: Locator;
readonly currentNoteSplitTitle: Locator;
readonly currentNoteSplitContent: Locator;
readonly sidebar: Locator;
constructor(page: Page, context: BrowserContext) {
@ -24,12 +29,16 @@ export default class App {
this.tabBar = page.locator(".tab-row-widget-container");
this.noteTree = page.locator(".tree-wrapper");
this.noteTreeActiveNote = this.noteTree.locator(".fancytree-node.fancytree-active");
this.noteTreeHoistedNote = this.noteTree.locator(".fancytree-node", { has: page.locator(".unhoist-button") });
this.launcherBar = page.locator("#launcher-container");
this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)");
this.currentNoteSplitTitle = this.currentNoteSplit.locator(".note-title");
this.currentNoteSplitContent = this.currentNoteSplit.locator(".note-detail-printable.visible");
this.sidebar = page.locator("#right-pane");
}
async goto({ url, isMobile }: GotoOpts = {}) {
async goto({ url, isMobile, preserveTabs }: GotoOpts = {}) {
await this.context.addCookies([
{
url: BASE_URL,
@ -47,7 +56,9 @@ export default class App {
// Wait for the page to load.
if (url === "/") {
await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test");
await this.closeAllTabs();
if (!preserveTabs) {
await this.closeAllTabs();
}
}
}

View File

@ -7,7 +7,7 @@ import { initializeTranslations } from "./src/services/i18n.js";
import archiver, { type Archiver } from "archiver";
import type { WriteStream } from "fs";
import debounce from "./src/public/app/services/debounce.js";
import { extractZip, importData, initializeDatabase, startElectron } from "./electron-utils.js";
import { extractZip, initializeDatabase, startElectron } from "./electron-utils.js";
const NOTE_ID_USER_GUIDE = "pOsGYCXsbNQG";
const markdownPath = path.join("docs", "User Guide");
@ -16,8 +16,7 @@ const htmlPath = path.join("src", "public", "app", "doc_notes", "en", "User Guid
async function main() {
await initializeTranslations();
const zipBuffer = await createImportZip();
await initializeDatabase();
await importData(zipBuffer, NOTE_ID_USER_GUIDE, "User Guide", "The sub-children of this note are automatically synced.");
await initializeDatabase(zipBuffer);
await startElectron();
await registerHandlers();
}

View File

@ -3,12 +3,12 @@ import fs from "fs/promises";
import fsExtra from "fs-extra";
import path from "path";
export async function initializeDatabase() {
export async function initializeDatabase(customDbBuffer?: Buffer) {
const sqlInit = (await import("./src/services/sql_init.js")).default;
cls.init(() => {
if (!sqlInit.isDbInitialized()) {
sqlInit.createInitialDatabase(true);
sqlInit.createInitialDatabase(true, customDbBuffer);
}
});
}
@ -17,48 +17,6 @@ export async function startElectron() {
await import("./electron-main.js");
}
export function importData(input: Buffer, rootId: string, rootTitle: string, rootContent: string) {
return new Promise<void>((resolve, reject) => {
cls.init(async () => {
const beccaLoader = ((await import("./src/becca/becca_loader.js")).default);
const notes = ((await import("./src/services/notes.js")).default);
beccaLoader.load();
const becca = ((await import("./src/becca/becca.js")).default);
const utils = ((await import("./src/services/utils.js")).default);
const eraseService = ((await import("./src/services/erase.js")).default);
const deleteId = utils.randomString(10);
const existingNote = becca.getNote(rootId);
if (existingNote) {
existingNote.deleteNote(deleteId);
}
eraseService.eraseNotesWithDeleteId(deleteId);
const { note } = notes.createNewNoteWithTarget("into", "none_root", {
parentNoteId: "root",
noteId: rootId,
title: rootTitle,
content: rootContent,
type: "text"
});
const TaskContext = (await import("./src/services/task_context.js")).default;
const { importZip } = ((await import("./src/services/import/zip.js")).default);
const context = new TaskContext("no-report");
await importZip(context, input, note, { preserveIds: true });
const { runOnDemandChecks } = (await import("./src/services/consistency_checks.js")).default;
await runOnDemandChecks(true);
becca.reset();
beccaLoader.load();
resolve();
});
});
}
export async function extractZip(zipFilePath: string, outputPath: string) {
const deferred = (await import("./src/services/utils.js")).deferred;

View File

@ -24,10 +24,11 @@ export default tseslint.config(
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
]
],
"sort-imports": [ "error", { ignoreCase: false } ]
}
},
{
@ -35,6 +36,7 @@ export default tseslint.config(
"build/*",
"dist/*",
"docs/*",
"demo/*",
"libraries/*",
"src/public/app-dist/*",
"src/public/app/doc_notes/*"

48
eslint.format.config.js Normal file
View File

@ -0,0 +1,48 @@
import stylistic from "@stylistic/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
// eslint config just for formatting rules
// potentially to be merged with the linting rules into one single config,
// once we have fixed the majority of lint errors
// Go to https://eslint.style/rules/default/${rule_without_prefix} to check the rule details
export const stylisticRules = {
"@stylistic/indent": [ "error", 4 ],
"@stylistic/quotes": [ "error", "double", { avoidEscape: true, allowTemplateLiterals: "always" } ],
"@stylistic/semi": [ "error", "always" ],
"@stylistic/quote-props": [ "error", "consistent-as-needed" ],
"@stylistic/max-len": [ "error", { code: 100 } ],
"@stylistic/comma-dangle": [ "error", "never" ],
"@stylistic/linebreak-style": [ "error", "unix" ],
"@stylistic/array-bracket-spacing": [ "error", "always" ],
"@stylistic/object-curly-spacing": [ "error", "always" ],
"@stylistic/padded-blocks": [ "error", { classes: "always" } ]
};
export default [
{
files: [ "**/*.{js,ts,mjs,cjs}" ],
languageOptions: {
parser: tsParser
},
plugins: {
"@stylistic": stylistic
},
rules: {
...stylisticRules
}
},
{
ignores: [
"build/*",
"dist/*",
"docs/*",
"demo/*",
"libraries/*",
// TriliumNextTODO: check if we want to format packages here as well - for now skipping it
"packages/*",
"src/public/app-dist/*",
"src/public/app/doc_notes/*"
]
}
];

176
package-lock.json generated
View File

@ -117,6 +117,7 @@
"@mind-elixir/node-menu": "1.0.5",
"@playwright/test": "1.51.1",
"@popperjs/core": "2.11.8",
"@stylistic/eslint-plugin": "4.2.0",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
@ -201,8 +202,7 @@
"webpack-dev-middleware": "7.4.2"
},
"optionalDependencies": {
"appdmg": "0.6.6",
"dprint": "0.49.1"
"appdmg": "0.6.6"
}
},
"node_modules/@ampproject/remapping": {
@ -574,123 +574,6 @@
"node": ">=14.17.0"
}
},
"node_modules/@dprint/darwin-arm64": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/darwin-arm64/-/darwin-arm64-0.49.1.tgz",
"integrity": "sha512-ib6KcJWo/M5RJWXOQKhP664FG1hAvG7nrbkh+j8n+oXdzmbyDdXTP+zW+aM3/sIQUkGaZky1xy1j2VeScMEEHQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@dprint/darwin-x64": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/darwin-x64/-/darwin-x64-0.49.1.tgz",
"integrity": "sha512-vIVgnYxV7YYa1d6Uyz707RbgB9rwefGPam+rzaueFNPQjdOxPOTQDuMEJDS+Z3BlI00MfeoupIfIUGsXoM4dpQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@dprint/linux-arm64-glibc": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/linux-arm64-glibc/-/linux-arm64-glibc-0.49.1.tgz",
"integrity": "sha512-ZeIh6qMPWLBBifDtU0XadpK36b4WoaTqCOt0rWKfoTjq1RAt78EgqETWp43Dbr6et/HvTgYdoWF0ZNEu2FJFFA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@dprint/linux-arm64-musl": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/linux-arm64-musl/-/linux-arm64-musl-0.49.1.tgz",
"integrity": "sha512-/nuRyx+TykN6MqhlSCRs/t3o1XXlikiwTc9emWdzMeLGllYvJrcht9gRJ1/q1SqwCFhzgnD9H7roxxfji1tc+Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@dprint/linux-riscv64-glibc": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/linux-riscv64-glibc/-/linux-riscv64-glibc-0.49.1.tgz",
"integrity": "sha512-RHBqrnvGO+xW4Oh0QuToBqWtkXMcfjqa1TqbBFF03yopFzZA2oRKX83PhjTWgd/IglaOns0BgmaLJy/JBSxOfQ==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@dprint/linux-x64-glibc": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/linux-x64-glibc/-/linux-x64-glibc-0.49.1.tgz",
"integrity": "sha512-MjFE894mIQXOKBencuakKyzAI4KcDe/p0Y9lRp9YSw/FneR4QWH9VBH90h8fRxcIlWMArjFFJJAtsBnn5qgxeg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@dprint/linux-x64-musl": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/linux-x64-musl/-/linux-x64-musl-0.49.1.tgz",
"integrity": "sha512-CvGBWOksHgrL1uzYqtPFvZz0+E82BzgoCIEHJeuYaveEn37qWZS5jqoCm/vz6BfoivE1dVuyyOT78Begj9KxkQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@dprint/win32-arm64": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/win32-arm64/-/win32-arm64-0.49.1.tgz",
"integrity": "sha512-gQa4s82lMcXjfdxjWBQun6IJlXdPZZaIj2/2cqXWVEOYPKxAZ/JvGzt2pPG+i73h9KHjNLIV8M9ckqEH3oHufg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@dprint/win32-x64": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/@dprint/win32-x64/-/win32-x64-0.49.1.tgz",
"integrity": "sha512-nPU6+hoVze5JJlgET7woYWElBw0IUaB/9XKTaglknQuUUfsmD75D9pkgJTxdIxl9Bg/i5O7c9wb3Nj4XNiTIfw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@electron-forge/cli": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-7.8.0.tgz",
@ -4554,6 +4437,39 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@stylistic/eslint-plugin": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz",
"integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/utils": "^8.23.0",
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0",
"estraverse": "^5.3.0",
"picomatch": "^4.0.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"eslint": ">=9.0.0"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -9507,28 +9423,6 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dprint": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/dprint/-/dprint-0.49.1.tgz",
"integrity": "sha512-pO9XH79SyXybj2Vhc9ITZMEI8cJkdlQQRoD8oEfPH6Jjpp/7WX5kIgECVd3DBOjjAdCSiW6R47v3gJBx/qZVkw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"bin": {
"dprint": "bin.js"
},
"optionalDependencies": {
"@dprint/darwin-arm64": "0.49.1",
"@dprint/darwin-x64": "0.49.1",
"@dprint/linux-arm64-glibc": "0.49.1",
"@dprint/linux-arm64-musl": "0.49.1",
"@dprint/linux-riscv64-glibc": "0.49.1",
"@dprint/linux-x64-glibc": "0.49.1",
"@dprint/linux-x64-musl": "0.49.1",
"@dprint/win32-arm64": "0.49.1",
"@dprint/win32-x64": "0.49.1"
}
},
"node_modules/draggabilly": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/draggabilly/-/draggabilly-3.0.0.tgz",

View File

@ -36,8 +36,8 @@
"electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"electron:qstart": "npm run electron:switch && npm run electron:start",
"electron:switch": "electron-rebuild",
"docs:edit": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data-docs TRILIUM_ENV=dev TRILIUM_PORT=37741 electron ./electron-docs-main.ts .",
"docs:edit-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data-docs TRILIUM_PORT=37741 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-docs-main.ts .\"",
"docs:edit": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data-docs TRILIUM_ENV=dev TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=37741 electron ./electron-docs-main.ts .",
"docs:edit-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data-docs TRILIUM_PORT=37741 TRILIUM_ENV=dev TRILIUM_INTEGRATION_TEST=memory-no-store nix-shell -p electron_33 --run \"electron ./electron-docs-main.ts .\"",
"demo:edit": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data-demo TRILIUM_ENV=dev TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=37741 electron ./electron-edit-demo.ts .",
"electron-forge:start": "npm run build:prepare-dist && cd ./build && electron-forge start",
"electron-forge:make": "npm run build:prepare-dist && cross-env DEBUG=electron-windows-installer:* electron-forge make ./build",
@ -59,8 +59,8 @@
"test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"dev:watch-dist": "tsx ./bin/watch-dist.ts",
"dev:format-check": "dprint check",
"dev:format-fix": "dprint fmt",
"dev:format-check": "eslint -c eslint.format.config.js .",
"dev:format-fix": "eslint -c eslint.format.config.js . --fix",
"dev:linter-check": "eslint .",
"dev:linter-fix": "eslint . --fix",
"chore:update-build-info": "tsx bin/update-build-info.ts",
@ -174,6 +174,7 @@
"@mind-elixir/node-menu": "1.0.5",
"@playwright/test": "1.51.1",
"@popperjs/core": "2.11.8",
"@stylistic/eslint-plugin": "4.2.0",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
@ -258,7 +259,6 @@
"webpack-dev-middleware": "7.4.2"
},
"optionalDependencies": {
"appdmg": "0.6.6",
"dprint": "0.49.1"
"appdmg": "0.6.6"
}
}

View File

@ -75,7 +75,7 @@ export default class TabManager extends Component {
const filteredNoteContexts = noteContextsToOpen.filter((openTab: NoteContextState) => {
const noteId = treeService.getNoteIdFromUrl(openTab.notePath);
if (noteId && !(noteId in froca.notes)) {
if (!noteId || !(noteId in froca.notes)) {
// note doesn't exist so don't try to open tab for it
return false;
}

View File

@ -14,8 +14,10 @@ export default function renderDoc(note: FNote) {
// fallback to english doc if no translation available
if (status === "error") {
const fallbackUrl = getUrl(docName, "en");
$content.load(fallbackUrl, () => processContent(fallbackUrl, $content));
resolve($content);
$content.load(fallbackUrl, () => {
processContent(fallbackUrl, $content)
resolve($content);
});
return;
}

View File

@ -1,4 +1,5 @@
import "../stylesheets/bootstrap.scss";
import "../stylesheets/auth.css";
// @TriliumNextTODO: is this even needed anymore?
// @ts-ignore - module = undefined

View File

@ -11,6 +11,7 @@ const TPL = /*html*/`
.global-menu {
width: 53px;
height: 53px;
flex-shrink: 0;
}
.global-menu .dropdown-menu {

View File

@ -11,7 +11,19 @@ export default class LauncherContainer extends FlexContainer<LauncherWidget> {
super(isHorizontalLayout ? "row" : "column");
this.id("launcher-container");
this.css(isHorizontalLayout ? "width" : "height", "100%");
if (isHorizontalLayout) {
this.css("width", "100%");
this.css("height", "100%");
this.css("overflow-x", "auto");
this.css("overflow-y", "hidden");
} else {
this.css("height", "100%");
this.css("overflow-x", "hidden");
this.css("overflow-y", "auto");
}
this.css("scrollbar-gutter", "stable both-edges");
this.filling();
this.isHorizontalLayout = isHorizontalLayout;

View File

@ -16,7 +16,7 @@ const TPL = /*html*/`
<form class="protected-session-password-form">
<div class="modal-body">
<label for="protected-session-password" class="col-form-label">${t("protected_session_password.form_label")}</label>
<input id="protected-session-password" class="form-control protected-session-password" type="password">
<input id="protected-session-password" class="form-control protected-session-password" type="password" autocomplete="current-password">
</div>
<div class="modal-footer">
<button class="btn btn-primary">${t("protected_session_password.start_button")}</button>

View File

@ -82,7 +82,6 @@ class NoteContextAwareWidget extends BasicWidget {
async refreshWithNote(note: FNote | null | undefined) {}
async noteSwitchedEvent({ noteContext, notePath }: EventData<"noteSwitched">) {
this.noteContext = noteContext;
// if notePath does not match, then the noteContext has been switched to another note in the meantime
if (noteContext.notePath === notePath) {
await this.noteSwitched();

View File

@ -20,7 +20,7 @@ const TPL = /*html*/`
<form class="protected-session-password-form">
<div class="form-group">
<label for="protected-session-password-in-detail">${t("protected_session.enter_password_instruction")}</label>
<input id="protected-session-password-in-detail" class="form-control protected-session-password" type="password" autofocus>
<input id="protected-session-password-in-detail" class="form-control protected-session-password" type="password" autofocus autocomplete="current-password">
</div>
<button class="btn btn-primary">${t("protected_session.start_session_button")}</button>

View File

@ -0,0 +1,3 @@
.set-password .form-group {
margin-bottom: 1rem;
}

View File

@ -1177,6 +1177,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
left: calc(-100% + 10px);
}
.right-dropdown-widget {
flex-shrink: 0;
}
#launcher-pane.horizontal .right-dropdown-widget {
width: 53px;
}
@ -1288,6 +1292,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
border: none;
color: var(--launcher-pane-text-color);
background-color: var(--launcher-pane-background-color);
flex-shrink: 0;
}
#launcher-pane.vertical .launcher-button {
@ -1302,6 +1307,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
#launcher-pane.horizontal .quick-search {
width: 350px;
min-width: 150px;
}
#launcher-pane .icon-action:hover {

View File

@ -7,7 +7,7 @@ function setRecoveryCodes(req: Request) {
return { success: success, message: 'Recovery codes set!' };
}
function veryifyRecoveryCode(req: Request) {
function verifyRecoveryCode(req: Request) {
const success = recovery_codes.verifyRecoveryCode(req.body.recovery_code_guess);
return { success: success };
@ -59,7 +59,7 @@ function getUsedRecoveryCodes() {
export default {
setRecoveryCodes,
generateRecoveryCodes,
veryifyRecoveryCode,
verifyRecoveryCode,
checkForRecoveryKeys,
getUsedRecoveryCodes
};

View File

@ -124,7 +124,7 @@ function register(app: express.Application) {
apiRoute(GET, '/api/oauth/validate', openID.isTokenValid);
apiRoute(PST, '/api/totp_recovery/set', recoveryCodes.setRecoveryCodes);
apiRoute(PST, '/api/totp_recovery/verify', recoveryCodes.veryifyRecoveryCode);
apiRoute(PST, '/api/totp_recovery/verify', recoveryCodes.verifyRecoveryCode);
apiRoute(GET, '/api/totp_recovery/generate', recoveryCodes.generateRecoveryCodes);
apiRoute(GET, '/api/totp_recovery/enabled', recoveryCodes.checkForRecoveryKeys);
apiRoute(GET, '/api/totp_recovery/used', recoveryCodes.getUsedRecoveryCodes);

View File

@ -127,4 +127,10 @@ second line 2</code></pre><ul><li>Hello</li><li>world</li></ul><ol><li>Hello</li
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});
it("does not escape unneeded characters", () => {
const input = `It's important to note that these examples are not natively supported by Trilium out of the box; instead, they demonstrate what you can build within Trilium.`;
const expected = `<p>It's important to note that these examples are not natively supported by Trilium out of the box; instead, they demonstrate what you can build within Trilium.</p>`;
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});
});

View File

@ -82,7 +82,7 @@ function renderToHtml(content: string, title: string) {
// h1 handling needs to come before sanitization
html = importUtils.handleH1(html, title);
// html = htmlSanitizer.sanitize(html);
html = htmlSanitizer.sanitize(html);
// Remove slash for self-closing tags to match CKEditor's approach.
html = html.replace(/<(\w+)([^>]*)\s+\/>/g, "<$1$2>");

View File

@ -64,13 +64,19 @@ async function initDbConnection() {
dbReady.resolve();
}
async function createInitialDatabase(preserveIds?: boolean) {
/**
* Applies the database schema, creating the necessary tables and importing the demo content.
*
* @param preserveIds `true` if the note IDs from the meta file should be preserved, or `false` to generate new ones (normal behaviour).
* @param customDbBuffer a custom database buffer to use, otherwise the default demo one is going to be used.
*/
async function createInitialDatabase(preserveIds?: boolean, customDbBuffer?: Buffer) {
if (isDbInitialized()) {
throw new Error("DB is already initialized");
}
const schema = fs.readFileSync(`${resourceDir.DB_INIT_DIR}/schema.sql`, "utf-8");
const demoFile = fs.readFileSync(`${resourceDir.DB_INIT_DIR}/demo.zip`);
const demoFile = customDbBuffer ?? fs.readFileSync(`${resourceDir.DB_INIT_DIR}/demo.zip`);
let rootNote!: BNote;

View File

@ -3,8 +3,8 @@ import options from './options.js';
import totpEncryptionService from './encryption/totp_encryption.js';
function isTotpEnabled(): boolean {
return options.getOption('mfaEnabled') === "true" &&
options.getOption('mfaMethod') === "totp" &&
return options.getOptionOrNull('mfaEnabled') === "true" &&
options.getOptionOrNull('mfaMethod') === "totp" &&
totpEncryptionService.isTotpSecretSet();
}

View File

@ -156,6 +156,8 @@ async function createMainWindow(app: App) {
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 500,
minHeight: 400,
title: "TriliumNext Notes",
webPreferences: {
nodeIntegration: true,
@ -255,9 +257,12 @@ function getIcon() {
async function createSetupWindow() {
const { BrowserWindow } = await import("electron"); // should not be statically imported
const width = 750;
const height = 650;
setupWindow = new BrowserWindow({
width: 800,
height: 800,
width,
height,
resizable: false,
title: "TriliumNext Notes Setup",
icon: getIcon(),
webPreferences: {

View File

@ -34,14 +34,14 @@
<div class="form-group">
<label for="password"><%= t("login.password") %></label>
<div class="controls">
<input id="password" name="password" placeholder="" class="form-control" type="password" autofocus>
<input id="password" name="password" placeholder="" class="form-control" type="password" autocomplete="current-password" autofocus>
</div>
</div>
<% if( totpEnabled ) { %>
<div class="form-group">
<label for="totpToken">TOTP Token</label>
<div class="controls">
<input id="totpToken" name="totpToken" placeholder="" class="form-control" type="text" required />
<input id="totpToken" name="totpToken" placeholder="" class="form-control" type="text" autocomplete="one-time-code" required />
</div>
</div>
<% } %>

View File

@ -13,7 +13,7 @@
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/style.css">
</head>
<body>
<div class="container">
<div class="container set-password">
<div class="col-xs-12 col-sm-10 col-md-6 col-lg-4 col-xl-4 mx-auto pt-4">
<h1><%= t("set_password.heading") %></h1>
@ -29,13 +29,13 @@
<div class="form-group">
<label for="password1"><%= t("set_password.password") %></label>
<div class="controls">
<input id="password1" name="password1" placeholder="" class="form-control" type="password">
<input id="password1" name="password1" placeholder="" class="form-control" type="password" autocomplete="new-password">
</div>
</div>
<div class="form-group">
<label for="password2"><%= t("set_password.password-confirmation") %></label>
<div class="controls">
<input id="password2" name="password2" placeholder="" class="form-control" type="password">
<input id="password2" name="password2" placeholder="" class="form-control" type="password" autocomplete="new-password">
</div>
</div>

View File

@ -14,7 +14,7 @@
"esModuleInterop": true,
"verbatimModuleSyntax": true
},
"include": ["./src/**/*.js", "./src/**/*.ts", "./*.ts", "./spec/**/*.ts"],
"include": ["./src/**/*.js", "./src/**/*.ts", "./*.ts", "./*.js", "./spec/**/*.ts"],
"exclude": ["./node_modules/**/*", "./spec-es6/**/*.ts"],
"files": ["src/types.d.ts", "src/public/app/types.d.ts"]
}