Compare commits
332 Commits
fix/resolv
...
v0.97.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3014434cf | ||
|
|
3ebab2c126 | ||
|
|
954619bd36 | ||
|
|
6995fbfd06 | ||
|
|
83b72eafa6 | ||
|
|
757a6777be | ||
|
|
d003e91b89 | ||
|
|
4a35df745a | ||
|
|
713a0f5b09 | ||
|
|
2cf9c98b43 | ||
|
|
d7af196a0c | ||
|
|
c363be57b7 | ||
|
|
10645790de | ||
|
|
8b18cf382c | ||
|
|
7a131e0bcc | ||
|
|
3d264379cc | ||
|
|
f405682ec1 | ||
|
|
3debf3ce1c | ||
|
|
5a76883969 | ||
|
|
6f51c5e0cc | ||
|
|
2c730d1f0b | ||
|
|
d487da0b2f | ||
|
|
cb8a5cbb62 | ||
|
|
ceb08593d8 | ||
|
|
9dd0eb7b9b | ||
|
|
ebff644d24 | ||
|
|
beb1c15fa5 | ||
|
|
40a5eee211 | ||
|
|
8f393d0bae | ||
|
|
94dad49e2f | ||
|
|
409638151c | ||
|
|
0d3de92890 | ||
|
|
5d619131ec | ||
|
|
e2c8443778 | ||
|
|
daa4743967 | ||
|
|
56553078ef | ||
|
|
5584a06cb3 | ||
|
|
cfeb69ace6 | ||
|
|
b0c8f110de | ||
|
|
aba1266c45 | ||
|
|
c331e0103d | ||
|
|
13978574e0 | ||
|
|
be85963558 | ||
|
|
8c19261ced | ||
|
|
7ca17fa609 | ||
|
|
3d107572df | ||
|
|
f7488655a7 | ||
|
|
876e0a29d4 | ||
|
|
af74375695 | ||
|
|
896965fec5 | ||
|
|
ba5ef93c1a | ||
|
|
ef1153d336 | ||
|
|
0d347f8823 | ||
|
|
897cdc26ae | ||
|
|
aba621c099 | ||
|
|
839813ebde | ||
|
|
545e2ddbfc | ||
|
|
1d63a5903a | ||
|
|
2b34c00a0c | ||
|
|
123068062a | ||
|
|
9a668e8709 | ||
|
|
f6f8937d64 | ||
|
|
c9f53a2880 | ||
|
|
2887e712c3 | ||
|
|
5d3a0ed1b4 | ||
|
|
334b6319de | ||
|
|
4c118c0fd4 | ||
|
|
db00d60684 | ||
|
|
25b74af363 | ||
|
|
eb57cf97ad | ||
|
|
c92e24363f | ||
|
|
8d5d00ac0f | ||
|
|
8b457384ba | ||
|
|
fab2d53ece | ||
|
|
774f27d8d2 | ||
|
|
d7f02ef1b3 | ||
|
|
97eaa6294c | ||
|
|
dc02bb0850 | ||
|
|
2c8c041e1c | ||
|
|
874b1c6654 | ||
|
|
fb982c7097 | ||
|
|
b7f5ce600e | ||
|
|
91604c9e26 | ||
|
|
c874333a37 | ||
|
|
1298b968f2 | ||
|
|
6fe5a854a7 | ||
|
|
aba3b5cb19 | ||
|
|
282aed22b5 | ||
|
|
669a3d9dcf | ||
|
|
9d7455d28a | ||
|
|
4f0c8b081c | ||
|
|
a5db5298a0 | ||
|
|
876c6e9252 | ||
|
|
aef824d262 | ||
|
|
a25ce42490 | ||
|
|
8b0fdaccf4 | ||
|
|
bd840a2421 | ||
|
|
27d515f289 | ||
|
|
df3b9faf8d | ||
|
|
0f129734ae | ||
|
|
275aacfba9 | ||
|
|
e7f47a0663 | ||
|
|
66486541fe | ||
|
|
34f1a84769 | ||
|
|
2244f0368f | ||
|
|
9d85005255 | ||
|
|
ad8629dca6 | ||
|
|
cccfe0e05a | ||
|
|
a8874257e8 | ||
|
|
f689c55f56 | ||
|
|
853c7be8b8 | ||
|
|
823df1e12d | ||
|
|
7570f818e9 | ||
|
|
03aa5aea2c | ||
|
|
a4e86ac353 | ||
|
|
cf6efc050a | ||
|
|
3e0802176b | ||
|
|
697954d4d9 | ||
|
|
741f6c1114 | ||
|
|
b2237ffa51 | ||
|
|
7b6d11bffa | ||
|
|
97565e8f36 | ||
|
|
c0dfee8439 | ||
|
|
fc98240614 | ||
|
|
169d1203c2 | ||
|
|
f3350bc8f5 | ||
|
|
504a19275c | ||
|
|
14cdc52670 | ||
|
|
cf8063f311 | ||
|
|
aa8902f5b9 | ||
|
|
7cd0e664ac | ||
|
|
a04804d3fa | ||
|
|
86f90e6685 | ||
|
|
8131a4b3d2 | ||
|
|
b91a3e13b0 | ||
|
|
5a7a0d32d1 | ||
|
|
3f5df18d6c | ||
|
|
df2cede075 | ||
|
|
4321c161ac | ||
|
|
b1f0c64ef2 | ||
|
|
c9b37dcc77 | ||
|
|
ab093ed9a0 | ||
|
|
cf31367acd | ||
|
|
e3d306cac3 | ||
|
|
960d321019 | ||
|
|
2d4ac93221 | ||
|
|
d4a4f15416 | ||
|
|
504a842d37 | ||
|
|
ded5b1f5d2 | ||
|
|
fcbbc21a80 | ||
|
|
38fce25b86 | ||
|
|
4cc2fa5300 | ||
|
|
4a82c3f65a | ||
|
|
b255d70e18 | ||
|
|
caa842cd55 | ||
|
|
cd338085fb | ||
|
|
e703ce92a8 | ||
|
|
84479a2c2a | ||
|
|
c13969217c | ||
|
|
402540f483 | ||
|
|
8c56315313 | ||
|
|
b29c3eff6e | ||
|
|
ec7dacfc9b | ||
|
|
5f9a6a9f76 | ||
|
|
28f4aea3d5 | ||
|
|
8d29c5fe1b | ||
|
|
ccd935b562 | ||
|
|
d77a49857b | ||
|
|
e30478e5d4 | ||
|
|
71863752cd | ||
|
|
e4a2a8e56d | ||
|
|
0f1c505823 | ||
|
|
1ecce11113 | ||
|
|
2287d67fb5 | ||
|
|
5b4f17ef3d | ||
|
|
3720ab6df6 | ||
|
|
3c893d69e5 | ||
|
|
b93a4a3e42 | ||
|
|
23cef0ab94 | ||
|
|
c8ffb8d694 | ||
|
|
08e08d8920 | ||
|
|
7acd300163 | ||
|
|
d8d95db4ec | ||
|
|
af97d3ef1d | ||
|
|
c65ec14943 | ||
|
|
adfdc7edb4 | ||
|
|
8cced607eb | ||
|
|
5dd5af90c2 | ||
|
|
7a48333b4f | ||
|
|
7044533398 | ||
|
|
560aad8df6 | ||
|
|
36c2099b2e | ||
|
|
6c157675d7 | ||
|
|
458d66cb21 | ||
|
|
201e8911c5 | ||
|
|
1b1ed2408f | ||
|
|
62487d21d8 | ||
|
|
bc752bdb0b | ||
|
|
9e00d421fb | ||
|
|
e7f02fe22b | ||
|
|
6d694f8e53 | ||
|
|
977befd0a7 | ||
|
|
1566ae4fbd | ||
|
|
4e97490cc6 | ||
|
|
446d5a0fcc | ||
|
|
1fd6465012 | ||
|
|
6cea8e3b87 | ||
|
|
28a63e0326 | ||
|
|
b73da46111 | ||
|
|
abafa8c2d2 | ||
|
|
4ae3272cdf | ||
|
|
6aa3b8dbd7 | ||
|
|
395e9b2228 | ||
|
|
be33f68c52 | ||
|
|
29d96381fa | ||
|
|
da8eecf774 | ||
|
|
de91326c12 | ||
|
|
ee1c3c35d7 | ||
|
|
70eece1429 | ||
|
|
b4f2be332b | ||
|
|
23fe76989b | ||
|
|
275d07659d | ||
|
|
a901e92573 | ||
|
|
6ead31b45f | ||
|
|
d4ce12dca9 | ||
|
|
bb6e22cdb7 | ||
|
|
2c9fc4812e | ||
|
|
60f4554afa | ||
|
|
3c486bfd1b | ||
|
|
26b9a95bb2 | ||
|
|
f7c9217cea | ||
|
|
e92022b73c | ||
|
|
61ff2353c8 | ||
|
|
c8cca26ca4 | ||
|
|
aa556ed4d5 | ||
|
|
5d694a7bdf | ||
|
|
c4787dae23 | ||
|
|
9f5f329c53 | ||
|
|
f82b96fcc4 | ||
|
|
d4b24fa427 | ||
|
|
c852f67c59 | ||
|
|
92c228a3c9 | ||
|
|
42f948e2b3 | ||
|
|
13e8932117 | ||
|
|
910d34bd42 | ||
|
|
b204ba29e7 | ||
|
|
d49244cbc8 | ||
|
|
ef2f2f17b4 | ||
|
|
b9f21dcf4c | ||
|
|
808fe690cc | ||
|
|
901eec04e5 | ||
|
|
9272394ada | ||
|
|
4457982fae | ||
|
|
7f67b2b461 | ||
|
|
7f3934f4c3 | ||
|
|
a3b80a2cc4 | ||
|
|
6d967e5e51 | ||
|
|
b674ca90d1 | ||
|
|
95edb60a84 | ||
|
|
40add78ccb | ||
|
|
1029c24c06 | ||
|
|
94d94fe8fb | ||
|
|
49489c0f45 | ||
|
|
215833a2c9 | ||
|
|
a7471a3d47 | ||
|
|
909aaefbd7 | ||
|
|
15c2f56bf2 | ||
|
|
84cdfec415 | ||
|
|
91572ab8b9 | ||
|
|
ed758f4c92 | ||
|
|
f1fc15e115 | ||
|
|
22300e8151 | ||
|
|
292646e14a | ||
|
|
b4921a20d8 | ||
|
|
54be79a725 | ||
|
|
4fc47370fe | ||
|
|
9e30bcf233 | ||
|
|
e5712c54e6 | ||
|
|
2a4fe21a39 | ||
|
|
b259558f0f | ||
|
|
e2f6d9e0d6 | ||
|
|
4fc2b0fa5e | ||
|
|
8dca79ecf2 | ||
|
|
c7f49f0e21 | ||
|
|
bce2094fb2 | ||
|
|
65c33e1aa0 | ||
|
|
8e108bc5e2 | ||
|
|
4e75ce7fdb | ||
|
|
1e42574d28 | ||
|
|
85ebaf6afa | ||
|
|
661c7e4056 | ||
|
|
1e8ea54dbc | ||
|
|
ddbe7e9936 | ||
|
|
cab86175ef | ||
|
|
ec7414b174 | ||
|
|
8343a5d1dd | ||
|
|
18c55784c7 | ||
|
|
39eac83d38 | ||
|
|
55bd6fb57d | ||
|
|
6fdec52332 | ||
|
|
824a3c5fcc | ||
|
|
87da644027 | ||
|
|
4f42f543d8 | ||
|
|
97ea3ac3fc | ||
|
|
f04b75fd36 | ||
|
|
f5bffc38f1 | ||
|
|
27738acefc | ||
|
|
59ce2072c5 | ||
|
|
ed68dda70b | ||
|
|
892ab02f06 | ||
|
|
7d9196d5e1 | ||
|
|
dccdb5ceb7 | ||
|
|
f961698e44 | ||
|
|
278fe3262e | ||
|
|
1fc860b052 | ||
|
|
88a8311173 | ||
|
|
63dc5697dd | ||
|
|
b595d1fade | ||
|
|
d91c59b7d0 | ||
|
|
aa2ab0da31 | ||
|
|
91f94106fb | ||
|
|
308f319138 | ||
|
|
fa0c01591a | ||
|
|
cb5a771490 | ||
|
|
0c17a13462 | ||
|
|
04593cb2d7 | ||
|
|
aa872f47f2 | ||
|
|
5adca76a9a | ||
|
|
e7467f6446 | ||
|
|
e49473fbd3 | ||
|
|
bfec44aa5a | ||
|
|
55b3bf6036 |
2
.github/FUNDING.yml
vendored
@@ -2,3 +2,5 @@
|
|||||||
|
|
||||||
github: [eliandoran]
|
github: [eliandoran]
|
||||||
custom: ["https://paypal.me/eliandoran"]
|
custom: ["https://paypal.me/eliandoran"]
|
||||||
|
liberapay: ElianDoran
|
||||||
|
buy_me_a_coffee: eliandoran
|
||||||
|
|||||||
17
.github/workflows/checks.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Checks
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request_target:
|
||||||
|
types: [synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Check if PRs have conflicts
|
||||||
|
uses: eps1lon/actions-label-merge-conflict@v3
|
||||||
|
with:
|
||||||
|
dirtyLabel: "merge-conflicts"
|
||||||
|
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"
|
||||||
4
.mailmap
@@ -1,2 +1,2 @@
|
|||||||
Adam Zivner <adam.zivner@gmail.com>
|
zadam <adam.zivner@gmail.com>
|
||||||
Adam Zivner <zadam.apps@gmail.com>
|
zadam <zadam.apps@gmail.com>
|
||||||
9
.vscode/settings.json
vendored
@@ -28,5 +28,12 @@
|
|||||||
"typescript.validate.enable": true,
|
"typescript.validate.enable": true,
|
||||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"docs/**/*.html": true,
|
||||||
|
"docs/**/*.png": true,
|
||||||
|
"apps/server/src/assets/doc_notes/**": true,
|
||||||
|
"apps/edit-docs/demo/**": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
15
README.md
@@ -1,6 +1,7 @@
|
|||||||
# Trilium Notes
|
# Trilium Notes
|
||||||
|
|
||||||

|
Donate:  
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
|
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
|
||||||
@@ -119,8 +120,8 @@ To install TriliumNext on your own server (including via Docker from [Dockerhub]
|
|||||||
|
|
||||||
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Notes.git
|
git clone https://github.com/TriliumNext/Trilium.git
|
||||||
cd Notes
|
cd Trilium
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm run server:start
|
pnpm run server:start
|
||||||
```
|
```
|
||||||
@@ -129,8 +130,8 @@ pnpm run server:start
|
|||||||
|
|
||||||
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Notes.git
|
git clone https://github.com/TriliumNext/Trilium.git
|
||||||
cd Notes
|
cd Trilium
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm nx run edit-docs:edit-docs
|
pnpm nx run edit-docs:edit-docs
|
||||||
```
|
```
|
||||||
@@ -138,8 +139,8 @@ pnpm nx run edit-docs:edit-docs
|
|||||||
### Building the Executable
|
### Building the Executable
|
||||||
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Notes.git
|
git clone https://github.com/TriliumNext/Trilium.git
|
||||||
cd Notes
|
cd Trilium
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -35,13 +35,13 @@
|
|||||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.53.2",
|
"@playwright/test": "1.54.1",
|
||||||
"@stylistic/eslint-plugin": "5.1.0",
|
"@stylistic/eslint-plugin": "5.2.0",
|
||||||
"@types/express": "5.0.3",
|
"@types/express": "5.0.3",
|
||||||
"@types/node": "22.16.2",
|
"@types/node": "22.16.5",
|
||||||
"@types/yargs": "17.0.33",
|
"@types/yargs": "17.0.33",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"eslint": "9.30.1",
|
"eslint": "9.31.0",
|
||||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||||
"esm": "3.2.25",
|
"esm": "3.2.25",
|
||||||
"jsdoc": "4.0.4",
|
"jsdoc": "4.0.4",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/client",
|
"name": "@triliumnext/client",
|
||||||
"version": "0.96.0",
|
"version": "0.97.0",
|
||||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"url": "https://github.com/TriliumNext/Notes"
|
"url": "https://github.com/TriliumNext/Notes"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/js": "9.30.1",
|
"@eslint/js": "9.31.0",
|
||||||
"@excalidraw/excalidraw": "0.18.0",
|
"@excalidraw/excalidraw": "0.18.0",
|
||||||
"@fullcalendar/core": "6.1.18",
|
"@fullcalendar/core": "6.1.18",
|
||||||
"@fullcalendar/daygrid": "6.1.18",
|
"@fullcalendar/daygrid": "6.1.18",
|
||||||
@@ -46,9 +46,9 @@
|
|||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-gpx": "2.2.0",
|
"leaflet-gpx": "2.2.0",
|
||||||
"mark.js": "8.11.1",
|
"mark.js": "8.11.1",
|
||||||
"marked": "16.0.0",
|
"marked": "16.1.1",
|
||||||
"mermaid": "11.8.1",
|
"mermaid": "11.9.0",
|
||||||
"mind-elixir": "5.0.1",
|
"mind-elixir": "5.0.2",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
"preact": "10.26.9",
|
"preact": "10.26.9",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"vanilla-js-wheel-zoom": "9.0.4"
|
"vanilla-js-wheel-zoom": "9.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ckeditor/ckeditor5-inspector": "4.1.0",
|
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||||
"@types/bootstrap": "5.2.10",
|
"@types/bootstrap": "5.2.10",
|
||||||
"@types/jquery": "3.5.32",
|
"@types/jquery": "3.5.32",
|
||||||
"@types/leaflet": "1.9.20",
|
"@types/leaflet": "1.9.20",
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"copy-webpack-plugin": "13.0.0",
|
"copy-webpack-plugin": "13.0.0",
|
||||||
"happy-dom": "18.0.1",
|
"happy-dom": "18.0.1",
|
||||||
"script-loader": "0.7.2",
|
"script-loader": "0.7.2",
|
||||||
"vite-plugin-static-copy": "3.1.0"
|
"vite-plugin-static-copy": "3.1.1"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import TouchBarComponent from "./touch_bar.js";
|
|||||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||||
import type CodeMirror from "@triliumnext/codemirror";
|
import type CodeMirror from "@triliumnext/codemirror";
|
||||||
import { StartupChecks } from "./startup_checks.js";
|
import { StartupChecks } from "./startup_checks.js";
|
||||||
|
import type { CreateNoteOpts } from "../services/note_create.js";
|
||||||
|
import { ColumnComponent } from "tabulator-tables";
|
||||||
|
|
||||||
interface Layout {
|
interface Layout {
|
||||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||||
@@ -122,6 +124,7 @@ export type CommandMappings = {
|
|||||||
showImportDialog: CommandData & { noteId: string };
|
showImportDialog: CommandData & { noteId: string };
|
||||||
openNewNoteSplit: NoteCommandData;
|
openNewNoteSplit: NoteCommandData;
|
||||||
openInWindow: NoteCommandData;
|
openInWindow: NoteCommandData;
|
||||||
|
openInPopup: CommandData & { noteIdOrPath: string; };
|
||||||
openNoteInNewTab: CommandData;
|
openNoteInNewTab: CommandData;
|
||||||
openNoteInNewSplit: CommandData;
|
openNoteInNewSplit: CommandData;
|
||||||
openNoteInNewWindow: CommandData;
|
openNoteInNewWindow: CommandData;
|
||||||
@@ -140,6 +143,7 @@ export type CommandMappings = {
|
|||||||
};
|
};
|
||||||
openInTab: ContextMenuCommandData;
|
openInTab: ContextMenuCommandData;
|
||||||
openNoteInSplit: ContextMenuCommandData;
|
openNoteInSplit: ContextMenuCommandData;
|
||||||
|
openNoteInPopup: ContextMenuCommandData;
|
||||||
toggleNoteHoisting: ContextMenuCommandData;
|
toggleNoteHoisting: ContextMenuCommandData;
|
||||||
insertNoteAfter: ContextMenuCommandData;
|
insertNoteAfter: ContextMenuCommandData;
|
||||||
insertChildNote: ContextMenuCommandData;
|
insertChildNote: ContextMenuCommandData;
|
||||||
@@ -274,6 +278,21 @@ export type CommandMappings = {
|
|||||||
|
|
||||||
geoMapCreateChildNote: CommandData;
|
geoMapCreateChildNote: CommandData;
|
||||||
|
|
||||||
|
// Table view
|
||||||
|
addNewRow: CommandData & {
|
||||||
|
customOpts: CreateNoteOpts;
|
||||||
|
parentNotePath?: string;
|
||||||
|
};
|
||||||
|
addNewTableColumn: CommandData & {
|
||||||
|
columnToEdit?: ColumnComponent;
|
||||||
|
referenceColumn?: ColumnComponent;
|
||||||
|
direction?: "before" | "after";
|
||||||
|
type?: "label" | "relation";
|
||||||
|
};
|
||||||
|
deleteTableColumn: CommandData & {
|
||||||
|
columnToDelete?: ColumnComponent;
|
||||||
|
};
|
||||||
|
|
||||||
buildTouchBar: CommandData & {
|
buildTouchBar: CommandData & {
|
||||||
TouchBar: typeof TouchBar;
|
TouchBar: typeof TouchBar;
|
||||||
buildIcon(name: string): NativeImage;
|
buildIcon(name: string): NativeImage;
|
||||||
|
|||||||
@@ -256,6 +256,20 @@ class FNote {
|
|||||||
return this.children;
|
return this.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSubtreeNoteIds() {
|
||||||
|
let noteIds: (string | string[])[] = [];
|
||||||
|
for (const child of await this.getChildNotes()) {
|
||||||
|
noteIds.push(child.noteId);
|
||||||
|
noteIds.push(await child.getSubtreeNoteIds());
|
||||||
|
}
|
||||||
|
return noteIds.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubtreeNotes() {
|
||||||
|
const noteIds = await this.getSubtreeNoteIds();
|
||||||
|
return this.froca.getNotes(noteIds);
|
||||||
|
}
|
||||||
|
|
||||||
async getChildNotes() {
|
async getChildNotes() {
|
||||||
return await this.froca.getNotes(this.children);
|
return await this.froca.getNotes(this.children);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,28 +46,7 @@ import SharedInfoWidget from "../widgets/shared_info.js";
|
|||||||
import FindWidget from "../widgets/find.js";
|
import FindWidget from "../widgets/find.js";
|
||||||
import TocWidget from "../widgets/toc.js";
|
import TocWidget from "../widgets/toc.js";
|
||||||
import HighlightsListWidget from "../widgets/highlights_list.js";
|
import HighlightsListWidget from "../widgets/highlights_list.js";
|
||||||
import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js";
|
|
||||||
import AboutDialog from "../widgets/dialogs/about.js";
|
|
||||||
import HelpDialog from "../widgets/dialogs/help.js";
|
|
||||||
import RecentChangesDialog from "../widgets/dialogs/recent_changes.js";
|
|
||||||
import BranchPrefixDialog from "../widgets/dialogs/branch_prefix.js";
|
|
||||||
import SortChildNotesDialog from "../widgets/dialogs/sort_child_notes.js";
|
|
||||||
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
|
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
|
||||||
import IncludeNoteDialog from "../widgets/dialogs/include_note.js";
|
|
||||||
import NoteTypeChooserDialog from "../widgets/dialogs/note_type_chooser.js";
|
|
||||||
import JumpToNoteDialog from "../widgets/dialogs/jump_to_note.js";
|
|
||||||
import AddLinkDialog from "../widgets/dialogs/add_link.js";
|
|
||||||
import CloneToDialog from "../widgets/dialogs/clone_to.js";
|
|
||||||
import MoveToDialog from "../widgets/dialogs/move_to.js";
|
|
||||||
import ImportDialog from "../widgets/dialogs/import.js";
|
|
||||||
import ExportDialog from "../widgets/dialogs/export.js";
|
|
||||||
import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js";
|
|
||||||
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
|
|
||||||
import RevisionsDialog from "../widgets/dialogs/revisions.js";
|
|
||||||
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
|
|
||||||
import InfoDialog from "../widgets/dialogs/info.js";
|
|
||||||
import ConfirmDialog from "../widgets/dialogs/confirm.js";
|
|
||||||
import PromptDialog from "../widgets/dialogs/prompt.js";
|
|
||||||
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||||
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||||
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
|
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
|
||||||
@@ -83,7 +62,7 @@ import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_ref
|
|||||||
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
|
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
|
||||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
import utils, { hasTouchBar } from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
|
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
|
||||||
import ContextualHelpButton from "../widgets/floating_buttons/help_button.js";
|
import ContextualHelpButton from "../widgets/floating_buttons/help_button.js";
|
||||||
import CloseZenButton from "../widgets/close_zen_button.js";
|
import CloseZenButton from "../widgets/close_zen_button.js";
|
||||||
@@ -229,7 +208,7 @@ export default class DesktopLayout {
|
|||||||
.child(new PromotedAttributesWidget())
|
.child(new PromotedAttributesWidget())
|
||||||
.child(new SqlTableSchemasWidget())
|
.child(new SqlTableSchemasWidget())
|
||||||
.child(new NoteDetailWidget())
|
.child(new NoteDetailWidget())
|
||||||
.child(new NoteListWidget())
|
.child(new NoteListWidget(false))
|
||||||
.child(new SearchResultWidget())
|
.child(new SearchResultWidget())
|
||||||
.child(new SqlResultWidget())
|
.child(new SqlResultWidget())
|
||||||
.child(new ScrollPaddingWidget())
|
.child(new ScrollPaddingWidget())
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ import RevisionsDialog from "../widgets/dialogs/revisions.js";
|
|||||||
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
|
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
|
||||||
import InfoDialog from "../widgets/dialogs/info.js";
|
import InfoDialog from "../widgets/dialogs/info.js";
|
||||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||||
|
import PopupEditorDialog from "../widgets/dialogs/popup_editor.js";
|
||||||
|
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||||
|
import NoteIconWidget from "../widgets/note_icon.js";
|
||||||
|
import NoteTitleWidget from "../widgets/note_title.js";
|
||||||
|
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
||||||
|
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||||
|
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||||
|
import NoteListWidget from "../widgets/note_list.js";
|
||||||
|
|
||||||
export function applyModals(rootContainer: RootContainer) {
|
export function applyModals(rootContainer: RootContainer) {
|
||||||
rootContainer
|
rootContainer
|
||||||
@@ -47,4 +55,15 @@ export function applyModals(rootContainer: RootContainer) {
|
|||||||
.child(new ConfirmDialog())
|
.child(new ConfirmDialog())
|
||||||
.child(new PromptDialog())
|
.child(new PromptDialog())
|
||||||
.child(new IncorrectCpuArchDialog())
|
.child(new IncorrectCpuArchDialog())
|
||||||
|
.child(new PopupEditorDialog()
|
||||||
|
.child(new FlexContainer("row")
|
||||||
|
.class("title-row")
|
||||||
|
.css("align-items", "center")
|
||||||
|
.cssBlock(".title-row > * { margin: 5px; }")
|
||||||
|
.child(new NoteIconWidget())
|
||||||
|
.child(new NoteTitleWidget()))
|
||||||
|
.child(new ClassicEditorToolbar())
|
||||||
|
.child(new PromotedAttributesWidget())
|
||||||
|
.child(new NoteDetailWidget())
|
||||||
|
.child(new NoteListWidget(true)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export default class MobileLayout {
|
|||||||
.filling()
|
.filling()
|
||||||
.contentSized()
|
.contentSized()
|
||||||
.child(new NoteDetailWidget())
|
.child(new NoteDetailWidget())
|
||||||
.child(new NoteListWidget())
|
.child(new NoteListWidget(false))
|
||||||
.child(new FilePropertiesWidget().css("font-size", "smaller"))
|
.child(new FilePropertiesWidget().css("font-size", "smaller"))
|
||||||
)
|
)
|
||||||
.child(new MobileEditorToolbar())
|
.child(new MobileEditorToolbar())
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ export interface MenuCommandItem<T> {
|
|||||||
title: string;
|
title: string;
|
||||||
command?: T;
|
command?: T;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
/**
|
||||||
|
* The icon to display in the menu item.
|
||||||
|
*
|
||||||
|
* If not set, no icon is displayed and the item will appear shifted slightly to the left if there are other items with icons. To avoid this, use `bx bx-empty`.
|
||||||
|
*/
|
||||||
uiIcon?: string;
|
uiIcon?: string;
|
||||||
badges?: MenuItemBadge[];
|
badges?: MenuItemBadge[];
|
||||||
templateNoteId?: string;
|
templateNoteId?: string;
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ function getItems(): MenuItem<CommandNames>[] {
|
|||||||
return [
|
return [
|
||||||
{ title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" },
|
{ title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" },
|
||||||
{ title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
|
{ title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
|
||||||
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" }
|
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" },
|
||||||
|
{ title: t("link_context_menu.open_note_in_popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit" }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +41,8 @@ function handleLinkContextMenuItem(command: string | undefined, notePath: string
|
|||||||
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
|
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
|
||||||
} else if (command === "openNoteInNewWindow") {
|
} else if (command === "openNoteInNewWindow") {
|
||||||
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
|
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
|
||||||
|
} else if (command === "openNoteInPopup") {
|
||||||
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
|
|
||||||
const items: (MenuItem<TreeCommandNames> | null)[] = [
|
const items: (MenuItem<TreeCommandNames> | null)[] = [
|
||||||
{ title: `${t("tree-context-menu.open-in-a-new-tab")}`, command: "openInTab", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
|
{ title: `${t("tree-context-menu.open-in-a-new-tab")}`, command: "openInTab", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
|
||||||
|
|
||||||
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||||
|
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
|
||||||
|
|
||||||
isHoisted
|
isHoisted
|
||||||
? null
|
? null
|
||||||
@@ -129,12 +129,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
|
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
|
||||||
},
|
},
|
||||||
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },
|
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },
|
||||||
{
|
|
||||||
title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`,
|
|
||||||
command: "duplicateSubtree",
|
|
||||||
uiIcon: "bx bx-outline",
|
|
||||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
|
||||||
},
|
|
||||||
|
|
||||||
{ title: "----" },
|
{ title: "----" },
|
||||||
|
|
||||||
@@ -188,6 +182,13 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
|
|
||||||
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
|
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: `${t("tree-context-menu.duplicate")} <kbd data-command="duplicateSubtree">`,
|
||||||
|
command: "duplicateSubtree",
|
||||||
|
uiIcon: "bx bx-outline",
|
||||||
|
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
|
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
|
||||||
command: "deleteNotes",
|
command: "deleteNotes",
|
||||||
@@ -246,6 +247,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
|
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
|
||||||
|
|
||||||
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
|
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
|
||||||
|
} else if (command === "openNoteInPopup") {
|
||||||
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
|
||||||
} else if (command === "convertNoteToAttachment") {
|
} else if (command === "convertNoteToAttachment") {
|
||||||
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
|
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ async function addLabel(noteId: string, name: string, value: string = "", isInhe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setLabel(noteId: string, name: string, value: string = "") {
|
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
|
||||||
await server.put(`notes/${noteId}/set-attribute`, {
|
await server.put(`notes/${noteId}/set-attribute`, {
|
||||||
type: "label",
|
type: "label",
|
||||||
name: name,
|
name: name,
|
||||||
value: value
|
value: value,
|
||||||
|
isInheritable
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,15 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false) {
|
/**
|
||||||
|
* Shows the delete confirmation screen
|
||||||
|
*
|
||||||
|
* @param branchIdsToDelete the list of branch IDs to delete.
|
||||||
|
* @param forceDeleteAllClones whether to check by default the "Delete also all clones" checkbox.
|
||||||
|
* @param moveToParent whether to automatically go to the parent note path after a succesful delete. Usually makes sense if deleting the active note(s).
|
||||||
|
* @returns promise that returns false if the operation was cancelled or there was nothing to delete, true if the operation succeeded.
|
||||||
|
*/
|
||||||
|
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
|
||||||
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
||||||
|
|
||||||
if (branchIdsToDelete.length === 0) {
|
if (branchIdsToDelete.length === 0) {
|
||||||
@@ -110,11 +118,13 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (moveToParent) {
|
||||||
try {
|
try {
|
||||||
await activateParentNotePath();
|
await activateParentNotePath();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const taskId = utils.randomString(10);
|
const taskId = utils.randomString(10);
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import AddRelationBulkAction from "../widgets/bulk_actions/relation/add_relation
|
|||||||
import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js";
|
import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
|
import toast from "./toast.js";
|
||||||
|
import { BulkAction } from "@triliumnext/commons";
|
||||||
|
|
||||||
const ACTION_GROUPS = [
|
const ACTION_GROUPS = [
|
||||||
{
|
{
|
||||||
@@ -89,6 +91,17 @@ function parseActions(note: FNote) {
|
|||||||
.filter((action) => !!action);
|
.filter((action) => !!action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function executeBulkActions(parentNoteId: string, actions: BulkAction[]) {
|
||||||
|
await server.post("bulk-action/execute", {
|
||||||
|
noteIds: [ parentNoteId ],
|
||||||
|
includeDescendants: true,
|
||||||
|
actions
|
||||||
|
});
|
||||||
|
|
||||||
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
addAction,
|
addAction,
|
||||||
parseActions,
|
parseActions,
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptio
|
|||||||
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
||||||
import { focusSavedElement, saveFocusedElement } from "./focus.js";
|
import { focusSavedElement, saveFocusedElement } from "./focus.js";
|
||||||
|
|
||||||
export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true) {
|
export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true, config?: Partial<Modal.Options>) {
|
||||||
if (closeActDialog) {
|
if (closeActDialog) {
|
||||||
closeActiveDialog();
|
closeActiveDialog();
|
||||||
glob.activeDialog = $dialog;
|
glob.activeDialog = $dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFocusedElement();
|
saveFocusedElement();
|
||||||
Modal.getOrCreateInstance($dialog[0]).show();
|
Modal.getOrCreateInstance($dialog[0], config).show();
|
||||||
|
|
||||||
$dialog.on("hidden.bs.modal", () => {
|
$dialog.on("hidden.bs.modal", () => {
|
||||||
const $autocompleteEl = $(".aa-input");
|
const $autocompleteEl = $(".aa-input");
|
||||||
@@ -41,8 +41,14 @@ async function info(message: string) {
|
|||||||
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res }));
|
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog with the given message.
|
||||||
|
*
|
||||||
|
* @param message the message to display in the dialog.
|
||||||
|
* @returns A promise that resolves to true if the user confirmed, false otherwise.
|
||||||
|
*/
|
||||||
async function confirm(message: string) {
|
async function confirm(message: string) {
|
||||||
return new Promise((res) =>
|
return new Promise<boolean>((res) =>
|
||||||
appContext.triggerCommand("showConfirmDialog", <ConfirmWithMessageOptions>{
|
appContext.triggerCommand("showConfirmDialog", <ConfirmWithMessageOptions>{
|
||||||
message,
|
message,
|
||||||
callback: (x: false | ConfirmDialogOptions) => res(x && x.confirmed)
|
callback: (x: false | ConfirmDialogOptions) => res(x && x.confirmed)
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
|
|||||||
let ntxId: string | null = null;
|
let ntxId: string | null = null;
|
||||||
let hoistedNoteId: string | null = null;
|
let hoistedNoteId: string | null = null;
|
||||||
let searchString: string | null = null;
|
let searchString: string | null = null;
|
||||||
|
let openInPopup = false;
|
||||||
|
|
||||||
if (paramString) {
|
if (paramString) {
|
||||||
for (const pair of paramString.split("&")) {
|
for (const pair of paramString.split("&")) {
|
||||||
@@ -246,6 +247,8 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
|
|||||||
searchString = value; // supports triggering search from URL, e.g. #?searchString=blabla
|
searchString = value; // supports triggering search from URL, e.g. #?searchString=blabla
|
||||||
} else if (["viewMode", "attachmentId"].includes(name)) {
|
} else if (["viewMode", "attachmentId"].includes(name)) {
|
||||||
(viewScope as any)[name] = value;
|
(viewScope as any)[name] = value;
|
||||||
|
} else if (name === "popup") {
|
||||||
|
openInPopup = true;
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Unrecognized hash parameter '${name}'.`);
|
console.warn(`Unrecognized hash parameter '${name}'.`);
|
||||||
}
|
}
|
||||||
@@ -266,7 +269,8 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
|
|||||||
ntxId,
|
ntxId,
|
||||||
hoistedNoteId,
|
hoistedNoteId,
|
||||||
viewScope,
|
viewScope,
|
||||||
searchString
|
searchString,
|
||||||
|
openInPopup
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,11 +303,12 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
|
const { notePath, viewScope, openInPopup } = parseNavigationStateFromUrl(hrefLink);
|
||||||
|
|
||||||
const ctrlKey = evt && utils.isCtrlKey(evt);
|
const ctrlKey = evt && utils.isCtrlKey(evt);
|
||||||
const shiftKey = evt?.shiftKey;
|
const shiftKey = evt?.shiftKey;
|
||||||
const isLeftClick = !evt || ("which" in evt && evt.which === 1);
|
const isLeftClick = !evt || ("which" in evt && evt.which === 1);
|
||||||
|
// Right click is handled separately.
|
||||||
const isMiddleClick = evt && "which" in evt && evt.which === 2;
|
const isMiddleClick = evt && "which" in evt && evt.which === 2;
|
||||||
const targetIsBlank = ($link?.attr("target") === "_blank");
|
const targetIsBlank = ($link?.attr("target") === "_blank");
|
||||||
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
|
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
|
||||||
@@ -311,7 +316,9 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
|||||||
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
||||||
|
|
||||||
if (notePath) {
|
if (notePath) {
|
||||||
if (openInNewWindow) {
|
if (isLeftClick && openInPopup) {
|
||||||
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||||
|
} else if (openInNewWindow) {
|
||||||
appContext.triggerCommand("openInWindow", { notePath, viewScope });
|
appContext.triggerCommand("openInWindow", { notePath, viewScope });
|
||||||
} else if (openInNewTab) {
|
} else if (openInNewTab) {
|
||||||
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
|
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
|
||||||
@@ -387,12 +394,18 @@ function linkContextMenu(e: PointerEvent) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (utils.isCtrlKey(e) && e.button === 2) {
|
||||||
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
|
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
|
async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
|
||||||
const $link = $el[0].tagName === "A" ? $el : $el.find("a");
|
const $link = $el[0].tagName === "A" ? $el : $el.find("a");
|
||||||
|
|
||||||
href = href || $link.attr("href");
|
href = href || $link.attr("href");
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ interface Options {
|
|||||||
allowCreatingNotes?: boolean;
|
allowCreatingNotes?: boolean;
|
||||||
allowJumpToSearchNotes?: boolean;
|
allowJumpToSearchNotes?: boolean;
|
||||||
allowExternalLinks?: boolean;
|
allowExternalLinks?: boolean;
|
||||||
|
/** If set, hides the right-side button corresponding to go to selected note. */
|
||||||
hideGoToSelectedNoteButton?: boolean;
|
hideGoToSelectedNoteButton?: boolean;
|
||||||
|
/** If set, hides all right-side buttons in the autocomplete dropdown */
|
||||||
|
hideAllButtons?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autocompleteSourceForCKEditor(queryText: string) {
|
async function autocompleteSourceForCKEditor(queryText: string) {
|
||||||
@@ -190,9 +193,11 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
|||||||
|
|
||||||
const $goToSelectedNoteButton = $("<a>").addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
const $goToSelectedNoteButton = $("<a>").addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
||||||
|
|
||||||
|
if (!options.hideAllButtons) {
|
||||||
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
||||||
|
}
|
||||||
|
|
||||||
if (!options.hideGoToSelectedNoteButton) {
|
if (!options.hideGoToSelectedNoteButton && !options.hideAllButtons) {
|
||||||
$el.after($goToSelectedNoteButton);
|
$el.after($goToSelectedNoteButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type FBranch from "../entities/fbranch.js";
|
|||||||
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
|
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
|
||||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||||
|
|
||||||
interface CreateNoteOpts {
|
export interface CreateNoteOpts {
|
||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
saveSelection?: boolean;
|
saveSelection?: boolean;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
|
|||||||
@@ -6,33 +6,18 @@ import TableView from "../widgets/view_widgets/table_view/index.js";
|
|||||||
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
|
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
|
||||||
import type ViewMode from "../widgets/view_widgets/view_mode.js";
|
import type ViewMode from "../widgets/view_widgets/view_mode.js";
|
||||||
|
|
||||||
|
export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">;
|
||||||
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table" | "geoMap";
|
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table" | "geoMap";
|
||||||
|
|
||||||
export default class NoteListRenderer {
|
export default class NoteListRenderer {
|
||||||
|
|
||||||
private viewType: ViewTypeOptions;
|
private viewType: ViewTypeOptions;
|
||||||
public viewMode: ViewMode<any> | null;
|
private args: ArgsWithoutNoteId;
|
||||||
|
public viewMode?: ViewMode<any>;
|
||||||
|
|
||||||
constructor(args: ViewModeArgs) {
|
constructor(args: ArgsWithoutNoteId) {
|
||||||
|
this.args = args;
|
||||||
this.viewType = this.#getViewType(args.parentNote);
|
this.viewType = this.#getViewType(args.parentNote);
|
||||||
|
|
||||||
switch (this.viewType) {
|
|
||||||
case "list":
|
|
||||||
case "grid":
|
|
||||||
this.viewMode = new ListOrGridView(this.viewType, args);
|
|
||||||
break;
|
|
||||||
case "calendar":
|
|
||||||
this.viewMode = new CalendarView(args);
|
|
||||||
break;
|
|
||||||
case "table":
|
|
||||||
this.viewMode = new TableView(args);
|
|
||||||
break;
|
|
||||||
case "geoMap":
|
|
||||||
this.viewMode = new GeoView(args);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.viewMode = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#getViewType(parentNote: FNote): ViewTypeOptions {
|
#getViewType(parentNote: FNote): ViewTypeOptions {
|
||||||
@@ -47,15 +32,36 @@ export default class NoteListRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isFullHeight() {
|
get isFullHeight() {
|
||||||
return this.viewMode?.isFullHeight;
|
switch (this.viewType) {
|
||||||
|
case "list":
|
||||||
|
case "grid":
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderList() {
|
async renderList() {
|
||||||
if (!this.viewMode) {
|
const args = this.args;
|
||||||
return null;
|
const viewMode = this.#buildViewMode(args);
|
||||||
|
this.viewMode = viewMode;
|
||||||
|
await viewMode.beforeRender();
|
||||||
|
return await viewMode.renderList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.viewMode.renderList();
|
#buildViewMode(args: ViewModeArgs) {
|
||||||
|
switch (this.viewType) {
|
||||||
|
case "calendar":
|
||||||
|
return new CalendarView(args);
|
||||||
|
case "table":
|
||||||
|
return new TableView(args);
|
||||||
|
case "geoMap":
|
||||||
|
return new GeoView(args);
|
||||||
|
case "list":
|
||||||
|
case "grid":
|
||||||
|
default:
|
||||||
|
return new ListOrGridView(this.viewType, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,10 @@ async function renderTooltip(note: FNote | null) {
|
|||||||
if (isContentEmpty) {
|
if (isContentEmpty) {
|
||||||
classes.push("note-no-content");
|
classes.push("note-no-content");
|
||||||
}
|
}
|
||||||
content = `<h5 class="${classes.join(" ")}"><a href="#${note.noteId}" data-no-context-menu="true">${noteTitleWithPathAsSuffix.prop("outerHTML")}</a></h5>`;
|
content = `\
|
||||||
|
<h5 class="${classes.join(" ")}">
|
||||||
|
<a href="#${note.noteId}" data-no-context-menu="true">${noteTitleWithPathAsSuffix.prop("outerHTML")}</a>
|
||||||
|
</h5>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
content = `${content}<div class="note-tooltip-attributes">${$renderedAttributes[0].outerHTML}</div>`;
|
content = `${content}<div class="note-tooltip-attributes">${$renderedAttributes[0].outerHTML}</div>`;
|
||||||
@@ -176,6 +179,7 @@ async function renderTooltip(note: FNote | null) {
|
|||||||
content += $renderedContent[0].outerHTML;
|
content += $renderedContent[0].outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content += `<a class="open-popup-button" title="${t("note_tooltip.quick-edit")}" href="#${note.noteId}?popup"><span class="bx bx-edit" /></a>`;
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ body {
|
|||||||
|
|
||||||
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
|
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
|
||||||
|
|
||||||
--ck-color-image-caption-background: var(--main-background-color);
|
--ck-content-color-image-caption-background: var(--main-background-color);
|
||||||
--ck-color-image-caption-text: var(--main-text-color);
|
--ck-content-color-image-caption-text: var(--main-text-color);
|
||||||
|
|
||||||
/* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */
|
/* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */
|
||||||
|
|
||||||
|
|||||||
@@ -327,7 +327,8 @@ button kbd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu,
|
||||||
|
.tabulator-popup-container {
|
||||||
color: var(--menu-text-color) !important;
|
color: var(--menu-text-color) !important;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
background-color: var(--menu-background-color) !important;
|
background-color: var(--menu-background-color) !important;
|
||||||
@@ -342,7 +343,8 @@ button kbd {
|
|||||||
break-after: avoid;
|
break-after: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .dropdown-menu {
|
body.desktop .dropdown-menu,
|
||||||
|
body.desktop .tabulator-popup-container {
|
||||||
border: 1px solid var(--dropdown-border-color);
|
border: 1px solid var(--dropdown-border-color);
|
||||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
||||||
animation: dropdown-menu-opening 100ms ease-in;
|
animation: dropdown-menu-opening 100ms ease-in;
|
||||||
@@ -385,7 +387,8 @@ body.desktop .dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu a:hover:not(.disabled),
|
.dropdown-menu a:hover:not(.disabled),
|
||||||
.dropdown-item:hover:not(.disabled, .dropdown-item-container) {
|
.dropdown-item:hover:not(.disabled, .dropdown-item-container),
|
||||||
|
.tabulator-menu-item:hover {
|
||||||
color: var(--hover-item-text-color) !important;
|
color: var(--hover-item-text-color) !important;
|
||||||
background-color: var(--hover-item-background-color) !important;
|
background-color: var(--hover-item-background-color) !important;
|
||||||
border-color: var(--hover-item-border-color) !important;
|
border-color: var(--hover-item-border-color) !important;
|
||||||
@@ -540,6 +543,7 @@ button.btn-sm {
|
|||||||
/* Making this narrower because https://github.com/zadam/trilium/issues/502 (problem only in smaller font sizes) */
|
/* Making this narrower because https://github.com/zadam/trilium/issues/502 (problem only in smaller font sizes) */
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre:not(.hljs) {
|
pre:not(.hljs) {
|
||||||
@@ -771,6 +775,14 @@ table.promoted-attributes-in-tooltip th {
|
|||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-tooltip-content .open-popup-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 8px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.note-tooltip-attributes {
|
.note-tooltip-attributes {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
@@ -912,6 +924,13 @@ div[data-notify="container"] {
|
|||||||
font-family: var(--monospace-font-family);
|
font-family: var(--monospace-font-family);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ck-content {
|
||||||
|
--ck-content-font-family: var(--detail-font-family);
|
||||||
|
--ck-content-font-size: 1.1em;
|
||||||
|
--ck-content-font-color: var(--main-text-color);
|
||||||
|
--ck-content-line-height: var(--bs-body-line-height);
|
||||||
|
}
|
||||||
|
|
||||||
.ck-content .table table th {
|
.ck-content .table table th {
|
||||||
background-color: var(--accented-background-color);
|
background-color: var(--accented-background-color);
|
||||||
}
|
}
|
||||||
@@ -1198,12 +1217,14 @@ body.mobile .dropdown-submenu > .dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#context-menu-container,
|
#context-menu-container,
|
||||||
#context-menu-container .dropdown-menu {
|
#context-menu-container .dropdown-menu,
|
||||||
padding: 3px 0 0;
|
.tabulator-popup-container {
|
||||||
|
padding: 3px 0;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#context-menu-container .dropdown-item {
|
#context-menu-container .dropdown-item,
|
||||||
|
.tabulator-menu .tabulator-menu-item {
|
||||||
padding: 0 7px 0 10px;
|
padding: 0 7px 0 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|||||||
199
apps/client/src/stylesheets/table.css
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
.tabulator {
|
||||||
|
--table-background-color: var(--main-background-color);
|
||||||
|
|
||||||
|
--col-header-background-color: var(--main-background-color);
|
||||||
|
--col-header-hover-background-color: var(--accented-background-color);
|
||||||
|
--col-header-text-color: var(--main-text-color);
|
||||||
|
--col-header-arrow-active-color: var(--main-text-color);
|
||||||
|
--col-header-arrow-inactive-color: var(--more-accented-background-color);
|
||||||
|
--col-header-separator-border: none;
|
||||||
|
--col-header-bottom-border: 2px solid var(--main-border-color);
|
||||||
|
|
||||||
|
--row-background-color: var(--main-background-color);
|
||||||
|
--row-alternate-background-color: var(--main-background-color);
|
||||||
|
--row-moving-background-color: var(--accented-background-color);
|
||||||
|
--row-text-color: var(--main-text-color);
|
||||||
|
--row-delimiter-color: var(--more-accented-background-color);
|
||||||
|
|
||||||
|
--cell-horiz-padding-size: 8px;
|
||||||
|
--cell-vert-padding-size: 8px;
|
||||||
|
|
||||||
|
--cell-editable-hover-outline-color: var(--main-border-color);
|
||||||
|
--cell-read-only-text-color: var(--muted-text-color);
|
||||||
|
|
||||||
|
--cell-editing-border-color: var(--main-border-color);
|
||||||
|
--cell-editing-border-width: 2px;
|
||||||
|
--cell-editing-background-color: var(--ck-color-selector-focused-cell-background);
|
||||||
|
--cell-editing-text-color: initial;
|
||||||
|
|
||||||
|
background: unset;
|
||||||
|
border: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tabulator-tableholder .tabulator-table {
|
||||||
|
background: var(--table-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Column headers */
|
||||||
|
|
||||||
|
.tabulator div.tabulator-header {
|
||||||
|
border-bottom: var(--col-header-bottom-border);
|
||||||
|
background: var(--col-header-background-color);
|
||||||
|
color: var(--col-header-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tabulator-col-content {
|
||||||
|
padding: 8px 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover {
|
||||||
|
background-color: var(--col-header-hover-background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator div.tabulator-header .tabulator-col.tabulator-moving {
|
||||||
|
border: none;
|
||||||
|
background: var(--col-header-hover-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {
|
||||||
|
border-bottom-color: var(--col-header-arrow-active-color);
|
||||||
|
border-top-color: var(--col-header-arrow-active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort="none"] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {
|
||||||
|
border-bottom-color: var(--col-header-arrow-inactive-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
|
||||||
|
margin-left: var(--cell-editing-border-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator div.tabulator-header .tabulator-col,
|
||||||
|
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
|
||||||
|
background: var(--col-header-background-color);
|
||||||
|
border-right: var(--col-header-separator-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table body */
|
||||||
|
|
||||||
|
.tabulator-tableholder {
|
||||||
|
padding-top: 10px;
|
||||||
|
height: unset !important; /* Don't extend on the full height */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rows */
|
||||||
|
|
||||||
|
.tabulator-row .tabulator-cell {
|
||||||
|
padding: var(--cell-vert-padding-size) var(--cell-horiz-padding-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row .tabulator-cell input {
|
||||||
|
padding-left: var(--cell-horiz-padding-size) !important;
|
||||||
|
padding-right: var(--cell-horiz-padding-size) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row {
|
||||||
|
background: transparent;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 1px solid var(--row-delimiter-color);
|
||||||
|
color: var(--row-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row.tabulator-row-odd {
|
||||||
|
background: var(--row-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row.tabulator-row-even {
|
||||||
|
background: var(--row-alternate-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row.tabulator-moving {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: var(--row-moving-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cell */
|
||||||
|
|
||||||
|
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
|
||||||
|
margin-right: var(--cell-editing-border-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
|
||||||
|
.tabulator-row .tabulator-cell {
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row .tabulator-cell:not(.tabulator-editable) {
|
||||||
|
color: var(--cell-read-only-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator:not(.tabulator-editing) .tabulator-row .tabulator-cell.tabulator-editable:hover {
|
||||||
|
outline: 2px solid var(--cell-editable-hover-outline-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row .tabulator-cell.tabulator-editing {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row:not(.tabulator-moving) .tabulator-cell.tabulator-editing {
|
||||||
|
outline: calc(var(--cell-editing-border-width) - 1px) solid var(--cell-editing-border-color);
|
||||||
|
border-color: var(--cell-editing-border-color);
|
||||||
|
background: var(--cell-editing-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-row:not(.tabulator-moving) .tabulator-cell.tabulator-editing > * {
|
||||||
|
color: var(--cell-editing-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tree-collapse,
|
||||||
|
.tabulator .tree-expand {
|
||||||
|
color: var(--row-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Align items without children/expander to the ones with. */
|
||||||
|
.tabulator-cell[tabulator-field="title"] > span:first-child, /* 1st level */
|
||||||
|
.tabulator-cell[tabulator-field="title"] > div:first-child + span { /* sub-level */
|
||||||
|
padding-left: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox cells */
|
||||||
|
|
||||||
|
.tabulator .tabulator-cell:has(svg),
|
||||||
|
.tabulator .tabulator-cell:has(input[type="checkbox"]) {
|
||||||
|
padding-left: 8px;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tabulator-cell input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator .tabulator-footer {
|
||||||
|
color: var(--main-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Context menus */
|
||||||
|
|
||||||
|
.tabulator-popup-container {
|
||||||
|
min-width: 10em;
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator-menu .tabulator-menu-item {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: var(--menu-text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
|
||||||
|
:root .tabulator .tabulator-footer {
|
||||||
|
border-top: unset;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
@import url(./pages.css);
|
@import url(./pages.css);
|
||||||
@import url(./ribbon.css);
|
@import url(./ribbon.css);
|
||||||
@import url(./notes/text.css);
|
@import url(./notes/text.css);
|
||||||
|
@import url(./notes/collections/table.css);
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter";
|
font-family: "Inter";
|
||||||
@@ -183,7 +184,7 @@ html body .dropdown-item[disabled] {
|
|||||||
|
|
||||||
/* Menu item icon */
|
/* Menu item icon */
|
||||||
.dropdown-item .bx {
|
.dropdown-item .bx {
|
||||||
transform: translateY(var(--menu-item-icon-vert-offset));
|
translate: 0 var(--menu-item-icon-vert-offset);
|
||||||
color: var(--menu-item-icon-color) !important;
|
color: var(--menu-item-icon-color) !important;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
:root .tabulator {
|
||||||
|
--col-header-hover-background-color: var(--hover-item-background-color);
|
||||||
|
--col-header-arrow-active-color: var(--active-item-text-color);
|
||||||
|
--col-header-arrow-inactive-color: var(--main-border-color);
|
||||||
|
|
||||||
|
--row-moving-background-color: var(--more-accented-background-color);
|
||||||
|
|
||||||
|
--cell-editable-hover-outline-color: var(--input-focus-outline-color);
|
||||||
|
|
||||||
|
--cell-editing-border-color: var(--input-focus-outline-color);
|
||||||
|
--cell-editing-background-color: var(--input-background-color);
|
||||||
|
--cell-editing-text-color: var(--input-text-color);
|
||||||
|
}
|
||||||
@@ -1431,7 +1431,6 @@
|
|||||||
"move-to": "移动到...",
|
"move-to": "移动到...",
|
||||||
"paste-into": "粘贴到里面",
|
"paste-into": "粘贴到里面",
|
||||||
"paste-after": "粘贴到后面",
|
"paste-after": "粘贴到后面",
|
||||||
"duplicate-subtree": "复制子树",
|
|
||||||
"export": "导出",
|
"export": "导出",
|
||||||
"import-into-note": "导入到笔记",
|
"import-into-note": "导入到笔记",
|
||||||
"apply-bulk-actions": "应用批量操作",
|
"apply-bulk-actions": "应用批量操作",
|
||||||
|
|||||||
@@ -1384,7 +1384,7 @@
|
|||||||
"move-to": "Verschieben nach...",
|
"move-to": "Verschieben nach...",
|
||||||
"paste-into": "Als Unternotiz einfügen",
|
"paste-into": "Als Unternotiz einfügen",
|
||||||
"paste-after": "Danach einfügen",
|
"paste-after": "Danach einfügen",
|
||||||
"duplicate-subtree": "Notizbaum duplizieren",
|
"duplicate": "Duplizieren",
|
||||||
"export": "Exportieren",
|
"export": "Exportieren",
|
||||||
"import-into-note": "In Notiz importieren",
|
"import-into-note": "In Notiz importieren",
|
||||||
"apply-bulk-actions": "Massenaktionen ausführen",
|
"apply-bulk-actions": "Massenaktionen ausführen",
|
||||||
|
|||||||
@@ -1025,7 +1025,7 @@
|
|||||||
"title": "Consistency Checks",
|
"title": "Consistency Checks",
|
||||||
"find_and_fix_button": "Find and fix consistency issues",
|
"find_and_fix_button": "Find and fix consistency issues",
|
||||||
"finding_and_fixing_message": "Finding and fixing consistency issues...",
|
"finding_and_fixing_message": "Finding and fixing consistency issues...",
|
||||||
"issues_fixed_message": "Consistency issues should be fixed."
|
"issues_fixed_message": "Any consistency issue which may have been found is now fixed."
|
||||||
},
|
},
|
||||||
"database_anonymization": {
|
"database_anonymization": {
|
||||||
"title": "Database Anonymization",
|
"title": "Database Anonymization",
|
||||||
@@ -1595,12 +1595,13 @@
|
|||||||
"move-to": "Move to...",
|
"move-to": "Move to...",
|
||||||
"paste-into": "Paste into",
|
"paste-into": "Paste into",
|
||||||
"paste-after": "Paste after",
|
"paste-after": "Paste after",
|
||||||
"duplicate-subtree": "Duplicate subtree",
|
"duplicate": "Duplicate",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"import-into-note": "Import into note",
|
"import-into-note": "Import into note",
|
||||||
"apply-bulk-actions": "Apply bulk actions",
|
"apply-bulk-actions": "Apply bulk actions",
|
||||||
"converted-to-attachments": "{{count}} notes have been converted to attachments.",
|
"converted-to-attachments": "{{count}} notes have been converted to attachments.",
|
||||||
"convert-to-attachment-confirm": "Are you sure you want to convert note selected notes into attachments of their parent notes?"
|
"convert-to-attachment-confirm": "Are you sure you want to convert note selected notes into attachments of their parent notes?",
|
||||||
|
"open-in-popup": "Quick edit"
|
||||||
},
|
},
|
||||||
"shared_info": {
|
"shared_info": {
|
||||||
"shared_publicly": "This note is shared publicly on",
|
"shared_publicly": "This note is shared publicly on",
|
||||||
@@ -1832,7 +1833,8 @@
|
|||||||
"link_context_menu": {
|
"link_context_menu": {
|
||||||
"open_note_in_new_tab": "Open note in a new tab",
|
"open_note_in_new_tab": "Open note in a new tab",
|
||||||
"open_note_in_new_split": "Open note in a new split",
|
"open_note_in_new_split": "Open note in a new split",
|
||||||
"open_note_in_new_window": "Open note in a new window"
|
"open_note_in_new_window": "Open note in a new window",
|
||||||
|
"open_note_in_popup": "Quick edit"
|
||||||
},
|
},
|
||||||
"electron_integration": {
|
"electron_integration": {
|
||||||
"desktop-application": "Desktop Application",
|
"desktop-application": "Desktop Application",
|
||||||
@@ -1852,7 +1854,8 @@
|
|||||||
"full-text-search": "Full text search"
|
"full-text-search": "Full text search"
|
||||||
},
|
},
|
||||||
"note_tooltip": {
|
"note_tooltip": {
|
||||||
"note-has-been-deleted": "Note has been deleted."
|
"note-has-been-deleted": "Note has been deleted.",
|
||||||
|
"quick-edit": "Quick edit"
|
||||||
},
|
},
|
||||||
"geo-map": {
|
"geo-map": {
|
||||||
"create-child-note-title": "Create a new child note and add it to the map",
|
"create-child-note-title": "Create a new child note and add it to the map",
|
||||||
@@ -1941,10 +1944,29 @@
|
|||||||
},
|
},
|
||||||
"table_view": {
|
"table_view": {
|
||||||
"new-row": "New row",
|
"new-row": "New row",
|
||||||
"new-column": "New column"
|
"new-column": "New column",
|
||||||
|
"sort-column-by": "Sort by \"{{title}}\"",
|
||||||
|
"sort-column-ascending": "Ascending",
|
||||||
|
"sort-column-descending": "Descending",
|
||||||
|
"sort-column-clear": "Clear sorting",
|
||||||
|
"hide-column": "Hide column \"{{title}}\"",
|
||||||
|
"show-hide-columns": "Show/hide columns",
|
||||||
|
"row-insert-above": "Insert row above",
|
||||||
|
"row-insert-below": "Insert row below",
|
||||||
|
"row-insert-child": "Insert child note",
|
||||||
|
"add-column-to-the-left": "Add column to the left",
|
||||||
|
"add-column-to-the-right": "Add column to the right",
|
||||||
|
"edit-column": "Edit column",
|
||||||
|
"delete_column_confirmation": "Are you sure you want to delete this column? The corresponding attribute will be removed from all notes.",
|
||||||
|
"delete-column": "Delete column",
|
||||||
|
"new-column-label": "Label",
|
||||||
|
"new-column-relation": "Relation"
|
||||||
},
|
},
|
||||||
"book_properties_config": {
|
"book_properties_config": {
|
||||||
"hide-weekends": "Hide weekends",
|
"hide-weekends": "Hide weekends",
|
||||||
"display-week-numbers": "Display week numbers"
|
"display-week-numbers": "Display week numbers"
|
||||||
|
},
|
||||||
|
"table_context_menu": {
|
||||||
|
"delete_row": "Delete row"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1593,7 +1593,7 @@
|
|||||||
"move-to": "Mover a...",
|
"move-to": "Mover a...",
|
||||||
"paste-into": "Pegar en",
|
"paste-into": "Pegar en",
|
||||||
"paste-after": "Pegar después de",
|
"paste-after": "Pegar después de",
|
||||||
"duplicate-subtree": "Duplicar subárbol",
|
"duplicate": "Duplicar",
|
||||||
"export": "Exportar",
|
"export": "Exportar",
|
||||||
"import-into-note": "Importar a nota",
|
"import-into-note": "Importar a nota",
|
||||||
"apply-bulk-actions": "Aplicar acciones en lote",
|
"apply-bulk-actions": "Aplicar acciones en lote",
|
||||||
|
|||||||
@@ -1389,7 +1389,7 @@
|
|||||||
"move-to": "Déplacer vers...",
|
"move-to": "Déplacer vers...",
|
||||||
"paste-into": "Coller dans",
|
"paste-into": "Coller dans",
|
||||||
"paste-after": "Coller après",
|
"paste-after": "Coller après",
|
||||||
"duplicate-subtree": "Dupliquer le sous-arbre",
|
"duplicate": "Dupliquer",
|
||||||
"export": "Exporter",
|
"export": "Exporter",
|
||||||
"import-into-note": "Importer dans la note",
|
"import-into-note": "Importer dans la note",
|
||||||
"apply-bulk-actions": "Appliquer des Actions groupées",
|
"apply-bulk-actions": "Appliquer des Actions groupées",
|
||||||
|
|||||||
@@ -1349,7 +1349,7 @@
|
|||||||
"copy-note-path-to-clipboard": "Copiază calea notiței în clipboard",
|
"copy-note-path-to-clipboard": "Copiază calea notiței în clipboard",
|
||||||
"cut": "Decupează",
|
"cut": "Decupează",
|
||||||
"delete": "Șterge",
|
"delete": "Șterge",
|
||||||
"duplicate-subtree": "Dublifică ierarhia",
|
"duplicate": "Dublifică",
|
||||||
"edit-branch-prefix": "Editează prefixul ramurii",
|
"edit-branch-prefix": "Editează prefixul ramurii",
|
||||||
"expand-subtree": "Expandează subnotițele",
|
"expand-subtree": "Expandează subnotițele",
|
||||||
"export": "Exportă",
|
"export": "Exportă",
|
||||||
|
|||||||
@@ -1336,7 +1336,6 @@
|
|||||||
"move-to": "移動到...",
|
"move-to": "移動到...",
|
||||||
"paste-into": "貼上到裡面",
|
"paste-into": "貼上到裡面",
|
||||||
"paste-after": "貼上到後面",
|
"paste-after": "貼上到後面",
|
||||||
"duplicate-subtree": "複製子樹",
|
|
||||||
"export": "匯出",
|
"export": "匯出",
|
||||||
"import-into-note": "匯入到筆記",
|
"import-into-note": "匯入到筆記",
|
||||||
"apply-bulk-actions": "應用批量操作",
|
"apply-bulk-actions": "應用批量操作",
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const TPL = /*html*/`
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
|
||||||
<h5 class="attr-detail-title">${t("attribute_detail.attr_detail_title")}</h5>
|
<h5 class="attr-detail-title">${t("attribute_detail.attr_detail_title")}</h5>
|
||||||
|
|
||||||
<span class="bx bx-x close-attr-detail-button tn-tool-button" title="${t("attribute_detail.close_button_title")}"></span>
|
<span class="bx bx-x close-attr-detail-button tn-tool-button" title="${t("attribute_detail.close_button_title")}"></span>
|
||||||
@@ -295,6 +295,8 @@ interface AttributeDetailOpts {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
focus?: "name";
|
focus?: "name";
|
||||||
|
parent?: HTMLElement;
|
||||||
|
hideMultiplicity?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SearchRelatedResponse {
|
interface SearchRelatedResponse {
|
||||||
@@ -477,7 +479,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }: AttributeDetailOpts) {
|
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus, hideMultiplicity }: AttributeDetailOpts) {
|
||||||
if (!attribute) {
|
if (!attribute) {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
||||||
@@ -528,7 +530,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
|||||||
this.$rowPromotedAlias.toggle(!!definition.isPromoted);
|
this.$rowPromotedAlias.toggle(!!definition.isPromoted);
|
||||||
this.$inputPromotedAlias.val(definition.promotedAlias || "").attr("disabled", disabledFn);
|
this.$inputPromotedAlias.val(definition.promotedAlias || "").attr("disabled", disabledFn);
|
||||||
|
|
||||||
this.$rowMultiplicity.toggle(["label-definition", "relation-definition"].includes(this.attrType || ""));
|
this.$rowMultiplicity.toggle(["label-definition", "relation-definition"].includes(this.attrType || "") && !hideMultiplicity);
|
||||||
this.$inputMultiplicity.val(definition.multiplicity || "").attr("disabled", disabledFn);
|
this.$inputMultiplicity.val(definition.multiplicity || "").attr("disabled", disabledFn);
|
||||||
|
|
||||||
this.$rowLabelType.toggle(this.attrType === "label-definition");
|
this.$rowLabelType.toggle(this.attrType === "label-definition");
|
||||||
@@ -560,18 +562,21 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
this.toggleInt(true);
|
this.toggleInt(true);
|
||||||
|
|
||||||
const offset = this.parent?.$widget.offset() || { top: 0, left: 0 };
|
const offset = this.parent?.$widget?.offset() || { top: 0, left: 0 };
|
||||||
const detPosition = this.getDetailPosition(x, offset);
|
const detPosition = this.getDetailPosition(x, offset);
|
||||||
const outerHeight = this.$widget.outerHeight();
|
const outerHeight = this.$widget.outerHeight();
|
||||||
const height = $(window).height();
|
const height = $(window).height();
|
||||||
|
|
||||||
if (detPosition && outerHeight && height) {
|
if (!detPosition || !outerHeight || !height) {
|
||||||
|
console.warn("Can't position popup, is it attached?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.$widget
|
this.$widget
|
||||||
.css("left", detPosition.left)
|
.css("left", detPosition.left)
|
||||||
.css("right", detPosition.right)
|
.css("right", detPosition.right)
|
||||||
.css("top", y - offset.top + 70)
|
.css("top", y - offset.top + 70)
|
||||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||||
}
|
|
||||||
|
|
||||||
if (focus === "name") {
|
if (focus === "name") {
|
||||||
this.$inputName.trigger("focus").trigger("select");
|
this.$inputName.trigger("focus").trigger("select");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import noteAutocompleteService, { type Suggestion } from "../../services/note_au
|
|||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import contextMenuService from "../../menus/context_menu.js";
|
import contextMenuService from "../../menus/context_menu.js";
|
||||||
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
|
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
|
||||||
import { AttributeEditor, type EditorConfig, type Element, type MentionFeed, type Node, type Position } from "@triliumnext/ckeditor5";
|
import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5";
|
||||||
import froca from "../../services/froca.js";
|
import froca from "../../services/froca.js";
|
||||||
import attributeRenderer from "../../services/attribute_renderer.js";
|
import attributeRenderer from "../../services/attribute_renderer.js";
|
||||||
import noteCreateService from "../../services/note_create.js";
|
import noteCreateService from "../../services/note_create.js";
|
||||||
@@ -417,16 +417,16 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
this.$editor.tooltip("show");
|
this.$editor.tooltip("show");
|
||||||
}
|
}
|
||||||
|
|
||||||
getClickIndex(pos: Position) {
|
getClickIndex(pos: ModelPosition) {
|
||||||
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
|
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
|
||||||
|
|
||||||
let curNode: Node | Text | Element | null = pos.textNode;
|
let curNode: ModelNode | Text | ModelElement | null = pos.textNode;
|
||||||
|
|
||||||
while (curNode?.previousSibling) {
|
while (curNode?.previousSibling) {
|
||||||
curNode = curNode.previousSibling;
|
curNode = curNode.previousSibling;
|
||||||
|
|
||||||
if ((curNode as Element).name === "reference") {
|
if ((curNode as ModelElement).name === "reference") {
|
||||||
clickIndex += (curNode.getAttribute("notePath") as string).length + 1;
|
clickIndex += (curNode.getAttribute("href") as string).length + 1;
|
||||||
} else if ("data" in curNode) {
|
} else if ("data" in curNode) {
|
||||||
clickIndex += (curNode.data as string).length;
|
clickIndex += (curNode.data as string).length;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { default as Component, TypedComponent } from "../../components/component.js";
|
import type { TypedComponent } from "../../components/component.js";
|
||||||
import BasicWidget, { TypedBasicWidget } from "../basic_widget.js";
|
import { TypedBasicWidget } from "../basic_widget.js";
|
||||||
|
|
||||||
export default class Container<T extends TypedComponent<any>> extends TypedBasicWidget<T> {
|
export default class Container<T extends TypedComponent<any>> extends TypedBasicWidget<T> {
|
||||||
doRender() {
|
doRender() {
|
||||||
|
|||||||
157
apps/client/src/widgets/dialogs/popup_editor.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import type { EventNames, EventData } from "../../components/app_context.js";
|
||||||
|
import NoteContext from "../../components/note_context.js";
|
||||||
|
import { openDialog } from "../../services/dialog.js";
|
||||||
|
import BasicWidget from "../basic_widget.js";
|
||||||
|
import Container from "../containers/container.js";
|
||||||
|
import TypeWidget from "../type_widgets/type_widget.js";
|
||||||
|
|
||||||
|
const TPL = /*html*/`\
|
||||||
|
<div class="popup-editor-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||||
|
<style>
|
||||||
|
body.desktop .modal.popup-editor-dialog .modal-dialog {
|
||||||
|
max-width: 75vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .modal-header .modal-title {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .modal-body {
|
||||||
|
padding: 0;
|
||||||
|
height: 75vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .note-detail-editable-text {
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .title-row,
|
||||||
|
.modal.popup-editor-dialog .modal-title,
|
||||||
|
.modal.popup-editor-dialog .note-icon-widget {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .note-icon-widget {
|
||||||
|
width: 32px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .note-icon-widget button.note-icon,
|
||||||
|
.modal.popup-editor-dialog .note-title-widget input.note-title {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .classic-toolbar-widget {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--modal-background-color);
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.popup-editor-dialog .note-detail-file {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">
|
||||||
|
<!-- This is where the first child will be injected -->
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- This is where all but the first child will be injected. -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default class PopupEditorDialog extends Container<BasicWidget> {
|
||||||
|
|
||||||
|
private noteContext: NoteContext;
|
||||||
|
private $modalHeader!: JQuery<HTMLElement>;
|
||||||
|
private $modalBody!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.noteContext = new NoteContext("_popup-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
doRender() {
|
||||||
|
// This will populate this.$widget with the content of the children.
|
||||||
|
super.doRender();
|
||||||
|
|
||||||
|
// Now we wrap it in the modal.
|
||||||
|
const $newWidget = $(TPL);
|
||||||
|
this.$modalHeader = $newWidget.find(".modal-title");
|
||||||
|
this.$modalBody = $newWidget.find(".modal-body");
|
||||||
|
|
||||||
|
const children = this.$widget.children();
|
||||||
|
this.$modalHeader.append(children[0]);
|
||||||
|
this.$modalBody.append(children.slice(1));
|
||||||
|
this.$widget = $newWidget;
|
||||||
|
this.setVisibility(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async openInPopupEvent({ noteIdOrPath }: EventData<"openInPopup">) {
|
||||||
|
const $dialog = await openDialog(this.$widget, false, {
|
||||||
|
focus: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.noteContext.setNote(noteIdOrPath);
|
||||||
|
|
||||||
|
const activeEl = document.activeElement;
|
||||||
|
if (activeEl && "blur" in activeEl) {
|
||||||
|
(activeEl as HTMLElement).blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dialog.on("shown.bs.modal", async () => {
|
||||||
|
// Reduce the z-index of modals so that ckeditor popups are properly shown on top of it.
|
||||||
|
// The backdrop instance is not shared so it's OK to make a one-off modification.
|
||||||
|
$("body > .modal-backdrop").css("z-index", "998");
|
||||||
|
$dialog.css("z-index", "999");
|
||||||
|
|
||||||
|
await this.handleEventInChildren("activeContextChanged", { noteContext: this.noteContext });
|
||||||
|
this.setVisibility(true);
|
||||||
|
await this.handleEventInChildren("focusOnDetail", { ntxId: this.noteContext.ntxId });
|
||||||
|
});
|
||||||
|
$dialog.on("hidden.bs.modal", () => {
|
||||||
|
const $typeWidgetEl = $dialog.find(".note-detail-printable");
|
||||||
|
if ($typeWidgetEl.length) {
|
||||||
|
const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as TypeWidget;
|
||||||
|
typeWidget.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setVisibility(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisibility(visible: boolean) {
|
||||||
|
const $bodyItems = this.$modalBody.find("> div");
|
||||||
|
if (visible) {
|
||||||
|
$bodyItems.fadeIn();
|
||||||
|
this.$modalHeader.children().show();
|
||||||
|
} else {
|
||||||
|
$bodyItems.hide();
|
||||||
|
this.$modalHeader.children().hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
|
||||||
|
// Avoid events related to the current tab interfere with our popup.
|
||||||
|
if (["noteSwitched", "noteSwitchedAndActivated"].includes(name)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.handleEventInChildren(name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,8 +31,8 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||||
list: null,
|
list: "mULW0Q3VojwY",
|
||||||
grid: null,
|
grid: "8QqnMzx393bx",
|
||||||
calendar: "xWbu3jpNWapp",
|
calendar: "xWbu3jpNWapp",
|
||||||
table: "2FvYrpmOXm29",
|
table: "2FvYrpmOXm29",
|
||||||
geoMap: "81SGnPGMk7Xc"
|
geoMap: "81SGnPGMk7Xc"
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
|||||||
// https://github.com/zadam/trilium/issues/2522
|
// https://github.com/zadam/trilium/issues/2522
|
||||||
const isBackendNote = this.noteContext?.noteId === "_backendLog";
|
const isBackendNote = this.noteContext?.noteId === "_backendLog";
|
||||||
const isSqlNote = this.mime === "text/x-sqlite;schema=trilium";
|
const isSqlNote = this.mime === "text/x-sqlite;schema=trilium";
|
||||||
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "mermaid"].includes(this.type ?? "");
|
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "mermaid", "file"].includes(this.type ?? "");
|
||||||
const isFullHeight = (!this.noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|
const isFullHeight = (!this.noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|
||||||
|| this.noteContext?.viewScope?.viewMode === "attachments"
|
|| this.noteContext?.viewScope?.viewMode === "attachments"
|
||||||
|| isBackendNote;
|
|| isBackendNote;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import NoteListRenderer from "../services/note_list_renderer.js";
|
|||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData, EventNames } from "../components/app_context.js";
|
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData, EventNames } from "../components/app_context.js";
|
||||||
import type ViewMode from "./view_widgets/view_mode.js";
|
import type ViewMode from "./view_widgets/view_mode.js";
|
||||||
import AttributeDetailWidget from "./attribute_widgets/attribute_detail.js";
|
|
||||||
import { Attribute } from "../services/attribute_parser.js";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="note-list-widget">
|
<div class="note-list-widget">
|
||||||
@@ -39,24 +37,36 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
|||||||
private noteIdRefreshed?: string;
|
private noteIdRefreshed?: string;
|
||||||
private shownNoteId?: string | null;
|
private shownNoteId?: string | null;
|
||||||
private viewMode?: ViewMode<any> | null;
|
private viewMode?: ViewMode<any> | null;
|
||||||
private attributeDetailWidget: AttributeDetailWidget;
|
private displayOnlyCollections: boolean;
|
||||||
|
|
||||||
constructor() {
|
/**
|
||||||
|
* @param displayOnlyCollections if set to `true` then only collection-type views are displayed such as geo-map and the calendar. The original book types grid and list will be ignored.
|
||||||
|
*/
|
||||||
|
constructor(displayOnlyCollections: boolean) {
|
||||||
super();
|
super();
|
||||||
this.attributeDetailWidget = new AttributeDetailWidget()
|
|
||||||
.contentSized()
|
this.displayOnlyCollections = displayOnlyCollections;
|
||||||
.setParent(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return super.isEnabled() && this.noteContext?.hasNoteList();
|
if (!super.isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.displayOnlyCollections && this.note?.type !== "book") {
|
||||||
|
const viewType = this.note?.getLabelValue("viewType");
|
||||||
|
if (!viewType || ["grid", "list"].includes(viewType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.noteContext?.hasNoteList();
|
||||||
}
|
}
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.contentSized();
|
this.contentSized();
|
||||||
this.$content = this.$widget.find(".note-list-widget-content");
|
this.$content = this.$widget.find(".note-list-widget-content");
|
||||||
this.$widget.append(this.attributeDetailWidget.render());
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
@@ -75,23 +85,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
|||||||
setTimeout(() => observer.observe(this.$widget[0]), 10);
|
setTimeout(() => observer.observe(this.$widget[0]), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
addNoteListItemEvent() {
|
|
||||||
const attr: Attribute = {
|
|
||||||
type: "label",
|
|
||||||
name: "label:myLabel",
|
|
||||||
value: "promoted,single,text"
|
|
||||||
};
|
|
||||||
|
|
||||||
this.attributeDetailWidget!.showAttributeDetail({
|
|
||||||
attribute: attr,
|
|
||||||
allAttributes: [ attr ],
|
|
||||||
isOwned: true,
|
|
||||||
x: 100,
|
|
||||||
y: 200,
|
|
||||||
focus: "name"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkRenderStatus() {
|
checkRenderStatus() {
|
||||||
// console.log("this.isIntersecting", this.isIntersecting);
|
// console.log("this.isIntersecting", this.isIntersecting);
|
||||||
// console.log(`${this.noteIdRefreshed} === ${this.noteId}`, this.noteIdRefreshed === this.noteId);
|
// console.log(`${this.noteIdRefreshed} === ${this.noteId}`, this.noteIdRefreshed === this.noteId);
|
||||||
@@ -107,8 +100,7 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
|||||||
const noteListRenderer = new NoteListRenderer({
|
const noteListRenderer = new NoteListRenderer({
|
||||||
$parent: this.$content,
|
$parent: this.$content,
|
||||||
parentNote: note,
|
parentNote: note,
|
||||||
parentNotePath: this.notePath,
|
parentNotePath: this.notePath
|
||||||
noteIds: note.getChildNoteIds()
|
|
||||||
});
|
});
|
||||||
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
|
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
|
||||||
await noteListRenderer.renderList();
|
await noteListRenderer.renderList();
|
||||||
@@ -153,12 +145,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
this.checkRenderStatus();
|
this.checkRenderStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform the view mode of changes and refresh if needed.
|
|
||||||
if (this.viewMode && this.viewMode.onEntitiesReloaded(e)) {
|
|
||||||
this.refresh();
|
|
||||||
this.checkRenderStatus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
|
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
|
||||||
|
|||||||
@@ -240,24 +240,25 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
this.$tree.on("mousedown", ".fancytree-title", (e) => {
|
this.$tree.on("mousedown", ".fancytree-title", (e) => {
|
||||||
if (e.which === 2) {
|
if (e.which === 2) {
|
||||||
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
|
|
||||||
const notePath = treeService.getNotePath(node);
|
const notePath = treeService.getNotePath(node);
|
||||||
|
|
||||||
if (notePath) {
|
if (notePath) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
|
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
|
||||||
activate: e.shiftKey ? true : false
|
activate: e.shiftKey ? true : false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$tree.on("mouseup", ".fancytree-title", (e) => {
|
||||||
|
// Prevent middle click from pasting in the editor.
|
||||||
|
if (e.which === 2) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.$tree.on("auxclick", (e) => {
|
|
||||||
// Prevent middle click from pasting in the editor.
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$treeSettingsPopup = this.$widget.find(".tree-settings-popup");
|
this.$treeSettingsPopup = this.$widget.find(".tree-settings-popup");
|
||||||
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find(".hide-archived-notes");
|
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find(".hide-archived-notes");
|
||||||
@@ -712,7 +713,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$tree.on("contextmenu", ".fancytree-node", (e) => {
|
this.$tree.on("contextmenu", ".fancytree-node", (e) => {
|
||||||
|
if (!utils.isCtrlKey(e)) {
|
||||||
this.showContextMenu(e);
|
this.showContextMenu(e);
|
||||||
|
} else {
|
||||||
|
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||||
|
const notePath = treeService.getNotePath(node);
|
||||||
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||||
|
}
|
||||||
return false; // blocks default browser right click menu
|
return false; // blocks default browser right click menu
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ const TPL = /*html*/`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-properties-container > * {
|
.book-properties-container > div {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.book-properties-container > .type-number > label {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
.book-properties-container input[type="checkbox"] {
|
.book-properties-container input[type="checkbox"] {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
@@ -127,6 +132,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
renderBookProperty(property: BookProperty) {
|
renderBookProperty(property: BookProperty) {
|
||||||
const $container = $("<div>");
|
const $container = $("<div>");
|
||||||
|
$container.addClass(`type-${property.type}`);
|
||||||
const note = this.note;
|
const note = this.note;
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return $container;
|
return $container;
|
||||||
@@ -168,6 +174,27 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
|||||||
});
|
});
|
||||||
$container.append($button);
|
$container.append($button);
|
||||||
break;
|
break;
|
||||||
|
case "number":
|
||||||
|
const $numberInput = $("<input>", {
|
||||||
|
type: "number",
|
||||||
|
class: "form-control form-control-sm",
|
||||||
|
value: note.getLabelValue(property.bindToLabel) || "",
|
||||||
|
width: property.width ?? 100,
|
||||||
|
min: property.min ?? 0
|
||||||
|
});
|
||||||
|
$numberInput.on("change", () => {
|
||||||
|
const value = $numberInput.val();
|
||||||
|
if (value === "") {
|
||||||
|
attributes.removeOwnedLabelByName(note, property.bindToLabel);
|
||||||
|
} else {
|
||||||
|
attributes.setLabel(note.noteId, property.bindToLabel, String(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$container.append($("<label>")
|
||||||
|
.text(property.label)
|
||||||
|
.append(" ".repeat(2))
|
||||||
|
.append($numberInput));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $container;
|
return $container;
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import attributes from "../../services/attributes";
|
|||||||
import { ViewTypeOptions } from "../../services/note_list_renderer"
|
import { ViewTypeOptions } from "../../services/note_list_renderer"
|
||||||
import NoteContextAwareWidget from "../note_context_aware_widget";
|
import NoteContextAwareWidget from "../note_context_aware_widget";
|
||||||
|
|
||||||
export type BookProperty = CheckBoxProperty | ButtonProperty;
|
|
||||||
|
|
||||||
interface BookConfig {
|
interface BookConfig {
|
||||||
properties: BookProperty[];
|
properties: BookProperty[];
|
||||||
}
|
}
|
||||||
@@ -24,6 +22,16 @@ interface ButtonProperty {
|
|||||||
onClick: (context: BookContext) => void;
|
onClick: (context: BookContext) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NumberProperty {
|
||||||
|
type: "number",
|
||||||
|
label: string;
|
||||||
|
bindToLabel: string;
|
||||||
|
width?: number;
|
||||||
|
min?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty;
|
||||||
|
|
||||||
interface BookContext {
|
interface BookContext {
|
||||||
note: FNote;
|
note: FNote;
|
||||||
triggerCommand: NoteContextAwareWidget["triggerCommand"];
|
triggerCommand: NoteContextAwareWidget["triggerCommand"];
|
||||||
@@ -85,6 +93,13 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
properties: []
|
properties: []
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
properties: []
|
properties: [
|
||||||
|
{
|
||||||
|
label: "Max nesting depth:",
|
||||||
|
type: "number",
|
||||||
|
bindToLabel: "maxNestingDepth",
|
||||||
|
width: 65
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,18 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
|||||||
this.contentSized();
|
this.contentSized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEnabled(): boolean | null | undefined {
|
||||||
|
if (options.get("textNoteEditorType") !== "ckeditor-classic") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.note || this.note.type !== "text") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async getTitle() {
|
async getTitle() {
|
||||||
return {
|
return {
|
||||||
show: await this.#shouldDisplay(),
|
show: await this.#shouldDisplay(),
|
||||||
@@ -58,11 +70,7 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #shouldDisplay() {
|
async #shouldDisplay() {
|
||||||
if (options.get("textNoteEditorType") !== "ckeditor-classic") {
|
if (!this.isEnabled()) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.note || this.note.type !== "text") {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ interface AttributeResult {
|
|||||||
attributeId: string;
|
attributeId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This widget is quite special because it's used in the desktop ribbon, but in mobile outside of ribbon.
|
|
||||||
* This works without many issues (apart from autocomplete), but it should be kept in mind when changing things
|
|
||||||
* and testing.
|
|
||||||
*/
|
|
||||||
export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||||
|
|
||||||
private $container!: JQuery<HTMLElement>;
|
private $container!: JQuery<HTMLElement>;
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export default class SearchResultWidget extends NoteContextAwareWidget {
|
|||||||
const noteListRenderer = new NoteListRenderer({
|
const noteListRenderer = new NoteListRenderer({
|
||||||
$parent: this.$content,
|
$parent: this.$content,
|
||||||
parentNote: note,
|
parentNote: note,
|
||||||
noteIds: note.getChildNoteIds(),
|
|
||||||
showNotePath: true
|
showNotePath: true
|
||||||
});
|
});
|
||||||
await noteListRenderer.renderList();
|
await noteListRenderer.renderList();
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ async function handleContentUpdate(affectedNoteIds: string[]) {
|
|||||||
const templateNoteIds = new Set(templateCache.keys());
|
const templateNoteIds = new Set(templateCache.keys());
|
||||||
const affectedTemplateNoteIds = templateNoteIds.intersection(updatedNoteIds);
|
const affectedTemplateNoteIds = templateNoteIds.intersection(updatedNoteIds);
|
||||||
|
|
||||||
await froca.getNotes(affectedNoteIds);
|
await froca.getNotes(affectedNoteIds, true);
|
||||||
|
|
||||||
let fullReloadNeeded = false;
|
let fullReloadNeeded = false;
|
||||||
for (const affectedTemplateNoteId of affectedTemplateNoteIds) {
|
for (const affectedTemplateNoteId of affectedTemplateNoteIds) {
|
||||||
|
|||||||
@@ -178,13 +178,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isClassicEditor) {
|
if (isClassicEditor) {
|
||||||
let $classicToolbarWidget;
|
const $classicToolbarWidget = this.findClassicToolbar();
|
||||||
if (!utils.isMobile()) {
|
|
||||||
const $parentSplit = this.$widget.parents(".note-split.type-text");
|
|
||||||
$classicToolbarWidget = $parentSplit.find("> .ribbon-container .classic-toolbar-widget");
|
|
||||||
} else {
|
|
||||||
$classicToolbarWidget = $("body").find(".classic-toolbar-widget");
|
|
||||||
}
|
|
||||||
|
|
||||||
$classicToolbarWidget.empty();
|
$classicToolbarWidget.empty();
|
||||||
if ($classicToolbarWidget.length) {
|
if ($classicToolbarWidget.length) {
|
||||||
@@ -271,8 +265,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
|
const editor = this.watchdog.editor;
|
||||||
|
if (editor) {
|
||||||
|
editor.editing.view.focus();
|
||||||
|
} else {
|
||||||
this.$editor.trigger("focus");
|
this.$editor.trigger("focus");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scrollToEnd() {
|
scrollToEnd() {
|
||||||
this.watchdog?.editor?.model.change((writer) => {
|
this.watchdog?.editor?.model.change((writer) => {
|
||||||
@@ -515,6 +514,22 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findClassicToolbar(): JQuery<HTMLElement> {
|
||||||
|
if (!utils.isMobile()) {
|
||||||
|
const $parentSplit = this.$widget.parents(".note-split.type-text");
|
||||||
|
|
||||||
|
if ($parentSplit.length) {
|
||||||
|
// The editor is in a normal tab.
|
||||||
|
return $parentSplit.find("> .ribbon-container .classic-toolbar-widget");
|
||||||
|
} else {
|
||||||
|
// The editor is in a popup.
|
||||||
|
return this.$widget.closest(".modal-body").find(".classic-toolbar-widget");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $("body").find(".classic-toolbar-widget");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
|
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
|
||||||
const { TouchBar, buildIcon } = data;
|
const { TouchBar, buildIcon } = data;
|
||||||
const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;
|
const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import TypeWidget from "./type_widget.js";
|
|||||||
import appContext from "../../components/app_context.js";
|
import appContext from "../../components/app_context.js";
|
||||||
import searchService from "../../services/search.js";
|
import searchService from "../../services/search.js";
|
||||||
import { t } from "../../services/i18n.js";
|
import { t } from "../../services/i18n.js";
|
||||||
import type FNote from "../../entities/fnote.js";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="note-detail-empty note-detail-printable">
|
<div class="note-detail-empty note-detail-printable">
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ const TPL = /*html*/`
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-split.full-content-width .note-detail-file[data-preview-type="video"] {
|
.note-detail.full-height .note-detail-file[data-preview-type="pdf"],
|
||||||
|
.note-detail.full-height .note-detail-file[data-preview-type="video"] {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,17 @@ export default abstract class TypeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeNoteChangedEvent() {
|
||||||
|
if (!this.isActiveNoteContext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore focus to the editor when switching tabs, but only if the note tree is not already focused.
|
||||||
|
if (!document.activeElement?.classList.contains("fancytree-title")) {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
|
|
||||||
private $root: JQuery<HTMLElement>;
|
private $root: JQuery<HTMLElement>;
|
||||||
private $calendarContainer: JQuery<HTMLElement>;
|
private $calendarContainer: JQuery<HTMLElement>;
|
||||||
private noteIds: string[];
|
|
||||||
private calendar?: Calendar;
|
private calendar?: Calendar;
|
||||||
private isCalendarRoot: boolean;
|
private isCalendarRoot: boolean;
|
||||||
private lastView?: string;
|
private lastView?: string;
|
||||||
@@ -124,15 +123,10 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
|
|
||||||
this.$root = $(TPL);
|
this.$root = $(TPL);
|
||||||
this.$calendarContainer = this.$root.find(".calendar-container");
|
this.$calendarContainer = this.$root.find(".calendar-container");
|
||||||
this.noteIds = args.noteIds;
|
|
||||||
this.isCalendarRoot = false;
|
this.isCalendarRoot = false;
|
||||||
args.$parent.append(this.$root);
|
args.$parent.append(this.$root);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFullHeight(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
||||||
this.isCalendarRoot = this.parentNote.hasLabel("calendarRoot") || this.parentNote.hasLabel("workspaceCalendarRoot");
|
this.isCalendarRoot = this.parentNote.hasLabel("calendarRoot") || this.parentNote.hasLabel("workspaceCalendarRoot");
|
||||||
const isEditable = !this.isCalendarRoot;
|
const isEditable = !this.isCalendarRoot;
|
||||||
@@ -225,6 +219,7 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
$(mainContainer ?? e.el).append($(promotedAttributesHtml));
|
$(mainContainer ?? e.el).append($(promotedAttributesHtml));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Called upon when clicking the day number in the calendar, opens or creates the day note but only if in a calendar root.
|
||||||
dateClick: async (e) => {
|
dateClick: async (e) => {
|
||||||
if (!this.isCalendarRoot) {
|
if (!this.isCalendarRoot) {
|
||||||
return;
|
return;
|
||||||
@@ -232,7 +227,8 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
|
|
||||||
const note = await date_notes.getDayNote(e.dateStr);
|
const note = await date_notes.getDayNote(e.dateStr);
|
||||||
if (note) {
|
if (note) {
|
||||||
appContext.tabManager.getActiveContext()?.setNote(note.noteId);
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId });
|
||||||
|
appContext.triggerCommand("refreshNoteList", { noteId: this.parentNote.noteId });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
datesSet: (e) => this.#onDatesSet(e),
|
datesSet: (e) => this.#onDatesSet(e),
|
||||||
@@ -394,7 +390,7 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
// Refresh note IDs if they got changed.
|
// Refresh note IDs if they got changed.
|
||||||
if (loadResults.getBranchRows().some((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
if (loadResults.getBranchRows().some((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
||||||
this.noteIds = this.parentNote.getChildNoteIds();
|
this.noteIds = this.parentNote.getChildNoteIds();
|
||||||
@@ -405,9 +401,14 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh on note title change.
|
||||||
|
if (loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId))) {
|
||||||
|
this.calendar?.refetchEvents();
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh dataset on subnote change.
|
// Refresh dataset on subnote change.
|
||||||
if (this.calendar && loadResults.getAttributeRows().some((a) => this.noteIds.includes(a.noteId ?? ""))) {
|
if (loadResults.getAttributeRows().some((a) => this.noteIds.includes(a.noteId ?? ""))) {
|
||||||
this.calendar.refetchEvents();
|
this.calendar?.refetchEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +437,7 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
events.push(await CalendarView.buildEvent(dateNote, { startDate }));
|
events.push(await CalendarView.buildEvent(dateNote, { startDate }));
|
||||||
|
|
||||||
if (dateNote.hasChildren()) {
|
if (dateNote.hasChildren()) {
|
||||||
const childNoteIds = dateNote.getChildNoteIds();
|
const childNoteIds = await dateNote.getSubtreeNoteIds();
|
||||||
for (const childNoteId of childNoteIds) {
|
for (const childNoteId of childNoteIds) {
|
||||||
childNoteToDateMapping[childNoteId] = startDate;
|
childNoteToDateMapping[childNoteId] = startDate;
|
||||||
}
|
}
|
||||||
@@ -462,13 +463,6 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
const startDate = CalendarView.#getCustomisableLabel(note, "startDate", "calendar:startDate");
|
const startDate = CalendarView.#getCustomisableLabel(note, "startDate", "calendar:startDate");
|
||||||
|
|
||||||
if (note.hasChildren()) {
|
|
||||||
const childrenEventData = await this.buildEvents(note.getChildNoteIds());
|
|
||||||
if (childrenEventData.length > 0) {
|
|
||||||
events.push(childrenEventData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!startDate) {
|
if (!startDate) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -533,7 +527,7 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
const eventData: EventInput = {
|
const eventData: EventInput = {
|
||||||
title: title,
|
title: title,
|
||||||
start: startDate,
|
start: startDate,
|
||||||
url: `#${note.noteId}`,
|
url: `#${note.noteId}?popup`,
|
||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
color: color ?? undefined,
|
color: color ?? undefined,
|
||||||
iconClass: note.getLabelValue("iconClass"),
|
iconClass: note.getLabelValue("iconClass"),
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ const TPL = /*html*/`
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaflet-top,
|
||||||
|
.leaflet-bottom {
|
||||||
|
z-index: 997;
|
||||||
|
}
|
||||||
|
|
||||||
.geo-map-container.placing-note {
|
.geo-map-container.placing-note {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
}
|
}
|
||||||
@@ -221,7 +226,7 @@ export default class GeoView extends ViewMode<MapData> {
|
|||||||
|
|
||||||
// Add the new markers.
|
// Add the new markers.
|
||||||
this.currentMarkerData = {};
|
this.currentMarkerData = {};
|
||||||
const notes = await this.parentNote.getChildNotes();
|
const notes = await this.parentNote.getSubtreeNotes();
|
||||||
const draggable = !this.isReadOnly;
|
const draggable = !this.isReadOnly;
|
||||||
for (const childNote of notes) {
|
for (const childNote of notes) {
|
||||||
if (childNote.mime === "application/gpx+xml") {
|
if (childNote.mime === "application/gpx+xml") {
|
||||||
@@ -238,10 +243,6 @@ export default class GeoView extends ViewMode<MapData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFullHeight(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changeState(newState: State) {
|
#changeState(newState: State) {
|
||||||
this._state = newState;
|
this._state = newState;
|
||||||
this.$container.toggleClass("placing-note", newState === State.NewNote);
|
this.$container.toggleClass("placing-note", newState === State.NewNote);
|
||||||
@@ -250,7 +251,7 @@ export default class GeoView extends ViewMode<MapData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">): boolean | void {
|
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
// If any of the children branches are altered.
|
// If any of the children branches are altered.
|
||||||
if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
||||||
this.#reloadMarkers();
|
this.#reloadMarkers();
|
||||||
|
|||||||
@@ -36,10 +36,17 @@ export default function processNoteWithMarker(map: Map, note: FNote, location: s
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
newMarker.on("contextmenu", (e) => {
|
newMarker.on("contextmenu", (e) => {
|
||||||
openContextMenu(note.noteId, e, isEditable);
|
openContextMenu(note.noteId, e, isEditable);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isEditable) {
|
||||||
|
newMarker.on("click", (e) => {
|
||||||
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return newMarker;
|
return newMarker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const TPL = /*html*/`
|
|||||||
class ListOrGridView extends ViewMode<{}> {
|
class ListOrGridView extends ViewMode<{}> {
|
||||||
private $noteList: JQuery<HTMLElement>;
|
private $noteList: JQuery<HTMLElement>;
|
||||||
|
|
||||||
private noteIds: string[];
|
private filteredNoteIds!: string[];
|
||||||
private page?: number;
|
private page?: number;
|
||||||
private pageSize?: number;
|
private pageSize?: number;
|
||||||
private showNotePath?: boolean;
|
private showNotePath?: boolean;
|
||||||
@@ -174,13 +174,6 @@ class ListOrGridView extends ViewMode<{}> {
|
|||||||
super(args, viewType);
|
super(args, viewType);
|
||||||
this.$noteList = $(TPL);
|
this.$noteList = $(TPL);
|
||||||
|
|
||||||
const includedNoteIds = this.getIncludedNoteIds();
|
|
||||||
|
|
||||||
this.noteIds = args.noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden");
|
|
||||||
|
|
||||||
if (this.noteIds.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.$parent.append(this.$noteList);
|
args.$parent.append(this.$noteList);
|
||||||
|
|
||||||
@@ -204,8 +197,14 @@ class ListOrGridView extends ViewMode<{}> {
|
|||||||
return new Set(includedLinks.map((rel) => rel.value));
|
return new Set(includedLinks.map((rel) => rel.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async beforeRender() {
|
||||||
|
super.beforeRender();
|
||||||
|
const includedNoteIds = this.getIncludedNoteIds();
|
||||||
|
this.filteredNoteIds = this.noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden");
|
||||||
|
}
|
||||||
|
|
||||||
async renderList() {
|
async renderList() {
|
||||||
if (this.noteIds.length === 0 || !this.page || !this.pageSize) {
|
if (this.filteredNoteIds.length === 0 || !this.page || !this.pageSize) {
|
||||||
this.$noteList.hide();
|
this.$noteList.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -226,7 +225,7 @@ class ListOrGridView extends ViewMode<{}> {
|
|||||||
const startIdx = (this.page - 1) * this.pageSize;
|
const startIdx = (this.page - 1) * this.pageSize;
|
||||||
const endIdx = startIdx + this.pageSize;
|
const endIdx = startIdx + this.pageSize;
|
||||||
|
|
||||||
const pageNoteIds = this.noteIds.slice(startIdx, Math.min(endIdx, this.noteIds.length));
|
const pageNoteIds = this.filteredNoteIds.slice(startIdx, Math.min(endIdx, this.filteredNoteIds.length));
|
||||||
const pageNotes = await froca.getNotes(pageNoteIds);
|
const pageNotes = await froca.getNotes(pageNoteIds);
|
||||||
|
|
||||||
for (const note of pageNotes) {
|
for (const note of pageNotes) {
|
||||||
@@ -246,7 +245,7 @@ class ListOrGridView extends ViewMode<{}> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageCount = Math.ceil(this.noteIds.length / this.pageSize);
|
const pageCount = Math.ceil(this.filteredNoteIds.length / this.pageSize);
|
||||||
|
|
||||||
$pager.toggle(pageCount > 1);
|
$pager.toggle(pageCount > 1);
|
||||||
|
|
||||||
@@ -257,7 +256,7 @@ class ListOrGridView extends ViewMode<{}> {
|
|||||||
lastPrinted = true;
|
lastPrinted = true;
|
||||||
|
|
||||||
const startIndex = (i - 1) * this.pageSize + 1;
|
const startIndex = (i - 1) * this.pageSize + 1;
|
||||||
const endIndex = Math.min(this.noteIds.length, i * this.pageSize);
|
const endIndex = Math.min(this.filteredNoteIds.length, i * this.pageSize);
|
||||||
|
|
||||||
$pager.append(
|
$pager.append(
|
||||||
i === this.page
|
i === this.page
|
||||||
@@ -279,7 +278,7 @@ class ListOrGridView extends ViewMode<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all
|
// no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all
|
||||||
$pager.append(`<span class="note-list-pager-total-count">(${this.noteIds.length} notes)</span>`);
|
$pager.append(`<span class="note-list-pager-total-count">(${this.filteredNoteIds.length} notes)</span>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderNote(note: FNote, expand: boolean = false) {
|
async renderNote(note: FNote, expand: boolean = false) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { executeBulkActions } from "../../../services/bulk_action.js";
|
||||||
|
|
||||||
|
export async function renameColumn(parentNoteId: string, type: "label" | "relation", originalName: string, newName: string) {
|
||||||
|
if (type === "label") {
|
||||||
|
return executeBulkActions(parentNoteId, [{
|
||||||
|
name: "renameLabel",
|
||||||
|
oldLabelName: originalName,
|
||||||
|
newLabelName: newName
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
return executeBulkActions(parentNoteId, [{
|
||||||
|
name: "renameRelation",
|
||||||
|
oldRelationName: originalName,
|
||||||
|
newRelationName: newName
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteColumn(parentNoteId: string, type: "label" | "relation", columnName: string) {
|
||||||
|
if (type === "label") {
|
||||||
|
return executeBulkActions(parentNoteId, [{
|
||||||
|
name: "deleteLabel",
|
||||||
|
labelName: columnName
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
return executeBulkActions(parentNoteId, [{
|
||||||
|
name: "deleteRelation",
|
||||||
|
relationName: columnName
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
152
apps/client/src/widgets/view_widgets/table_view/col_editing.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { Tabulator } from "tabulator-tables";
|
||||||
|
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
|
||||||
|
import { Attribute } from "../../../services/attribute_parser";
|
||||||
|
import Component from "../../../components/component";
|
||||||
|
import { CommandListenerData, EventData } from "../../../components/app_context";
|
||||||
|
import attributes from "../../../services/attributes";
|
||||||
|
import FNote from "../../../entities/fnote";
|
||||||
|
import { deleteColumn, renameColumn } from "./bulk_actions";
|
||||||
|
import dialog from "../../../services/dialog";
|
||||||
|
import { t } from "../../../services/i18n";
|
||||||
|
|
||||||
|
export default class TableColumnEditing extends Component {
|
||||||
|
|
||||||
|
private attributeDetailWidget: AttributeDetailWidget;
|
||||||
|
private api: Tabulator;
|
||||||
|
private parentNote: FNote;
|
||||||
|
|
||||||
|
private newAttribute?: Attribute;
|
||||||
|
private newAttributePosition?: number;
|
||||||
|
private existingAttributeToEdit?: Attribute;
|
||||||
|
|
||||||
|
constructor($parent: JQuery<HTMLElement>, parentNote: FNote, api: Tabulator) {
|
||||||
|
super();
|
||||||
|
const parentComponent = glob.getComponentByEl($parent[0]);
|
||||||
|
this.attributeDetailWidget = new AttributeDetailWidget()
|
||||||
|
.contentSized()
|
||||||
|
.setParent(parentComponent);
|
||||||
|
$parent.append(this.attributeDetailWidget.render());
|
||||||
|
this.api = api;
|
||||||
|
this.parentNote = parentNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewTableColumnCommand({ referenceColumn, columnToEdit, direction, type }: EventData<"addNewTableColumn">) {
|
||||||
|
let attr: Attribute | undefined;
|
||||||
|
|
||||||
|
this.existingAttributeToEdit = undefined;
|
||||||
|
if (columnToEdit) {
|
||||||
|
attr = this.getAttributeFromField(columnToEdit.getField());
|
||||||
|
if (attr) {
|
||||||
|
this.existingAttributeToEdit = { ...attr };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attr) {
|
||||||
|
attr = {
|
||||||
|
type: "label",
|
||||||
|
name: `${type ?? "label"}:myLabel`,
|
||||||
|
value: "promoted,single,text",
|
||||||
|
isInheritable: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (referenceColumn && this.api) {
|
||||||
|
this.newAttributePosition = this.api.getColumns().indexOf(referenceColumn);
|
||||||
|
|
||||||
|
if (direction === "after") {
|
||||||
|
this.newAttributePosition++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.newAttributePosition = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attributeDetailWidget!.showAttributeDetail({
|
||||||
|
attribute: attr,
|
||||||
|
allAttributes: [ attr ],
|
||||||
|
isOwned: true,
|
||||||
|
x: 0,
|
||||||
|
y: 150,
|
||||||
|
focus: "name",
|
||||||
|
hideMultiplicity: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAttributeListCommand({ attributes }: CommandListenerData<"updateAttributeList">) {
|
||||||
|
this.newAttribute = attributes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveAttributesCommand() {
|
||||||
|
if (!this.newAttribute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, value, isInheritable } = this.newAttribute;
|
||||||
|
|
||||||
|
this.api.blockRedraw();
|
||||||
|
const isRename = (this.existingAttributeToEdit && this.existingAttributeToEdit.name !== name);
|
||||||
|
try {
|
||||||
|
if (isRename) {
|
||||||
|
const oldName = this.existingAttributeToEdit!.name.split(":")[1];
|
||||||
|
const [ type, newName ] = name.split(":");
|
||||||
|
await renameColumn(this.parentNote.noteId, type as "label" | "relation", oldName, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.existingAttributeToEdit && (isRename || this.existingAttributeToEdit.isInheritable !== isInheritable)) {
|
||||||
|
attributes.removeOwnedLabelByName(this.parentNote, this.existingAttributeToEdit.name);
|
||||||
|
}
|
||||||
|
attributes.setLabel(this.parentNote.noteId, name, value, isInheritable);
|
||||||
|
} finally {
|
||||||
|
this.api.restoreRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteTableColumnCommand({ columnToDelete }: CommandListenerData<"deleteTableColumn">) {
|
||||||
|
if (!columnToDelete || !await dialog.confirm(t("table_view.delete_column_confirmation"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let [ type, name ] = columnToDelete.getField()?.split(".", 2);
|
||||||
|
if (!type || !name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type = type.replace("s", "");
|
||||||
|
|
||||||
|
this.api.blockRedraw();
|
||||||
|
try {
|
||||||
|
await deleteColumn(this.parentNote.noteId, type as "label" | "relation", name);
|
||||||
|
attributes.removeOwnedLabelByName(this.parentNote, `${type}:${name}`);
|
||||||
|
} finally {
|
||||||
|
this.api.restoreRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewAttributePosition() {
|
||||||
|
return this.newAttributePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetNewAttributePosition() {
|
||||||
|
this.newAttribute = undefined;
|
||||||
|
this.newAttributePosition = undefined;
|
||||||
|
this.existingAttributeToEdit = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFAttributeFromField(field: string) {
|
||||||
|
const [ type, name ] = field.split(".", 2);
|
||||||
|
const attrName = `${type.replace("s", "")}:${name}`;
|
||||||
|
return this.parentNote.getLabel(attrName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttributeFromField(field: string): Attribute | undefined {
|
||||||
|
const fAttribute = this.getFAttributeFromField(field);
|
||||||
|
if (fAttribute) {
|
||||||
|
return {
|
||||||
|
name: fAttribute.name,
|
||||||
|
value: fAttribute.value,
|
||||||
|
type: fAttribute.type,
|
||||||
|
isInheritable: fAttribute.isInheritable
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
133
apps/client/src/widgets/view_widgets/table_view/columns.spec.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { restoreExistingData } from "./columns";
|
||||||
|
import type { ColumnDefinition } from "tabulator-tables";
|
||||||
|
|
||||||
|
describe("restoreExistingData", () => {
|
||||||
|
it("maintains important columns properties", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", editor: "input" },
|
||||||
|
{ field: "noteId", title: "Note ID", formatter: "color", visible: false }
|
||||||
|
];
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", width: 300, visible: true },
|
||||||
|
{ field: "noteId", title: "Note ID", width: 200, visible: true }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored[0].editor).toBe("input");
|
||||||
|
expect(restored[1].formatter).toBe("color");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should restore existing column data", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", editor: "input" },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false }
|
||||||
|
];
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", width: 300, visible: true },
|
||||||
|
{ field: "noteId", title: "Note ID", width: 200, visible: true }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored[0].width).toBe(300);
|
||||||
|
expect(restored[1].width).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restores order of columns", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", editor: "input" },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false }
|
||||||
|
];
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "noteId", title: "Note ID", width: 200, visible: true },
|
||||||
|
{ field: "title", title: "Title", width: 300, visible: true }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored[0].field).toBe("noteId");
|
||||||
|
expect(restored[1].field).toBe("title");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("inserts new columns at given position", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", editor: "input" },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false },
|
||||||
|
{ field: "newColumn", title: "New Column", editor: "input" }
|
||||||
|
];
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", width: 300, visible: true },
|
||||||
|
{ field: "noteId", title: "Note ID", width: 200, visible: true }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs, 0);
|
||||||
|
expect(restored.length).toBe(3);
|
||||||
|
expect(restored[0].field).toBe("newColumn");
|
||||||
|
expect(restored[1].field).toBe("title");
|
||||||
|
expect(restored[2].field).toBe("noteId");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("inserts new columns at the end if no position is specified", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", editor: "input" },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false },
|
||||||
|
{ field: "newColumn", title: "New Column", editor: "input" }
|
||||||
|
];
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", width: 300, visible: true },
|
||||||
|
{ field: "noteId", title: "Note ID", width: 200, visible: true }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored.length).toBe(3);
|
||||||
|
expect(restored[0].field).toBe("title");
|
||||||
|
expect(restored[1].field).toBe("noteId");
|
||||||
|
expect(restored[2].field).toBe("newColumn");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports a rename", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", editor: "input" },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false },
|
||||||
|
{ field: "newColumn", title: "New Column", editor: "input" }
|
||||||
|
];
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ field: "title", title: "Title", width: 300, visible: true },
|
||||||
|
{ field: "noteId", title: "Note ID", width: 200, visible: true },
|
||||||
|
{ field: "oldColumn", title: "New Column", editor: "input" }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't alter the existing order", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ title: "#", headerSort: false, hozAlign: "center", resizable: false, frozen: true, rowHandle: false },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false },
|
||||||
|
{ field: "title", title: "Title", editor: "input", width: 400 }
|
||||||
|
]
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ title: "#", headerSort: false, hozAlign: "center", resizable: false, rowHandle: false },
|
||||||
|
{ field: "noteId", title: "Note ID", visible: false },
|
||||||
|
{ field: "title", title: "Title", editor: "input", width: 400 }
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored).toStrictEqual(newDefs);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows hiding the row number column", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ title: "#", headerSort: false, hozAlign: "center", resizable: false, frozen: true, rowHandle: false },
|
||||||
|
]
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ title: "#", headerSort: false, hozAlign: "center", resizable: false, rowHandle: false, visible: false },
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored[0].visible).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enforces size for non-resizable columns", () => {
|
||||||
|
const newDefs: ColumnDefinition[] = [
|
||||||
|
{ title: "#", resizable: false, width: "100px" },
|
||||||
|
]
|
||||||
|
const oldDefs: ColumnDefinition[] = [
|
||||||
|
{ title: "#", resizable: false, width: "120px" },
|
||||||
|
];
|
||||||
|
const restored = restoreExistingData(newDefs, oldDefs);
|
||||||
|
expect(restored[0].width).toStrictEqual("100px");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { RelationEditor } from "./relation_editor.js";
|
import { RelationEditor } from "./relation_editor.js";
|
||||||
import { NoteFormatter, NoteTitleFormatter } from "./formatters.js";
|
import { MonospaceFormatter, NoteFormatter, NoteTitleFormatter, RowNumberFormatter } from "./formatters.js";
|
||||||
import { applyHeaderMenu } from "./header-menu.js";
|
|
||||||
import type { ColumnDefinition } from "tabulator-tables";
|
import type { ColumnDefinition } from "tabulator-tables";
|
||||||
import { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
import { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
||||||
|
|
||||||
type ColumnType = LabelType | "relation";
|
type ColumnType = LabelType | "relation";
|
||||||
|
|
||||||
export interface PromotedAttributeInformation {
|
export interface AttributeDefinitionInformation {
|
||||||
name: string;
|
name: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
type?: ColumnType;
|
type?: ColumnType;
|
||||||
@@ -42,19 +41,30 @@ const labelTypeMappings: Record<ColumnType, Partial<ColumnDefinition>> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildColumnDefinitions(info: PromotedAttributeInformation[], existingColumnData?: ColumnDefinition[]) {
|
interface BuildColumnArgs {
|
||||||
const columnDefs: ColumnDefinition[] = [
|
info: AttributeDefinitionInformation[];
|
||||||
|
movableRows: boolean;
|
||||||
|
existingColumnData: ColumnDefinition[] | undefined;
|
||||||
|
rowNumberHint: number;
|
||||||
|
position?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildColumnDefinitions({ info, movableRows, existingColumnData, rowNumberHint, position }: BuildColumnArgs) {
|
||||||
|
let columnDefs: ColumnDefinition[] = [
|
||||||
{
|
{
|
||||||
title: "#",
|
title: "#",
|
||||||
formatter: "rownum",
|
|
||||||
headerSort: false,
|
headerSort: false,
|
||||||
hozAlign: "center",
|
hozAlign: "center",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
frozen: true
|
frozen: true,
|
||||||
|
rowHandle: movableRows,
|
||||||
|
width: calculateIndexColumnWidth(rowNumberHint, movableRows),
|
||||||
|
formatter: RowNumberFormatter(movableRows)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "noteId",
|
field: "noteId",
|
||||||
title: "Note ID",
|
title: "Note ID",
|
||||||
|
formatter: MonospaceFormatter,
|
||||||
visible: false
|
visible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -79,32 +89,59 @@ export function buildColumnDefinitions(info: PromotedAttributeInformation[], exi
|
|||||||
field,
|
field,
|
||||||
title: title ?? name,
|
title: title ?? name,
|
||||||
editor: "input",
|
editor: "input",
|
||||||
|
rowHandle: false,
|
||||||
...labelTypeMappings[type ?? "text"],
|
...labelTypeMappings[type ?? "text"],
|
||||||
});
|
});
|
||||||
seenFields.add(field);
|
seenFields.add(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyHeaderMenu(columnDefs);
|
|
||||||
if (existingColumnData) {
|
if (existingColumnData) {
|
||||||
restoreExistingData(columnDefs, existingColumnData);
|
columnDefs = restoreExistingData(columnDefs, existingColumnData, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
return columnDefs;
|
return columnDefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[]) {
|
export function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[], position?: number) {
|
||||||
const byField = new Map<string, ColumnDefinition>;
|
// 1. Keep existing columns, but restore their properties like width, visibility and order.
|
||||||
for (const def of oldDefs) {
|
const newItemsByField = new Map<string, ColumnDefinition>(
|
||||||
byField.set(def.field ?? "", def);
|
newDefs.map(def => [def.field!, def])
|
||||||
|
);
|
||||||
|
const existingColumns = oldDefs
|
||||||
|
.filter(item => (item.field && newItemsByField.has(item.field!)) || item.title === "#")
|
||||||
|
.map(oldItem => {
|
||||||
|
const data = newItemsByField.get(oldItem.field!)!;
|
||||||
|
if (oldItem.resizable !== false && oldItem.width !== undefined) {
|
||||||
|
data.width = oldItem.width;
|
||||||
}
|
}
|
||||||
|
if (oldItem.visible !== undefined) {
|
||||||
|
data.visible = oldItem.visible;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}) as ColumnDefinition[];
|
||||||
|
|
||||||
for (const newDef of newDefs) {
|
// 2. Determine new columns.
|
||||||
const oldDef = byField.get(newDef.field ?? "");
|
const existingFields = new Set(existingColumns.map(item => item.field));
|
||||||
if (!oldDef) {
|
const newColumns = newDefs
|
||||||
continue;
|
.filter(item => !existingFields.has(item.field!));
|
||||||
}
|
|
||||||
|
|
||||||
newDef.width = oldDef.width;
|
// Clamp position to a valid range
|
||||||
newDef.visible = oldDef.visible;
|
const insertPos = position !== undefined
|
||||||
}
|
? Math.min(Math.max(position, 0), existingColumns.length)
|
||||||
|
: existingColumns.length;
|
||||||
|
|
||||||
|
// 3. Insert new columns at the specified position
|
||||||
|
return [
|
||||||
|
...existingColumns.slice(0, insertPos),
|
||||||
|
...newColumns,
|
||||||
|
...existingColumns.slice(insertPos)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateIndexColumnWidth(rowNumberHint: number, movableRows: boolean): number {
|
||||||
|
let columnWidth = 16 * (rowNumberHint.toString().length || 1);
|
||||||
|
if (movableRows) {
|
||||||
|
columnWidth += 32;
|
||||||
|
}
|
||||||
|
return columnWidth;
|
||||||
}
|
}
|
||||||
|
|||||||
277
apps/client/src/widgets/view_widgets/table_view/context_menu.ts
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
import { ColumnComponent, RowComponent, Tabulator } from "tabulator-tables";
|
||||||
|
import contextMenu, { MenuItem } from "../../../menus/context_menu.js";
|
||||||
|
import { TableData } from "./rows.js";
|
||||||
|
import branches from "../../../services/branches.js";
|
||||||
|
import { t } from "../../../services/i18n.js";
|
||||||
|
import link_context_menu from "../../../menus/link_context_menu.js";
|
||||||
|
import type FNote from "../../../entities/fnote.js";
|
||||||
|
import froca from "../../../services/froca.js";
|
||||||
|
import type Component from "../../../components/component.js";
|
||||||
|
|
||||||
|
export function setupContextMenu(tabulator: Tabulator, parentNote: FNote) {
|
||||||
|
tabulator.on("rowContext", (e, row) => showRowContextMenu(e, row, parentNote, tabulator));
|
||||||
|
tabulator.on("headerContext", (e, col) => showColumnContextMenu(e, col, parentNote, tabulator));
|
||||||
|
tabulator.on("renderComplete", () => {
|
||||||
|
const headerRow = tabulator.element.querySelector(".tabulator-header-contents");
|
||||||
|
headerRow?.addEventListener("contextmenu", (e) => showHeaderContextMenu(e, tabulator));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pressing the expand button prevents bubbling and the context menu remains menu when it shouldn't.
|
||||||
|
if (tabulator.options.dataTree) {
|
||||||
|
const dismissContextMenu = () => contextMenu.hide();
|
||||||
|
tabulator.on("dataTreeRowExpanded", dismissContextMenu);
|
||||||
|
tabulator.on("dataTreeRowCollapsed", dismissContextMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showColumnContextMenu(_e: UIEvent, column: ColumnComponent, parentNote: FNote, tabulator: Tabulator) {
|
||||||
|
const e = _e as MouseEvent;
|
||||||
|
const { title, field } = column.getDefinition();
|
||||||
|
|
||||||
|
const sorters = tabulator.getSorters();
|
||||||
|
const sorter = sorters.find(sorter => sorter.field === field);
|
||||||
|
const isUserDefinedColumn = (!!field && (field?.startsWith("labels.") || field?.startsWith("relations.")));
|
||||||
|
|
||||||
|
contextMenu.show({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: t("table_view.sort-column-by", { title }),
|
||||||
|
enabled: !!field,
|
||||||
|
uiIcon: "bx bx-sort-alt-2",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: t("table_view.sort-column-ascending"),
|
||||||
|
checked: (sorter?.dir === "asc"),
|
||||||
|
uiIcon: "bx bx-empty",
|
||||||
|
handler: () => tabulator.setSort([
|
||||||
|
{
|
||||||
|
column: field!,
|
||||||
|
dir: "asc",
|
||||||
|
}
|
||||||
|
])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.sort-column-descending"),
|
||||||
|
checked: (sorter?.dir === "desc"),
|
||||||
|
uiIcon: "bx bx-empty",
|
||||||
|
handler: () => tabulator.setSort([
|
||||||
|
{
|
||||||
|
column: field!,
|
||||||
|
dir: "desc"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.sort-column-clear"),
|
||||||
|
enabled: sorters.length > 0,
|
||||||
|
uiIcon: "bx bx-x-circle",
|
||||||
|
handler: () => tabulator.clearSort()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "----"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.hide-column", { title }),
|
||||||
|
uiIcon: "bx bx-hide",
|
||||||
|
handler: () => column.hide()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.show-hide-columns"),
|
||||||
|
uiIcon: "bx bx-columns",
|
||||||
|
items: buildColumnItems(tabulator)
|
||||||
|
},
|
||||||
|
{ title: "----" },
|
||||||
|
{
|
||||||
|
title: t("table_view.add-column-to-the-left"),
|
||||||
|
uiIcon: "bx bx-horizontal-left",
|
||||||
|
enabled: !column.getDefinition().frozen,
|
||||||
|
items: buildInsertSubmenu(e, column, "before"),
|
||||||
|
handler: () => getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
||||||
|
referenceColumn: column
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.add-column-to-the-right"),
|
||||||
|
uiIcon: "bx bx-horizontal-right",
|
||||||
|
items: buildInsertSubmenu(e, column, "after"),
|
||||||
|
handler: () => getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
||||||
|
referenceColumn: column,
|
||||||
|
direction: "after"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ title: "----" },
|
||||||
|
{
|
||||||
|
title: t("table_view.edit-column"),
|
||||||
|
uiIcon: "bx bxs-edit-alt",
|
||||||
|
enabled: isUserDefinedColumn,
|
||||||
|
handler: () => getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
||||||
|
referenceColumn: column,
|
||||||
|
columnToEdit: column
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.delete-column"),
|
||||||
|
uiIcon: "bx bx-trash",
|
||||||
|
enabled: isUserDefinedColumn,
|
||||||
|
handler: () => getParentComponent(e)?.triggerCommand("deleteTableColumn", {
|
||||||
|
columnToDelete: column
|
||||||
|
})
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectMenuItemHandler() {},
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a context menu which has options dedicated to the header area (the part where the columns are, but in the empty space).
|
||||||
|
* Provides generic options such as toggling columns.
|
||||||
|
*/
|
||||||
|
function showHeaderContextMenu(_e: Event, tabulator: Tabulator) {
|
||||||
|
const e = _e as MouseEvent;
|
||||||
|
contextMenu.show({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: t("table_view.show-hide-columns"),
|
||||||
|
uiIcon: "bx bx-columns",
|
||||||
|
items: buildColumnItems(tabulator)
|
||||||
|
},
|
||||||
|
{ title: "----" },
|
||||||
|
{
|
||||||
|
title: t("table_view.new-column"),
|
||||||
|
uiIcon: "bx bx-empty",
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
...buildInsertSubmenu(e)
|
||||||
|
],
|
||||||
|
selectMenuItemHandler() {},
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showRowContextMenu(_e: UIEvent, row: RowComponent, parentNote: FNote, tabulator: Tabulator) {
|
||||||
|
const e = _e as MouseEvent;
|
||||||
|
const rowData = row.getData() as TableData;
|
||||||
|
|
||||||
|
let parentNoteId: string = parentNote.noteId;
|
||||||
|
|
||||||
|
if (tabulator.options.dataTree) {
|
||||||
|
const parentRow = row.getTreeParent();
|
||||||
|
if (parentRow) {
|
||||||
|
parentNoteId = parentRow.getData().noteId as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMenu.show({
|
||||||
|
items: [
|
||||||
|
...link_context_menu.getItems(),
|
||||||
|
{ title: "----" },
|
||||||
|
{
|
||||||
|
title: t("table_view.row-insert-above"),
|
||||||
|
uiIcon: "bx bx-horizontal-left bx-rotate-90",
|
||||||
|
handler: () => getParentComponent(e)?.triggerCommand("addNewRow", {
|
||||||
|
parentNotePath: parentNoteId,
|
||||||
|
customOpts: {
|
||||||
|
target: "before",
|
||||||
|
targetBranchId: rowData.branchId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.row-insert-child"),
|
||||||
|
uiIcon: "bx bx-subdirectory-right",
|
||||||
|
handler: async () => {
|
||||||
|
const branchId = row.getData().branchId;
|
||||||
|
const note = await froca.getBranch(branchId)?.getNote();
|
||||||
|
getParentComponent(e)?.triggerCommand("addNewRow", {
|
||||||
|
parentNotePath: note?.noteId,
|
||||||
|
customOpts: {
|
||||||
|
target: "after",
|
||||||
|
targetBranchId: branchId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.row-insert-below"),
|
||||||
|
uiIcon: "bx bx-horizontal-left bx-rotate-270",
|
||||||
|
handler: () => getParentComponent(e)?.triggerCommand("addNewRow", {
|
||||||
|
parentNotePath: parentNoteId,
|
||||||
|
customOpts: {
|
||||||
|
target: "after",
|
||||||
|
targetBranchId: rowData.branchId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ title: "----" },
|
||||||
|
{
|
||||||
|
title: t("table_context_menu.delete_row"),
|
||||||
|
uiIcon: "bx bx-trash",
|
||||||
|
handler: () => branches.deleteNotes([ rowData.branchId ], false, false)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, rowData.noteId),
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParentComponent(e: MouseEvent) {
|
||||||
|
if (!e.target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $(e.target)
|
||||||
|
.closest(".component")
|
||||||
|
.prop("component") as Component;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildColumnItems(tabulator: Tabulator) {
|
||||||
|
const items: MenuItem<unknown>[] = [];
|
||||||
|
for (const column of tabulator.getColumns()) {
|
||||||
|
const { title } = column.getDefinition();
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
title,
|
||||||
|
checked: column.isVisible(),
|
||||||
|
uiIcon: "bx bx-empty",
|
||||||
|
handler: () => column.toggle()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildInsertSubmenu(e: MouseEvent, referenceColumn?: ColumnComponent, direction?: "before" | "after"): MenuItem<unknown>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t("table_view.new-column-label"),
|
||||||
|
uiIcon: "bx bx-hash",
|
||||||
|
handler: () => {
|
||||||
|
getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
||||||
|
referenceColumn,
|
||||||
|
type: "label",
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("table_view.new-column-relation"),
|
||||||
|
uiIcon: "bx bx-transfer",
|
||||||
|
handler: () => {
|
||||||
|
getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
||||||
|
referenceColumn,
|
||||||
|
type: "relation",
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,12 +11,12 @@ export default function buildFooter(parentNote: FNote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return /*html*/`\
|
return /*html*/`\
|
||||||
<button class="btn btn-sm" style="padding: 0px 10px 0px 10px;" data-trigger-command="addNewRow">
|
<button class="btn btn-sm" data-trigger-command="addNewRow">
|
||||||
<span class="bx bx-plus"></span> ${t("table_view.new-row")}
|
<span class="bx bx-plus"></span> ${t("table_view.new-row")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-sm" style="padding: 0px 10px 0px 10px;" data-trigger-command="addNoteListItem">
|
<button class="btn btn-sm" data-trigger-command="addNewTableColumn">
|
||||||
<span class="bx bx-columns"></span> ${t("table_view.new-column")}
|
<span class="bx bx-carousel"></span> ${t("table_view.new-column")}
|
||||||
</button>
|
</button>
|
||||||
`.trimStart();
|
`.trimStart();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,89 @@
|
|||||||
import { CellComponent } from "tabulator-tables";
|
import { CellComponent } from "tabulator-tables";
|
||||||
import { loadReferenceLinkTitle } from "../../../services/link.js";
|
import froca from "../../../services/froca.js";
|
||||||
|
import FNote from "../../../entities/fnote.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom formatter to represent a note, with the icon and note title being rendered.
|
* Custom formatter to represent a note, with the icon and note title being rendered.
|
||||||
*
|
*
|
||||||
* The value of the cell must be the note ID.
|
* The value of the cell must be the note ID.
|
||||||
*/
|
*/
|
||||||
export function NoteFormatter(cell: CellComponent, _formatterParams, onRendered) {
|
export function NoteFormatter(cell: CellComponent, _formatterParams, onRendered): string {
|
||||||
let noteId = cell.getValue();
|
let noteId = cell.getValue();
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildLink(note: FNote | undefined) {
|
||||||
|
if (!note) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconClass = note.getIcon();
|
||||||
|
const title = note.title;
|
||||||
|
const { $noteRef } = buildNoteLink(noteId, title, iconClass, note.getColorClass());
|
||||||
|
return $noteRef[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cachedNote = froca.getNoteFromCache(noteId);
|
||||||
|
if (cachedNote) {
|
||||||
|
// Cache hit, build the link immediately
|
||||||
|
const el = buildLink(cachedNote);
|
||||||
|
return el?.outerHTML ?? "";
|
||||||
|
} else {
|
||||||
|
// Cache miss, load the note asynchronously
|
||||||
onRendered(async () => {
|
onRendered(async () => {
|
||||||
const { $noteRef, href } = buildNoteLink(noteId);
|
const note = await froca.getNote(noteId);
|
||||||
await loadReferenceLinkTitle($noteRef, href);
|
if (!note) {
|
||||||
cell.getElement().appendChild($noteRef[0]);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = buildLink(note);
|
||||||
|
if (el) {
|
||||||
|
cell.getElement().appendChild(el);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom formatter for the note title that is quite similar to {@link NoteFormatter}, but where the title and icons are read from separate fields.
|
* Custom formatter for the note title that is quite similar to {@link NoteFormatter}, but where the title and icons are read from separate fields.
|
||||||
*/
|
*/
|
||||||
export function NoteTitleFormatter(cell: CellComponent) {
|
export function NoteTitleFormatter(cell: CellComponent) {
|
||||||
const { noteId, iconClass } = cell.getRow().getData();
|
const { noteId, iconClass, colorClass } = cell.getRow().getData();
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const { $noteRef } = buildNoteLink(noteId);
|
const { $noteRef } = buildNoteLink(noteId, cell.getValue(), iconClass, colorClass);
|
||||||
$noteRef.text(cell.getValue());
|
|
||||||
$noteRef.prepend($("<span>").addClass(iconClass));
|
|
||||||
|
|
||||||
return $noteRef[0].outerHTML;
|
return $noteRef[0].outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNoteLink(noteId: string) {
|
export function RowNumberFormatter(draggableRows: boolean) {
|
||||||
|
return (cell: CellComponent) => {
|
||||||
|
let html = "";
|
||||||
|
if (draggableRows) {
|
||||||
|
html += `<span class="bx bx-dots-vertical-rounded"></span> `;
|
||||||
|
}
|
||||||
|
html += cell.getRow().getPosition(true);
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MonospaceFormatter(cell: CellComponent) {
|
||||||
|
return `<code>${cell.getValue()}</code>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildNoteLink(noteId: string, title: string, iconClass: string, colorClass?: string) {
|
||||||
const $noteRef = $("<span>");
|
const $noteRef = $("<span>");
|
||||||
const href = `#root/${noteId}`;
|
const href = `#root/${noteId}`;
|
||||||
$noteRef.addClass("reference-link");
|
$noteRef.addClass("reference-link");
|
||||||
$noteRef.attr("data-href", href);
|
$noteRef.attr("data-href", href);
|
||||||
|
$noteRef.text(title);
|
||||||
|
$noteRef.prepend($("<span>").addClass(iconClass));
|
||||||
|
if (colorClass) {
|
||||||
|
$noteRef.addClass(colorClass);
|
||||||
|
}
|
||||||
return { $noteRef, href };
|
return { $noteRef, href };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import type { ColumnComponent, ColumnDefinition, MenuObject, Tabulator } from "tabulator-tables";
|
|
||||||
|
|
||||||
export function applyHeaderMenu(columns: ColumnDefinition[]) {
|
|
||||||
for (let column of columns) {
|
|
||||||
if (column.headerSort !== false) {
|
|
||||||
column.headerMenu = headerMenu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function headerMenu(this: Tabulator) {
|
|
||||||
const menu: MenuObject<ColumnComponent>[] = [];
|
|
||||||
const columns = this.getColumns();
|
|
||||||
|
|
||||||
for (let column of columns) {
|
|
||||||
//create checkbox element using font awesome icons
|
|
||||||
let icon = document.createElement("i");
|
|
||||||
icon.classList.add("bx");
|
|
||||||
icon.classList.add(column.isVisible() ? "bx-check" : "bx-empty");
|
|
||||||
|
|
||||||
//build label
|
|
||||||
let label = document.createElement("span");
|
|
||||||
let title = document.createElement("span");
|
|
||||||
|
|
||||||
title.textContent = " " + column.getDefinition().title;
|
|
||||||
|
|
||||||
label.appendChild(icon);
|
|
||||||
label.appendChild(title);
|
|
||||||
|
|
||||||
//create menu item
|
|
||||||
menu.push({
|
|
||||||
label: label,
|
|
||||||
action: function (e) {
|
|
||||||
//prevent menu closing
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
//toggle current column visibility
|
|
||||||
column.toggle();
|
|
||||||
|
|
||||||
//change menu item icon
|
|
||||||
if (column.isVisible()) {
|
|
||||||
icon.classList.remove("bx-empty");
|
|
||||||
icon.classList.add("bx-check");
|
|
||||||
} else {
|
|
||||||
icon.classList.remove("bx-check");
|
|
||||||
icon.classList.add("bx-empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
};
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import froca from "../../../services/froca.js";
|
|
||||||
import ViewMode, { type ViewModeArgs } from "../view_mode.js";
|
import ViewMode, { type ViewModeArgs } from "../view_mode.js";
|
||||||
import attributes, { setAttribute, setLabel } from "../../../services/attributes.js";
|
import attributes from "../../../services/attributes.js";
|
||||||
import server from "../../../services/server.js";
|
|
||||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||||
import type { CommandListenerData, EventData } from "../../../components/app_context.js";
|
import type { EventData } from "../../../components/app_context.js";
|
||||||
import type { Attribute } from "../../../services/attribute_parser.js";
|
import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule, Options, RowComponent, ColumnComponent} from 'tabulator-tables';
|
||||||
import note_create from "../../../services/note_create.js";
|
import "tabulator-tables/dist/css/tabulator.css";
|
||||||
import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MenuModule, MoveRowsModule, ColumnDefinition} from 'tabulator-tables';
|
import "../../../../src/stylesheets/table.css";
|
||||||
import "tabulator-tables/dist/css/tabulator_bootstrap5.min.css";
|
|
||||||
import { canReorderRows, configureReorderingRows } from "./dragging.js";
|
import { canReorderRows, configureReorderingRows } from "./dragging.js";
|
||||||
import buildFooter from "./footer.js";
|
import buildFooter from "./footer.js";
|
||||||
import getPromotedAttributeInformation, { buildRowDefinitions } from "./rows.js";
|
import getAttributeDefinitionInformation, { buildRowDefinitions } from "./rows.js";
|
||||||
import { buildColumnDefinitions } from "./columns.js";
|
import { AttributeDefinitionInformation, buildColumnDefinitions } from "./columns.js";
|
||||||
|
import { setupContextMenu } from "./context_menu.js";
|
||||||
|
import TableColumnEditing from "./col_editing.js";
|
||||||
|
import TableRowEditing from "./row_editing.js";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="table-view">
|
<div class="table-view">
|
||||||
@@ -63,6 +63,26 @@ const TPL = /*html*/`
|
|||||||
justify-content: left;
|
justify-content: left;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabulator button.tree-expand,
|
||||||
|
.tabulator button.tree-collapse {
|
||||||
|
display: inline-block;
|
||||||
|
appearance: none;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
width: 1.5em;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabulator button.tree-expand span,
|
||||||
|
.tabulator button.tree-collapse span {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="table-view-container"></div>
|
<div class="table-view-container"></div>
|
||||||
@@ -79,29 +99,24 @@ export default class TableView extends ViewMode<StateInfo> {
|
|||||||
|
|
||||||
private $root: JQuery<HTMLElement>;
|
private $root: JQuery<HTMLElement>;
|
||||||
private $container: JQuery<HTMLElement>;
|
private $container: JQuery<HTMLElement>;
|
||||||
private args: ViewModeArgs;
|
|
||||||
private spacedUpdate: SpacedUpdate;
|
private spacedUpdate: SpacedUpdate;
|
||||||
private api?: Tabulator;
|
private api?: Tabulator;
|
||||||
private newAttribute?: Attribute;
|
|
||||||
private persistentData: StateInfo["tableData"];
|
private persistentData: StateInfo["tableData"];
|
||||||
/** If set to a note ID, whenever the rows will be updated, the title of the note will be automatically focused for editing. */
|
private colEditing?: TableColumnEditing;
|
||||||
private noteIdToEdit?: string;
|
private rowEditing?: TableRowEditing;
|
||||||
|
private maxDepth: number = -1;
|
||||||
|
private rowNumberHint: number = 1;
|
||||||
|
|
||||||
constructor(args: ViewModeArgs) {
|
constructor(args: ViewModeArgs) {
|
||||||
super(args, "table");
|
super(args, "table");
|
||||||
|
|
||||||
this.$root = $(TPL);
|
this.$root = $(TPL);
|
||||||
this.$container = this.$root.find(".table-view-container");
|
this.$container = this.$root.find(".table-view-container");
|
||||||
this.args = args;
|
|
||||||
this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000);
|
this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000);
|
||||||
this.persistentData = {};
|
this.persistentData = {};
|
||||||
args.$parent.append(this.$root);
|
args.$parent.append(this.$root);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFullHeight(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderList() {
|
async renderList() {
|
||||||
this.$container.empty();
|
this.$container.empty();
|
||||||
this.renderTable(this.$container[0]);
|
this.renderTable(this.$container[0]);
|
||||||
@@ -109,29 +124,34 @@ export default class TableView extends ViewMode<StateInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async renderTable(el: HTMLElement) {
|
private async renderTable(el: HTMLElement) {
|
||||||
const modules = [SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, MenuModule];
|
const info = getAttributeDefinitionInformation(this.parentNote);
|
||||||
|
const modules = [ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ];
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
Tabulator.registerModule(module);
|
Tabulator.registerModule(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialize(el);
|
this.initialize(el, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initialize(el: HTMLElement) {
|
private async initialize(el: HTMLElement, info: AttributeDefinitionInformation[]) {
|
||||||
const notes = await froca.getNotes(this.args.noteIds);
|
|
||||||
const info = getPromotedAttributeInformation(this.parentNote);
|
|
||||||
|
|
||||||
const viewStorage = await this.viewStorage.restore();
|
const viewStorage = await this.viewStorage.restore();
|
||||||
this.persistentData = viewStorage?.tableData || {};
|
this.persistentData = viewStorage?.tableData || {};
|
||||||
|
|
||||||
const columnDefs = buildColumnDefinitions(info);
|
this.maxDepth = parseInt(this.parentNote.getLabelValue("maxNestingDepth") ?? "-1", 10);
|
||||||
const movableRows = canReorderRows(this.parentNote);
|
const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(this.parentNote, info, this.maxDepth);
|
||||||
|
this.rowNumberHint = rowNumber;
|
||||||
this.api = new Tabulator(el, {
|
const movableRows = canReorderRows(this.parentNote) && !hasChildren;
|
||||||
|
const columnDefs = buildColumnDefinitions({
|
||||||
|
info,
|
||||||
|
movableRows,
|
||||||
|
existingColumnData: this.persistentData.columns,
|
||||||
|
rowNumberHint: this.rowNumberHint
|
||||||
|
});
|
||||||
|
let opts: Options = {
|
||||||
layout: "fitDataFill",
|
layout: "fitDataFill",
|
||||||
index: "noteId",
|
index: "branchId",
|
||||||
columns: columnDefs,
|
columns: columnDefs,
|
||||||
data: await buildRowDefinitions(this.parentNote, notes, info),
|
data: rowData,
|
||||||
persistence: true,
|
persistence: true,
|
||||||
movableColumns: true,
|
movableColumns: true,
|
||||||
movableRows,
|
movableRows,
|
||||||
@@ -141,9 +161,30 @@ export default class TableView extends ViewMode<StateInfo> {
|
|||||||
this.spacedUpdate.scheduleUpdate();
|
this.spacedUpdate.scheduleUpdate();
|
||||||
},
|
},
|
||||||
persistenceReaderFunc: (_id, type: string) => this.persistentData?.[type],
|
persistenceReaderFunc: (_id, type: string) => this.persistentData?.[type],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
opts = {
|
||||||
|
...opts,
|
||||||
|
dataTree: hasChildren,
|
||||||
|
dataTreeStartExpanded: true,
|
||||||
|
dataTreeBranchElement: false,
|
||||||
|
dataTreeElementColumn: "title",
|
||||||
|
dataTreeChildIndent: 20,
|
||||||
|
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
|
||||||
|
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api = new Tabulator(el, opts);
|
||||||
|
|
||||||
|
this.colEditing = new TableColumnEditing(this.args.$parent, this.args.parentNote, this.api);
|
||||||
|
this.rowEditing = new TableRowEditing(this.api, this.args.parentNotePath!);
|
||||||
|
|
||||||
|
if (movableRows) {
|
||||||
configureReorderingRows(this.api);
|
configureReorderingRows(this.api);
|
||||||
this.setupEditing();
|
}
|
||||||
|
setupContextMenu(this.api, this.parentNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSave() {
|
private onSave() {
|
||||||
@@ -152,82 +193,35 @@ export default class TableView extends ViewMode<StateInfo> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupEditing() {
|
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
this.api!.on("cellEdited", async (cell) => {
|
|
||||||
const noteId = cell.getRow().getData().noteId;
|
|
||||||
const field = cell.getField();
|
|
||||||
const newValue = cell.getValue();
|
|
||||||
|
|
||||||
if (field === "title") {
|
|
||||||
server.put(`notes/${noteId}/title`, { title: newValue });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.includes(".")) {
|
|
||||||
const [ type, name ] = field.split(".", 2);
|
|
||||||
if (type === "labels") {
|
|
||||||
setLabel(noteId, name, newValue);
|
|
||||||
} else if (type === "relations") {
|
|
||||||
const note = await froca.getNote(noteId);
|
|
||||||
if (note) {
|
|
||||||
setAttribute(note, "relation", name, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async reloadAttributesCommand() {
|
|
||||||
console.log("Reload attributes");
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateAttributeListCommand({ attributes }: CommandListenerData<"updateAttributeList">) {
|
|
||||||
this.newAttribute = attributes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveAttributesCommand() {
|
|
||||||
if (!this.newAttribute) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, value } = this.newAttribute;
|
|
||||||
attributes.addLabel(this.parentNote.noteId, name, value, true);
|
|
||||||
console.log("Save attributes", this.newAttribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewRowCommand() {
|
|
||||||
const parentNotePath = this.args.parentNotePath;
|
|
||||||
if (parentNotePath) {
|
|
||||||
note_create.createNote(parentNotePath, {
|
|
||||||
activate: false
|
|
||||||
}).then(({ note }) => {
|
|
||||||
if (!note) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.noteIdToEdit = note.noteId;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">): boolean | void {
|
|
||||||
if (!this.api) {
|
if (!this.api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force a refresh if sorted is changed since we need to disable reordering.
|
||||||
|
if (loadResults.getAttributeRows().find(a => a.name === "sorted" && attributes.isAffecting(a, this.parentNote))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh if promoted attributes get changed.
|
// Refresh if promoted attributes get changed.
|
||||||
if (loadResults.getAttributeRows().find(attr =>
|
if (loadResults.getAttributeRows().find(attr =>
|
||||||
attr.type === "label" &&
|
attr.type === "label" &&
|
||||||
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
||||||
attributes.isAffecting(attr, this.parentNote))) {
|
attributes.isAffecting(attr, this.parentNote))) {
|
||||||
this.#manageColumnUpdate();
|
this.#manageColumnUpdate();
|
||||||
|
return await this.#manageRowsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId)) {
|
// Refresh max depth
|
||||||
this.#manageRowsUpdate();
|
if (loadResults.getAttributeRows().find(attr => attr.type === "label" && attr.name === "maxNestingDepth" && attributes.isAffecting(attr, this.parentNote))) {
|
||||||
|
this.maxDepth = parseInt(this.parentNote.getLabelValue("maxNestingDepth") ?? "-1", 10);
|
||||||
|
return await this.#manageRowsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadResults.getAttributeRows().some(attr => this.args.noteIds.includes(attr.noteId!))) {
|
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId || this.noteIds.includes(branch.parentNoteId ?? ""))
|
||||||
this.#manageRowsUpdate();
|
|| loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId))
|
||||||
|
|| loadResults.getAttributeRows().some(attr => this.noteIds.includes(attr.noteId!))) {
|
||||||
|
return await this.#manageRowsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -238,27 +232,40 @@ export default class TableView extends ViewMode<StateInfo> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = getPromotedAttributeInformation(this.parentNote);
|
const info = getAttributeDefinitionInformation(this.parentNote);
|
||||||
const columnDefs = buildColumnDefinitions(info, this.persistentData?.columns);
|
const columnDefs = buildColumnDefinitions({
|
||||||
|
info,
|
||||||
|
movableRows: !!this.api.options.movableRows,
|
||||||
|
existingColumnData: this.persistentData?.columns,
|
||||||
|
rowNumberHint: this.rowNumberHint,
|
||||||
|
position: this.colEditing?.getNewAttributePosition()
|
||||||
|
});
|
||||||
this.api.setColumns(columnDefs);
|
this.api.setColumns(columnDefs);
|
||||||
|
this.colEditing?.resetNewAttributePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addNewRowCommand(e) { this.rowEditing?.addNewRowCommand(e); }
|
||||||
|
addNewTableColumnCommand(e) { this.colEditing?.addNewTableColumnCommand(e); }
|
||||||
|
deleteTableColumnCommand(e) { this.colEditing?.deleteTableColumnCommand(e); }
|
||||||
|
updateAttributeListCommand(e) { this.colEditing?.updateAttributeListCommand(e); }
|
||||||
|
saveAttributesCommand() { this.colEditing?.saveAttributesCommand(); }
|
||||||
|
|
||||||
async #manageRowsUpdate() {
|
async #manageRowsUpdate() {
|
||||||
if (!this.api) {
|
if (!this.api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await froca.getNotes(this.args.noteIds);
|
const info = getAttributeDefinitionInformation(this.parentNote);
|
||||||
const info = getPromotedAttributeInformation(this.parentNote);
|
const { definitions, hasSubtree, rowNumber } = await buildRowDefinitions(this.parentNote, info, this.maxDepth);
|
||||||
this.api.replaceData(await buildRowDefinitions(this.parentNote, notes, info));
|
this.rowNumberHint = rowNumber;
|
||||||
|
|
||||||
if (this.noteIdToEdit) {
|
// Force a refresh if the data tree needs enabling/disabling.
|
||||||
const row = this.api?.getRows().find(r => r.getData().noteId === this.noteIdToEdit);
|
if (this.api.options.dataTree !== hasSubtree) {
|
||||||
if (row) {
|
return true;
|
||||||
row.getCell("title").edit();
|
|
||||||
}
|
|
||||||
this.noteIdToEdit = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.api.replaceData(definitions);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,24 +21,29 @@ export function RelationEditor(cell: CellComponent, onRendered, success, cancel,
|
|||||||
editor.style.boxSizing = "border-box";
|
editor.style.boxSizing = "border-box";
|
||||||
|
|
||||||
//Set value of editor to the current value of the cell
|
//Set value of editor to the current value of the cell
|
||||||
const noteId = cell.getValue();
|
const originalNoteId = cell.getValue();
|
||||||
if (noteId) {
|
if (originalNoteId) {
|
||||||
const note = froca.getNoteFromCache(noteId);
|
const note = froca.getNoteFromCache(originalNoteId);
|
||||||
editor.value = note.title;
|
editor.value = note.title;
|
||||||
|
} else {
|
||||||
|
editor.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
//set focus on the select box when the editor is selected
|
//set focus on the select box when the editor is selected
|
||||||
onRendered(function(){
|
onRendered(function(){
|
||||||
|
let newNoteId = originalNoteId;
|
||||||
|
|
||||||
note_autocomplete.initNoteAutocomplete($editor, {
|
note_autocomplete.initNoteAutocomplete($editor, {
|
||||||
allowCreatingNotes: true
|
allowCreatingNotes: true,
|
||||||
|
hideAllButtons: true
|
||||||
}).on("autocomplete:noteselected", (event, suggestion, dataset) => {
|
}).on("autocomplete:noteselected", (event, suggestion, dataset) => {
|
||||||
const notePath = suggestion.notePath;
|
const notePath = suggestion.notePath;
|
||||||
if (!notePath) {
|
newNoteId = (notePath ?? "").split("/").at(-1);
|
||||||
return;
|
}).on("blur", () => {
|
||||||
|
if (!editor.value) {
|
||||||
|
newNoteId = "";
|
||||||
}
|
}
|
||||||
|
success(newNoteId);
|
||||||
const noteId = notePath.split("/").at(-1);
|
|
||||||
success(noteId);
|
|
||||||
});
|
});
|
||||||
editor.focus();
|
editor.focus();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { RowComponent, Tabulator } from "tabulator-tables";
|
||||||
|
import Component from "../../../components/component.js";
|
||||||
|
import { setAttribute, setLabel } from "../../../services/attributes.js";
|
||||||
|
import server from "../../../services/server.js";
|
||||||
|
import froca from "../../../services/froca.js";
|
||||||
|
import note_create, { CreateNoteOpts } from "../../../services/note_create.js";
|
||||||
|
import { CommandListenerData } from "../../../components/app_context.js";
|
||||||
|
|
||||||
|
export default class TableRowEditing extends Component {
|
||||||
|
|
||||||
|
private parentNotePath: string;
|
||||||
|
private api: Tabulator;
|
||||||
|
|
||||||
|
constructor(api: Tabulator, parentNotePath: string) {
|
||||||
|
super();
|
||||||
|
this.api = api;
|
||||||
|
this.parentNotePath = parentNotePath;
|
||||||
|
api.on("cellEdited", async (cell) => {
|
||||||
|
const noteId = cell.getRow().getData().noteId;
|
||||||
|
const field = cell.getField();
|
||||||
|
let newValue = cell.getValue();
|
||||||
|
|
||||||
|
if (field === "title") {
|
||||||
|
server.put(`notes/${noteId}/title`, { title: newValue });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.includes(".")) {
|
||||||
|
const [ type, name ] = field.split(".", 2);
|
||||||
|
if (type === "labels") {
|
||||||
|
if (typeof newValue === "boolean") {
|
||||||
|
newValue = newValue ? "true" : "false";
|
||||||
|
}
|
||||||
|
setLabel(noteId, name, newValue);
|
||||||
|
} else if (type === "relations") {
|
||||||
|
const note = await froca.getNote(noteId);
|
||||||
|
if (note) {
|
||||||
|
setAttribute(note, "relation", name, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewRowCommand({ customOpts, parentNotePath: customNotePath }: CommandListenerData<"addNewRow">) {
|
||||||
|
const parentNotePath = customNotePath ?? this.parentNotePath;
|
||||||
|
if (parentNotePath) {
|
||||||
|
const opts: CreateNoteOpts = {
|
||||||
|
activate: false,
|
||||||
|
...customOpts
|
||||||
|
}
|
||||||
|
note_create.createNote(parentNotePath, opts).then(({ branch }) => {
|
||||||
|
if (branch) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.focusOnBranch(branch?.branchId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusOnBranch(branchId: string) {
|
||||||
|
if (!this.api) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = findRowDataById(this.api.getRows(), branchId);
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the parent tree if any.
|
||||||
|
if (this.api.options.dataTree) {
|
||||||
|
const parent = row.getTreeParent();
|
||||||
|
if (parent) {
|
||||||
|
parent.treeExpand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.getCell("title").edit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRowDataById(rows: RowComponent[], branchId: string): RowComponent | null {
|
||||||
|
for (let row of rows) {
|
||||||
|
const item = row.getIndex() as string;
|
||||||
|
|
||||||
|
if (item === branchId) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = findRowDataById(row.getTreeChildren(), branchId);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import FNote from "../../../entities/fnote.js";
|
import FNote from "../../../entities/fnote.js";
|
||||||
import type { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
import type { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
||||||
import type { PromotedAttributeInformation } from "./columns.js";
|
import type { AttributeDefinitionInformation } from "./columns.js";
|
||||||
|
|
||||||
export type TableData = {
|
export type TableData = {
|
||||||
iconClass: string;
|
iconClass: string;
|
||||||
@@ -9,11 +9,17 @@ export type TableData = {
|
|||||||
labels: Record<string, boolean | string | null>;
|
labels: Record<string, boolean | string | null>;
|
||||||
relations: Record<string, boolean | string | null>;
|
relations: Record<string, boolean | string | null>;
|
||||||
branchId: string;
|
branchId: string;
|
||||||
|
colorClass: string | undefined;
|
||||||
|
_children?: TableData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function buildRowDefinitions(parentNote: FNote, notes: FNote[], infos: PromotedAttributeInformation[]) {
|
export async function buildRowDefinitions(parentNote: FNote, infos: AttributeDefinitionInformation[], maxDepth = -1, currentDepth = 0) {
|
||||||
const definitions: TableData[] = [];
|
const definitions: TableData[] = [];
|
||||||
for (const branch of parentNote.getChildBranches()) {
|
const childBranches = parentNote.getChildBranches();
|
||||||
|
let hasSubtree = false;
|
||||||
|
let rowNumber = childBranches.length;
|
||||||
|
|
||||||
|
for (const branch of childBranches) {
|
||||||
const note = await branch.getNote();
|
const note = await branch.getNote();
|
||||||
if (!note) {
|
if (!note) {
|
||||||
continue; // Skip if the note is not found
|
continue; // Skip if the note is not found
|
||||||
@@ -24,36 +30,51 @@ export async function buildRowDefinitions(parentNote: FNote, notes: FNote[], inf
|
|||||||
for (const { name, type } of infos) {
|
for (const { name, type } of infos) {
|
||||||
if (type === "relation") {
|
if (type === "relation") {
|
||||||
relations[name] = note.getRelationValue(name);
|
relations[name] = note.getRelationValue(name);
|
||||||
} else if (type === "boolean") {
|
|
||||||
labels[name] = note.hasLabel(name);
|
|
||||||
} else {
|
} else {
|
||||||
labels[name] = note.getLabelValue(name);
|
labels[name] = note.getLabelValue(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
definitions.push({
|
|
||||||
|
const def: TableData = {
|
||||||
iconClass: note.getIcon(),
|
iconClass: note.getIcon(),
|
||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
labels,
|
labels,
|
||||||
relations,
|
relations,
|
||||||
branchId: branch.branchId
|
branchId: branch.branchId,
|
||||||
});
|
colorClass: note.getColorClass()
|
||||||
}
|
}
|
||||||
|
|
||||||
return definitions;
|
if (note.hasChildren() && (maxDepth < 0 || currentDepth < maxDepth)) {
|
||||||
|
const { definitions, rowNumber: subRowNumber } = (await buildRowDefinitions(note, infos, maxDepth, currentDepth + 1));
|
||||||
|
def._children = definitions;
|
||||||
|
hasSubtree = true;
|
||||||
|
rowNumber += subRowNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
definitions.push(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
definitions,
|
||||||
|
hasSubtree,
|
||||||
|
rowNumber
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getPromotedAttributeInformation(parentNote: FNote) {
|
export default function getAttributeDefinitionInformation(parentNote: FNote) {
|
||||||
const info: PromotedAttributeInformation[] = [];
|
const info: AttributeDefinitionInformation[] = [];
|
||||||
for (const promotedAttribute of parentNote.getPromotedDefinitionAttributes()) {
|
const attrDefs = parentNote.getAttributes()
|
||||||
const def = promotedAttribute.getDefinition();
|
.filter(attr => attr.isDefinition());
|
||||||
|
for (const attrDef of attrDefs) {
|
||||||
|
const def = attrDef.getDefinition();
|
||||||
if (def.multiplicity !== "single") {
|
if (def.multiplicity !== "single") {
|
||||||
console.warn("Multiple values are not supported for now");
|
console.warn("Multiple values are not supported for now");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ labelType, name ] = promotedAttribute.name.split(":", 2);
|
const [ labelType, name ] = attrDef.name.split(":", 2);
|
||||||
if (promotedAttribute.type !== "label") {
|
if (attrDef.type !== "label") {
|
||||||
console.warn("Relations are not supported for now");
|
console.warn("Relations are not supported for now");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -69,6 +90,5 @@ export default function getPromotedAttributeInformation(parentNote: FNote) {
|
|||||||
type
|
type
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Promoted attribute information", info);
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { EventData } from "../../components/app_context.js";
|
import type { EventData } from "../../components/app_context.js";
|
||||||
|
import appContext from "../../components/app_context.js";
|
||||||
import Component from "../../components/component.js";
|
import Component from "../../components/component.js";
|
||||||
import type FNote from "../../entities/fnote.js";
|
import type FNote from "../../entities/fnote.js";
|
||||||
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
|
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
|
||||||
@@ -8,7 +9,6 @@ export interface ViewModeArgs {
|
|||||||
$parent: JQuery<HTMLElement>;
|
$parent: JQuery<HTMLElement>;
|
||||||
parentNote: FNote;
|
parentNote: FNote;
|
||||||
parentNotePath?: string | null;
|
parentNotePath?: string | null;
|
||||||
noteIds: string[];
|
|
||||||
showNotePath?: boolean;
|
showNotePath?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,6 +17,8 @@ export default abstract class ViewMode<T extends object> extends Component {
|
|||||||
private _viewStorage: ViewModeStorage<T> | null;
|
private _viewStorage: ViewModeStorage<T> | null;
|
||||||
protected parentNote: FNote;
|
protected parentNote: FNote;
|
||||||
protected viewType: ViewTypeOptions;
|
protected viewType: ViewTypeOptions;
|
||||||
|
protected noteIds: string[];
|
||||||
|
protected args: ViewModeArgs;
|
||||||
|
|
||||||
constructor(args: ViewModeArgs, viewType: ViewTypeOptions) {
|
constructor(args: ViewModeArgs, viewType: ViewTypeOptions) {
|
||||||
super();
|
super();
|
||||||
@@ -25,6 +27,12 @@ export default abstract class ViewMode<T extends object> extends Component {
|
|||||||
// note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work
|
// note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work
|
||||||
args.$parent.empty();
|
args.$parent.empty();
|
||||||
this.viewType = viewType;
|
this.viewType = viewType;
|
||||||
|
this.args = args;
|
||||||
|
this.noteIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async beforeRender() {
|
||||||
|
await this.#refreshNoteIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract renderList(): Promise<JQuery<HTMLElement> | undefined>;
|
abstract renderList(): Promise<JQuery<HTMLElement> | undefined>;
|
||||||
@@ -35,13 +43,18 @@ export default abstract class ViewMode<T extends object> extends Component {
|
|||||||
* @param e the event data.
|
* @param e the event data.
|
||||||
* @return {@code true} if the view should be re-rendered, a falsy value otherwise.
|
* @return {@code true} if the view should be re-rendered, a falsy value otherwise.
|
||||||
*/
|
*/
|
||||||
onEntitiesReloaded(e: EventData<"entitiesReloaded">): boolean | void {
|
async onEntitiesReloaded(e: EventData<"entitiesReloaded">): Promise<boolean | void> {
|
||||||
// Do nothing by default.
|
// Do nothing by default.
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFullHeight() {
|
async entitiesReloadedEvent(e: EventData<"entitiesReloaded">) {
|
||||||
// Override to change its value.
|
if (e.loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId || this.noteIds.includes(branch.parentNoteId ?? ""))) {
|
||||||
return false;
|
this.#refreshNoteIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.onEntitiesReloaded(e)) {
|
||||||
|
appContext.triggerEvent("refreshNoteList", { noteId: this.parentNote.noteId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReadOnly() {
|
get isReadOnly() {
|
||||||
@@ -57,4 +70,14 @@ export default abstract class ViewMode<T extends object> extends Component {
|
|||||||
return this._viewStorage;
|
return this._viewStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #refreshNoteIds() {
|
||||||
|
let noteIds: string[];
|
||||||
|
if (this.viewType === "list" || this.viewType === "grid") {
|
||||||
|
noteIds = this.args.parentNote.getChildNoteIds();
|
||||||
|
} else {
|
||||||
|
noteIds = await this.args.parentNote.getSubtreeNoteIds();
|
||||||
|
}
|
||||||
|
this.noteIds = noteIds;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv": "17.1.0",
|
"dotenv": "17.2.0",
|
||||||
"electron": "37.2.0"
|
"electron": "37.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/desktop",
|
"name": "@triliumnext/desktop",
|
||||||
"version": "0.96.0",
|
"version": "0.97.0",
|
||||||
"description": "Build your personal knowledge base with Trilium Notes",
|
"description": "Build your personal knowledge base with Trilium Notes",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "main.cjs",
|
"main": "main.cjs",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"@types/electron-squirrel-startup": "1.0.2",
|
"@types/electron-squirrel-startup": "1.0.2",
|
||||||
"@triliumnext/server": "workspace:*",
|
"@triliumnext/server": "workspace:*",
|
||||||
"copy-webpack-plugin": "13.0.0",
|
"copy-webpack-plugin": "13.0.0",
|
||||||
"electron": "37.2.0",
|
"electron": "37.2.3",
|
||||||
"@electron-forge/cli": "7.8.1",
|
"@electron-forge/cli": "7.8.1",
|
||||||
"@electron-forge/maker-deb": "7.8.1",
|
"@electron-forge/maker-deb": "7.8.1",
|
||||||
"@electron-forge/maker-dmg": "7.8.1",
|
"@electron-forge/maker-dmg": "7.8.1",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"@triliumnext/desktop": "workspace:*",
|
"@triliumnext/desktop": "workspace:*",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"copy-webpack-plugin": "13.0.0",
|
"copy-webpack-plugin": "13.0.0",
|
||||||
"electron": "37.2.0",
|
"electron": "37.2.3",
|
||||||
"fs-extra": "11.3.0"
|
"fs-extra": "11.3.0"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
|
|||||||
@@ -17,6 +17,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv": "17.1.0"
|
"dotenv": "17.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.17.0-bullseye-slim AS builder
|
FROM node:22.17.1-bullseye-slim AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.17.0-bullseye-slim
|
FROM node:22.17.1-bullseye-slim
|
||||||
# Install only runtime dependencies
|
# Install only runtime dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.17.0-alpine AS builder
|
FROM node:22.17.1-alpine AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.17.0-alpine
|
FROM node:22.17.1-alpine
|
||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
RUN apk add --no-cache su-exec shadow
|
RUN apk add --no-cache su-exec shadow
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.17.0-alpine AS builder
|
FROM node:22.17.1-alpine AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.17.0-alpine
|
FROM node:22.17.1-alpine
|
||||||
# Create a non-root user with configurable UID/GID
|
# Create a non-root user with configurable UID/GID
|
||||||
ARG USER=trilium
|
ARG USER=trilium
|
||||||
ARG UID=1001
|
ARG UID=1001
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.17.0-bullseye-slim AS builder
|
FROM node:22.17.1-bullseye-slim AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.17.0-bullseye-slim
|
FROM node:22.17.1-bullseye-slim
|
||||||
# Create a non-root user with configurable UID/GID
|
# Create a non-root user with configurable UID/GID
|
||||||
ARG USER=trilium
|
ARG USER=trilium
|
||||||
ARG UID=1001
|
ARG UID=1001
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/server",
|
"name": "@triliumnext/server",
|
||||||
"version": "0.96.0",
|
"version": "0.97.0",
|
||||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -52,21 +52,21 @@
|
|||||||
"cheerio": "1.1.0",
|
"cheerio": "1.1.0",
|
||||||
"chokidar": "4.0.3",
|
"chokidar": "4.0.3",
|
||||||
"cls-hooked": "4.2.2",
|
"cls-hooked": "4.2.2",
|
||||||
"compression": "1.8.0",
|
"compression": "1.8.1",
|
||||||
"cookie-parser": "1.4.7",
|
"cookie-parser": "1.4.7",
|
||||||
"csrf-csrf": "3.2.2",
|
"csrf-csrf": "3.2.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"debounce": "2.2.0",
|
"debounce": "2.2.0",
|
||||||
"debug": "4.4.1",
|
"debug": "4.4.1",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"electron": "37.2.0",
|
"electron": "37.2.3",
|
||||||
"electron-debug": "4.1.0",
|
"electron-debug": "4.1.0",
|
||||||
"electron-window-state": "5.0.3",
|
"electron-window-state": "5.0.3",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"express": "5.1.0",
|
"express": "5.1.0",
|
||||||
"express-openid-connect": "^2.17.1",
|
"express-openid-connect": "^2.17.1",
|
||||||
"express-rate-limit": "7.5.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"express-session": "1.18.1",
|
"express-session": "1.18.2",
|
||||||
"file-uri-to-path": "2.0.0",
|
"file-uri-to-path": "2.0.0",
|
||||||
"fs-extra": "11.3.0",
|
"fs-extra": "11.3.0",
|
||||||
"helmet": "8.1.0",
|
"helmet": "8.1.0",
|
||||||
@@ -83,12 +83,12 @@
|
|||||||
"jimp": "1.6.0",
|
"jimp": "1.6.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsdom": "26.1.0",
|
"jsdom": "26.1.0",
|
||||||
"marked": "16.0.0",
|
"marked": "16.1.1",
|
||||||
"mime-types": "3.0.1",
|
"mime-types": "3.0.1",
|
||||||
"multer": "2.0.1",
|
"multer": "2.0.2",
|
||||||
"normalize-strings": "1.1.1",
|
"normalize-strings": "1.1.1",
|
||||||
"ollama": "0.5.16",
|
"ollama": "0.5.16",
|
||||||
"openai": "5.8.3",
|
"openai": "5.10.1",
|
||||||
"rand-token": "1.0.1",
|
"rand-token": "1.0.1",
|
||||||
"safe-compare": "1.1.4",
|
"safe-compare": "1.1.4",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
|
|||||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
10
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Sharing.html
generated
vendored
@@ -70,24 +70,28 @@ class="image">
|
|||||||
<th><a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>
|
<th><a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>
|
||||||
</th>
|
</th>
|
||||||
<td>Not supported.</td>
|
<td>Not supported.</td>
|
||||||
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
|
<th><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
|
||||||
</th>
|
</th>
|
||||||
<td>Not supported.</td>
|
<td>Not supported.</td>
|
||||||
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_bdUJEHsAPYQR">Note Map</a>
|
<th><a class="reference-link" href="#root/_help_bdUJEHsAPYQR">Note Map</a>
|
||||||
</th>
|
</th>
|
||||||
<td>Not supported.</td>
|
<td>Not supported.</td>
|
||||||
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
|
<th><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
|
||||||
</th>
|
</th>
|
||||||
<td>Not supported.</td>
|
<td>Not supported.</td>
|
||||||
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Book</a>
|
<th><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -132,6 +136,7 @@ class="image">
|
|||||||
<th><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
|
<th><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
|
||||||
</th>
|
</th>
|
||||||
<td>Not supported.</td>
|
<td>Not supported.</td>
|
||||||
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
|
<th><a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
|
||||||
@@ -144,9 +149,10 @@ class="image">
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map</a>
|
<th><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a>
|
||||||
</th>
|
</th>
|
||||||
<td>Not supported.</td>
|
<td>Not supported.</td>
|
||||||
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a>
|
<th><a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a>
|
||||||
|
|||||||
1
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<p>This is a clone of a note. Go to its <a href="../UI%20Elements/Quick%20edit.html">primary location</a>.</p>
|
||||||
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -1,3 +1,7 @@
|
|||||||
|
<figure class="image">
|
||||||
|
<img style="aspect-ratio:990/590;" src="Note List_image.png" width="990"
|
||||||
|
height="590">
|
||||||
|
</figure>
|
||||||
<p>When a note has one or more child notes, they will be listed at the end
|
<p>When a note has one or more child notes, they will be listed at the end
|
||||||
of the note for easy navigation.</p>
|
of the note for easy navigation.</p>
|
||||||
<h2>Configuration</h2>
|
<h2>Configuration</h2>
|
||||||
@@ -11,47 +15,11 @@
|
|||||||
the desired number.</li>
|
the desired number.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>View types</h2>
|
<h2>View types</h2>
|
||||||
|
<p>The view types dictate how the child notes are represented.</p>
|
||||||
<p>By default, the notes will be displayed in a grid, however there are also
|
<p>By default, the notes will be displayed in a grid, however there are also
|
||||||
some other view types available.</p>
|
some other view types available.</p>
|
||||||
<aside class="admonition tip">
|
<p>Generally the view type can only be changed in a <a class="reference-link"
|
||||||
<p>Generally the view type can only be changed in a <a class="reference-link"
|
href="#root/_help_GTwFsgaA0lCt">Collections</a> note from the
|
||||||
href="#root/_help_GTwFsgaA0lCt">Book</a> note from the <a class="reference-link"
|
<a
|
||||||
href="#root/_help_BlN9DFI679QC">Ribbon</a>, but it can also be changed
|
class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>, but it can also be changed manually on any type of note using
|
||||||
manually on any type of note using the <code>#viewType</code> attribute.</p>
|
the <code>#viewType</code> attribute.</p>
|
||||||
</aside>
|
|
||||||
<h3>Grid view</h3>
|
|
||||||
<figure class="image image-style-align-center">
|
|
||||||
<img style="aspect-ratio:1025/655;" src="1_Note List_image.png" width="1025"
|
|
||||||
height="655">
|
|
||||||
</figure>
|
|
||||||
<p>This view presents the child notes in a grid format, allowing for a more
|
|
||||||
visual navigation experience.</p>
|
|
||||||
<ul>
|
|
||||||
<li>For <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> notes,
|
|
||||||
the text can be slighly scrollable via the mouse wheel to reveal more context.</li>
|
|
||||||
<li>For <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> notes,
|
|
||||||
syntax highlighting is applied.</li>
|
|
||||||
<li>For <a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a> notes,
|
|
||||||
a preview is made available for audio, video and PDF notes.</li>
|
|
||||||
<li>If the note does not have a content, a list of its child notes will be
|
|
||||||
displayed instead.</li>
|
|
||||||
</ul>
|
|
||||||
<p>This is the default view type.</p>
|
|
||||||
<h3>List view</h3>
|
|
||||||
<figure class="image image-style-align-center">
|
|
||||||
<img style="aspect-ratio:1013/526;" src="Note List_image.png" width="1013"
|
|
||||||
height="526">
|
|
||||||
</figure>
|
|
||||||
<p>In the list view mode, each note is displayed in a single row with only
|
|
||||||
the title and the icon of the note being visible by the default. By pressing
|
|
||||||
the expand button it's possible to view the content of the note, as well
|
|
||||||
as the children of the note (recursively).</p>
|
|
||||||
<h3>Calendar view</h3>
|
|
||||||
<figure class="image image-style-align-center">
|
|
||||||
<img style="aspect-ratio:1090/598;" src="2_Note List_image.png" width="1090"
|
|
||||||
height="598">
|
|
||||||
</figure>
|
|
||||||
<p>In the calendar view, child notes are represented as events, with a start
|
|
||||||
date and optionally an end date. The view also has interaction support
|
|
||||||
such as moving or creating new events. See <a class="reference-link"
|
|
||||||
href="#root/_help_xWbu3jpNWapp">Calendar View</a> for more information.</p>
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
<img style="aspect-ratio:767/606;" src="4_Calendar View_image.png" width="767"
|
<img style="aspect-ratio:767/606;" src="4_Calendar View_image.png" width="767"
|
||||||
height="606">
|
height="606">
|
||||||
</figure>
|
</figure>
|
||||||
<p>The Calendar view of Book notes will display each child note in a calendar
|
<p>The Calendar view will display each child note in a calendar that has
|
||||||
that has a start date and optionally an end date, as an event.</p>
|
a start date and optionally an end date, as an event.</p>
|
||||||
<p>The Calendar view has multiple display modes:</p>
|
<p>The Calendar view has multiple display modes:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Week view, where all the 7 days of the week (or 5 if the weekends are
|
<li>Week view, where all the 7 days of the week (or 5 if the weekends are
|
||||||
@@ -14,8 +14,9 @@
|
|||||||
<li>Year view, which displays the entire year for quick reference.</li>
|
<li>Year view, which displays the entire year for quick reference.</li>
|
||||||
<li>List view, which displays all the events of a given month in sequence.</li>
|
<li>List view, which displays all the events of a given month in sequence.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Unlike other Book view types, the Calendar view also allows some kind
|
<p>Unlike other Collection view types, the Calendar view also allows some
|
||||||
of interaction, such as moving events around as well as creating new ones.</p>
|
kind of interaction, such as moving events around as well as creating new
|
||||||
|
ones.</p>
|
||||||
<h2>Creating a calendar</h2>
|
<h2>Creating a calendar</h2>
|
||||||
<figure class="table">
|
<figure class="table">
|
||||||
<table>
|
<table>
|
||||||
@@ -32,17 +33,17 @@
|
|||||||
<td>
|
<td>
|
||||||
<img src="2_Calendar View_image.png">
|
<img src="2_Calendar View_image.png">
|
||||||
</td>
|
</td>
|
||||||
<td>The Calendar View works only for Book note types. To create a new note,
|
<td>The Calendar View works only for Collection note types. To create a new
|
||||||
right click on the note tree on the left and select Insert note after,
|
note, right click on the note tree on the left and select Insert note after,
|
||||||
or Insert child note and then select <em>Book</em>.</td>
|
or Insert child note and then select <em>Collection</em>.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td>
|
<td>2</td>
|
||||||
<td>
|
<td>
|
||||||
<img src="3_Calendar View_image.png">
|
<img src="3_Calendar View_image.png">
|
||||||
</td>
|
</td>
|
||||||
<td>Once created, the “View type” of the Book needs changed to “Calendar”,
|
<td>Once created, the “View type” of the Collection needs changed to “Calendar”,
|
||||||
by selecting the “Book Properties” tab in the ribbon.</td>
|
by selecting the “Collection Properties” tab in the ribbon.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
<img src="Calendar View_image.png">
|
<img src="Calendar View_image.png">
|
||||||
</li>
|
</li>
|
||||||
<li>Creating new notes from the calendar will respect the <code>~child:template</code> relation
|
<li>Creating new notes from the calendar will respect the <code>~child:template</code> relation
|
||||||
if set on the book note.</li>
|
if set on the Collection note.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Interacting with events</h2>
|
<h2>Interacting with events</h2>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -71,16 +72,30 @@
|
|||||||
<br>
|
<br>
|
||||||
<img src="7_Calendar View_image.png">
|
<img src="7_Calendar View_image.png">
|
||||||
</li>
|
</li>
|
||||||
<li>Left clicking the event will go to that note. Middle clicking will open
|
<li>Left clicking the event will open a <a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> to
|
||||||
the note in a new tab and right click will offer more options including
|
edit the note in a popup while allowing easy return to the calendar by
|
||||||
opening the note in a new split or window.</li>
|
just dismissing the popup.
|
||||||
|
<ul>
|
||||||
|
<li>Middle clicking will open the note in a new tab.</li>
|
||||||
|
<li>Right click will offer more options including opening the note in a new
|
||||||
|
split or window.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>Drag and drop an event on the calendar to move it to another day.</li>
|
<li>Drag and drop an event on the calendar to move it to another day.</li>
|
||||||
<li>The length of an event can be changed by placing the mouse to the right
|
<li>The length of an event can be changed by placing the mouse to the right
|
||||||
edge of the event and dragging the mouse around.</li>
|
edge of the event and dragging the mouse around.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Configuring the calendar</h2>
|
<h2>Configuring the calendar view</h2>
|
||||||
<p>The following attributes can be added to the book type:</p>
|
<p>In the <em>Collections</em> tab in the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||||
<figure class="table">
|
it's possible to adjust the following:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Hide weekends from the week view.</li>
|
||||||
|
<li>Display week numbers on the calendar.</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Configuring the calendar using attributes</h2>
|
||||||
|
<p>The following attributes can be added to the Collection type:</p>
|
||||||
|
<figure
|
||||||
|
class="table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -126,13 +141,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</figure>
|
</figure>
|
||||||
<p>In addition, the first day of the week can be either Sunday or Monday
|
<p>In addition, the first day of the week can be either Sunday or Monday
|
||||||
and can be adjusted from the application settings.</p>
|
and can be adjusted from the application settings.</p>
|
||||||
<h2>Configuring the calendar events</h2>
|
<h2>Configuring the calendar events using attributes</h2>
|
||||||
<p>For each note of the calendar, the following attributes can be used:</p>
|
<p>For each note of the calendar, the following attributes can be used:</p>
|
||||||
<figure
|
<figure
|
||||||
class="table">
|
class="table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -244,10 +259,10 @@ class="table">
|
|||||||
<p>
|
<p>
|
||||||
<img src="11_Calendar View_image.png">
|
<img src="11_Calendar View_image.png">
|
||||||
</p>
|
</p>
|
||||||
<p>The calendar displays all the child notes of the book that have a <code>#startDate</code>.
|
<p>The calendar displays all the child notes of the Collection that have
|
||||||
An <code>#endDate</code> can optionally be added.</p>
|
a <code>#startDate</code>. An <code>#endDate</code> can optionally be added.</p>
|
||||||
<p>If editing the start date and end date from the note itself is desirable,
|
<p>If editing the start date and end date from the note itself is desirable,
|
||||||
the following attributes can be added to the book note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date"
|
the following attributes can be added to the Collection note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date"
|
||||||
#label:endDate(inheritable)="promoted,alias=End Date,single,date"
|
#label:endDate(inheritable)="promoted,alias=End Date,single,date"
|
||||||
#hidePromotedAttributes </code></pre>
|
#hidePromotedAttributes </code></pre>
|
||||||
<p>This will result in:</p>
|
<p>This will result in:</p>
|
||||||
@@ -261,7 +276,7 @@ class="table">
|
|||||||
<h3>Using with the Journal / calendar</h3>
|
<h3>Using with the Journal / calendar</h3>
|
||||||
<p>It is possible to integrate the calendar view into the Journal with day
|
<p>It is possible to integrate the calendar view into the Journal with day
|
||||||
notes. In order to do so change the note type of the Journal note (calendar
|
notes. In order to do so change the note type of the Journal note (calendar
|
||||||
root) to Book and then select the Calendar View.</p>
|
root) to Collection and then select the Calendar View.</p>
|
||||||
<p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
|
<p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
|
||||||
attribute, the calendar will know that it's in a calendar and apply the
|
attribute, the calendar will know that it's in a calendar and apply the
|
||||||
following:</p>
|
following:</p>
|
||||||
@@ -283,7 +298,7 @@ class="table">
|
|||||||
However, it is possible to configure a different attribute to be displayed
|
However, it is possible to configure a different attribute to be displayed
|
||||||
instead.</p>
|
instead.</p>
|
||||||
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
|
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
|
||||||
calendar/book note), with the value being <code>name</code> where <code>name</code> can
|
calendar/Collection note), with the value being <code>name</code> where <code>name</code> can
|
||||||
be any label (make not to add the <code>#</code> prefix). The attribute can
|
be any label (make not to add the <code>#</code> prefix). The attribute can
|
||||||
also come through inheritance such as a template attribute. If the note
|
also come through inheritance such as a template attribute. If the note
|
||||||
does not have the requested label, the title of the note will be used instead.</p>
|
does not have the requested label, the title of the note will be used instead.</p>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<aside class="admonition important">
|
<aside class="admonition important">
|
||||||
<p>Starting with Trilium v0.97.0, the geo map has been converted from a standalone
|
<p>Starting with Trilium v0.97.0, the geo map has been converted from a standalone
|
||||||
<a
|
<a
|
||||||
href="#root/pOsGYCXsbNQG/_help_KSZ04uQ2D1St">note type</a>to a type of view for the <a class="reference-link"
|
href="#root/_help_KSZ04uQ2D1St">note type</a>to a type of view for the <a class="reference-link"
|
||||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a>. </p>
|
href="#root/_help_0ESUbbAxVnoK">Note List</a>. </p>
|
||||||
</aside>
|
</aside>
|
||||||
<figure class="image image-style-align-center">
|
<figure class="image image-style-align-center">
|
||||||
<img style="aspect-ratio:892/675;" src="9_Geo Map View_image.png" width="892"
|
<img style="aspect-ratio:892/675;" src="9_Geo Map View_image.png" width="892"
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h2>Repositioning the map</h2>
|
<h2>Repositioning the map</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Click and drag the map in order to move across the map.</li>
|
<li>Click and drag the map in order to move across the map.</li>
|
||||||
@@ -109,6 +110,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h3>Adding a new note using the contextual menu</h3>
|
<h3>Adding a new note using the contextual menu</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Right click anywhere on the map, where to place the newly created marker
|
<li>Right click anywhere on the map, where to place the newly created marker
|
||||||
@@ -119,13 +121,13 @@
|
|||||||
</ol>
|
</ol>
|
||||||
<h3>Adding an existing note on note from the note tree</h3>
|
<h3>Adding an existing note on note from the note tree</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Select the desired note in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
<li>Select the desired note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||||
<li>Hold the mouse on the note and drag it to the map to the desired location.</li>
|
<li>Hold the mouse on the note and drag it to the map to the desired location.</li>
|
||||||
<li>The map should be updated with the new marker.</li>
|
<li>The map should be updated with the new marker.</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>This works for:</p>
|
<p>This works for:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Notes that are not part of the geo map, case in which a <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_IakOLONlIfGI">clone</a> will
|
<li>Notes that are not part of the geo map, case in which a <a href="#root/_help_IakOLONlIfGI">clone</a> will
|
||||||
be created.</li>
|
be created.</li>
|
||||||
<li>Notes that are a child of the geo map but not yet positioned on the map.</li>
|
<li>Notes that are a child of the geo map but not yet positioned on the map.</li>
|
||||||
<li>Notes that are a child of the geo map and also positioned, case in which
|
<li>Notes that are a child of the geo map and also positioned, case in which
|
||||||
@@ -134,9 +136,8 @@
|
|||||||
<h2>How the location of the markers is stored</h2>
|
<h2>How the location of the markers is stored</h2>
|
||||||
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
||||||
of the child notes:</p>
|
of the child notes:</p>
|
||||||
<p>
|
<img src="18_Geo Map View_image.png" width="1288"
|
||||||
<img src="18_Geo Map View_image.png" width="1288" height="278">
|
height="278">
|
||||||
</p>
|
|
||||||
<p>This value can be added manually if needed. The value of the attribute
|
<p>This value can be added manually if needed. The value of the attribute
|
||||||
is made up of the latitude and longitude separated by a comma.</p>
|
is made up of the latitude and longitude separated by a comma.</p>
|
||||||
<h2>Repositioning markers</h2>
|
<h2>Repositioning markers</h2>
|
||||||
@@ -148,19 +149,18 @@
|
|||||||
page (<kbd>Ctrl</kbd>+<kbd>R</kbd> ) to cancel it.</p>
|
page (<kbd>Ctrl</kbd>+<kbd>R</kbd> ) to cancel it.</p>
|
||||||
<h2>Interaction with the markers</h2>
|
<h2>Interaction with the markers</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Hovering over a marker will display the content of the note it belongs
|
<li>Hovering over a marker will display a <a class="reference-link" href="#root/_help_lgKX7r3aL30x">Note Tooltip</a> with
|
||||||
to.
|
the content of the note it belongs to.
|
||||||
<ul>
|
<ul>
|
||||||
<li>Clicking on the note title in the tooltip will navigate to the note in
|
<li>Clicking on the note title in the tooltip will navigate to the note in
|
||||||
the current view.</li>
|
the current view.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>Middle-clicking the marker will open the note in a new tab.</li>
|
<li>Middle-clicking the marker will open the note in a new tab.</li>
|
||||||
<li>Right-clicking the marker will open a contextual menu allowing:
|
<li>Right-clicking the marker will open a contextual menu (as described below).</li>
|
||||||
<ul>
|
<li>If the map is in read-only mode, clicking on a marker will open a
|
||||||
<li> </li>
|
<a
|
||||||
</ul>
|
class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> popup for the corresponding note.</li>
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Contextual menu</h2>
|
<h2>Contextual menu</h2>
|
||||||
<p>It's possible to press the right mouse button to display a contextual
|
<p>It's possible to press the right mouse button to display a contextual
|
||||||
@@ -261,6 +261,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h3>Adding from OpenStreetMap</h3>
|
<h3>Adding from OpenStreetMap</h3>
|
||||||
<p>Similarly to the Google Maps approach:</p>
|
<p>Similarly to the Google Maps approach:</p>
|
||||||
<figure class="table" style="width:100%;">
|
<figure class="table" style="width:100%;">
|
||||||
@@ -310,6 +311,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h2>Adding GPS tracks (.gpx)</h2>
|
<h2>Adding GPS tracks (.gpx)</h2>
|
||||||
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
||||||
<figure
|
<figure
|
||||||
@@ -377,19 +379,20 @@ class="table" style="width:100%;">
|
|||||||
<p>When a map is in read-only all editing features will be disabled such
|
<p>When a map is in read-only all editing features will be disabled such
|
||||||
as:</p>
|
as:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>The add button in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
<li>The add button in the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
||||||
<li>Dragging markers.</li>
|
<li>Dragging markers.</li>
|
||||||
<li>Editing from the contextual menu (removing locations or adding new items).</li>
|
<li>Editing from the contextual menu (removing locations or adding new items).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>To enable read-only mode simply press the <em>Lock</em> icon from the
|
<p>To enable read-only mode simply press the <em>Lock</em> icon from the
|
||||||
<a
|
<a
|
||||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
|
class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
|
||||||
<h2>Troubleshooting</h2>
|
<h2>Troubleshooting</h2>
|
||||||
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
|
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
|
||||||
<img style="aspect-ratio:678/499;" src="13_Geo Map View_image.png" width="678"
|
<img style="aspect-ratio:678/499;" src="13_Geo Map View_image.png" width="678"
|
||||||
height="499">
|
height="499">
|
||||||
</figure>
|
</figure>
|
||||||
<h3>Grid-like artifacts on the map</h3>
|
|
||||||
|
<h3>Grid-like artifacts on the map</h3>
|
||||||
<p>This occurs if the application is not at 100% zoom which causes the pixels
|
<p>This occurs if the application is not at 100% zoom which causes the pixels
|
||||||
of the map to not render correctly due to fractional scaling. The only
|
of the map to not render correctly due to fractional scaling. The only
|
||||||
possible solution is to set the UI zoom at 100% (default keyboard shortcut
|
possible solution is to set the UI zoom at 100% (default keyboard shortcut
|
||||||
|
|||||||
30
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Grid View.html
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<figure class="image">
|
||||||
|
<img style="aspect-ratio:990/590;" src="Grid View_image.png" width="990"
|
||||||
|
height="590">
|
||||||
|
</figure>
|
||||||
|
<p>This view presents the child notes in a grid format, allowing for a more
|
||||||
|
visual navigation experience.</p>
|
||||||
|
<p>Each tile contains:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The title of a note.</li>
|
||||||
|
<li>A snippet of the content.</li>
|
||||||
|
<li>For empty notes, the sub-children are also displayed, allowing for quick
|
||||||
|
navigation.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Depending on the type of note:</p>
|
||||||
|
<ul>
|
||||||
|
<li>For <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> notes,
|
||||||
|
the text can be slightly scrollable via the mouse wheel to reveal more
|
||||||
|
context.</li>
|
||||||
|
<li>For <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> notes,
|
||||||
|
syntax highlighting is applied.</li>
|
||||||
|
<li>For <a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a> notes,
|
||||||
|
a preview is made available for audio, video and PDF notes.</li>
|
||||||
|
<li>If the note does not have a content, a list of its child notes will be
|
||||||
|
displayed instead.</li>
|
||||||
|
</ul>
|
||||||
|
<p>The grid view is also used by default in the <a class="reference-link"
|
||||||
|
href="#root/_help_0ESUbbAxVnoK">Note List</a> of every note, making
|
||||||
|
it easy to navigate to children notes.</p>
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
<p>Unlike most other view types, the grid view is not actually configurable.</p>
|
||||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Grid View_image.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 78 KiB |
20
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/List View.html
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<figure class="image">
|
||||||
|
<img style="aspect-ratio:1387/758;" src="List View_image.png" width="1387"
|
||||||
|
height="758">
|
||||||
|
</figure>
|
||||||
|
<p>List view is similar to <a class="reference-link" href="#root/_help_8QqnMzx393bx">Grid View</a>,
|
||||||
|
but in the list view mode, each note is displayed in a single row with
|
||||||
|
only the title and the icon of the note being visible by the default. By
|
||||||
|
pressing the expand button it's possible to view the content of the note,
|
||||||
|
as well as the children of the note (recursively).</p>
|
||||||
|
<p>In the example above, the "Node.js" note on the left panel contains several
|
||||||
|
child notes. The right panel displays the content of these child notes
|
||||||
|
as a single continuous document.</p>
|
||||||
|
<h2>Interaction</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Each note can be expanded or collapsed by clicking on the arrow to the
|
||||||
|
left of the title.</li>
|
||||||
|
<li>In the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||||
|
in the <em>Collection</em> tab there are options to expand and to collapse
|
||||||
|
all notes easily.</li>
|
||||||
|
</ul>
|
||||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/List View_image.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 119 KiB |
@@ -5,30 +5,86 @@
|
|||||||
<p>The table view displays information in a grid, where the rows are individual
|
<p>The table view displays information in a grid, where the rows are individual
|
||||||
notes and the columns are <a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
notes and the columns are <a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||||
In addition, values are editable.</p>
|
In addition, values are editable.</p>
|
||||||
|
<h2>How it works</h2>
|
||||||
|
<p>The tabular structure is represented as such:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Each child note is a row in the table.</li>
|
||||||
|
<li>If child rows also have children, they will be displayed under an expander
|
||||||
|
(nested notes).</li>
|
||||||
|
<li>Each column is a <a href="#root/_help_OFXdgB2nNk1F">promoted attribute</a> that
|
||||||
|
is defined on the Collection note.
|
||||||
|
<ul>
|
||||||
|
<li>Actually, both promoted and unpromoted attributes are supported, but it's
|
||||||
|
a requirement to use a label/relation definition.</li>
|
||||||
|
<li>The promoted attributes are usually defined as inheritable in order to
|
||||||
|
show up in the child notes, but it's not a requirement.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>If there are multiple attribute definitions with the same <code>name</code>,
|
||||||
|
only one will be displayed.</li>
|
||||||
|
</ul>
|
||||||
|
<p>There are also a few predefined columns:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The current item number, identified by the <code>#</code> symbol.
|
||||||
|
<ul>
|
||||||
|
<li>This simply counts the note and is affected by sorting.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a class="reference-link" href="#root/_help_m1lbrzyKDaRB">Note ID</a>,
|
||||||
|
representing the unique ID used internally by Trilium</li>
|
||||||
|
<li>The title of the note.</li>
|
||||||
|
</ul>
|
||||||
<h2>Interaction</h2>
|
<h2>Interaction</h2>
|
||||||
<h3>Creating a new table</h3>
|
<h3>Creating a new table</h3>
|
||||||
<p>Right click the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
<p>Right click the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||||
select <em>Insert child note</em> and look for the <em>Table item</em>.</p>
|
select <em>Insert child note</em> and look for the <em>Table item</em>.</p>
|
||||||
<h3>Adding columns</h3>
|
<h3>Adding columns</h3>
|
||||||
<p>Each column is a <a href="#root/_help_OFXdgB2nNk1F">promoted attribute</a> that
|
<p>Each column is a <a href="#root/_help_OFXdgB2nNk1F">promoted or unpromoted attribute</a> that
|
||||||
is defined on the Book note. Ideally, the promoted attributes need to be
|
is defined on the Collection note.</p>
|
||||||
inheritable in order to show up in the child notes.</p>
|
<p>To create a new column, either:</p>
|
||||||
<p>To create a new column, simply press <em>Add new column</em> at the bottom
|
|
||||||
of the table.</p>
|
|
||||||
<p>There are also a few predefined columns:</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>The current item number, identified by the <code>#</code> symbol. This simply
|
<li>Press <em>Add new column</em> at the bottom of the table.</li>
|
||||||
counts the note and is affected by sorting.</li>
|
<li>Right click on an existing column and select Add column to the left/right.</li>
|
||||||
<li><a class="reference-link" href="#root/_help_m1lbrzyKDaRB">Note ID</a>,
|
<li>Right click on the empty space of the column header and select <em>Label</em> or <em>Relation</em> in
|
||||||
representing the unique ID used internally by Trilium</li>
|
the <em>New column</em> section.</li>
|
||||||
<li>The title of the note.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Adding new rows</h3>
|
<h3>Adding new rows</h3>
|
||||||
<p>Each row is actually a note that is a child of the book note.</p>
|
<p>Each row is actually a note that is a child of the Collection note.</p>
|
||||||
<p>To create a new note, press <em>Add new row</em> at the bottom of the table.
|
<p>To create a new note, either:</p>
|
||||||
By default it will try to edit the title of the newly created note.</p>
|
<ul>
|
||||||
<p>Alternatively, the note can be created from the<a class="reference-link"
|
<li>Press <em>Add new row</em> at the bottom of the table.</li>
|
||||||
|
<li>Right click on an existing row and select <em>Insert row above, Insert child note</em> or <em>Insert row below</em>.</li>
|
||||||
|
</ul>
|
||||||
|
<p>By default it will try to edit the title of the newly created note.</p>
|
||||||
|
<p>Alternatively, the note can be created from the <a class="reference-link"
|
||||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> or <a href="#root/_help_CdNpE2pqjmI6">scripting</a>.</p>
|
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> or <a href="#root/_help_CdNpE2pqjmI6">scripting</a>.</p>
|
||||||
|
<h3>Context menu</h3>
|
||||||
|
<p>There are multiple menus:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Right clicking on a column, allows:
|
||||||
|
<ul>
|
||||||
|
<li>Sorting by the selected column and resetting the sort.</li>
|
||||||
|
<li>Hiding the selected column or adjusting the visibility of every column.</li>
|
||||||
|
<li>Adding new columns to the left or the right of the column.</li>
|
||||||
|
<li>Editing the current column.</li>
|
||||||
|
<li>Deleting the current column.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Right clicking on the space to the right of the columns, allows:
|
||||||
|
<ul>
|
||||||
|
<li>Adjusting the visibility of every column.</li>
|
||||||
|
<li>Adding new columns.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Right clicking on a row, allows:
|
||||||
|
<ul>
|
||||||
|
<li>Opening the corresponding note of the row in a new tab, split, window
|
||||||
|
or quick editing it.</li>
|
||||||
|
<li>Inserting rows above, below or as a child note.</li>
|
||||||
|
<li>Deleting the row.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<h3>Editing data</h3>
|
<h3>Editing data</h3>
|
||||||
<p>Simply click on a cell within a row to change its value. The change will
|
<p>Simply click on a cell within a row to change its value. The change will
|
||||||
not only reflect in the table, but also as an attribute of the corresponding
|
not only reflect in the table, but also as an attribute of the corresponding
|
||||||
@@ -37,16 +93,34 @@
|
|||||||
<li>The editing will respect the type of the promoted attribute, by presenting
|
<li>The editing will respect the type of the promoted attribute, by presenting
|
||||||
a normal text box, a number selector or a date selector for example.</li>
|
a normal text box, a number selector or a date selector for example.</li>
|
||||||
<li>It also possible to change the title of a note.</li>
|
<li>It also possible to change the title of a note.</li>
|
||||||
<li>Editing relations is also possible, by using the note autocomplete.</li>
|
<li>Editing relations is also possible
|
||||||
|
<ul>
|
||||||
|
<li>Simply click on a relation and it will become editable. Enter the text
|
||||||
|
to look for a note and click on it.</li>
|
||||||
|
<li>To remove a relation, remove the title of the note from the text box and
|
||||||
|
click outside the cell.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h3>Editing columns</h3>
|
||||||
|
<p>It is possible to edit a column by right clicking it and selecting <em>Edit column.</em> This
|
||||||
|
will basically change the label/relation definition at the collection level.</p>
|
||||||
|
<p>If the <em>Name</em> field of a column is changed, this will trigger a batch
|
||||||
|
operation in which the corresponding label/relation will be renamed in
|
||||||
|
all the children.</p>
|
||||||
<h2>Working with the data</h2>
|
<h2>Working with the data</h2>
|
||||||
<h3>Sorting</h3>
|
<h3>Sorting by column</h3>
|
||||||
<p>It is possible to sort the data by the values of a column:</p>
|
<p>By default, the order of the notes matches the order in the <a class="reference-link"
|
||||||
|
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>. However, it is possible
|
||||||
|
to sort the data by the values of a column:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>To do so, simply click on a column.</li>
|
<li>To do so, simply click on a column.</li>
|
||||||
<li>To switch between ascending or descending sort, simply click again on
|
<li>To switch between ascending or descending sort, simply click again on
|
||||||
the same column. The arrow next to the column will indicate the direction
|
the same column. The arrow next to the column will indicate the direction
|
||||||
of the sort.</li>
|
of the sort.</li>
|
||||||
|
<li>To disable sorting and fall back to the original order, right click any
|
||||||
|
column on the header and select <em>Clear sorting.</em>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Reordering and hiding columns</h3>
|
<h3>Reordering and hiding columns</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -55,36 +129,52 @@
|
|||||||
the item corresponding to the column.</li>
|
the item corresponding to the column.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Reordering rows</h3>
|
<h3>Reordering rows</h3>
|
||||||
<p>Notes can be dragged around to change their order. This will also change
|
<p>Notes can be dragged around to change their order. To do so, move the
|
||||||
the order of the note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
mouse over the three vertical dots near the number row and drag the mouse
|
||||||
<p>Currently, it's possible to reorder notes even if sorting is used, but
|
to the desired position.</p>
|
||||||
the result might be inconsistent.</p>
|
<p>This will also change the order of the note in the <a class="reference-link"
|
||||||
<h2>Limitations</h2>
|
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
||||||
<p>The table functionality is still in its early stages, as such it faces
|
<p>Reordering does have some limitations:</p>
|
||||||
quite a few important limitations:</p>
|
<ul>
|
||||||
<ol>
|
<li>If the parent note has <code>#sorted</code>, reordering will be disabled.</li>
|
||||||
<li>As mentioned previously, the columns of the table are defined as
|
<li>If using nested tables, then reordering will also be disabled.</li>
|
||||||
<a
|
<li>Currently, it's possible to reorder notes even if column sorting is used,
|
||||||
class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
but the result might be inconsistent.</li>
|
||||||
<ol>
|
</ul>
|
||||||
<li>But only the promoted attributes that are defined at the level of the
|
<h3>Nested trees</h3>
|
||||||
Book note are actually taken into consideration.</li>
|
<p>If the child notes of the collection also have their own child notes,
|
||||||
<li>There are plans to recursively look for columns across the sub-hierarchy.</li>
|
then they will be displayed in a hierarchy.</p>
|
||||||
</ol>
|
<p>Next to the title of each element there will be a button to expand or
|
||||||
|
collapse. By default, all items are expanded.</p>
|
||||||
|
<p>Since nesting is not always desirable, it is possible to limit the nesting
|
||||||
|
to a certain number of levels or even disable it completely. To do so,
|
||||||
|
either:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Go to <em>Collection Properties</em> in the <a class="reference-link"
|
||||||
|
href="#root/_help_BlN9DFI679QC">Ribbon</a> and look for the <em>Max nesting depth</em> section.
|
||||||
|
<ul>
|
||||||
|
<li>To disable nesting, type 0 and press Enter.</li>
|
||||||
|
<li>To limit to a certain depth, type in the desired number (e.g. 2 to only
|
||||||
|
display children and sub-children).</li>
|
||||||
|
<li>To re-enable unlimited nesting, remove the number and press Enter.</li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>Hierarchy is not yet supported, so the table will only show the items
|
<li>Manually set <code>maxNestingDepth</code> to the desired value.</li>
|
||||||
that are direct children of the <em>Book</em> note.</li>
|
</ul>
|
||||||
<li>Multiple labels and relations are not supported. If a <a class="reference-link"
|
<p>Limitations:</p>
|
||||||
|
<ul>
|
||||||
|
<li>While in this mode, it's not possible to reorder notes.</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Limitations</h2>
|
||||||
|
<p>Multi-value labels and relations are not supported. If a <a class="reference-link"
|
||||||
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> is defined
|
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> is defined
|
||||||
with a <em>Multi value</em> specificity, they will be ignored.</li>
|
with a <em>Multi value</em> specificity, they will be ignored.</p>
|
||||||
</ol>
|
|
||||||
<h2>Use in search</h2>
|
<h2>Use in search</h2>
|
||||||
<p>The table view can be used in a <a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a> by
|
<p>The table view can be used in a <a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a> by
|
||||||
adding the <code>#viewType=table</code> attribute.</p>
|
adding the <code>#viewType=table</code> attribute.</p>
|
||||||
<p>Unlike when used in a book, saved searches are not limited to the sub-hierarchy
|
<p>Unlike when used in a Collection, saved searches are not limited to the
|
||||||
of a note and allows for advanced queries thanks to the power of the
|
sub-hierarchy of a note and allows for advanced queries thanks to the power
|
||||||
<a
|
of the <a class="reference-link" href="#root/_help_eIg8jdvaoNNd">Search</a>.</p>
|
||||||
class="reference-link" href="#root/_help_eIg8jdvaoNNd">Search</a>.</p>
|
|
||||||
<p>However, there are also some limitations:</p>
|
<p>However, there are also some limitations:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>It's not possible to reorder notes.</li>
|
<li>It's not possible to reorder notes.</li>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 78 KiB |
@@ -54,7 +54,7 @@
|
|||||||
hide the Mermaid source code and display the diagram preview in full-size.
|
hide the Mermaid source code and display the diagram preview in full-size.
|
||||||
In this case, the read-only mode can be easily toggled on or off via a
|
In this case, the read-only mode can be easily toggled on or off via a
|
||||||
dedicated button in the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> area.</li>
|
dedicated button in the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> area.</li>
|
||||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_81SGnPGMk7Xc">Geo Map View</a> will
|
<li><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a> will
|
||||||
disallow all interaction that would otherwise change the map (dragging
|
disallow all interaction that would otherwise change the map (dragging
|
||||||
notes, adding new items).</li>
|
notes, adding new items).</li>
|
||||||
</ul>
|
</ul>
|
||||||
36
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip.html
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<figure class="image image-style-align-right">
|
||||||
|
<img style="aspect-ratio:505/261;" src="Note Tooltip_image.png" width="505"
|
||||||
|
height="261">
|
||||||
|
</figure>
|
||||||
|
<p>The note tooltip is a convenience feature which displays a popup when
|
||||||
|
hovering over an <a href="#root/_help_hrZ1D00cLbal">internal link</a> to
|
||||||
|
another note.</p>
|
||||||
|
<p>The following information is displayed:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The note path, at the top of the popup.</li>
|
||||||
|
<li>The title of the note.
|
||||||
|
<ul>
|
||||||
|
<li>Clicking on the title will open the note in the current tab.</li>
|
||||||
|
<li>Holding <kbd>Ctrl</kbd> pressed while clicking the title will open in a
|
||||||
|
new tab instead of the current one.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>A snippet of the content will be displayed as well.</li>
|
||||||
|
<li>A button to <a href="#root/_help_ZjLYv08Rp3qC">quickly edit</a> the note
|
||||||
|
in a popup.</li>
|
||||||
|
</ul>
|
||||||
|
<p>The tooltip can be found in multiple places, including:</p>
|
||||||
|
<ul>
|
||||||
|
<li>In <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> notes,
|
||||||
|
when hovering over <a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a> .</li>
|
||||||
|
<li><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>:
|
||||||
|
<ul>
|
||||||
|
<li><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a>,
|
||||||
|
when hovering over a marker.</li>
|
||||||
|
<li><a class="reference-link" href="#root/_help_xWbu3jpNWapp">Calendar View</a>,
|
||||||
|
when hovering over an event.</li>
|
||||||
|
<li><a class="reference-link" href="#root/_help_2FvYrpmOXm29">Table View</a>,
|
||||||
|
when hovering over a note title, or over a <a href="#root/_help_Cq5X6iKQop6R">relation</a>.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip_image.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -30,5 +30,8 @@
|
|||||||
in the context menu, or with the associated keyboard <a href="#root/_help_A9Oc6YKKc65v">shortcuts</a>: <code>CTRL-C</code> (
|
in the context menu, or with the associated keyboard <a href="#root/_help_A9Oc6YKKc65v">shortcuts</a>: <code>CTRL-C</code> (
|
||||||
<a
|
<a
|
||||||
href="#root/_help_IakOLONlIfGI">copy</a>), <kbd>Ctrl</kbd> + <kbd>X</kbd> (cut) and <kbd>Ctrl</kbd> + <kbd>V</kbd> (paste).</p>
|
href="#root/_help_IakOLONlIfGI">copy</a>), <kbd>Ctrl</kbd> + <kbd>X</kbd> (cut) and <kbd>Ctrl</kbd> + <kbd>V</kbd> (paste).</p>
|
||||||
<p>See <a class="reference-link" href="#root/_help_YtSN43OrfzaA">Note Tree Menu</a> for
|
<p>See <a class="reference-link" href="#root/_help_YtSN43OrfzaA">Note tree contextual menu</a> for
|
||||||
more information.</p>
|
more information.</p>
|
||||||
|
<h2>Keyboard shortcuts</h2>
|
||||||
|
<p>The note tree comes with multiple keyboard shortcuts to make editing faster,
|
||||||
|
consult the dedicated <a class="reference-link" href="#root/_help_DvdZhoQZY9Yd">Keyboard shortcuts</a> section.</p>
|
||||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 43 KiB |