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

@ -1,4 +1,4 @@
# Note Map (Link map, Tree map)
# Note Map (Link map, Tree map)
Note map is a visualisation of connections between notes.
This provides an insight into a structure ("web") of notes.

View File

@ -1,4 +1,4 @@
# Note source
# Note source
## Understanding the source code of the different notes
Internally, the structure of the content of each note is different based on the [Note Types](../Note%20Types).
@ -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

@ -1,4 +1,4 @@
# Technologies used
# Technologies used
One core aspect of Trilium that allows it to have support for multiple [Note Types](../Note%20Types) is the fact that it makes use of various off-the-shelf or reusable libraries.
The sub-pages showcase some of the technologies used, for a better understanding of how Trilium works but also to credit the developers of that particular technology.

View File

@ -1,4 +1,4 @@
# CKEditor
# CKEditor
## Editor core
The CKEditor is the WYSIWYG (standing for What You See Is What You Get) editor behind [Text](../../Note%20Types/Text.md) notes.
@ -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

@ -1,4 +1,4 @@
# Excalidraw
# Excalidraw
[Excalidraw](https://excalidraw.com/) is the technology behind the [Canvas](../../Note%20Types/Canvas.md) notes. The source code of the library is available on [GitHub](https://github.com/excalidraw/excalidraw).
We are using an unmodified version of it, so it shares the same [issues](https://github.com/excalidraw/excalidraw/issues) as the original.

View File

@ -1,4 +1,4 @@
# Leaflet
# Leaflet
Leaflet is the library behind [Geo map](../../Note%20Types/Geo%20map.md) notes.
## Plugins

View File

@ -1,4 +1,4 @@
# MindElixir
# MindElixir
MindElixir is the library we are using for the [Mind Map](../../Note%20Types/Mind%20Map.md) note types.
The main library is available on [GitHub as mind-elixir-core](https://github.com/SSShooter/mind-elixir-core/issues).

View File

@ -1,4 +1,4 @@
# Evernote
# Evernote
Trilium can import ENEX files which are used by Evernote for backup/export. One ENEX file represents content (notes and resources) of one notebook.
## Export ENEX from Evernote

View File

@ -1,4 +1,4 @@
# Markdown
# Markdown
Trilium Notes supports importing Markdown restricted to the [CommonMark specification](https://spec.commonmark.org/current/) (where [tables are not supported](https://github.com/TriliumNext/Notes/issues/2026))
## Import

View File

@ -1,4 +1,4 @@
# OneNote
# OneNote
**This page describes a method to migrate via EverNote Legacy, but this app is no longer available/working.**
## Prep Onenote notes for best compatibility

View File

@ -1,4 +1,4 @@
# Keyboard Shortcuts
# Keyboard Shortcuts
This is supposed to be a complete list of keyboard shortcuts. Note that some of these may work only in certain contexts (e.g. in tree pane or note editor).
It is also possible to configure most keyboard shortcuts in Options -> Keyboard shortcuts. Using `global:` prefix, you can assign a shortcut which will work even without Trilium being in focus (requires app restart to take effect).

View File

@ -1,4 +1,4 @@
# Bookmarks
# Bookmarks
To easily access selected notes, you can bookmark them. See demo:
![](../../Attachments/bookmarks.gif)

View File

@ -1,4 +1,4 @@
# Note Hoisting
# Note Hoisting
Hoisting is a standard outliner feature which allows you to focus on (or "zoom into") a specific note and its subtree by hiding all parent and sibling notes. Demo:
![](../../Attachments/note-hoisting.gif)

View File

@ -1,4 +1,4 @@
# Note Navigation
# Note Navigation
One of the Trilium's goals is to provide fast and comfortable navigation between notes.
## Backwards and forward

View File

@ -1,4 +1,4 @@
# Search
# Search
## Local Search
Local search allows you to search within the currently displayed note. To initiate a local search, press <kbd>Ctrl</kbd> + <kbd>F</kbd>. If using a web browser, this will be handled by the browser's native search functionality. In the desktop (electron) version, a separate dialog will apear.

View File

@ -1,4 +1,4 @@
# Tree Concepts
# Tree Concepts
This page explains the basic concepts related to the tree structure of notes in TriliumNext.
## Note

View File

@ -1,4 +1,4 @@
# Workspace
# Workspace
Workspace is a concept built up on top of [note hoisting](Note%20Hoisting.md). It is based on the idea that a user has several distinct spheres of interest. An example might be "Personal" and "Work", these two spheres are quite distinct and don't interact together. When I focus on Work, I don't really care about personal notes.
So far workspace consists of these features:

View File

@ -1,4 +1,4 @@
# Notes
# Notes
Note is a central entity in Trilium. Main attributes of note are title and content.
### Note types

View File

@ -1,4 +1,4 @@
# Archived Notes
# Archived Notes
Archived notes are notes which have `archived` [attribute](../../Advanced%20Usage/Attributes.md) - either directly or [inherited](../../Advanced%20Usage/Attributes/Attribute%20Inheritance.md).
Such notes are then by default not shown in the autocomplete and in the full text [search](../Navigation/Search.md).

View File

@ -1,4 +1,4 @@
# Attachments
# Attachments
A [note](../Notes.md) in Trilium can _own_ one or more attachments, which can be either images or files. These attachments can be displayed or linked within the note that owns them.
This can be especially useful to include dependencies for your [scripts](../../Note%20Types/Code/Scripts.md). The [Weight Tracker](../../Advanced%20Usage/Advanced%20Showcases/Weight%20Tracker.md) shows how to use [chartjs](https://chartjs.org/) which is attached to the [script note](#root/HcUYTojFohtb).

View File

@ -1,4 +1,4 @@
# Cloning Notes
# Cloning Notes
## Motivation
Trilium's core feature is the ability to structure your notes into hierarchical tree-like structure.

View File

@ -1,4 +1,4 @@
# Export as PDF
# Export as PDF
![](Export%20as%20PDF_image.png)
Screenshot of the note contextual menu indicating the “Export as PDF” option.

View File

@ -1,4 +1,4 @@
# Note Icons
# Note Icons
Icons are useful for distinguishing notes. At the technical level, they are set by the `iconClass` attribute which adds a CSS class to the note. For example `#iconClass="bx bx-calendar"` will show a calendar instead of the default page or folder icon. Looking up and remembering the css class names is not necessary. While editing a note, click on the icon next to the title to bring up a chooser gallery:
![change note icon](../../Attachments/note-icon-change.png)

View File

@ -1,4 +1,4 @@
# Note Revisions
# Note Revisions
Trilium supports seamless versioning of notes by storing snapshots ("revisions") of notes at regular intervals.
## Note Revisions Snapshot Interval

View File

@ -1,4 +1,4 @@
# Protected Notes
# Protected Notes
Trilium is designed to store a wide variety of data, including sensitive information such as personal journals, credentials, or confidential documents. To safeguard this type of content, Trilium offers the option to protect notes, which involves the following measures:
* **Encryption:** Protected notes are encrypted using a key derived from your password. This ensures that without the correct password, protected notes remain indecipherable. Even if someone gains access to your Trilium [database](../../Advanced%20Usage/Database.md), they won't be able to read your encrypted notes.

View File

@ -1,4 +1,4 @@
# Read-Only Notes
# Read-Only Notes
Both [text](../../Note%20Types/Text.md) and [code](../../Note%20Types/Code.md) notes in Trilium can be set to read-only. When a note is in read-only mode, it is presented to the user in a non-editable view, with the option to switch to editing mode if needed.
## Setting Read-Only Mode with a Label

View File

@ -1,4 +1,4 @@
# Sorting Notes
# Sorting Notes
## Sorting Notes
You can sort notes by right-clicking the parent note in the note tree and selecting Advanced -> Sort notes by ... This will sort existing notes, but will not automatically sort future notes added to this parent note

View File

@ -1,4 +1,4 @@
# Themes
# Themes
## Default Themes
Trilium comes with a couple pre-installed color themes, with the default being a light theme. To switch to a dark theme or any other available theme, navigate to the Options menu (accessible via the app icon in the top-left corner), select the Appearance tab, and choose your preferred theme.

View File

@ -1,4 +1,4 @@
# Theme Gallery
# Theme Gallery
These are user-created themes which were made publicly available:
## Legacy Themes

View File

@ -1,4 +1,4 @@
# Global menu
# Global menu
The global menu configures the current window (zoom, keeping the window on top) and offers access to some more advanced options.
![](Global%20menu_image.png)

View File

@ -1,4 +1,4 @@
# Launch Bar
# Launch Bar
## Position of the Launch bar
Depending on the layout selected, the launcher bar will either be on the left side of the screen with buttons displayed vertically or at the top of the screen. See [Vertical and horizontal layout](Vertical%20and%20horizontal%20layout.md) for more information.

View File

@ -1,4 +1,4 @@
# Note Tree
# Note Tree
This page explains how to manipulate the note tree in TriliumNext, focusing on moving notes.
![](1_Note%20Tree_image.png)

View File

@ -1,4 +1,4 @@
# Note buttons
# Note buttons
To the right of the [Ribbon](Ribbon.md) there are a few more buttons: ![](Note%20buttons_image.png)
* The Note Revisions button displays the [Note Revisions](../Notes/Note%20Revisions.md) for that particular note.

View File

@ -1,4 +1,4 @@
# Ribbon
# Ribbon
![](Ribbon_image.png)
The ribbon allows changing options, attributes and viewing information about the current note.

View File

@ -1,4 +1,4 @@
# Vertical and horizontal layout
# Vertical and horizontal layout
## Layouts
Trilium supports two different layouts, based on your preference.

View File

@ -1,4 +1,4 @@
# Zen mode
# Zen mode
![](3_Zen%20mode_image.png)
Screenshot of Zen Mode activated on a Windows 11 system with native title bar off and background effects on.

View File

@ -1,4 +1,4 @@
# Note Map
# Note Map
A Note map is a note type which displays a standalone version of the feature of the same name: [Note Map (Link map, Tree map)](../Advanced%20Usage/Note%20Map%20\(Link%20map%2C%20Tree%20map\).md).
Once created, the note map will display the relations between notes. Only the notes that are part of the parent of the note map will be displayed (including their children).

View File

@ -1,4 +1,4 @@
# Relation Map
# Relation Map
Relation map is a type of [Note](../Basic%20Concepts%20and%20Features/Notes.md) which visualizes notes and their [relations](../Advanced%20Usage/Attributes.md). See an example:
## Development process demo

View File

@ -1,4 +1,4 @@
# Developer-specific formatting
# Developer-specific formatting
### Inline code
Inline code formats text using a monospace font to indicate technical content in a sentence such as code, paths, etc.

View File

@ -1,4 +1,4 @@
# Code blocks
# Code blocks
![](1_Code%20blocks_image.png)
The code blocks feature allows entering pieces of code in text notes.

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",
@ -219,7 +220,7 @@
"bootstrap": "5.3.3",
"copy-webpack-plugin": "13.0.0",
"cross-env": "7.0.3",
"css-loader": "7.1.2",
"css-loader": "7.1.2",
"electron": "35.1.2",
"eslint": "9.23.0",
"esm": "3.2.25",
@ -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,
@ -185,7 +187,7 @@ async function createMainWindow(app: App) {
if (lastFocusedWindow.isMinimized()) {
lastFocusedWindow.restore();
}
lastFocusedWindow.show();
lastFocusedWindow.show();
lastFocusedWindow.focus();
}
});
@ -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"]
}