mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
Merge branch 'develop' into ai-llm-integration
This commit is contained in:
commit
2acc30f94a
14
README.md
14
README.md
@ -16,7 +16,7 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
|
|||||||
|
|
||||||
### Migrating from Trilium?
|
### 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.
|
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.
|
* 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)
|
* Access TriliumNext via the web interface of a server installation (see below)
|
||||||
* Currently only the latest versions of Chrome & Firefox are supported (and tested).
|
* Currently only the latest versions of Chrome & Firefox are supported (and tested).
|
||||||
* (Coming Soon) TriliumNext will also be provided as a Flatpak
|
* TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
||||||
|
|
||||||
#### 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"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mobile
|
### Mobile
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
"hD3V4hiu2VW4",
|
"hD3V4hiu2VW4",
|
||||||
"VN3xnce1vLkX"
|
"VN3xnce1vLkX"
|
||||||
],
|
],
|
||||||
"title": "v0.92.8-beta",
|
"title": "v0.93.0",
|
||||||
"notePosition": 10,
|
"notePosition": 10,
|
||||||
"prefix": null,
|
"prefix": null,
|
||||||
"isExpanded": false,
|
"isExpanded": false,
|
||||||
@ -69,7 +69,7 @@
|
|||||||
"mime": "text/html",
|
"mime": "text/html",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"dataFileName": "v0.92.8-beta.md",
|
"dataFileName": "v0.93.0.md",
|
||||||
"attachments": []
|
"attachments": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# v0.92.8-beta
|
# v0.93.0
|
||||||
## 💡 Key highlights
|
## 💡 Key highlights
|
||||||
|
|
||||||
* …
|
* …
|
||||||
@ -11,6 +11,10 @@
|
|||||||
* Note tree not closing when selecting some of the menu actions.
|
* 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)
|
* [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)
|
* [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
|
## ✨ Improvements
|
||||||
|
|
||||||
@ -29,6 +33,8 @@
|
|||||||
* [Center Search results under quick search bar](https://github.com/TriliumNext/Notes/issues/1679)
|
* [Center Search results under quick search bar](https://github.com/TriliumNext/Notes/issues/1679)
|
||||||
* Native ARM builds for Windows are now back.
|
* Native ARM builds for Windows are now back.
|
||||||
* Basic Touch Bar support for macOS.
|
* 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
|
## 🌍 Internationalization
|
||||||
|
|
@ -9636,6 +9636,13 @@
|
|||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "relation",
|
||||||
|
"name": "internalLink",
|
||||||
|
"value": "habiZ3HU8Kw8",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 20
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
@ -9649,13 +9656,6 @@
|
|||||||
"value": "default-note-title",
|
"value": "default-note-title",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "habiZ3HU8Kw8",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 20
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@ -10014,6 +10014,13 @@
|
|||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "relation",
|
||||||
|
"name": "internalLink",
|
||||||
|
"value": "habiZ3HU8Kw8",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 50
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
@ -10027,13 +10034,6 @@
|
|||||||
"value": "bx bx-list-plus",
|
"value": "bx bx-list-plus",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "habiZ3HU8Kw8",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 50
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@ -11066,32 +11066,32 @@
|
|||||||
"mime": "text/markdown",
|
"mime": "text/markdown",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "relation",
|
||||||
"name": "shareAlias",
|
"name": "internalLink",
|
||||||
"value": "script-api",
|
"value": "CdNpE2pqjmI6",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "CdNpE2pqjmI6",
|
"value": "Q2z6av6JZVWm",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "Q2z6av6JZVWm",
|
"value": "MEtfsqa5VwNi",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "label",
|
||||||
"name": "internalLink",
|
"name": "shareAlias",
|
||||||
"value": "MEtfsqa5VwNi",
|
"value": "script-api",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
|
@ -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.
|
* [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
|
## 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
|
GET https://myserver.com/etapi/app-info
|
||||||
Authorization: ETAPITOKEN
|
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
|
GET https://myserver.com/etapi/app-info
|
||||||
|
98
package-lock.json
generated
98
package-lock.json
generated
@ -197,7 +197,7 @@
|
|||||||
"typedoc": "0.28.2",
|
"typedoc": "0.28.2",
|
||||||
"typedoc-plugin-missing-exports": "4.0.0",
|
"typedoc-plugin-missing-exports": "4.0.0",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.8.3",
|
||||||
"typescript-eslint": "8.29.1",
|
"typescript-eslint": "8.30.1",
|
||||||
"vanilla-js-wheel-zoom": "9.0.4",
|
"vanilla-js-wheel-zoom": "9.0.4",
|
||||||
"vitest": "3.1.1",
|
"vitest": "3.1.1",
|
||||||
"webpack": "5.99.5",
|
"webpack": "5.99.5",
|
||||||
@ -5571,17 +5571,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz",
|
||||||
"integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==",
|
"integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.29.1",
|
"@typescript-eslint/scope-manager": "8.30.1",
|
||||||
"@typescript-eslint/type-utils": "8.29.1",
|
"@typescript-eslint/type-utils": "8.30.1",
|
||||||
"@typescript-eslint/utils": "8.29.1",
|
"@typescript-eslint/utils": "8.30.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.29.1",
|
"@typescript-eslint/visitor-keys": "8.30.1",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -5601,16 +5601,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz",
|
||||||
"integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==",
|
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.29.1",
|
"@typescript-eslint/scope-manager": "8.30.1",
|
||||||
"@typescript-eslint/types": "8.29.1",
|
"@typescript-eslint/types": "8.30.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.29.1",
|
"@typescript-eslint/typescript-estree": "8.30.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.29.1",
|
"@typescript-eslint/visitor-keys": "8.30.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -5626,14 +5626,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz",
|
||||||
"integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==",
|
"integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.29.1",
|
"@typescript-eslint/types": "8.30.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.29.1"
|
"@typescript-eslint/visitor-keys": "8.30.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -5644,14 +5644,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz",
|
||||||
"integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==",
|
"integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.29.1",
|
"@typescript-eslint/typescript-estree": "8.30.1",
|
||||||
"@typescript-eslint/utils": "8.29.1",
|
"@typescript-eslint/utils": "8.30.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.0.1"
|
"ts-api-utils": "^2.0.1"
|
||||||
},
|
},
|
||||||
@ -5668,9 +5668,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz",
|
||||||
"integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==",
|
"integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -5682,14 +5682,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz",
|
||||||
"integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==",
|
"integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.29.1",
|
"@typescript-eslint/types": "8.30.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.29.1",
|
"@typescript-eslint/visitor-keys": "8.30.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -5735,16 +5735,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz",
|
||||||
"integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==",
|
"integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.29.1",
|
"@typescript-eslint/scope-manager": "8.30.1",
|
||||||
"@typescript-eslint/types": "8.29.1",
|
"@typescript-eslint/types": "8.30.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.29.1"
|
"@typescript-eslint/typescript-estree": "8.30.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -5759,13 +5759,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz",
|
||||||
"integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==",
|
"integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.29.1",
|
"@typescript-eslint/types": "8.30.1",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -20458,15 +20458,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.29.1",
|
"version": "8.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz",
|
||||||
"integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==",
|
"integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.29.1",
|
"@typescript-eslint/eslint-plugin": "8.30.1",
|
||||||
"@typescript-eslint/parser": "8.29.1",
|
"@typescript-eslint/parser": "8.30.1",
|
||||||
"@typescript-eslint/utils": "8.29.1"
|
"@typescript-eslint/utils": "8.30.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
@ -254,7 +254,7 @@
|
|||||||
"typedoc": "0.28.2",
|
"typedoc": "0.28.2",
|
||||||
"typedoc-plugin-missing-exports": "4.0.0",
|
"typedoc-plugin-missing-exports": "4.0.0",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.8.3",
|
||||||
"typescript-eslint": "8.29.1",
|
"typescript-eslint": "8.30.1",
|
||||||
"vanilla-js-wheel-zoom": "9.0.4",
|
"vanilla-js-wheel-zoom": "9.0.4",
|
||||||
"vitest": "3.1.1",
|
"vitest": "3.1.1",
|
||||||
"webpack": "5.99.5",
|
"webpack": "5.99.5",
|
||||||
|
@ -8,12 +8,19 @@
|
|||||||
<li><a href="https://github.com/Nriver/trilium-py">trilium-py</a>, you can
|
<li><a href="https://github.com/Nriver/trilium-py">trilium-py</a>, you can
|
||||||
use Python to communicate with Trilium.</li>
|
use Python to communicate with Trilium.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h2>Obtaining a token</h2>
|
||||||
|
<p>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 <code>/auth/login</code> REST call (see the <a href="https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml">spec</a>).</p>
|
||||||
<h2>Authentication</h2>
|
<h2>Authentication</h2>
|
||||||
<p>All operations have to be authenticated using a token. You can get this
|
<h3>Via the <code>Authorization</code> header</h3><pre><code class="language-text-x-trilium-auto">GET https://myserver.com/etapi/app-info
|
||||||
token either from Options -> ETAPI or programmatically using the <code>/auth/login</code> REST
|
|
||||||
call (see the <a href="https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml">spec</a>):</p><pre><code class="language-text-x-trilium-auto">GET https://myserver.com/etapi/app-info
|
|
||||||
Authorization: ETAPITOKEN</code></pre>
|
Authorization: ETAPITOKEN</code></pre>
|
||||||
<p>Alternatively, since 0.56 you can also use basic auth format:</p><pre><code class="language-text-x-trilium-auto">GET https://myserver.com/etapi/app-info
|
<p>where <code>ETAPITOKEN</code> is the token obtained in the previous step.</p>
|
||||||
|
<p>For compatibility with various tools, it's also possible to specify the
|
||||||
|
value of the <code>Authorization</code> header in the format <code>Bearer ETAPITOKEN</code> (since
|
||||||
|
0.93.0).</p>
|
||||||
|
<h3>Basic authentication</h3>
|
||||||
|
<p>Since v0.56 you can also use basic auth format:</p><pre><code class="language-text-x-trilium-auto">GET https://myserver.com/etapi/app-info
|
||||||
Authorization: Basic BATOKEN</code></pre>
|
Authorization: Basic BATOKEN</code></pre>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Where <code>BATOKEN = BASE64(username + ':' + password)</code> - this is
|
<li>Where <code>BATOKEN = BASE64(username + ':' + password)</code> - this is
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
<h3>Running the Docker Container</h3>
|
<h3>Running the Docker Container</h3>
|
||||||
<h4>Local Access Only</h4>
|
<h4>Local Access Only</h4>
|
||||||
<p>Run the container to make it accessible only from the localhost. This
|
<p>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
|
setup is suitable for testing or when using a proxy server like Nginx or
|
||||||
or Apache.</p><pre><code class="language-text-x-trilium-auto">sudo docker run -t -i -p 127.0.0.1:8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:[VERSION]</code></pre>
|
Apache.</p><pre><code class="language-text-x-trilium-auto">sudo docker run -t -i -p 127.0.0.1:8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:[VERSION]</code></pre>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Verify the container is running using <code>docker ps</code>.</li>
|
<li>Verify the container is running using <code>docker ps</code>.</li>
|
||||||
<li>Access Trilium via a web browser at <code>127.0.0.1:8080</code>.</li>
|
<li>Access Trilium via a web browser at <code>127.0.0.1:8080</code>.</li>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<p>For <a href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">script code notes</a>,
|
<p>For <a href="#root/_help_CdNpE2pqjmI6">script code notes</a>, Trilium offers
|
||||||
Trilium offers an API that gives them access to various features of the
|
an API that gives them access to various features of the application.</p>
|
||||||
application.</p>
|
|
||||||
<p>There are two APIs:</p>
|
<p>There are two APIs:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>One for the front-end scripts: <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/GLks18SNjxmC/_help_Q2z6av6JZVWm">Frontend API</a>
|
<li>One for the front-end scripts: <a class="reference-link" href="#root/_help_Q2z6av6JZVWm">Frontend API</a>
|
||||||
</li>
|
</li>
|
||||||
<li>One for the back-end scripts: <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/GLks18SNjxmC/_help_MEtfsqa5VwNi">Backend API</a>
|
<li>One for the back-end scripts: <a class="reference-link" href="#root/_help_MEtfsqa5VwNi">Backend API</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>In both cases, the API resides in a global variable, <code>api</code>,
|
<p>In both cases, the API resides in a global variable, <code>api</code>,
|
||||||
|
@ -122,6 +122,7 @@ export default class DesktopLayout {
|
|||||||
|
|
||||||
const rootContainer = new RootContainer(true)
|
const rootContainer = new RootContainer(true)
|
||||||
.setParent(appContext)
|
.setParent(appContext)
|
||||||
|
.class((launcherPaneIsHorizontal ? "horizontal" : "vertical") + "-layout")
|
||||||
.optChild(
|
.optChild(
|
||||||
fullWidthTabBar,
|
fullWidthTabBar,
|
||||||
new FlexContainer("row")
|
new FlexContainer("row")
|
||||||
|
@ -7,8 +7,6 @@ import ToggleSidebarButtonWidget from "../widgets/mobile_widgets/toggle_sidebar_
|
|||||||
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||||
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||||
import ScrollingContainer from "../widgets/containers/scrolling_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 FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||||
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||||
import EditButton from "../widgets/floating_buttons/edit_button.js";
|
import EditButton from "../widgets/floating_buttons/edit_button.js";
|
||||||
@ -118,6 +116,7 @@ export default class MobileLayout {
|
|||||||
getRootWidget(appContext: typeof AppContext) {
|
getRootWidget(appContext: typeof AppContext) {
|
||||||
const rootContainer = new RootContainer(true)
|
const rootContainer = new RootContainer(true)
|
||||||
.setParent(appContext)
|
.setParent(appContext)
|
||||||
|
.class("horizontal-layout")
|
||||||
.cssBlock(MOBILE_CSS)
|
.cssBlock(MOBILE_CSS)
|
||||||
.child(new FlexContainer("column").id("mobile-sidebar-container"))
|
.child(new FlexContainer("column").id("mobile-sidebar-container"))
|
||||||
.child(
|
.child(
|
||||||
|
2
src/public/app/types-lib.d.ts
vendored
2
src/public/app/types-lib.d.ts
vendored
@ -13,7 +13,7 @@ declare module "draggabilly" {
|
|||||||
containment: HTMLElement
|
containment: HTMLElement
|
||||||
});
|
});
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
on(event: "pointerDown" | "dragStart" | "dragEnd" | "dragMove", callback: Callback);
|
on(event: "staticClick" | "dragStart" | "dragEnd" | "dragMove", callback: Callback);
|
||||||
dragEnd();
|
dragEnd();
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
positionDrag: () => void;
|
positionDrag: () => void;
|
||||||
|
@ -19,7 +19,6 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
|||||||
|
|
||||||
this.id("root-widget");
|
this.id("root-widget");
|
||||||
this.css("height", "100dvh");
|
this.css("height", "100dvh");
|
||||||
this.class((isHorizontalLayout ? "horizontal" : "vertical") + "-layout");
|
|
||||||
this.originalViewportHeight = getViewportHeight();
|
this.originalViewportHeight = getViewportHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@ import type NoteContext from "../components/note_context.js";
|
|||||||
|
|
||||||
const isDesktop = utils.isDesktop();
|
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_MAX_WIDTH = 240;
|
||||||
const TAB_CONTAINER_LEFT_PADDING = 5;
|
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 MIN_FILLER_WIDTH = isDesktop ? 50 : 15;
|
||||||
const MARGIN_WIDTH = 5;
|
const MARGIN_WIDTH = 5;
|
||||||
|
|
||||||
@ -32,6 +33,8 @@ const TAB_TPL = `
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
const CONTAINER_ANCHOR_TPL = `<div class="tab-row-container-anchor"></div>`;
|
||||||
|
|
||||||
const NEW_TAB_BUTTON_TPL = `<div class="note-new-tab" data-trigger-command="openNewTab" title="${t("tab_row.add_new_tab")}">+</div>`;
|
const NEW_TAB_BUTTON_TPL = `<div class="note-new-tab" data-trigger-command="openNewTab" title="${t("tab_row.add_new_tab")}">+</div>`;
|
||||||
const FILLER_TPL = `<div class="tab-row-filler"></div>`;
|
const FILLER_TPL = `<div class="tab-row-filler"></div>`;
|
||||||
|
|
||||||
@ -39,11 +42,11 @@ const TAB_ROW_TPL = `
|
|||||||
<div class="tab-row-widget">
|
<div class="tab-row-widget">
|
||||||
<style>
|
<style>
|
||||||
.tab-row-widget {
|
.tab-row-widget {
|
||||||
|
display:flex;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--main-background-color);
|
background: var(--main-background-color);
|
||||||
overflow: hidden;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +62,6 @@ const TAB_ROW_TPL = `
|
|||||||
.tab-row-widget .tab-row-widget-container {
|
.tab-row-widget .tab-row-widget-container {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,15 +76,12 @@ const TAB_ROW_TPL = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.note-new-tab {
|
.note-new-tab {
|
||||||
position: absolute;
|
display: flex;
|
||||||
left: 0;
|
align-items: center;
|
||||||
width: 36px;
|
justify-content: center;
|
||||||
height: 36px;
|
flex: 0 0 ${NEW_TAB_WIDTH}px;
|
||||||
|
height: ${NEW_TAB_WIDTH}px;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
z-index: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -96,11 +95,22 @@ const TAB_ROW_TPL = `
|
|||||||
.tab-row-filler {
|
.tab-row-filler {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-width: ${MIN_FILLER_WIDTH}px;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-row-container-anchor{
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 0px;
|
||||||
|
height: 36px;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
body.mobile .tab-row-filler {
|
body.mobile .tab-row-filler {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -184,6 +194,38 @@ const TAB_ROW_TPL = `
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-scroll-button-left, .tab-scroll-button-right {
|
||||||
|
display: none;
|
||||||
|
flex: 0 0 ${SCROLL_BUTTON_WIDTH}px;
|
||||||
|
height: ${SCROLL_BUTTON_WIDTH}px;
|
||||||
|
padding: 1px 1px 1px 1px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-scroll-button-left {
|
||||||
|
color: var(--active-tab-text-color);
|
||||||
|
box-shadow: inset -1px 0 0 0 var(--main-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-scroll-button-right {
|
||||||
|
color: var(--active-tab-text-color);
|
||||||
|
box-shadow: inset 1px 0 0 0 var(--main-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-scroll-button-left.disabled,
|
||||||
|
.tab-scroll-button-right.disabled {
|
||||||
|
color: var(--inactive-tab-text-color);
|
||||||
|
box-shadow: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-scroll-button-left:hover,
|
||||||
|
.tab-scroll-button-right:hover {
|
||||||
|
background-color: var(--tab-background-color, var(--inactive-tab-hover-background-color));
|
||||||
|
}
|
||||||
|
|
||||||
.tab-row-widget .note-tab:hover .note-tab-wrapper {
|
.tab-row-widget .note-tab:hover .note-tab-wrapper {
|
||||||
background-color: var(--tab-background-color, var(--inactive-tab-hover-background-color));
|
background-color: var(--tab-background-color, var(--inactive-tab-hover-background-color));
|
||||||
}
|
}
|
||||||
@ -231,9 +273,29 @@ const TAB_ROW_TPL = `
|
|||||||
.tab-row-widget:not(.tab-row-widget-is-sorting) .note-tab.note-tab-was-just-dragged {
|
.tab-row-widget:not(.tab-row-widget-is-sorting) .note-tab.note-tab-was-just-dragged {
|
||||||
transition: transform 120ms ease-in-out;
|
transition: transform 120ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
.tab-row-widget-wrapper {
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-row-widget-scrolling-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
/* Chrome/Safari */
|
||||||
|
.tab-row-widget-scrolling-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
<div class="tab-scroll-button-left bx bx-chevron-left"></div>
|
||||||
<div class="tab-row-widget-container"></div>
|
<div class="tab-row-widget-scrolling-container">
|
||||||
|
<div class="tab-row-widget-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-scroll-button-right bx bx-chevron-right"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class TabRowWidget extends BasicWidget {
|
export default class TabRowWidget extends BasicWidget {
|
||||||
@ -244,11 +306,24 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
private draggabillyDragging?: Draggabilly | null;
|
private draggabillyDragging?: Draggabilly | null;
|
||||||
|
|
||||||
private $style!: JQuery<HTMLElement>;
|
private $style!: JQuery<HTMLElement>;
|
||||||
|
private $tabScrollingContainer!: JQuery<HTMLElement>;
|
||||||
|
private $tabContainer!: JQuery<HTMLElement>;
|
||||||
|
private $scrollButtonLeft!: JQuery<HTMLElement>;
|
||||||
|
private $scrollButtonRight!: JQuery<HTMLElement>;
|
||||||
|
private $containerAnchor!: JQuery<HTMLElement>;
|
||||||
private $filler!: JQuery<HTMLElement>;
|
private $filler!: JQuery<HTMLElement>;
|
||||||
private $newTab!: JQuery<HTMLElement>;
|
private $newTab!: JQuery<HTMLElement>;
|
||||||
|
private updateScrollTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
|
private newTabOuterWidth: number = 0;
|
||||||
|
private scrollButtonsOuterWidth: number = 0;
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TAB_ROW_TPL);
|
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);
|
const documentStyle = window.getComputedStyle(document.documentElement);
|
||||||
this.showNoteIcons = documentStyle.getPropertyValue("--tab-note-icons") === "true";
|
this.showNoteIcons = documentStyle.getPropertyValue("--tab-note-icons") === "true";
|
||||||
@ -257,11 +332,13 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
|
|
||||||
this.setupStyle();
|
this.setupStyle();
|
||||||
this.setupEvents();
|
this.setupEvents();
|
||||||
|
this.setupContainerAnchor();
|
||||||
this.setupDraggabilly();
|
this.setupDraggabilly();
|
||||||
this.setupNewButton();
|
this.setupNewButton();
|
||||||
this.setupFiller();
|
this.setupFiller();
|
||||||
this.layoutTabs();
|
this.layoutTabs();
|
||||||
this.setVisibility();
|
this.setVisibility();
|
||||||
|
this.setupScrollEvents();
|
||||||
|
|
||||||
this.$widget.on("contextmenu", ".note-tab", (e) => {
|
this.$widget.on("contextmenu", ".note-tab", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -300,6 +377,60 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
this.$widget.append(this.$style);
|
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() {
|
setupEvents() {
|
||||||
new ResizeObserver((_) => {
|
new ResizeObserver((_) => {
|
||||||
this.cleanUpPreviouslyDraggedTabs();
|
this.cleanUpPreviouslyDraggedTabs();
|
||||||
@ -317,14 +448,32 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
return Array.prototype.slice.call(this.$widget.find(".note-tab"));
|
return Array.prototype.slice.call(this.$widget.find(".note-tab"));
|
||||||
}
|
}
|
||||||
|
|
||||||
get $tabContainer() {
|
updateOuterWidth() {
|
||||||
return this.$widget.find(".tab-row-widget-container");
|
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() {
|
get tabWidths() {
|
||||||
const numberOfTabs = this.tabEls.length;
|
const numberOfTabs = this.tabEls.length;
|
||||||
const tabsContainerWidth = this.$tabContainer[0].clientWidth - NEW_TAB_WIDTH - MIN_FILLER_WIDTH;
|
// this.$newTab may include margin, and using NEW_TAB_WIDTH could cause tabsContainerWidth to be slightly larger,
|
||||||
const marginWidth = (numberOfTabs - 1) * MARGIN_WIDTH;
|
// 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 targetWidth = (tabsContainerWidth - marginWidth) / numberOfTabs;
|
||||||
const clampedTargetWidth = Math.max(TAB_CONTAINER_MIN_WIDTH, Math.min(TAB_CONTAINER_MAX_WIDTH, targetWidth));
|
const clampedTargetWidth = Math.max(TAB_CONTAINER_MIN_WIDTH, Math.min(TAB_CONTAINER_MAX_WIDTH, targetWidth));
|
||||||
const flooredClampedTargetWidth = Math.floor(clampedTargetWidth);
|
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;
|
return widths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,10 +507,9 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
|
|
||||||
position -= MARGIN_WIDTH; // the last margin should not be applied
|
position -= MARGIN_WIDTH; // the last margin should not be applied
|
||||||
|
|
||||||
const newTabPosition = position;
|
const anchorPosition = position;
|
||||||
const fillerPosition = position + 32;
|
|
||||||
|
|
||||||
return { tabPositions, newTabPosition, fillerPosition };
|
return { tabPositions, anchorPosition };
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutTabs() {
|
layoutTabs() {
|
||||||
@ -386,15 +530,14 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
|
|
||||||
let styleHTML = "";
|
let styleHTML = "";
|
||||||
|
|
||||||
const { tabPositions, newTabPosition, fillerPosition } = this.getTabPositions();
|
const { tabPositions, anchorPosition } = this.getTabPositions();
|
||||||
|
|
||||||
tabPositions.forEach((position, i) => {
|
tabPositions.forEach((position, i) => {
|
||||||
styleHTML += `.note-tab:nth-child(${i + 1}) { transform: translate3d(${position}px, 0, 0)} `;
|
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-container-anchor { transform: translate3d(${anchorPosition}px, 0, 0) } `;
|
||||||
styleHTML += `.tab-row-filler { transform: translate3d(${fillerPosition}px, 0, 0) } `;
|
styleHTML += `.tab-row-widget-container {width: ${anchorPosition}px}`;
|
||||||
|
|
||||||
this.$style.html(styleHTML);
|
this.$style.html(styleHTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,8 +549,7 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
$tab.addClass("note-tab-was-just-added");
|
$tab.addClass("note-tab-was-just-added");
|
||||||
|
|
||||||
setTimeout(() => $tab.removeClass("note-tab-was-just-added"), 500);
|
setTimeout(() => $tab.removeClass("note-tab-was-just-added"), 500);
|
||||||
|
this.$containerAnchor.before($tab);
|
||||||
this.$newTab.before($tab);
|
|
||||||
this.setVisibility();
|
this.setVisibility();
|
||||||
this.setTabCloseEvent($tab);
|
this.setTabCloseEvent($tab);
|
||||||
this.updateTitle($tab, t("tab_row.new_tab"));
|
this.updateTitle($tab, t("tab_row.new_tab"));
|
||||||
@ -507,6 +649,7 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
setupDraggabilly() {
|
setupDraggabilly() {
|
||||||
const tabEls = this.tabEls;
|
const tabEls = this.tabEls;
|
||||||
const { tabPositions } = this.getTabPositions();
|
const { tabPositions } = this.getTabPositions();
|
||||||
|
let initialScrollLeft = 0;
|
||||||
|
|
||||||
if (this.isDragging && this.draggabillyDragging) {
|
if (this.isDragging && this.draggabillyDragging) {
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
@ -533,7 +676,7 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
|
|
||||||
this.draggabillies.push(draggabilly);
|
this.draggabillies.push(draggabilly);
|
||||||
|
|
||||||
draggabilly.on("pointerDown", () => {
|
draggabilly.on("staticClick", () => {
|
||||||
appContext.tabManager.activateNoteContext(tabEl.getAttribute("data-ntx-id"));
|
appContext.tabManager.activateNoteContext(tabEl.getAttribute("data-ntx-id"));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -542,11 +685,20 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
this.draggabillyDragging = draggabilly;
|
this.draggabillyDragging = draggabilly;
|
||||||
tabEl.classList.add("note-tab-is-dragging");
|
tabEl.classList.add("note-tab-is-dragging");
|
||||||
this.$widget.addClass("tab-row-widget-is-sorting");
|
this.$widget.addClass("tab-row-widget-is-sorting");
|
||||||
|
|
||||||
|
initialScrollLeft = this.$tabScrollingContainer?.scrollLeft() ?? 0;
|
||||||
|
draggabilly.positionDrag = () => { };
|
||||||
});
|
});
|
||||||
|
|
||||||
draggabilly.on("dragEnd", () => {
|
draggabilly.on("dragEnd", () => {
|
||||||
this.isDragging = false;
|
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)`;
|
tabEl.style.transform = `translate3d(0, 0, 0)`;
|
||||||
|
|
||||||
// Animate dragged tab back into its place
|
// 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
|
// The current index be computed within the event since it can change during the dragMove
|
||||||
const tabEls = this.tabEls;
|
const tabEls = this.tabEls;
|
||||||
const currentIndex = tabEls.indexOf(tabEl);
|
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 destinationIndexTarget = this.closest(currentTabPositionX, tabPositions);
|
||||||
const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget));
|
const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget));
|
||||||
|
|
||||||
@ -594,8 +765,7 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
if (destinationIndex < originIndex) {
|
if (destinationIndex < originIndex) {
|
||||||
tabEl.parentNode?.insertBefore(tabEl, this.tabEls[destinationIndex]);
|
tabEl.parentNode?.insertBefore(tabEl, this.tabEls[destinationIndex]);
|
||||||
} else {
|
} else {
|
||||||
const beforeEl = this.tabEls[destinationIndex + 1] || this.$newTab[0];
|
const beforeEl = this.tabEls[destinationIndex + 1] || this.$containerAnchor[0];
|
||||||
|
|
||||||
tabEl.parentNode?.insertBefore(tabEl, beforeEl);
|
tabEl.parentNode?.insertBefore(tabEl, beforeEl);
|
||||||
}
|
}
|
||||||
this.triggerEvent("tabReorder", { ntxIdsInOrder: this.getNtxIdsInOrder() });
|
this.triggerEvent("tabReorder", { ntxIdsInOrder: this.getNtxIdsInOrder() });
|
||||||
@ -604,14 +774,19 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
|
|
||||||
setupNewButton() {
|
setupNewButton() {
|
||||||
this.$newTab = $(NEW_TAB_BUTTON_TPL);
|
this.$newTab = $(NEW_TAB_BUTTON_TPL);
|
||||||
|
this.$widget.append(this.$newTab);
|
||||||
this.$tabContainer.append(this.$newTab);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupFiller() {
|
setupFiller() {
|
||||||
this.$filler = $(FILLER_TPL);
|
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[]) {
|
closest(value: number, array: number[]) {
|
||||||
@ -660,7 +835,9 @@ export default class TabRowWidget extends BasicWidget {
|
|||||||
|
|
||||||
updateTabById(ntxId: string | null) {
|
updateTabById(ntxId: string | null) {
|
||||||
const $tab = this.getTabById(ntxId);
|
const $tab = this.getTabById(ntxId);
|
||||||
|
$tab[0].scrollIntoView({
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
|
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
|
||||||
|
|
||||||
this.updateTab($tab, noteContext);
|
this.updateTab($tab, noteContext);
|
||||||
|
@ -74,7 +74,7 @@ export function buildConfig() {
|
|||||||
heading: {
|
heading: {
|
||||||
options: [
|
options: [
|
||||||
{ model: "paragraph" as const, title: "Paragraph", class: "ck-heading_paragraph" },
|
{ 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: "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: "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" },
|
{ model: "heading4" as const, view: "h4", title: "Heading 4", class: "ck-heading_heading4" },
|
||||||
|
@ -869,46 +869,23 @@ body.mobile .fancytree-node > span {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #region Apply a border to the tab bar that avoids the current tab but also allows a transparent active tab. */
|
/* 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-container {
|
||||||
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;
|
|
||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
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 {
|
body.layout-vertical.electron.platform-darwin .tab-row-container {
|
||||||
border-bottom: 1px solid var(--subtle-border-color);
|
border-bottom: 1px solid var(--subtle-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-row-widget-container {
|
.tab-row-widget-container {
|
||||||
margin-top: calc((var(--tab-bar-height) - var(--tab-height)) / 2);
|
|
||||||
height: var(--tab-height) !important;
|
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 {
|
body.layout-horizontal .tab-row-container {
|
||||||
padding-top: calc((var(--tab-bar-height) - var(--tab-height)));
|
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
|
/* Limit the drag area for the previous elements to include just to the element itself
|
||||||
and not its descendants also */
|
and not its descendants also */
|
||||||
body.layout-horizontal .tab-row-container > *,
|
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 > * {
|
body.layout-vertical #left-pane .quick-search > * {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.layout-horizontal .tab-row-widget,
|
||||||
body.layout-horizontal .tab-row-widget-container {
|
body.layout-horizontal .tab-row-widget-container {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -991,7 +969,7 @@ body.layout-horizontal .tab-row-widget .note-tab .note-tab-wrapper {
|
|||||||
text-overflow: ellipsis;
|
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);
|
transform: scale(0.85);
|
||||||
box-shadow: var(--active-tab-dragging-shadow) !important;
|
box-shadow: var(--active-tab-dragging-shadow) !important;
|
||||||
}
|
}
|
||||||
|
@ -92,11 +92,10 @@ function login(req: Request, res: Response) {
|
|||||||
const rememberMe = req.body.rememberMe;
|
const rememberMe = req.body.rememberMe;
|
||||||
|
|
||||||
req.session.regenerate(() => {
|
req.session.regenerate(() => {
|
||||||
if (rememberMe) {
|
if (!rememberMe) {
|
||||||
req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks
|
|
||||||
} else {
|
|
||||||
// unset default maxAge set by sessionParser
|
// 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;
|
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.`);
|
log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('login', {
|
res.status(401).render('login', {
|
||||||
wrongPassword: errorType === 'password',
|
wrongPassword: errorType === 'password',
|
||||||
wrongTotp: errorType === 'totp',
|
wrongTotp: errorType === 'totp',
|
||||||
totpEnabled: totp.isTotpEnabled(),
|
totpEnabled: totp.isTotpEnabled(),
|
||||||
|
@ -48,6 +48,11 @@ function parseAuthToken(auth: string | undefined) {
|
|||||||
auth = basicAuthChunks[1];
|
auth = basicAuthChunks[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auth.startsWith("Bearer ")) {
|
||||||
|
// allow also bearer auth format
|
||||||
|
auth = auth.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
const chunks = auth.split("_");
|
const chunks = auth.split("_");
|
||||||
|
|
||||||
if (chunks.length === 1) {
|
if (chunks.length === 1) {
|
||||||
|
@ -112,12 +112,21 @@ function upsert<T extends {}>(tableName: string, primaryKey: string, rec: T) {
|
|||||||
execute(query, rec);
|
execute(query, rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stmt(sql: string) {
|
/**
|
||||||
if (!(sql in statementCache)) {
|
* For the given SQL query, returns a prepared statement. For the same query (string comparison), the same statement is returned.
|
||||||
statementCache[sql] = dbConnection.prepare(sql);
|
*
|
||||||
|
* @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<T>(query: string, params: Params = []): T {
|
function getRow<T>(query: string, params: Params = []): T {
|
||||||
@ -172,7 +181,7 @@ function getRows<T>(query: string, params: Params = []): T[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRawRows<T extends {} | unknown[]>(query: string, params: Params = []): T[] {
|
function getRawRows<T extends {} | unknown[]>(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<T>(query: string, params: Params = []): IterableIterator<T> {
|
function iterateRows<T>(query: string, params: Params = []): IterableIterator<T> {
|
||||||
@ -234,7 +243,10 @@ function executeScript(query: string): DatabaseType {
|
|||||||
return dbConnection.exec(query);
|
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();
|
const startTimestamp = Date.now();
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
@ -243,7 +255,7 @@ function wrap(query: string, func: (statement: Statement) => unknown): unknown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = func(stmt(query));
|
result = func(stmt(query, isRaw));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.message.includes("The database connection is not open")) {
|
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
|
// this often happens on killing the app which puts these alerts in front of user
|
||||||
|
Loading…
x
Reference in New Issue
Block a user