diff --git a/README.md b/README.md index 30f4dc124..bb6e45836 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q ### Migrating from Trilium? -There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Just upgrade your Trilium instance to the latest version and [install TriliumNext/Notes as usual](#-installation) +There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Simply [install TriliumNext/Notes](#-installation) as usual and it will use your existing database. Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext have their sync versions incremented. @@ -66,17 +66,7 @@ To use TriliumNext on your desktop machine (Linux, MacOS, and Windows) you have * Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the ```trilium``` executable. * Access TriliumNext via the web interface of a server installation (see below) * Currently only the latest versions of Chrome & Firefox are supported (and tested). -* (Coming Soon) TriliumNext will also be provided as a Flatpak - -#### MacOS -Currently when running TriliumNext/Notes on MacOS, you may get the following error: -> Apple could not verify "TriliumNext Notes" is free of malware and may harm your Mac or compromise your privacy. - -You will need to run the command on your shell to resolve the error (documented [here](https://github.com/TriliumNext/Notes/issues/329#issuecomment-2287164137)): - -```bash -xattr -c "/path/to/Trilium Next.app" -``` +* TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. ### Mobile diff --git a/docs/Release Notes/!!!meta.json b/docs/Release Notes/!!!meta.json index c9e9b98a2..9a5c041f1 100644 --- a/docs/Release Notes/!!!meta.json +++ b/docs/Release Notes/!!!meta.json @@ -61,7 +61,7 @@ "hD3V4hiu2VW4", "VN3xnce1vLkX" ], - "title": "v0.92.8-beta", + "title": "v0.93.0", "notePosition": 10, "prefix": null, "isExpanded": false, @@ -69,7 +69,7 @@ "mime": "text/html", "attributes": [], "format": "markdown", - "dataFileName": "v0.92.8-beta.md", + "dataFileName": "v0.93.0.md", "attachments": [] }, { diff --git a/docs/Release Notes/Release Notes/v0.92.8-beta.md b/docs/Release Notes/Release Notes/v0.93.0.md similarity index 75% rename from docs/Release Notes/Release Notes/v0.92.8-beta.md rename to docs/Release Notes/Release Notes/v0.93.0.md index 58b5e6188..fd13d57b4 100644 --- a/docs/Release Notes/Release Notes/v0.92.8-beta.md +++ b/docs/Release Notes/Release Notes/v0.93.0.md @@ -1,4 +1,4 @@ -# v0.92.8-beta +# v0.93.0 ## 💡 Key highlights * … @@ -11,6 +11,10 @@ * Note tree not closing when selecting some of the menu actions. * [Most tree context menu on mobile are broken](https://github.com/TriliumNext/Notes/issues/671) * [Quick search launch bar item does nothing in vertical layout](https://github.com/TriliumNext/Notes/issues/1680) +* [Note background is gray in 0.92.7 (light theme)](https://github.com/TriliumNext/Notes/issues/1689) +* [config.Session.cookieMaxAge is ignored](https://github.com/TriliumNext/Notes/issues/1709) by @pano9000 +* [Return correct HTTP status code on failed login attempts instead of 200](https://github.com/TriliumNext/Notes/issues/1707) by @pano9000 +* [Calendar stops displaying notes after adding a Day Note](https://github.com/TriliumNext/Notes/issues/1705) ## ✨ Improvements @@ -29,6 +33,8 @@ * [Center Search results under quick search bar](https://github.com/TriliumNext/Notes/issues/1679) * Native ARM builds for Windows are now back. * Basic Touch Bar support for macOS. +* [Support Bearer Token](https://github.com/TriliumNext/Notes/issues/1701) +* The tab bar is now scrollable when there are many tabs by @SiriusXT ## 🌍 Internationalization diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index b548e1846..cd9f21f61 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -9636,6 +9636,13 @@ "isInheritable": false, "position": 10 }, + { + "type": "relation", + "name": "internalLink", + "value": "habiZ3HU8Kw8", + "isInheritable": false, + "position": 20 + }, { "type": "relation", "name": "internalLink", @@ -9649,13 +9656,6 @@ "value": "default-note-title", "isInheritable": false, "position": 30 - }, - { - "type": "relation", - "name": "internalLink", - "value": "habiZ3HU8Kw8", - "isInheritable": false, - "position": 20 } ], "format": "markdown", @@ -10014,6 +10014,13 @@ "isInheritable": false, "position": 40 }, + { + "type": "relation", + "name": "internalLink", + "value": "habiZ3HU8Kw8", + "isInheritable": false, + "position": 50 + }, { "type": "relation", "name": "internalLink", @@ -10027,13 +10034,6 @@ "value": "bx bx-list-plus", "isInheritable": false, "position": 10 - }, - { - "type": "relation", - "name": "internalLink", - "value": "habiZ3HU8Kw8", - "isInheritable": false, - "position": 50 } ], "format": "markdown", @@ -11066,32 +11066,32 @@ "mime": "text/markdown", "attributes": [ { - "type": "label", - "name": "shareAlias", - "value": "script-api", + "type": "relation", + "name": "internalLink", + "value": "CdNpE2pqjmI6", "isInheritable": false, "position": 10 }, { "type": "relation", "name": "internalLink", - "value": "CdNpE2pqjmI6", + "value": "Q2z6av6JZVWm", "isInheritable": false, "position": 20 }, { "type": "relation", "name": "internalLink", - "value": "Q2z6av6JZVWm", + "value": "MEtfsqa5VwNi", "isInheritable": false, "position": 30 }, { - "type": "relation", - "name": "internalLink", - "value": "MEtfsqa5VwNi", + "type": "label", + "name": "shareAlias", + "value": "script-api", "isInheritable": false, - "position": 40 + "position": 10 } ], "format": "markdown", diff --git a/docs/User Guide/User Guide/Advanced Usage/ETAPI (REST API).md b/docs/User Guide/User Guide/Advanced Usage/ETAPI (REST API).md index b9c4db4f3..c24200049 100644 --- a/docs/User Guide/User Guide/Advanced Usage/ETAPI (REST API).md +++ b/docs/User Guide/User Guide/Advanced Usage/ETAPI (REST API).md @@ -9,16 +9,26 @@ As an alternative to calling the API directly, there are client libraries to sim * [trilium-py](https://github.com/Nriver/trilium-py), you can use Python to communicate with Trilium. +## Obtaining a token + +All operations with the REST API have to be authenticated using a token. You can get this token either from Options -> ETAPI or programmatically using the `/auth/login` REST call (see the [spec](https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml)). + ## Authentication -All operations have to be authenticated using a token. You can get this token either from Options -> ETAPI or programmatically using the `/auth/login` REST call (see the [spec](https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml)): +### Via the `Authorization` header ``` GET https://myserver.com/etapi/app-info Authorization: ETAPITOKEN ``` -Alternatively, since 0.56 you can also use basic auth format: +where `ETAPITOKEN` is the token obtained in the previous step. + +For compatibility with various tools, it's also possible to specify the value of the `Authorization` header in the format `Bearer ETAPITOKEN` (since 0.93.0). + +### Basic authentication + +Since v0.56 you can also use basic auth format: ``` GET https://myserver.com/etapi/app-info diff --git a/package-lock.json b/package-lock.json index 73632549e..a7f5e8fa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -197,7 +197,7 @@ "typedoc": "0.28.2", "typedoc-plugin-missing-exports": "4.0.0", "typescript": "5.8.3", - "typescript-eslint": "8.29.1", + "typescript-eslint": "8.30.1", "vanilla-js-wheel-zoom": "9.0.4", "vitest": "3.1.1", "webpack": "5.99.5", @@ -5571,17 +5571,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", - "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", + "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/type-utils": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/type-utils": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -5601,16 +5601,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", - "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", + "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "debug": "^4.3.4" }, "engines": { @@ -5626,14 +5626,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz", + "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5644,14 +5644,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", - "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz", + "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/utils": "8.30.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -5668,9 +5668,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", - "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", + "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", "dev": true, "license": "MIT", "engines": { @@ -5682,14 +5682,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz", + "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -5735,16 +5735,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz", + "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5759,13 +5759,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", - "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz", + "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/types": "8.30.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -20458,15 +20458,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", - "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz", + "integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@typescript-eslint/utils": "8.29.1" + "@typescript-eslint/eslint-plugin": "8.30.1", + "@typescript-eslint/parser": "8.30.1", + "@typescript-eslint/utils": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index f8dc64f89..fd474a3dd 100644 --- a/package.json +++ b/package.json @@ -254,7 +254,7 @@ "typedoc": "0.28.2", "typedoc-plugin-missing-exports": "4.0.0", "typescript": "5.8.3", - "typescript-eslint": "8.29.1", + "typescript-eslint": "8.30.1", "vanilla-js-wheel-zoom": "9.0.4", "vitest": "3.1.1", "webpack": "5.99.5", diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Advanced Usage/ETAPI (REST API).html b/src/public/app/doc_notes/en/User Guide/User Guide/Advanced Usage/ETAPI (REST API).html index 0aa779d60..a7ea78e55 100644 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Advanced Usage/ETAPI (REST API).html +++ b/src/public/app/doc_notes/en/User Guide/User Guide/Advanced Usage/ETAPI (REST API).html @@ -8,12 +8,19 @@
All operations with the REST API have to be authenticated using a token.
+ You can get this token either from Options -> ETAPI or programmatically
+ using the /auth/login
REST call (see the spec).
All operations have to be authenticated using a token. You can get this
- token either from Options -> ETAPI or programmatically using the /auth/login
REST
- call (see the spec):
GET https://myserver.com/etapi/app-info
+Via the Authorization
header
GET https://myserver.com/etapi/app-info
Authorization: ETAPITOKEN
-Alternatively, since 0.56 you can also use basic auth format:
GET https://myserver.com/etapi/app-info
+where ETAPITOKEN
is the token obtained in the previous step.
+For compatibility with various tools, it's also possible to specify the
+ value of the Authorization
header in the format Bearer ETAPITOKEN
(since
+ 0.93.0).
+Basic authentication
+Since v0.56 you can also use basic auth format:
GET https://myserver.com/etapi/app-info
Authorization: Basic BATOKEN
- Where
BATOKEN = BASE64(username + ':' + password)
- this is
diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.html b/src/public/app/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.html
index 72b77aef3..26e837d8b 100644
--- a/src/public/app/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.html
+++ b/src/public/app/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.html
@@ -36,8 +36,8 @@
Running the Docker Container
Local Access Only
Run the container to make it accessible only from the localhost. This
- setup is suitable for testing or when using a prox ay server like Nginx
- or Apache.
sudo docker run -t -i -p 127.0.0.1:8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:[VERSION]
+ setup is suitable for testing or when using a proxy server like Nginx or
+ Apache.sudo docker run -t -i -p 127.0.0.1:8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:[VERSION]
- Verify the container is running using
docker ps
.
- Access Trilium via a web browser at
127.0.0.1:8080
.
diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Script API.html b/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Script API.html
index a2ea49ead..25b181b48 100644
--- a/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Script API.html
+++ b/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Script API.html
@@ -1,11 +1,10 @@
-For script code notes,
- Trilium offers an API that gives them access to various features of the
- application.
+For script code notes, Trilium offers
+ an API that gives them access to various features of the application.
There are two APIs:
- - One for the front-end scripts: Frontend API
+
- One for the front-end scripts: Frontend API
- - One for the back-end scripts: Backend API
+
- One for the back-end scripts: Backend API
In both cases, the API resides in a global variable, api
,
diff --git a/src/public/app/layouts/desktop_layout.ts b/src/public/app/layouts/desktop_layout.ts
index ee6faed63..4ca86197a 100644
--- a/src/public/app/layouts/desktop_layout.ts
+++ b/src/public/app/layouts/desktop_layout.ts
@@ -122,6 +122,7 @@ export default class DesktopLayout {
const rootContainer = new RootContainer(true)
.setParent(appContext)
+ .class((launcherPaneIsHorizontal ? "horizontal" : "vertical") + "-layout")
.optChild(
fullWidthTabBar,
new FlexContainer("row")
diff --git a/src/public/app/layouts/mobile_layout.ts b/src/public/app/layouts/mobile_layout.ts
index f652e5f23..f6d69f4c6 100644
--- a/src/public/app/layouts/mobile_layout.ts
+++ b/src/public/app/layouts/mobile_layout.ts
@@ -7,8 +7,6 @@ import ToggleSidebarButtonWidget from "../widgets/mobile_widgets/toggle_sidebar_
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
-import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
-import ConfirmDialog from "../widgets/dialogs/confirm.js";
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
import EditButton from "../widgets/floating_buttons/edit_button.js";
@@ -118,6 +116,7 @@ export default class MobileLayout {
getRootWidget(appContext: typeof AppContext) {
const rootContainer = new RootContainer(true)
.setParent(appContext)
+ .class("horizontal-layout")
.cssBlock(MOBILE_CSS)
.child(new FlexContainer("column").id("mobile-sidebar-container"))
.child(
diff --git a/src/public/app/types-lib.d.ts b/src/public/app/types-lib.d.ts
index 51155abfb..57b810f76 100644
--- a/src/public/app/types-lib.d.ts
+++ b/src/public/app/types-lib.d.ts
@@ -13,7 +13,7 @@ declare module "draggabilly" {
containment: HTMLElement
});
element: HTMLElement;
- on(event: "pointerDown" | "dragStart" | "dragEnd" | "dragMove", callback: Callback);
+ on(event: "staticClick" | "dragStart" | "dragEnd" | "dragMove", callback: Callback);
dragEnd();
isDragging: boolean;
positionDrag: () => void;
diff --git a/src/public/app/widgets/containers/root_container.ts b/src/public/app/widgets/containers/root_container.ts
index 251a92691..c941cdd88 100644
--- a/src/public/app/widgets/containers/root_container.ts
+++ b/src/public/app/widgets/containers/root_container.ts
@@ -19,7 +19,6 @@ export default class RootContainer extends FlexContainer {
this.id("root-widget");
this.css("height", "100dvh");
- this.class((isHorizontalLayout ? "horizontal" : "vertical") + "-layout");
this.originalViewportHeight = getViewportHeight();
}
diff --git a/src/public/app/widgets/tab_row.ts b/src/public/app/widgets/tab_row.ts
index 4de766357..c5fb4e582 100644
--- a/src/public/app/widgets/tab_row.ts
+++ b/src/public/app/widgets/tab_row.ts
@@ -11,10 +11,11 @@ import type NoteContext from "../components/note_context.js";
const isDesktop = utils.isDesktop();
-const TAB_CONTAINER_MIN_WIDTH = 24;
+const TAB_CONTAINER_MIN_WIDTH = 100;
const TAB_CONTAINER_MAX_WIDTH = 240;
const TAB_CONTAINER_LEFT_PADDING = 5;
-const NEW_TAB_WIDTH = 32;
+const SCROLL_BUTTON_WIDTH = 36;
+const NEW_TAB_WIDTH = 36;
const MIN_FILLER_WIDTH = isDesktop ? 50 : 15;
const MARGIN_WIDTH = 5;
@@ -32,6 +33,8 @@ const TAB_TPL = `
`;
+const CONTAINER_ANCHOR_TPL = ``;
+
const NEW_TAB_BUTTON_TPL = `+`;
const FILLER_TPL = ``;
@@ -39,11 +42,11 @@ const TAB_ROW_TPL = `
`;
export default class TabRowWidget extends BasicWidget {
@@ -244,11 +306,24 @@ export default class TabRowWidget extends BasicWidget {
private draggabillyDragging?: Draggabilly | null;
private $style!: JQuery;
+ private $tabScrollingContainer!: JQuery;
+ private $tabContainer!: JQuery;
+ private $scrollButtonLeft!: JQuery;
+ private $scrollButtonRight!: JQuery;
+ private $containerAnchor!: JQuery;
private $filler!: JQuery;
private $newTab!: JQuery;
+ private updateScrollTimeout: ReturnType | undefined;
+
+ private newTabOuterWidth: number = 0;
+ private scrollButtonsOuterWidth: number = 0;
doRender() {
this.$widget = $(TAB_ROW_TPL);
+ this.$tabScrollingContainer = this.$widget.children(".tab-row-widget-scrolling-container");
+ this.$tabContainer = this.$widget.find(".tab-row-widget-container");
+ this.$scrollButtonLeft = this.$widget.children(".tab-scroll-button-left");
+ this.$scrollButtonRight = this.$widget.children(".tab-scroll-button-right");
const documentStyle = window.getComputedStyle(document.documentElement);
this.showNoteIcons = documentStyle.getPropertyValue("--tab-note-icons") === "true";
@@ -257,11 +332,13 @@ export default class TabRowWidget extends BasicWidget {
this.setupStyle();
this.setupEvents();
+ this.setupContainerAnchor();
this.setupDraggabilly();
this.setupNewButton();
this.setupFiller();
this.layoutTabs();
this.setVisibility();
+ this.setupScrollEvents();
this.$widget.on("contextmenu", ".note-tab", (e) => {
e.preventDefault();
@@ -300,6 +377,60 @@ export default class TabRowWidget extends BasicWidget {
this.$widget.append(this.$style);
}
+ scrollTabContainer(direction: number, behavior: ScrollBehavior = "smooth") {
+ const currentScrollLeft = this.$tabScrollingContainer[0]?.scrollLeft;
+ this.$tabScrollingContainer[0].scrollTo({
+ left: currentScrollLeft + direction,
+ behavior
+ });
+ };
+
+ setupScrollEvents() {
+ let isScrolling = false;
+ this.$tabScrollingContainer[0].addEventListener('wheel', (event) => {
+ if (!isScrolling) {
+ isScrolling = true;
+ requestAnimationFrame(() => {
+ this.scrollTabContainer(event.deltaY * 1.5, 'instant');
+ isScrolling = false;
+ });
+ }
+ });
+
+ this.$scrollButtonLeft[0].addEventListener('click', () => this.scrollTabContainer(-200));
+ this.$scrollButtonRight[0].addEventListener('click', () => this.scrollTabContainer(200));
+
+ this.$tabScrollingContainer[0].addEventListener('scroll', () => {
+ clearTimeout(this.updateScrollTimeout);
+ this.updateScrollTimeout = setTimeout(() => {
+ this.updateScrollButtonState();
+ }, 100);
+ });
+ }
+
+ updateScrollButtonState() {
+ const scrollLeft = this.$tabScrollingContainer[0].scrollLeft;
+ const scrollWidth = this.$tabScrollingContainer[0].scrollWidth;
+ const clientWidth = this.$tabScrollingContainer[0].clientWidth;
+ // Detect whether the scrollbar is at the far left or far right.
+ this.$scrollButtonLeft.toggleClass("disabled", Math.abs(scrollLeft) <= 1);
+ this.$scrollButtonRight.toggleClass("disabled", Math.abs(scrollLeft + clientWidth - scrollWidth) <= 1);
+ }
+
+ setScrollButtonVisibility(show: boolean = true) {
+ if (show) {
+ this.$scrollButtonLeft.css("display", "flex");
+ this.$scrollButtonRight.css("display", "flex");
+ clearTimeout(this.updateScrollTimeout);
+ this.updateScrollTimeout = setTimeout(() => {
+ this.updateScrollButtonState();
+ }, 200);
+ } else {
+ this.$scrollButtonLeft.css("display", "none");
+ this.$scrollButtonRight.css("display", "none");
+ }
+ }
+
setupEvents() {
new ResizeObserver((_) => {
this.cleanUpPreviouslyDraggedTabs();
@@ -317,14 +448,32 @@ export default class TabRowWidget extends BasicWidget {
return Array.prototype.slice.call(this.$widget.find(".note-tab"));
}
- get $tabContainer() {
- return this.$widget.find(".tab-row-widget-container");
+ updateOuterWidth() {
+ if (this.newTabOuterWidth == 0) {
+ this.newTabOuterWidth = this.$newTab?.outerWidth(true) ?? 0;
+ }
+ if (this.scrollButtonsOuterWidth == 0) {
+ this.scrollButtonsOuterWidth = (this.$scrollButtonLeft?.outerWidth(true) ?? 0) + (this.$scrollButtonRight?.outerWidth(true) ?? 0);
+ }
}
+
get tabWidths() {
const numberOfTabs = this.tabEls.length;
- const tabsContainerWidth = this.$tabContainer[0].clientWidth - NEW_TAB_WIDTH - MIN_FILLER_WIDTH;
- const marginWidth = (numberOfTabs - 1) * MARGIN_WIDTH;
+ // this.$newTab may include margin, and using NEW_TAB_WIDTH could cause tabsContainerWidth to be slightly larger,
+ // resulting in misaligned scrollbars/buttons. Therefore, use outerwidth.
+ this.updateOuterWidth();
+ let tabsContainerWidth = Math.floor(this.$widget.width() ?? 0);
+ tabsContainerWidth -= this.newTabOuterWidth + MIN_FILLER_WIDTH;
+ // Check whether the scroll buttons need to be displayed.
+ if ((TAB_CONTAINER_MIN_WIDTH + MARGIN_WIDTH) * numberOfTabs > tabsContainerWidth) {
+ tabsContainerWidth -= this.scrollButtonsOuterWidth;
+ this.setScrollButtonVisibility(true);
+ } else {
+ this.setScrollButtonVisibility(false);
+ }
+
+ const marginWidth = (numberOfTabs - 1) * MARGIN_WIDTH + TAB_CONTAINER_LEFT_PADDING;
const targetWidth = (tabsContainerWidth - marginWidth) / numberOfTabs;
const clampedTargetWidth = Math.max(TAB_CONTAINER_MIN_WIDTH, Math.min(TAB_CONTAINER_MAX_WIDTH, targetWidth));
const flooredClampedTargetWidth = Math.floor(clampedTargetWidth);
@@ -344,10 +493,6 @@ export default class TabRowWidget extends BasicWidget {
}
}
- if (this.$filler) {
- this.$filler.css("width", `${extraWidthRemaining + MIN_FILLER_WIDTH}px`);
- }
-
return widths;
}
@@ -362,10 +507,9 @@ export default class TabRowWidget extends BasicWidget {
position -= MARGIN_WIDTH; // the last margin should not be applied
- const newTabPosition = position;
- const fillerPosition = position + 32;
+ const anchorPosition = position;
- return { tabPositions, newTabPosition, fillerPosition };
+ return { tabPositions, anchorPosition };
}
layoutTabs() {
@@ -386,15 +530,14 @@ export default class TabRowWidget extends BasicWidget {
let styleHTML = "";
- const { tabPositions, newTabPosition, fillerPosition } = this.getTabPositions();
+ const { tabPositions, anchorPosition } = this.getTabPositions();
tabPositions.forEach((position, i) => {
styleHTML += `.note-tab:nth-child(${i + 1}) { transform: translate3d(${position}px, 0, 0)} `;
});
- styleHTML += `.note-new-tab { transform: translate3d(${newTabPosition}px, 0, 0) } `;
- styleHTML += `.tab-row-filler { transform: translate3d(${fillerPosition}px, 0, 0) } `;
-
+ styleHTML += `.tab-row-container-anchor { transform: translate3d(${anchorPosition}px, 0, 0) } `;
+ styleHTML += `.tab-row-widget-container {width: ${anchorPosition}px}`;
this.$style.html(styleHTML);
}
@@ -406,8 +549,7 @@ export default class TabRowWidget extends BasicWidget {
$tab.addClass("note-tab-was-just-added");
setTimeout(() => $tab.removeClass("note-tab-was-just-added"), 500);
-
- this.$newTab.before($tab);
+ this.$containerAnchor.before($tab);
this.setVisibility();
this.setTabCloseEvent($tab);
this.updateTitle($tab, t("tab_row.new_tab"));
@@ -507,6 +649,7 @@ export default class TabRowWidget extends BasicWidget {
setupDraggabilly() {
const tabEls = this.tabEls;
const { tabPositions } = this.getTabPositions();
+ let initialScrollLeft = 0;
if (this.isDragging && this.draggabillyDragging) {
this.isDragging = false;
@@ -533,7 +676,7 @@ export default class TabRowWidget extends BasicWidget {
this.draggabillies.push(draggabilly);
- draggabilly.on("pointerDown", () => {
+ draggabilly.on("staticClick", () => {
appContext.tabManager.activateNoteContext(tabEl.getAttribute("data-ntx-id"));
});
@@ -542,11 +685,20 @@ export default class TabRowWidget extends BasicWidget {
this.draggabillyDragging = draggabilly;
tabEl.classList.add("note-tab-is-dragging");
this.$widget.addClass("tab-row-widget-is-sorting");
+
+ initialScrollLeft = this.$tabScrollingContainer?.scrollLeft() ?? 0;
+ draggabilly.positionDrag = () => { };
});
draggabilly.on("dragEnd", () => {
this.isDragging = false;
- const finalTranslateX = parseFloat(tabEl.style.left);
+ const currentScrollLeft = this.$tabScrollingContainer?.scrollLeft() ?? 0;
+ const scrollDelta = currentScrollLeft - initialScrollLeft;
+ const translateX = parseFloat(tabEl.style.left) + scrollDelta;
+ const maxTranslateX = this.$tabContainer[0]?.offsetWidth - tabEl.offsetWidth;
+ const minTranslateX = 0;
+ const finalTranslateX = Math.min(maxTranslateX, Math.max(minTranslateX, translateX));
+
tabEl.style.transform = `translate3d(0, 0, 0)`;
// Animate dragged tab back into its place
@@ -570,12 +722,31 @@ export default class TabRowWidget extends BasicWidget {
});
});
- draggabilly.on("dragMove", (event: unknown, pointer: unknown, moveVector: MoveVector) => {
+ draggabilly.on("dragMove", (event: unknown, pointer: PointerEvent, moveVector: MoveVector) => {
// The current index be computed within the event since it can change during the dragMove
const tabEls = this.tabEls;
const currentIndex = tabEls.indexOf(tabEl);
- const currentTabPositionX = originalTabPositionX + moveVector.x;
+ const scorllContainerBounds = this.$tabScrollingContainer[0]?.getBoundingClientRect();
+ const pointerX = pointer.pageX;
+ const scrollSpeed = 100; // The increment of each scroll.
+ // Check if the mouse is near the edge of the container and trigger scrolling.
+ if (pointerX < scorllContainerBounds.left) {
+ this.scrollTabContainer(- scrollSpeed);
+ } else if (pointerX > scorllContainerBounds.right) {
+ this.scrollTabContainer(scrollSpeed);
+ }
+
+ const currentScrollLeft = this.$tabScrollingContainer?.scrollLeft() ?? 0;
+ const scrollDelta = currentScrollLeft - initialScrollLeft;
+ let translateX = moveVector.x + scrollDelta;
+
+ // Limit the `translateX` so that `tabEl` cannot exceed the left and right boundaries of the container.
+ const maxTranslateX = this.$tabContainer[0]?.offsetWidth - tabEl.offsetWidth - originalTabPositionX;
+ const minTranslateX = - originalTabPositionX;
+ translateX = Math.min(maxTranslateX, Math.max(minTranslateX, translateX));
+ tabEl.style.transform = `translate3d(${translateX}px, 0, 0)`;
+ const currentTabPositionX = originalTabPositionX + translateX;
const destinationIndexTarget = this.closest(currentTabPositionX, tabPositions);
const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget));
@@ -594,8 +765,7 @@ export default class TabRowWidget extends BasicWidget {
if (destinationIndex < originIndex) {
tabEl.parentNode?.insertBefore(tabEl, this.tabEls[destinationIndex]);
} else {
- const beforeEl = this.tabEls[destinationIndex + 1] || this.$newTab[0];
-
+ const beforeEl = this.tabEls[destinationIndex + 1] || this.$containerAnchor[0];
tabEl.parentNode?.insertBefore(tabEl, beforeEl);
}
this.triggerEvent("tabReorder", { ntxIdsInOrder: this.getNtxIdsInOrder() });
@@ -604,14 +774,19 @@ export default class TabRowWidget extends BasicWidget {
setupNewButton() {
this.$newTab = $(NEW_TAB_BUTTON_TPL);
-
- this.$tabContainer.append(this.$newTab);
+ this.$widget.append(this.$newTab);
}
setupFiller() {
this.$filler = $(FILLER_TPL);
- this.$tabContainer.append(this.$filler);
+ this.$widget.append(this.$filler);
+ }
+
+ setupContainerAnchor() {
+ this.$containerAnchor = $(CONTAINER_ANCHOR_TPL);
+
+ this.$tabContainer.append(this.$containerAnchor);
}
closest(value: number, array: number[]) {
@@ -660,7 +835,9 @@ export default class TabRowWidget extends BasicWidget {
updateTabById(ntxId: string | null) {
const $tab = this.getTabById(ntxId);
-
+ $tab[0].scrollIntoView({
+ behavior: 'smooth'
+ });
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
this.updateTab($tab, noteContext);
diff --git a/src/public/app/widgets/type_widgets/ckeditor/config.ts b/src/public/app/widgets/type_widgets/ckeditor/config.ts
index 6d4726999..817825ac3 100644
--- a/src/public/app/widgets/type_widgets/ckeditor/config.ts
+++ b/src/public/app/widgets/type_widgets/ckeditor/config.ts
@@ -74,7 +74,7 @@ export function buildConfig() {
heading: {
options: [
{ model: "paragraph" as const, title: "Paragraph", class: "ck-heading_paragraph" },
- // // heading1 is not used since that should be a note's title
+ // heading1 is not used since that should be a note's title
{ model: "heading2" as const, view: "h2", title: "Heading 2", class: "ck-heading_heading2" },
{ model: "heading3" as const, view: "h3", title: "Heading 3", class: "ck-heading_heading3" },
{ model: "heading4" as const, view: "h4", title: "Heading 4", class: "ck-heading_heading4" },
diff --git a/src/public/stylesheets/theme-next/shell.css b/src/public/stylesheets/theme-next/shell.css
index da8f3bb1d..483d6910d 100644
--- a/src/public/stylesheets/theme-next/shell.css
+++ b/src/public/stylesheets/theme-next/shell.css
@@ -869,46 +869,23 @@ body.mobile .fancytree-node > span {
position: relative;
}
-/* #region Apply a border to the tab bar that avoids the current tab but also allows a transparent active tab. */
-body.layout-horizontal .tab-row-widget,
-body.layout-horizontal .tab-row-widget-container,
-body.layout-horizontal .tab-row-container .note-tab[active] {
- overflow: visible !important;
-}
-
-body.layout-horizontal .tab-row-container .note-tab[active]:before {
- content: "";
- position: absolute;
- bottom: 0;
- left: -100vw;
- top: var(--tab-height);
- right: calc(100% - 1px);
- height: 1px;
+/* Apply a border to the tab bar that avoids the current tab but also allows a transparent active tab. */
+body.layout-horizontal .tab-row-container {
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
-body.layout-horizontal .tab-row-container .note-tab[active]:after {
- content: "";
- position: absolute;
- bottom: 0;
- left: 100%;
- top: var(--tab-height);
- right: 0;
- width: 100vw;
- height: 1px;
- border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
-}
-/* #endregion */
-
body.layout-vertical.electron.platform-darwin .tab-row-container {
border-bottom: 1px solid var(--subtle-border-color);
}
.tab-row-widget-container {
- margin-top: calc((var(--tab-bar-height) - var(--tab-height)) / 2);
height: var(--tab-height) !important;
}
+.tab-row-widget > * {
+ margin-top: calc((var(--tab-bar-height) - var(--tab-height)) / 2);
+}
+
body.layout-horizontal .tab-row-container {
padding-top: calc((var(--tab-bar-height) - var(--tab-height)));
}
@@ -923,11 +900,12 @@ body.layout-vertical #left-pane .quick-search {
/* Limit the drag area for the previous elements to include just to the element itself
and not its descendants also */
body.layout-horizontal .tab-row-container > *,
-body.layout-vertical .tab-row-widget > *,
+body.layout-vertical .tab-row-widget > *:not(.tab-row-filler),
body.layout-vertical #left-pane .quick-search > * {
-webkit-app-region: no-drag;
}
+body.layout-horizontal .tab-row-widget,
body.layout-horizontal .tab-row-widget-container {
margin-top: 0;
position: relative;
@@ -991,7 +969,7 @@ body.layout-horizontal .tab-row-widget .note-tab .note-tab-wrapper {
text-overflow: ellipsis;
}
-body.layout-vertical .tab-row-widget-is-sorting .note-tab[active] .note-tab-wrapper {
+body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .note-tab-wrapper {
transform: scale(0.85);
box-shadow: var(--active-tab-dragging-shadow) !important;
}
diff --git a/src/routes/login.ts b/src/routes/login.ts
index 3f4d52f32..1b2d42b25 100644
--- a/src/routes/login.ts
+++ b/src/routes/login.ts
@@ -92,11 +92,10 @@ function login(req: Request, res: Response) {
const rememberMe = req.body.rememberMe;
req.session.regenerate(() => {
- if (rememberMe) {
- req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks
- } else {
+ if (!rememberMe) {
// unset default maxAge set by sessionParser
- // Cookie becomes non-persistent and expires after current browser session (e.g. when browser is closed)
+ // Cookie becomes non-persistent and expires
+ // after current browser session (e.g. when browser is closed)
req.session.cookie.maxAge = undefined;
}
@@ -134,7 +133,7 @@ function sendLoginError(req: Request, res: Response, errorType: 'password' | 'to
log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`);
}
- res.render('login', {
+ res.status(401).render('login', {
wrongPassword: errorType === 'password',
wrongTotp: errorType === 'totp',
totpEnabled: totp.isTotpEnabled(),
diff --git a/src/services/etapi_tokens.ts b/src/services/etapi_tokens.ts
index 93d916a60..031856f2a 100644
--- a/src/services/etapi_tokens.ts
+++ b/src/services/etapi_tokens.ts
@@ -48,6 +48,11 @@ function parseAuthToken(auth: string | undefined) {
auth = basicAuthChunks[1];
}
+ if (auth.startsWith("Bearer ")) {
+ // allow also bearer auth format
+ auth = auth.substring(7);
+ }
+
const chunks = auth.split("_");
if (chunks.length === 1) {
diff --git a/src/services/sql.ts b/src/services/sql.ts
index d135b54da..f686b0876 100644
--- a/src/services/sql.ts
+++ b/src/services/sql.ts
@@ -112,12 +112,21 @@ function upsert(tableName: string, primaryKey: string, rec: T) {
execute(query, rec);
}
-function stmt(sql: string) {
- if (!(sql in statementCache)) {
- statementCache[sql] = dbConnection.prepare(sql);
+/**
+ * For the given SQL query, returns a prepared statement. For the same query (string comparison), the same statement is returned.
+ *
+ * @param sql the SQL query for which to return a prepared statement.
+ * @param isRaw indicates whether `.raw()` is going to be called on the prepared statement in order to return the raw rows (e.g. via {@link getRawRows()}). The reason is that the raw state is preserved in the saved statement and would break non-raw calls for the same query.
+ * @returns the corresponding {@link Statement}.
+ */
+function stmt(sql: string, isRaw?: boolean) {
+ const key = (isRaw ? "raw/" + sql : sql);
+
+ if (!(key in statementCache)) {
+ statementCache[key] = dbConnection.prepare(sql);
}
- return statementCache[sql];
+ return statementCache[key];
}
function getRow(query: string, params: Params = []): T {
@@ -172,7 +181,7 @@ function getRows(query: string, params: Params = []): T[] {
}
function getRawRows(query: string, params: Params = []): T[] {
- return (wrap(query, (s) => s.raw().all(params)) as T[]) || [];
+ return (wrap(query, (s) => s.raw().all(params), true) as T[]) || [];
}
function iterateRows(query: string, params: Params = []): IterableIterator {
@@ -234,7 +243,10 @@ function executeScript(query: string): DatabaseType {
return dbConnection.exec(query);
}
-function wrap(query: string, func: (statement: Statement) => unknown): unknown {
+/**
+ * @param isRaw indicates whether `.raw()` is going to be called on the prepared statement in order to return the raw rows (e.g. via {@link getRawRows()}). The reason is that the raw state is preserved in the saved statement and would break non-raw calls for the same query.
+ */
+function wrap(query: string, func: (statement: Statement) => unknown, isRaw?: boolean): unknown {
const startTimestamp = Date.now();
let result;
@@ -243,7 +255,7 @@ function wrap(query: string, func: (statement: Statement) => unknown): unknown {
}
try {
- result = func(stmt(query));
+ result = func(stmt(query, isRaw));
} catch (e: any) {
if (e.message.includes("The database connection is not open")) {
// this often happens on killing the app which puts these alerts in front of user