mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 15:56:29 +01:00
Compare commits
275 Commits
fix/resolv
...
v0.97.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd25c735c1 | ||
|
|
7de33907c5 | ||
|
|
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 | ||
|
|
ed758f4c92 | ||
|
|
22300e8151 | ||
|
|
18c55784c7 | ||
|
|
824a3c5fcc | ||
|
|
87da644027 | ||
|
|
4f42f543d8 | ||
|
|
aa872f47f2 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -2,3 +2,5 @@
|
||||
|
||||
github: [eliandoran]
|
||||
custom: ["https://paypal.me/eliandoran"]
|
||||
liberapay: ElianDoran
|
||||
buy_me_a_coffee: eliandoran
|
||||
|
||||
17
.github/workflows/checks.yml
vendored
Normal file
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
4
.mailmap
@@ -1,2 +1,2 @@
|
||||
Adam Zivner <adam.zivner@gmail.com>
|
||||
Adam Zivner <zadam.apps@gmail.com>
|
||||
zadam <adam.zivner@gmail.com>
|
||||
zadam <zadam.apps@gmail.com>
|
||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -28,5 +28,12 @@
|
||||
"typescript.validate.enable": true,
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||
"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
15
README.md
@@ -1,6 +1,7 @@
|
||||
# Trilium Notes
|
||||
|
||||

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

|
||||

|
||||
[](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):
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
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:
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm nx run edit-docs:edit-docs
|
||||
```
|
||||
@@ -138,8 +139,8 @@ pnpm nx run edit-docs:edit-docs
|
||||
### Building the Executable
|
||||
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
||||
```
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.53.2",
|
||||
"@stylistic/eslint-plugin": "5.1.0",
|
||||
"@playwright/test": "1.54.1",
|
||||
"@stylistic/eslint-plugin": "5.2.0",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.16.2",
|
||||
"@types/node": "22.16.5",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.30.1",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
"jsdoc": "4.0.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.96.0",
|
||||
"version": "0.97.1",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
@@ -10,7 +10,7 @@
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "9.30.1",
|
||||
"@eslint/js": "9.31.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.18",
|
||||
"@fullcalendar/daygrid": "6.1.18",
|
||||
@@ -46,9 +46,9 @@
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.0.0",
|
||||
"mermaid": "11.8.1",
|
||||
"mind-elixir": "5.0.1",
|
||||
"marked": "16.1.1",
|
||||
"mermaid": "11.9.0",
|
||||
"mind-elixir": "5.0.2",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.26.9",
|
||||
@@ -58,7 +58,7 @@
|
||||
"vanilla-js-wheel-zoom": "9.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-inspector": "4.1.0",
|
||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/leaflet": "1.9.20",
|
||||
@@ -68,7 +68,7 @@
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"happy-dom": "18.0.1",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.0"
|
||||
"vite-plugin-static-copy": "3.1.1"
|
||||
},
|
||||
"nx": {
|
||||
"name": "client",
|
||||
|
||||
@@ -28,6 +28,8 @@ import TouchBarComponent from "./touch_bar.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
import type CodeMirror from "@triliumnext/codemirror";
|
||||
import { StartupChecks } from "./startup_checks.js";
|
||||
import type { CreateNoteOpts } from "../services/note_create.js";
|
||||
import { ColumnComponent } from "tabulator-tables";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||
@@ -276,6 +278,21 @@ export type CommandMappings = {
|
||||
|
||||
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 & {
|
||||
TouchBar: typeof TouchBar;
|
||||
buildIcon(name: string): NativeImage;
|
||||
|
||||
@@ -256,6 +256,20 @@ class FNote {
|
||||
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() {
|
||||
return await this.froca.getNotes(this.children);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ export interface MenuCommandItem<T> {
|
||||
title: string;
|
||||
command?: T;
|
||||
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;
|
||||
badges?: MenuItemBadge[];
|
||||
templateNoteId?: string;
|
||||
|
||||
@@ -129,13 +129,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
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.duplicate-subtree")} <kbd data-command="duplicateSubtree">`,
|
||||
command: "duplicateSubtree",
|
||||
uiIcon: "bx bx-outline",
|
||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
||||
},
|
||||
|
||||
|
||||
{ title: "----" },
|
||||
|
||||
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
||||
@@ -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.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>`,
|
||||
command: "deleteNotes",
|
||||
|
||||
@@ -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`, {
|
||||
type: "label",
|
||||
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);
|
||||
|
||||
if (branchIdsToDelete.length === 0) {
|
||||
@@ -110,10 +118,12 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await activateParentNotePath();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (moveToParent) {
|
||||
try {
|
||||
await activateParentNotePath();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
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 { t } from "./i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import toast from "./toast.js";
|
||||
import { BulkAction } from "@triliumnext/commons";
|
||||
|
||||
const ACTION_GROUPS = [
|
||||
{
|
||||
@@ -89,6 +91,17 @@ function parseActions(note: FNote) {
|
||||
.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 {
|
||||
addAction,
|
||||
parseActions,
|
||||
|
||||
@@ -41,8 +41,14 @@ async function info(message: string) {
|
||||
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) {
|
||||
return new Promise((res) =>
|
||||
return new Promise<boolean>((res) =>
|
||||
appContext.triggerCommand("showConfirmDialog", <ConfirmWithMessageOptions>{
|
||||
message,
|
||||
callback: (x: false | ConfirmDialogOptions) => res(x && x.confirmed)
|
||||
|
||||
@@ -316,7 +316,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
||||
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
||||
|
||||
if (notePath) {
|
||||
if (openInPopup) {
|
||||
if (isLeftClick && openInPopup) {
|
||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||
} else if (openInNewWindow) {
|
||||
appContext.triggerCommand("openInWindow", { notePath, viewScope });
|
||||
@@ -405,7 +405,7 @@ function linkContextMenu(e: PointerEvent) {
|
||||
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");
|
||||
|
||||
href = href || $link.attr("href");
|
||||
|
||||
@@ -40,7 +40,10 @@ interface Options {
|
||||
allowCreatingNotes?: boolean;
|
||||
allowJumpToSearchNotes?: boolean;
|
||||
allowExternalLinks?: boolean;
|
||||
/** If set, hides the right-side button corresponding to go to selected note. */
|
||||
hideGoToSelectedNoteButton?: boolean;
|
||||
/** If set, hides all right-side buttons in the autocomplete dropdown */
|
||||
hideAllButtons?: boolean;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
||||
if (!options.hideAllButtons) {
|
||||
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
||||
}
|
||||
|
||||
if (!options.hideGoToSelectedNoteButton) {
|
||||
if (!options.hideGoToSelectedNoteButton && !options.hideAllButtons) {
|
||||
$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 { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
|
||||
interface CreateNoteOpts {
|
||||
export interface CreateNoteOpts {
|
||||
isProtected?: boolean;
|
||||
saveSelection?: boolean;
|
||||
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 ViewMode from "../widgets/view_widgets/view_mode.js";
|
||||
|
||||
export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">;
|
||||
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table" | "geoMap";
|
||||
|
||||
export default class NoteListRenderer {
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
@@ -47,15 +32,36 @@ export default class NoteListRenderer {
|
||||
}
|
||||
|
||||
get isFullHeight() {
|
||||
return this.viewMode?.isFullHeight;
|
||||
switch (this.viewType) {
|
||||
case "list":
|
||||
case "grid":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async renderList() {
|
||||
if (!this.viewMode) {
|
||||
return null;
|
||||
}
|
||||
const args = this.args;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ body {
|
||||
|
||||
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
|
||||
|
||||
--ck-color-image-caption-background: var(--main-background-color);
|
||||
--ck-color-image-caption-text: var(--main-text-color);
|
||||
--ck-content-color-image-caption-background: var(--main-background-color);
|
||||
--ck-content-color-image-caption-text: var(--main-text-color);
|
||||
|
||||
/* -- 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;
|
||||
font-size: inherit;
|
||||
background-color: var(--menu-background-color) !important;
|
||||
@@ -342,7 +343,8 @@ button kbd {
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu {
|
||||
body.desktop .dropdown-menu,
|
||||
body.desktop .tabulator-popup-container {
|
||||
border: 1px solid var(--dropdown-border-color);
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
||||
animation: dropdown-menu-opening 100ms ease-in;
|
||||
@@ -385,7 +387,8 @@ body.desktop .dropdown-menu {
|
||||
}
|
||||
|
||||
.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;
|
||||
background-color: var(--hover-item-background-color) !important;
|
||||
border-color: var(--hover-item-border-color) !important;
|
||||
@@ -921,6 +924,13 @@ div[data-notify="container"] {
|
||||
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 {
|
||||
background-color: var(--accented-background-color);
|
||||
}
|
||||
@@ -1207,12 +1217,14 @@ body.mobile .dropdown-submenu > .dropdown-menu {
|
||||
}
|
||||
|
||||
#context-menu-container,
|
||||
#context-menu-container .dropdown-menu {
|
||||
padding: 3px 0 0;
|
||||
#context-menu-container .dropdown-menu,
|
||||
.tabulator-popup-container {
|
||||
padding: 3px 0;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
#context-menu-container .dropdown-item {
|
||||
#context-menu-container .dropdown-item,
|
||||
.tabulator-menu .tabulator-menu-item {
|
||||
padding: 0 7px 0 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
199
apps/client/src/stylesheets/table.css
Normal file
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(./ribbon.css);
|
||||
@import url(./notes/text.css);
|
||||
@import url(./notes/collections/table.css);
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
@@ -183,7 +184,7 @@ html body .dropdown-item[disabled] {
|
||||
|
||||
/* Menu item icon */
|
||||
.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;
|
||||
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": "移动到...",
|
||||
"paste-into": "粘贴到里面",
|
||||
"paste-after": "粘贴到后面",
|
||||
"duplicate-subtree": "复制子树",
|
||||
"export": "导出",
|
||||
"import-into-note": "导入到笔记",
|
||||
"apply-bulk-actions": "应用批量操作",
|
||||
|
||||
@@ -1384,7 +1384,7 @@
|
||||
"move-to": "Verschieben nach...",
|
||||
"paste-into": "Als Unternotiz einfügen",
|
||||
"paste-after": "Danach einfügen",
|
||||
"duplicate-subtree": "Notizbaum duplizieren",
|
||||
"duplicate": "Duplizieren",
|
||||
"export": "Exportieren",
|
||||
"import-into-note": "In Notiz importieren",
|
||||
"apply-bulk-actions": "Massenaktionen ausführen",
|
||||
|
||||
@@ -1595,7 +1595,7 @@
|
||||
"move-to": "Move to...",
|
||||
"paste-into": "Paste into",
|
||||
"paste-after": "Paste after",
|
||||
"duplicate-subtree": "Duplicate subtree",
|
||||
"duplicate": "Duplicate",
|
||||
"export": "Export",
|
||||
"import-into-note": "Import into note",
|
||||
"apply-bulk-actions": "Apply bulk actions",
|
||||
@@ -1944,10 +1944,29 @@
|
||||
},
|
||||
"table_view": {
|
||||
"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": {
|
||||
"hide-weekends": "Hide weekends",
|
||||
"display-week-numbers": "Display week numbers"
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "Delete row"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1593,7 +1593,7 @@
|
||||
"move-to": "Mover a...",
|
||||
"paste-into": "Pegar en",
|
||||
"paste-after": "Pegar después de",
|
||||
"duplicate-subtree": "Duplicar subárbol",
|
||||
"duplicate": "Duplicar",
|
||||
"export": "Exportar",
|
||||
"import-into-note": "Importar a nota",
|
||||
"apply-bulk-actions": "Aplicar acciones en lote",
|
||||
|
||||
@@ -1389,7 +1389,7 @@
|
||||
"move-to": "Déplacer vers...",
|
||||
"paste-into": "Coller dans",
|
||||
"paste-after": "Coller après",
|
||||
"duplicate-subtree": "Dupliquer le sous-arbre",
|
||||
"duplicate": "Dupliquer",
|
||||
"export": "Exporter",
|
||||
"import-into-note": "Importer dans la note",
|
||||
"apply-bulk-actions": "Appliquer des Actions groupées",
|
||||
|
||||
@@ -1349,7 +1349,7 @@
|
||||
"copy-note-path-to-clipboard": "Copiază calea notiței în clipboard",
|
||||
"cut": "Decupează",
|
||||
"delete": "Șterge",
|
||||
"duplicate-subtree": "Dublifică ierarhia",
|
||||
"duplicate": "Dublifică",
|
||||
"edit-branch-prefix": "Editează prefixul ramurii",
|
||||
"expand-subtree": "Expandează subnotițele",
|
||||
"export": "Exportă",
|
||||
|
||||
@@ -1336,7 +1336,6 @@
|
||||
"move-to": "移動到...",
|
||||
"paste-into": "貼上到裡面",
|
||||
"paste-after": "貼上到後面",
|
||||
"duplicate-subtree": "複製子樹",
|
||||
"export": "匯出",
|
||||
"import-into-note": "匯入到筆記",
|
||||
"apply-bulk-actions": "應用批量操作",
|
||||
|
||||
@@ -78,7 +78,7 @@ const TPL = /*html*/`
|
||||
}
|
||||
</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>
|
||||
|
||||
<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;
|
||||
y: number;
|
||||
focus?: "name";
|
||||
parent?: HTMLElement;
|
||||
hideMultiplicity?: boolean;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.hide();
|
||||
|
||||
@@ -528,7 +530,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.$rowPromotedAlias.toggle(!!definition.isPromoted);
|
||||
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.$rowLabelType.toggle(this.attrType === "label-definition");
|
||||
@@ -560,19 +562,22 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
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 outerHeight = this.$widget.outerHeight();
|
||||
const height = $(window).height();
|
||||
|
||||
if (detPosition && outerHeight && height) {
|
||||
this.$widget
|
||||
.css("left", detPosition.left)
|
||||
.css("right", detPosition.right)
|
||||
.css("top", y - offset.top + 70)
|
||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||
if (!detPosition || !outerHeight || !height) {
|
||||
console.warn("Can't position popup, is it attached?");
|
||||
return;
|
||||
}
|
||||
|
||||
this.$widget
|
||||
.css("left", detPosition.left)
|
||||
.css("right", detPosition.right)
|
||||
.css("top", y - offset.top + 70)
|
||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||
|
||||
if (focus === "name") {
|
||||
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 contextMenuService from "../../menus/context_menu.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 attributeRenderer from "../../services/attribute_renderer.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
@@ -417,16 +417,16 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
this.$editor.tooltip("show");
|
||||
}
|
||||
|
||||
getClickIndex(pos: Position) {
|
||||
getClickIndex(pos: ModelPosition) {
|
||||
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) {
|
||||
curNode = curNode.previousSibling;
|
||||
|
||||
if ((curNode as Element).name === "reference") {
|
||||
clickIndex += (curNode.getAttribute("notePath") as string).length + 1;
|
||||
if ((curNode as ModelElement).name === "reference") {
|
||||
clickIndex += (curNode.getAttribute("href") as string).length + 1;
|
||||
} else if ("data" in curNode) {
|
||||
clickIndex += (curNode.data as string).length;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ const TPL = /*html*/`\
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--modal-background-color);
|
||||
z-index: 1000;
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-file {
|
||||
|
||||
@@ -3,8 +3,6 @@ import NoteListRenderer from "../services/note_list_renderer.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData, EventNames } from "../components/app_context.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*/`
|
||||
<div class="note-list-widget">
|
||||
@@ -39,7 +37,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
private noteIdRefreshed?: string;
|
||||
private shownNoteId?: string | null;
|
||||
private viewMode?: ViewMode<any> | null;
|
||||
private attributeDetailWidget: AttributeDetailWidget;
|
||||
private displayOnlyCollections: boolean;
|
||||
|
||||
/**
|
||||
@@ -47,9 +44,7 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
*/
|
||||
constructor(displayOnlyCollections: boolean) {
|
||||
super();
|
||||
this.attributeDetailWidget = new AttributeDetailWidget()
|
||||
.contentSized()
|
||||
.setParent(this);
|
||||
|
||||
this.displayOnlyCollections = displayOnlyCollections;
|
||||
}
|
||||
|
||||
@@ -72,7 +67,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
this.$widget = $(TPL);
|
||||
this.contentSized();
|
||||
this.$content = this.$widget.find(".note-list-widget-content");
|
||||
this.$widget.append(this.attributeDetailWidget.render());
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
@@ -91,23 +85,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
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() {
|
||||
// console.log("this.isIntersecting", this.isIntersecting);
|
||||
// console.log(`${this.noteIdRefreshed} === ${this.noteId}`, this.noteIdRefreshed === this.noteId);
|
||||
@@ -123,8 +100,7 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
const noteListRenderer = new NoteListRenderer({
|
||||
$parent: this.$content,
|
||||
parentNote: note,
|
||||
parentNotePath: this.notePath,
|
||||
noteIds: note.getChildNoteIds()
|
||||
parentNotePath: this.notePath
|
||||
});
|
||||
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
|
||||
await noteListRenderer.renderList();
|
||||
@@ -169,12 +145,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
this.refresh();
|
||||
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">) {
|
||||
|
||||
@@ -23,10 +23,15 @@ const TPL = /*html*/`
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.book-properties-container > * {
|
||||
.book-properties-container > div {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.book-properties-container > .type-number > label {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.book-properties-container input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -127,6 +132,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
||||
|
||||
renderBookProperty(property: BookProperty) {
|
||||
const $container = $("<div>");
|
||||
$container.addClass(`type-${property.type}`);
|
||||
const note = this.note;
|
||||
if (!note) {
|
||||
return $container;
|
||||
@@ -168,6 +174,27 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
||||
});
|
||||
$container.append($button);
|
||||
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;
|
||||
|
||||
@@ -4,8 +4,6 @@ import attributes from "../../services/attributes";
|
||||
import { ViewTypeOptions } from "../../services/note_list_renderer"
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget";
|
||||
|
||||
export type BookProperty = CheckBoxProperty | ButtonProperty;
|
||||
|
||||
interface BookConfig {
|
||||
properties: BookProperty[];
|
||||
}
|
||||
@@ -24,6 +22,16 @@ interface ButtonProperty {
|
||||
onClick: (context: BookContext) => void;
|
||||
}
|
||||
|
||||
interface NumberProperty {
|
||||
type: "number",
|
||||
label: string;
|
||||
bindToLabel: string;
|
||||
width?: number;
|
||||
min?: number;
|
||||
}
|
||||
|
||||
export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty;
|
||||
|
||||
interface BookContext {
|
||||
note: FNote;
|
||||
triggerCommand: NoteContextAwareWidget["triggerCommand"];
|
||||
@@ -85,6 +93,13 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
||||
properties: []
|
||||
},
|
||||
table: {
|
||||
properties: []
|
||||
properties: [
|
||||
{
|
||||
label: "Max nesting depth:",
|
||||
type: "number",
|
||||
bindToLabel: "maxNestingDepth",
|
||||
width: 65
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,7 +68,6 @@ export default class SearchResultWidget extends NoteContextAwareWidget {
|
||||
const noteListRenderer = new NoteListRenderer({
|
||||
$parent: this.$content,
|
||||
parentNote: note,
|
||||
noteIds: note.getChildNoteIds(),
|
||||
showNotePath: true
|
||||
});
|
||||
await noteListRenderer.renderList();
|
||||
|
||||
@@ -59,7 +59,7 @@ async function handleContentUpdate(affectedNoteIds: string[]) {
|
||||
const templateNoteIds = new Set(templateCache.keys());
|
||||
const affectedTemplateNoteIds = templateNoteIds.intersection(updatedNoteIds);
|
||||
|
||||
await froca.getNotes(affectedNoteIds);
|
||||
await froca.getNotes(affectedNoteIds, true);
|
||||
|
||||
let fullReloadNeeded = false;
|
||||
for (const affectedTemplateNoteId of affectedTemplateNoteIds) {
|
||||
|
||||
@@ -265,7 +265,12 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.$editor.trigger("focus");
|
||||
const editor = this.watchdog.editor;
|
||||
if (editor) {
|
||||
editor.editing.view.focus();
|
||||
} else {
|
||||
this.$editor.trigger("focus");
|
||||
}
|
||||
}
|
||||
|
||||
scrollToEnd() {
|
||||
|
||||
@@ -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}
|
||||
*
|
||||
|
||||
@@ -113,7 +113,6 @@ export default class CalendarView extends ViewMode<{}> {
|
||||
|
||||
private $root: JQuery<HTMLElement>;
|
||||
private $calendarContainer: JQuery<HTMLElement>;
|
||||
private noteIds: string[];
|
||||
private calendar?: Calendar;
|
||||
private isCalendarRoot: boolean;
|
||||
private lastView?: string;
|
||||
@@ -124,15 +123,10 @@ export default class CalendarView extends ViewMode<{}> {
|
||||
|
||||
this.$root = $(TPL);
|
||||
this.$calendarContainer = this.$root.find(".calendar-container");
|
||||
this.noteIds = args.noteIds;
|
||||
this.isCalendarRoot = false;
|
||||
args.$parent.append(this.$root);
|
||||
}
|
||||
|
||||
get isFullHeight(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
||||
this.isCalendarRoot = this.parentNote.hasLabel("calendarRoot") || this.parentNote.hasLabel("workspaceCalendarRoot");
|
||||
const isEditable = !this.isCalendarRoot;
|
||||
@@ -396,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.
|
||||
if (loadResults.getBranchRows().some((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
||||
this.noteIds = this.parentNote.getChildNoteIds();
|
||||
@@ -407,9 +401,14 @@ export default class CalendarView extends ViewMode<{}> {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Refresh on note title change.
|
||||
if (loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId))) {
|
||||
this.calendar?.refetchEvents();
|
||||
}
|
||||
|
||||
// Refresh dataset on subnote change.
|
||||
if (this.calendar && loadResults.getAttributeRows().some((a) => this.noteIds.includes(a.noteId ?? ""))) {
|
||||
this.calendar.refetchEvents();
|
||||
if (loadResults.getAttributeRows().some((a) => this.noteIds.includes(a.noteId ?? ""))) {
|
||||
this.calendar?.refetchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +437,7 @@ export default class CalendarView extends ViewMode<{}> {
|
||||
events.push(await CalendarView.buildEvent(dateNote, { startDate }));
|
||||
|
||||
if (dateNote.hasChildren()) {
|
||||
const childNoteIds = dateNote.getChildNoteIds();
|
||||
const childNoteIds = await dateNote.getSubtreeNoteIds();
|
||||
for (const childNoteId of childNoteIds) {
|
||||
childNoteToDateMapping[childNoteId] = startDate;
|
||||
}
|
||||
@@ -464,13 +463,6 @@ export default class CalendarView extends ViewMode<{}> {
|
||||
for (const note of notes) {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ export default class GeoView extends ViewMode<MapData> {
|
||||
|
||||
// Add the new markers.
|
||||
this.currentMarkerData = {};
|
||||
const notes = await this.parentNote.getChildNotes();
|
||||
const notes = await this.parentNote.getSubtreeNotes();
|
||||
const draggable = !this.isReadOnly;
|
||||
for (const childNote of notes) {
|
||||
if (childNote.mime === "application/gpx+xml") {
|
||||
@@ -243,10 +243,6 @@ export default class GeoView extends ViewMode<MapData> {
|
||||
}
|
||||
}
|
||||
|
||||
get isFullHeight(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
#changeState(newState: State) {
|
||||
this._state = newState;
|
||||
this.$container.toggleClass("placing-note", newState === State.NewNote);
|
||||
@@ -255,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 (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
||||
this.#reloadMarkers();
|
||||
|
||||
@@ -161,7 +161,7 @@ const TPL = /*html*/`
|
||||
class ListOrGridView extends ViewMode<{}> {
|
||||
private $noteList: JQuery<HTMLElement>;
|
||||
|
||||
private noteIds: string[];
|
||||
private filteredNoteIds!: string[];
|
||||
private page?: number;
|
||||
private pageSize?: number;
|
||||
private showNotePath?: boolean;
|
||||
@@ -174,13 +174,6 @@ class ListOrGridView extends ViewMode<{}> {
|
||||
super(args, viewType);
|
||||
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);
|
||||
|
||||
@@ -204,8 +197,14 @@ class ListOrGridView extends ViewMode<{}> {
|
||||
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() {
|
||||
if (this.noteIds.length === 0 || !this.page || !this.pageSize) {
|
||||
if (this.filteredNoteIds.length === 0 || !this.page || !this.pageSize) {
|
||||
this.$noteList.hide();
|
||||
return;
|
||||
}
|
||||
@@ -226,7 +225,7 @@ class ListOrGridView extends ViewMode<{}> {
|
||||
const startIdx = (this.page - 1) * 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);
|
||||
|
||||
for (const note of pageNotes) {
|
||||
@@ -246,7 +245,7 @@ class ListOrGridView extends ViewMode<{}> {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageCount = Math.ceil(this.noteIds.length / this.pageSize);
|
||||
const pageCount = Math.ceil(this.filteredNoteIds.length / this.pageSize);
|
||||
|
||||
$pager.toggle(pageCount > 1);
|
||||
|
||||
@@ -257,7 +256,7 @@ class ListOrGridView extends ViewMode<{}> {
|
||||
lastPrinted = true;
|
||||
|
||||
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(
|
||||
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
|
||||
$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) {
|
||||
|
||||
@@ -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
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
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 { NoteFormatter, NoteTitleFormatter } from "./formatters.js";
|
||||
import { applyHeaderMenu } from "./header-menu.js";
|
||||
import { MonospaceFormatter, NoteFormatter, NoteTitleFormatter, RowNumberFormatter } from "./formatters.js";
|
||||
import type { ColumnDefinition } from "tabulator-tables";
|
||||
import { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
||||
|
||||
type ColumnType = LabelType | "relation";
|
||||
|
||||
export interface PromotedAttributeInformation {
|
||||
export interface AttributeDefinitionInformation {
|
||||
name: string;
|
||||
title?: string;
|
||||
type?: ColumnType;
|
||||
@@ -42,19 +41,30 @@ const labelTypeMappings: Record<ColumnType, Partial<ColumnDefinition>> = {
|
||||
}
|
||||
};
|
||||
|
||||
export function buildColumnDefinitions(info: PromotedAttributeInformation[], existingColumnData?: ColumnDefinition[]) {
|
||||
const columnDefs: ColumnDefinition[] = [
|
||||
interface BuildColumnArgs {
|
||||
info: AttributeDefinitionInformation[];
|
||||
movableRows: boolean;
|
||||
existingColumnData: ColumnDefinition[] | undefined;
|
||||
rowNumberHint: number;
|
||||
position?: number;
|
||||
}
|
||||
|
||||
export function buildColumnDefinitions({ info, movableRows, existingColumnData, rowNumberHint, position }: BuildColumnArgs) {
|
||||
let columnDefs: ColumnDefinition[] = [
|
||||
{
|
||||
title: "#",
|
||||
formatter: "rownum",
|
||||
headerSort: false,
|
||||
hozAlign: "center",
|
||||
resizable: false,
|
||||
frozen: true
|
||||
frozen: true,
|
||||
rowHandle: movableRows,
|
||||
width: calculateIndexColumnWidth(rowNumberHint, movableRows),
|
||||
formatter: RowNumberFormatter(movableRows)
|
||||
},
|
||||
{
|
||||
field: "noteId",
|
||||
title: "Note ID",
|
||||
formatter: MonospaceFormatter,
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
@@ -79,32 +89,59 @@ export function buildColumnDefinitions(info: PromotedAttributeInformation[], exi
|
||||
field,
|
||||
title: title ?? name,
|
||||
editor: "input",
|
||||
rowHandle: false,
|
||||
...labelTypeMappings[type ?? "text"],
|
||||
});
|
||||
seenFields.add(field);
|
||||
}
|
||||
|
||||
applyHeaderMenu(columnDefs);
|
||||
if (existingColumnData) {
|
||||
restoreExistingData(columnDefs, existingColumnData);
|
||||
columnDefs = restoreExistingData(columnDefs, existingColumnData, position);
|
||||
}
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[]) {
|
||||
const byField = new Map<string, ColumnDefinition>;
|
||||
for (const def of oldDefs) {
|
||||
byField.set(def.field ?? "", def);
|
||||
}
|
||||
export function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[], position?: number) {
|
||||
// 1. Keep existing columns, but restore their properties like width, visibility and order.
|
||||
const newItemsByField = new Map<string, ColumnDefinition>(
|
||||
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) {
|
||||
const oldDef = byField.get(newDef.field ?? "");
|
||||
if (!oldDef) {
|
||||
continue;
|
||||
}
|
||||
// 2. Determine new columns.
|
||||
const existingFields = new Set(existingColumns.map(item => item.field));
|
||||
const newColumns = newDefs
|
||||
.filter(item => !existingFields.has(item.field!));
|
||||
|
||||
newDef.width = oldDef.width;
|
||||
newDef.visible = oldDef.visible;
|
||||
}
|
||||
// Clamp position to a valid range
|
||||
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
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*/`\
|
||||
<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")}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm" style="padding: 0px 10px 0px 10px;" data-trigger-command="addNoteListItem">
|
||||
<span class="bx bx-columns"></span> ${t("table_view.new-column")}
|
||||
<button class="btn btn-sm" data-trigger-command="addNewTableColumn">
|
||||
<span class="bx bx-carousel"></span> ${t("table_view.new-column")}
|
||||
</button>
|
||||
`.trimStart();
|
||||
}
|
||||
|
||||
@@ -1,45 +1,89 @@
|
||||
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.
|
||||
*
|
||||
* 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();
|
||||
if (!noteId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
onRendered(async () => {
|
||||
const { $noteRef, href } = buildNoteLink(noteId);
|
||||
await loadReferenceLinkTitle($noteRef, href);
|
||||
cell.getElement().appendChild($noteRef[0]);
|
||||
});
|
||||
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 () => {
|
||||
const note = await froca.getNote(noteId);
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = buildLink(note);
|
||||
if (el) {
|
||||
cell.getElement().appendChild(el);
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
||||
*/
|
||||
export function NoteTitleFormatter(cell: CellComponent) {
|
||||
const { noteId, iconClass } = cell.getRow().getData();
|
||||
const { noteId, iconClass, colorClass } = cell.getRow().getData();
|
||||
if (!noteId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { $noteRef } = buildNoteLink(noteId);
|
||||
$noteRef.text(cell.getValue());
|
||||
$noteRef.prepend($("<span>").addClass(iconClass));
|
||||
|
||||
const { $noteRef } = buildNoteLink(noteId, cell.getValue(), iconClass, colorClass);
|
||||
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 href = `#root/${noteId}`;
|
||||
$noteRef.addClass("reference-link");
|
||||
$noteRef.attr("data-href", href);
|
||||
$noteRef.text(title);
|
||||
$noteRef.prepend($("<span>").addClass(iconClass));
|
||||
if (colorClass) {
|
||||
$noteRef.addClass(colorClass);
|
||||
}
|
||||
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 attributes, { setAttribute, setLabel } from "../../../services/attributes.js";
|
||||
import server from "../../../services/server.js";
|
||||
import attributes from "../../../services/attributes.js";
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import type { CommandListenerData, EventData } from "../../../components/app_context.js";
|
||||
import type { Attribute } from "../../../services/attribute_parser.js";
|
||||
import note_create from "../../../services/note_create.js";
|
||||
import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MenuModule, MoveRowsModule, ColumnDefinition} from 'tabulator-tables';
|
||||
import "tabulator-tables/dist/css/tabulator_bootstrap5.min.css";
|
||||
import type { EventData } from "../../../components/app_context.js";
|
||||
import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule, Options, RowComponent, ColumnComponent} from 'tabulator-tables';
|
||||
import "tabulator-tables/dist/css/tabulator.css";
|
||||
import "../../../../src/stylesheets/table.css";
|
||||
import { canReorderRows, configureReorderingRows } from "./dragging.js";
|
||||
import buildFooter from "./footer.js";
|
||||
import getPromotedAttributeInformation, { buildRowDefinitions } from "./rows.js";
|
||||
import { buildColumnDefinitions } from "./columns.js";
|
||||
import getAttributeDefinitionInformation, { buildRowDefinitions } from "./rows.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*/`
|
||||
<div class="table-view">
|
||||
@@ -63,6 +63,26 @@ const TPL = /*html*/`
|
||||
justify-content: left;
|
||||
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>
|
||||
|
||||
<div class="table-view-container"></div>
|
||||
@@ -79,29 +99,24 @@ export default class TableView extends ViewMode<StateInfo> {
|
||||
|
||||
private $root: JQuery<HTMLElement>;
|
||||
private $container: JQuery<HTMLElement>;
|
||||
private args: ViewModeArgs;
|
||||
private spacedUpdate: SpacedUpdate;
|
||||
private api?: Tabulator;
|
||||
private newAttribute?: Attribute;
|
||||
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 noteIdToEdit?: string;
|
||||
private colEditing?: TableColumnEditing;
|
||||
private rowEditing?: TableRowEditing;
|
||||
private maxDepth: number = -1;
|
||||
private rowNumberHint: number = 1;
|
||||
|
||||
constructor(args: ViewModeArgs) {
|
||||
super(args, "table");
|
||||
|
||||
this.$root = $(TPL);
|
||||
this.$container = this.$root.find(".table-view-container");
|
||||
this.args = args;
|
||||
this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000);
|
||||
this.persistentData = {};
|
||||
args.$parent.append(this.$root);
|
||||
}
|
||||
|
||||
get isFullHeight(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async renderList() {
|
||||
this.$container.empty();
|
||||
this.renderTable(this.$container[0]);
|
||||
@@ -109,29 +124,34 @@ export default class TableView extends ViewMode<StateInfo> {
|
||||
}
|
||||
|
||||
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) {
|
||||
Tabulator.registerModule(module);
|
||||
}
|
||||
|
||||
this.initialize(el);
|
||||
this.initialize(el, info);
|
||||
}
|
||||
|
||||
private async initialize(el: HTMLElement) {
|
||||
const notes = await froca.getNotes(this.args.noteIds);
|
||||
const info = getPromotedAttributeInformation(this.parentNote);
|
||||
|
||||
private async initialize(el: HTMLElement, info: AttributeDefinitionInformation[]) {
|
||||
const viewStorage = await this.viewStorage.restore();
|
||||
this.persistentData = viewStorage?.tableData || {};
|
||||
|
||||
const columnDefs = buildColumnDefinitions(info);
|
||||
const movableRows = canReorderRows(this.parentNote);
|
||||
|
||||
this.api = new Tabulator(el, {
|
||||
this.maxDepth = parseInt(this.parentNote.getLabelValue("maxNestingDepth") ?? "-1", 10);
|
||||
const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(this.parentNote, info, this.maxDepth);
|
||||
this.rowNumberHint = rowNumber;
|
||||
const movableRows = canReorderRows(this.parentNote) && !hasChildren;
|
||||
const columnDefs = buildColumnDefinitions({
|
||||
info,
|
||||
movableRows,
|
||||
existingColumnData: this.persistentData.columns,
|
||||
rowNumberHint: this.rowNumberHint
|
||||
});
|
||||
let opts: Options = {
|
||||
layout: "fitDataFill",
|
||||
index: "noteId",
|
||||
index: "branchId",
|
||||
columns: columnDefs,
|
||||
data: await buildRowDefinitions(this.parentNote, notes, info),
|
||||
data: rowData,
|
||||
persistence: true,
|
||||
movableColumns: true,
|
||||
movableRows,
|
||||
@@ -141,9 +161,30 @@ export default class TableView extends ViewMode<StateInfo> {
|
||||
this.spacedUpdate.scheduleUpdate();
|
||||
},
|
||||
persistenceReaderFunc: (_id, type: string) => this.persistentData?.[type],
|
||||
});
|
||||
configureReorderingRows(this.api);
|
||||
this.setupEditing();
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
setupContextMenu(this.api, this.parentNote);
|
||||
}
|
||||
|
||||
private onSave() {
|
||||
@@ -152,82 +193,35 @@ export default class TableView extends ViewMode<StateInfo> {
|
||||
});
|
||||
}
|
||||
|
||||
private setupEditing() {
|
||||
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 {
|
||||
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (!this.api) {
|
||||
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.
|
||||
if (loadResults.getAttributeRows().find(attr =>
|
||||
attr.type === "label" &&
|
||||
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
||||
attributes.isAffecting(attr, this.parentNote))) {
|
||||
this.#manageColumnUpdate();
|
||||
return await this.#manageRowsUpdate();
|
||||
}
|
||||
|
||||
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId)) {
|
||||
this.#manageRowsUpdate();
|
||||
// Refresh max depth
|
||||
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!))) {
|
||||
this.#manageRowsUpdate();
|
||||
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId || this.noteIds.includes(branch.parentNoteId ?? ""))
|
||||
|| loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId))
|
||||
|| loadResults.getAttributeRows().some(attr => this.noteIds.includes(attr.noteId!))) {
|
||||
return await this.#manageRowsUpdate();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -238,27 +232,40 @@ export default class TableView extends ViewMode<StateInfo> {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = getPromotedAttributeInformation(this.parentNote);
|
||||
const columnDefs = buildColumnDefinitions(info, this.persistentData?.columns);
|
||||
const info = getAttributeDefinitionInformation(this.parentNote);
|
||||
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.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() {
|
||||
if (!this.api) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notes = await froca.getNotes(this.args.noteIds);
|
||||
const info = getPromotedAttributeInformation(this.parentNote);
|
||||
this.api.replaceData(await buildRowDefinitions(this.parentNote, notes, info));
|
||||
const info = getAttributeDefinitionInformation(this.parentNote);
|
||||
const { definitions, hasSubtree, rowNumber } = await buildRowDefinitions(this.parentNote, info, this.maxDepth);
|
||||
this.rowNumberHint = rowNumber;
|
||||
|
||||
if (this.noteIdToEdit) {
|
||||
const row = this.api?.getRows().find(r => r.getData().noteId === this.noteIdToEdit);
|
||||
if (row) {
|
||||
row.getCell("title").edit();
|
||||
}
|
||||
this.noteIdToEdit = undefined;
|
||||
// Force a refresh if the data tree needs enabling/disabling.
|
||||
if (this.api.options.dataTree !== hasSubtree) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.api.replaceData(definitions);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,24 +21,29 @@ export function RelationEditor(cell: CellComponent, onRendered, success, cancel,
|
||||
editor.style.boxSizing = "border-box";
|
||||
|
||||
//Set value of editor to the current value of the cell
|
||||
const noteId = cell.getValue();
|
||||
if (noteId) {
|
||||
const note = froca.getNoteFromCache(noteId);
|
||||
const originalNoteId = cell.getValue();
|
||||
if (originalNoteId) {
|
||||
const note = froca.getNoteFromCache(originalNoteId);
|
||||
editor.value = note.title;
|
||||
} else {
|
||||
editor.value = "";
|
||||
}
|
||||
|
||||
//set focus on the select box when the editor is selected
|
||||
onRendered(function(){
|
||||
let newNoteId = originalNoteId;
|
||||
|
||||
note_autocomplete.initNoteAutocomplete($editor, {
|
||||
allowCreatingNotes: true
|
||||
allowCreatingNotes: true,
|
||||
hideAllButtons: true
|
||||
}).on("autocomplete:noteselected", (event, suggestion, dataset) => {
|
||||
const notePath = suggestion.notePath;
|
||||
if (!notePath) {
|
||||
return;
|
||||
newNoteId = (notePath ?? "").split("/").at(-1);
|
||||
}).on("blur", () => {
|
||||
if (!editor.value) {
|
||||
newNoteId = "";
|
||||
}
|
||||
|
||||
const noteId = notePath.split("/").at(-1);
|
||||
success(noteId);
|
||||
success(newNoteId);
|
||||
});
|
||||
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 type { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
|
||||
import type { PromotedAttributeInformation } from "./columns.js";
|
||||
import type { AttributeDefinitionInformation } from "./columns.js";
|
||||
|
||||
export type TableData = {
|
||||
iconClass: string;
|
||||
@@ -9,11 +9,17 @@ export type TableData = {
|
||||
labels: Record<string, boolean | string | null>;
|
||||
relations: Record<string, boolean | string | null>;
|
||||
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[] = [];
|
||||
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();
|
||||
if (!note) {
|
||||
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) {
|
||||
if (type === "relation") {
|
||||
relations[name] = note.getRelationValue(name);
|
||||
} else if (type === "boolean") {
|
||||
labels[name] = note.hasLabel(name);
|
||||
} else {
|
||||
labels[name] = note.getLabelValue(name);
|
||||
}
|
||||
}
|
||||
definitions.push({
|
||||
|
||||
const def: TableData = {
|
||||
iconClass: note.getIcon(),
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
labels,
|
||||
relations,
|
||||
branchId: branch.branchId
|
||||
});
|
||||
branchId: branch.branchId,
|
||||
colorClass: note.getColorClass()
|
||||
}
|
||||
|
||||
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;
|
||||
return {
|
||||
definitions,
|
||||
hasSubtree,
|
||||
rowNumber
|
||||
};
|
||||
}
|
||||
|
||||
export default function getPromotedAttributeInformation(parentNote: FNote) {
|
||||
const info: PromotedAttributeInformation[] = [];
|
||||
for (const promotedAttribute of parentNote.getPromotedDefinitionAttributes()) {
|
||||
const def = promotedAttribute.getDefinition();
|
||||
export default function getAttributeDefinitionInformation(parentNote: FNote) {
|
||||
const info: AttributeDefinitionInformation[] = [];
|
||||
const attrDefs = parentNote.getAttributes()
|
||||
.filter(attr => attr.isDefinition());
|
||||
for (const attrDef of attrDefs) {
|
||||
const def = attrDef.getDefinition();
|
||||
if (def.multiplicity !== "single") {
|
||||
console.warn("Multiple values are not supported for now");
|
||||
continue;
|
||||
}
|
||||
|
||||
const [ labelType, name ] = promotedAttribute.name.split(":", 2);
|
||||
if (promotedAttribute.type !== "label") {
|
||||
const [ labelType, name ] = attrDef.name.split(":", 2);
|
||||
if (attrDef.type !== "label") {
|
||||
console.warn("Relations are not supported for now");
|
||||
continue;
|
||||
}
|
||||
@@ -69,6 +90,5 @@ export default function getPromotedAttributeInformation(parentNote: FNote) {
|
||||
type
|
||||
});
|
||||
}
|
||||
console.log("Promoted attribute information", info);
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import Component from "../../components/component.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
|
||||
@@ -8,7 +9,6 @@ export interface ViewModeArgs {
|
||||
$parent: JQuery<HTMLElement>;
|
||||
parentNote: FNote;
|
||||
parentNotePath?: string | null;
|
||||
noteIds: string[];
|
||||
showNotePath?: boolean;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export default abstract class ViewMode<T extends object> extends Component {
|
||||
private _viewStorage: ViewModeStorage<T> | null;
|
||||
protected parentNote: FNote;
|
||||
protected viewType: ViewTypeOptions;
|
||||
protected noteIds: string[];
|
||||
protected args: ViewModeArgs;
|
||||
|
||||
constructor(args: ViewModeArgs, viewType: ViewTypeOptions) {
|
||||
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
|
||||
args.$parent.empty();
|
||||
this.viewType = viewType;
|
||||
this.args = args;
|
||||
this.noteIds = [];
|
||||
}
|
||||
|
||||
async beforeRender() {
|
||||
await this.#refreshNoteIds();
|
||||
}
|
||||
|
||||
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.
|
||||
* @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.
|
||||
}
|
||||
|
||||
get isFullHeight() {
|
||||
// Override to change its value.
|
||||
return false;
|
||||
async entitiesReloadedEvent(e: EventData<"entitiesReloaded">) {
|
||||
if (e.loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId || this.noteIds.includes(branch.parentNoteId ?? ""))) {
|
||||
this.#refreshNoteIds();
|
||||
}
|
||||
|
||||
if (await this.onEntitiesReloaded(e)) {
|
||||
appContext.triggerEvent("refreshNoteList", { noteId: this.parentNote.noteId });
|
||||
}
|
||||
}
|
||||
|
||||
get isReadOnly() {
|
||||
@@ -57,4 +70,14 @@ export default abstract class ViewMode<T extends object> extends Component {
|
||||
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": {
|
||||
"dotenv": "17.1.0",
|
||||
"electron": "37.2.0"
|
||||
"dotenv": "17.2.0",
|
||||
"electron": "37.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop",
|
||||
"version": "0.96.0",
|
||||
"version": "0.97.1",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "main.cjs",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "37.2.0",
|
||||
"electron": "37.2.3",
|
||||
"@electron-forge/cli": "7.8.1",
|
||||
"@electron-forge/maker-deb": "7.8.1",
|
||||
"@electron-forge/maker-dmg": "7.8.1",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@triliumnext/desktop": "workspace:*",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "37.2.0",
|
||||
"electron": "37.2.3",
|
||||
"fs-extra": "11.3.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
}
|
||||
},
|
||||
"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
|
||||
|
||||
# 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
|
||||
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
|
||||
RUN apt-get update && \
|
||||
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
|
||||
|
||||
# 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
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.17.0-alpine
|
||||
FROM node:22.17.1-alpine
|
||||
# Install runtime dependencies
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
ARG USER=trilium
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/server",
|
||||
"version": "0.96.0",
|
||||
"version": "0.97.1",
|
||||
"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,
|
||||
"dependencies": {
|
||||
@@ -52,21 +52,21 @@
|
||||
"cheerio": "1.1.0",
|
||||
"chokidar": "4.0.3",
|
||||
"cls-hooked": "4.2.2",
|
||||
"compression": "1.8.0",
|
||||
"compression": "1.8.1",
|
||||
"cookie-parser": "1.4.7",
|
||||
"csrf-csrf": "3.2.2",
|
||||
"dayjs": "1.11.13",
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.1",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "37.2.0",
|
||||
"electron": "37.2.3",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"express": "5.1.0",
|
||||
"express-openid-connect": "^2.17.1",
|
||||
"express-rate-limit": "7.5.1",
|
||||
"express-session": "1.18.1",
|
||||
"express-rate-limit": "8.0.1",
|
||||
"express-session": "1.18.2",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"helmet": "8.1.0",
|
||||
@@ -83,12 +83,12 @@
|
||||
"jimp": "1.6.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "26.1.0",
|
||||
"marked": "16.0.0",
|
||||
"marked": "16.1.1",
|
||||
"mime-types": "3.0.1",
|
||||
"multer": "2.0.1",
|
||||
"multer": "2.0.2",
|
||||
"normalize-strings": "1.1.1",
|
||||
"ollama": "0.5.16",
|
||||
"openai": "5.8.3",
|
||||
"openai": "5.10.1",
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
File diff suppressed because one or more lines are too long
16
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Sharing.html
generated
vendored
16
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Sharing.html
generated
vendored
@@ -6,7 +6,8 @@ class="image">
|
||||
<img style="aspect-ratio:1144/660;" src="Sharing_image.png" width="1144"
|
||||
height="660">
|
||||
</figure>
|
||||
<h2>Features, interaction and limitations</h2>
|
||||
|
||||
<h2>Features, interaction and limitations</h2>
|
||||
<ul>
|
||||
<li>Searching by note title.</li>
|
||||
<li>Automatic dark/light mode based on the user's browser settings.</li>
|
||||
@@ -90,7 +91,7 @@ class="image">
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>
|
||||
<th><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
|
||||
</th>
|
||||
<td>
|
||||
<ul>
|
||||
@@ -189,9 +190,11 @@ class="image">
|
||||
<img src="Sharing_share-single-note.png" alt="Share Note">
|
||||
</p>
|
||||
</li>
|
||||
<li><strong>Access the Shared Note</strong>: The link provided will open the
|
||||
note in your browser. If your server is not configured with a public IP,
|
||||
the URL will refer to <code>localhost (127.0.0.1)</code>.</li>
|
||||
<li>
|
||||
<p><strong>Access the Shared Note</strong>: The link provided will open the
|
||||
note in your browser. If your server is not configured with a public IP,
|
||||
the URL will refer to <code>localhost (127.0.0.1)</code>.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>Sharing a Note Subtree</h2>
|
||||
<p>When you share a note, you actually share the entire subtree of notes
|
||||
@@ -343,7 +346,8 @@ for (const attr of parentNote.attributes) {
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Credits</h2>
|
||||
|
||||
<h2>Credits</h2>
|
||||
<p>Since v0.95.0, a new theme was introduced (and enabled by default) which
|
||||
greatly improves the visual aspect of the Share feature, as well as its
|
||||
functionality (such as mobile support, dark/light mode, collapsible tree,
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h2>Creating a new event/note</h2>
|
||||
<ul>
|
||||
<li>Clicking on a day will create a new child note and assign it to that particular
|
||||
@@ -71,7 +72,7 @@
|
||||
<br>
|
||||
<img src="7_Calendar View_image.png">
|
||||
</li>
|
||||
<li>Left clicking the event will open a <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_ZjLYv08Rp3qC">Quick edit</a> to
|
||||
<li>Left clicking the event will open a <a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> to
|
||||
edit the note in a popup while allowing easy return to the calendar by
|
||||
just dismissing the popup.
|
||||
<ul>
|
||||
@@ -85,7 +86,7 @@
|
||||
edge of the event and dragging the mouse around.</li>
|
||||
</ul>
|
||||
<h2>Configuring the calendar view</h2>
|
||||
<p>In the <em>Collections </em>tab in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_BlN9DFI679QC">Ribbon</a>,
|
||||
<p>In the <em>Collections</em> tab in the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||
it's possible to adjust the following:</p>
|
||||
<ul>
|
||||
<li>Hide weekends from the week view.</li>
|
||||
@@ -253,7 +254,8 @@ class="table">
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>How the calendar works</h2>
|
||||
|
||||
<h2>How the calendar works</h2>
|
||||
<p>
|
||||
<img src="11_Calendar View_image.png">
|
||||
</p>
|
||||
@@ -289,10 +291,9 @@ class="table">
|
||||
not having a <code>dateNote</code> attribute. Children of the child notes
|
||||
will not be displayed.</li>
|
||||
</ul>
|
||||
<p>
|
||||
<img src="8_Calendar View_image.png" width="1217" height="724">
|
||||
</p>
|
||||
<h3>Using a different attribute as event title</h3>
|
||||
<img src="8_Calendar View_image.png" width="1217" height="724">
|
||||
|
||||
<h3>Using a different attribute as event title</h3>
|
||||
<p>By default, events are displayed on the calendar by their note title.
|
||||
However, it is possible to configure a different attribute to be displayed
|
||||
instead.</p>
|
||||
@@ -325,7 +326,8 @@ class="table">
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h3>Using a relation attribute as event title</h3>
|
||||
|
||||
<h3>Using a relation attribute as event title</h3>
|
||||
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set
|
||||
it to <code>name</code> where <code>name</code> is the name of the relation
|
||||
to use.</p>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h2>Repositioning the map</h2>
|
||||
<ul>
|
||||
<li>Click and drag the map in order to move across the map.</li>
|
||||
@@ -109,6 +110,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h3>Adding a new note using the contextual menu</h3>
|
||||
<ol>
|
||||
<li>Right click anywhere on the map, where to place the newly created marker
|
||||
@@ -134,9 +136,8 @@
|
||||
<h2>How the location of the markers is stored</h2>
|
||||
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
||||
of the child notes:</p>
|
||||
<p>
|
||||
<img src="18_Geo Map View_image.png" width="1288" height="278">
|
||||
</p>
|
||||
<img src="18_Geo Map View_image.png" width="1288"
|
||||
height="278">
|
||||
<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>
|
||||
<h2>Repositioning markers</h2>
|
||||
@@ -148,7 +149,7 @@
|
||||
page (<kbd>Ctrl</kbd>+<kbd>R</kbd> ) to cancel it.</p>
|
||||
<h2>Interaction with the markers</h2>
|
||||
<ul>
|
||||
<li>Hovering over a marker will display a <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_lgKX7r3aL30x">Note Tooltip</a> with
|
||||
<li>Hovering over a marker will display a <a class="reference-link" href="#root/_help_lgKX7r3aL30x">Note Tooltip</a> with
|
||||
the content of the note it belongs to.
|
||||
<ul>
|
||||
<li>Clicking on the note title in the tooltip will navigate to the note in
|
||||
@@ -159,7 +160,7 @@
|
||||
<li>Right-clicking the marker will open a contextual menu (as described below).</li>
|
||||
<li>If the map is in read-only mode, clicking on a marker will open a
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_ZjLYv08Rp3qC">Quick edit</a> popup for the corresponding note.</li>
|
||||
class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> popup for the corresponding note.</li>
|
||||
</ul>
|
||||
<h2>Contextual menu</h2>
|
||||
<p>It's possible to press the right mouse button to display a contextual
|
||||
@@ -260,6 +261,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h3>Adding from OpenStreetMap</h3>
|
||||
<p>Similarly to the Google Maps approach:</p>
|
||||
<figure class="table" style="width:100%;">
|
||||
@@ -309,6 +311,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h2>Adding GPS tracks (.gpx)</h2>
|
||||
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
||||
<figure
|
||||
@@ -388,7 +391,8 @@ class="table" style="width:100%;">
|
||||
<img style="aspect-ratio:678/499;" src="13_Geo Map View_image.png" width="678"
|
||||
height="499">
|
||||
</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
|
||||
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
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
displayed instead.</li>
|
||||
</ul>
|
||||
<p>The grid view is also used by default in the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a> of
|
||||
every note, making it easy to navigate to children notes.</p>
|
||||
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>
|
||||
@@ -2,7 +2,7 @@
|
||||
<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/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_8QqnMzx393bx">Grid View</a>,
|
||||
<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,
|
||||
@@ -14,7 +14,7 @@
|
||||
<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/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_BlN9DFI679QC">Ribbon</a>,
|
||||
<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>
|
||||
@@ -5,30 +5,86 @@
|
||||
<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>.
|
||||
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>
|
||||
<h3>Creating a new table</h3>
|
||||
<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>
|
||||
<h3>Adding columns</h3>
|
||||
<p>Each column is a <a href="#root/_help_OFXdgB2nNk1F">promoted attribute</a> that
|
||||
is defined on the Collection note. Ideally, the promoted attributes need
|
||||
to be inheritable in order to show up in the child notes.</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>
|
||||
<p>Each column is a <a href="#root/_help_OFXdgB2nNk1F">promoted or unpromoted attribute</a> that
|
||||
is defined on the Collection note.</p>
|
||||
<p>To create a new column, either:</p>
|
||||
<ul>
|
||||
<li>The current item number, identified by the <code>#</code> symbol. This simply
|
||||
counts the note and is affected by sorting.</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>
|
||||
<li>Press <em>Add new column</em> at the bottom of the table.</li>
|
||||
<li>Right click on an existing column and select Add column to the left/right.</li>
|
||||
<li>Right click on the empty space of the column header and select <em>Label</em> or <em>Relation</em> in
|
||||
the <em>New column</em> section.</li>
|
||||
</ul>
|
||||
<h3>Adding new rows</h3>
|
||||
<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.
|
||||
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"
|
||||
<p>To create a new note, either:</p>
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
<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
|
||||
@@ -37,16 +93,34 @@
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<h3>Sorting</h3>
|
||||
<p>It is possible to sort the data by the values of a column:</p>
|
||||
<h3>Sorting by column</h3>
|
||||
<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>
|
||||
<li>To do so, simply click on a column.</li>
|
||||
<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
|
||||
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>
|
||||
<h3>Reordering and hiding columns</h3>
|
||||
<ul>
|
||||
@@ -55,29 +129,46 @@
|
||||
the item corresponding to the column.</li>
|
||||
</ul>
|
||||
<h3>Reordering rows</h3>
|
||||
<p>Notes can be dragged around to change their order. This will also change
|
||||
the order of the note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
||||
<p>Currently, it's possible to reorder notes even if sorting is used, but
|
||||
the result might be inconsistent.</p>
|
||||
<h2>Limitations</h2>
|
||||
<p>The table functionality is still in its early stages, as such it faces
|
||||
quite a few important limitations:</p>
|
||||
<ol>
|
||||
<li>As mentioned previously, the columns of the table are defined as
|
||||
<a
|
||||
class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
<ol>
|
||||
<li>But only the promoted attributes that are defined at the level of the
|
||||
Collection note are actually taken into consideration.</li>
|
||||
<li>There are plans to recursively look for columns across the sub-hierarchy.</li>
|
||||
</ol>
|
||||
<p>Notes can be dragged around to change their order. To do so, move the
|
||||
mouse over the three vertical dots near the number row and drag the mouse
|
||||
to the desired position.</p>
|
||||
<p>This will also change the order of the note in the <a class="reference-link"
|
||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
||||
<p>Reordering does have some limitations:</p>
|
||||
<ul>
|
||||
<li>If the parent note has <code>#sorted</code>, reordering will be disabled.</li>
|
||||
<li>If using nested tables, then reordering will also be disabled.</li>
|
||||
<li>Currently, it's possible to reorder notes even if column sorting is used,
|
||||
but the result might be inconsistent.</li>
|
||||
</ul>
|
||||
<h3>Nested trees</h3>
|
||||
<p>If the child notes of the collection also have their own child notes,
|
||||
then they will be displayed in a hierarchy.</p>
|
||||
<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>Hierarchy is not yet supported, so the table will only show the items
|
||||
that are direct children of the <em>Collection</em> note.</li>
|
||||
<li>Multiple labels and relations are not supported. If a <a class="reference-link"
|
||||
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> is defined
|
||||
with a <em>Multi value</em> specificity, they will be ignored.</li>
|
||||
</ol>
|
||||
<li>Manually set <code>maxNestingDepth</code> to the desired value.</li>
|
||||
</ul>
|
||||
<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
|
||||
with a <em>Multi value</em> specificity, they will be ignored.</p>
|
||||
<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
|
||||
adding the <code>#viewType=table</code> attribute.</p>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
height="261">
|
||||
</figure>
|
||||
<p>The note tooltip is a convenience feature which displays a popup when
|
||||
hovering over an <a href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_hrZ1D00cLbal">internal link</a> to
|
||||
hovering over an <a href="#root/_help_hrZ1D00cLbal">internal link</a> to
|
||||
another note.</p>
|
||||
<p>The following information is displayed:</p>
|
||||
<ul>
|
||||
@@ -16,22 +16,21 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li>A snippet of the content will be displayed as well.</li>
|
||||
<li>A button to <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_ZjLYv08Rp3qC">quickly edit</a> the
|
||||
note in a popup.</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/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> notes,
|
||||
when hovering over <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_hrZ1D00cLbal">Internal (reference) links</a> .</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>:
|
||||
<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/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_81SGnPGMk7Xc">Geo Map View</a>,
|
||||
<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/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_xWbu3jpNWapp">Calendar View</a>,
|
||||
<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/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_2FvYrpmOXm29">Table View</a>,
|
||||
when hovering over a note title, or over a <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_Cq5X6iKQop6R">relation</a>.</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>
|
||||
<p> </p>
|
||||
</ul>
|
||||
@@ -34,4 +34,4 @@
|
||||
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/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_DvdZhoQZY9Yd">Keyboard shortcuts</a> section.</p>
|
||||
consult the dedicated <a class="reference-link" href="#root/_help_DvdZhoQZY9Yd">Keyboard shortcuts</a> section.</p>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 43 KiB |
@@ -1,4 +1,4 @@
|
||||
<p>The <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> comes
|
||||
<p>The <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> comes
|
||||
with multiple keyboard shortcuts to make editing faster:</p>
|
||||
<ul>
|
||||
<li>Opening notes:
|
||||
@@ -7,7 +7,7 @@
|
||||
<li><kbd>Ctrl</kbd>+<kbd>Click</kbd> or <kbd>Middle click</kbd> to open the note
|
||||
in a new tab.</li>
|
||||
<li><kbd>Ctrl</kbd>+<kbd>Right click</kbd> to open the note in <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_ZjLYv08Rp3qC">Quick edit</a>.</li>
|
||||
href="#root/_help_ZjLYv08Rp3qC">Quick edit</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Navigation within the tree:
|
||||
@@ -23,7 +23,7 @@
|
||||
<li><kbd>Ctrl</kbd>+<kbd>V</kbd> to paste it somewhere.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>For <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_yTjUdsOi4CIE">Multiple selection</a>:
|
||||
<li>For <a class="reference-link" href="#root/_help_yTjUdsOi4CIE">Multiple selection</a>:
|
||||
<ul>
|
||||
<li><kbd>Alt</kbd>+<kbd>Click</kbd>to add a single note to the current selection.</li>
|
||||
<li><kbd>Shift</kbd>+<kbd>Click</kbd>to select a range of notes, starting
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<figure class="image image-style-align-right">
|
||||
<img style="aspect-ratio:372/760;" src="1_Note tree contextual menu_.png"
|
||||
width="372" height="760">
|
||||
<img style="aspect-ratio:269/608;" src="1_Note tree contextual menu_.png"
|
||||
width="269" height="608">
|
||||
</figure>
|
||||
<p>The <em>note tree menu</em> can be accessed by right-clicking in the
|
||||
<a
|
||||
@@ -111,6 +111,15 @@
|
||||
desired notes.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Duplicate</strong>
|
||||
<ul>
|
||||
<li>Creates a copy of the note and its descendants.</li>
|
||||
<li>This process is different from <a class="reference-link" href="#root/_help_IakOLONlIfGI">Cloning Notes</a> since
|
||||
the duplicated note can be edited independently from the original.</li>
|
||||
<li>An alternative to this, if done regularly, would be <a class="reference-link"
|
||||
href="#root/_help_KC1HB96bqqHX">Templates</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Delete</strong>
|
||||
<ul>
|
||||
<li>Will delete the given notes, asking for confirmation first.</li>
|
||||
@@ -146,8 +155,8 @@
|
||||
</ul>
|
||||
<h2>Advanced options</h2>
|
||||
<figure class="image image-style-align-right">
|
||||
<img style="aspect-ratio:289/355;" src="Note tree contextual menu_.png"
|
||||
width="289" height="355">
|
||||
<img style="aspect-ratio:231/263;" src="Note tree contextual menu_.png"
|
||||
width="231" height="263">
|
||||
</figure>
|
||||
<p>The advanced options menu offers some of the less frequently used actions
|
||||
for notes.</p>
|
||||
@@ -177,15 +186,6 @@
|
||||
from an external source or an older version of Trilium.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Duplicate subtree</strong>
|
||||
<ul>
|
||||
<li>Creates a copy of the note and its descendants.</li>
|
||||
<li>This process is different from <a class="reference-link" href="#root/_help_IakOLONlIfGI">Cloning Notes</a> since
|
||||
the duplicated note can be edited independently from the original.</li>
|
||||
<li>An alternative to this, if done regularly, would be <a class="reference-link"
|
||||
href="#root/_help_KC1HB96bqqHX">Templates</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Expand subtree</strong>
|
||||
<ul>
|
||||
<li>Expands all the child notes in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
@@ -2,33 +2,32 @@
|
||||
<img style="aspect-ratio:895/694;" src="Quick edit_image.png" width="895"
|
||||
height="694">
|
||||
</figure>
|
||||
<p><em>Quick edit </em>provides an alternative to the standard tab-based
|
||||
navigation and editing.</p>
|
||||
<p><em>Quick edit</em> provides an alternative to the standard tab-based navigation
|
||||
and editing.</p>
|
||||
<p>Instead of clicking on a note which switches the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> to
|
||||
the newly selected note, or navigating between two different <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_3seOhtN8uLIY">Tabs</a>,
|
||||
the <em>Quick edit</em> feature opens as a popup window that can be easily
|
||||
dismissed.</p>
|
||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> to the newly selected
|
||||
note, or navigating between two different <a class="reference-link"
|
||||
href="#root/_help_3seOhtN8uLIY">Tabs</a>, the <em>Quick edit</em> feature
|
||||
opens as a popup window that can be easily dismissed.</p>
|
||||
<p>This feature is also well integrated with <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a> such
|
||||
as the calendar view, which makes it easy to edit entries without having
|
||||
to go back and forth between the child note and the calendar.</p>
|
||||
href="#root/_help_GTwFsgaA0lCt">Collections</a> such as the calendar
|
||||
view, which makes it easy to edit entries without having to go back and
|
||||
forth between the child note and the calendar.</p>
|
||||
<h2>Feature highlights</h2>
|
||||
<ul>
|
||||
<li>All note types are supported, including <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
<li>Note that the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a> will
|
||||
href="#root/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
<li>Note that the <a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a> will
|
||||
not be displayed, except for notes of type <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
<li>For <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> notes,
|
||||
href="#root/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
<li>For <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> notes,
|
||||
depending on user preference, both the floating and classic editors are
|
||||
supported. See <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_nRhnJkTT8cPs">Formatting toolbar</a>.</li>
|
||||
supported. See <a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>.</li>
|
||||
<li>The title and the note and the icon are editable, just like a normal tab.</li>
|
||||
<li>The <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> are
|
||||
<li>The <a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> are
|
||||
also displayed.
|
||||
<ul>
|
||||
<li>This integrates well with <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a> where
|
||||
<li>This integrates well with <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a> where
|
||||
there are predefined attributes such as the <em>Start date</em> and <em>End date</em>,
|
||||
allowing for easy editing.</li>
|
||||
</ul>
|
||||
@@ -36,30 +35,30 @@
|
||||
</ul>
|
||||
<h2>Accessing the quick edit</h2>
|
||||
<ul>
|
||||
<li> From the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>:
|
||||
<li>From the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>:
|
||||
<ul>
|
||||
<li>Right click on a note and select <em>Quick edit</em>.</li>
|
||||
<li>or, press <kbd>Ctrl</kbd>+<kbd>Right click</kbd> on a note.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>On <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_hrZ1D00cLbal">Internal (reference) links</a>:
|
||||
<li>On <a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a>:
|
||||
<ul>
|
||||
<li>Right click and select <em>Quick edit</em>.</li>
|
||||
<li>or, press <kbd>Ctrl</kbd>+<kbd>Right click</kbd> on the link.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>On a <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_lgKX7r3aL30x">Note Tooltip</a>,
|
||||
<li>On a <a class="reference-link" href="#root/_help_lgKX7r3aL30x">Note Tooltip</a>,
|
||||
press the quick edit icon.</li>
|
||||
<li>In <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>:
|
||||
<li>In <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>:
|
||||
<ul>
|
||||
<li>For <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_xWbu3jpNWapp">Calendar View</a>:
|
||||
<li>For <a class="reference-link" href="#root/_help_xWbu3jpNWapp">Calendar View</a>:
|
||||
<ul>
|
||||
<li>Clicking on an event will open that event for quick editing.</li>
|
||||
<li>If the calendar is for the <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/5668rwcirq1t/_help_l0tKav7yLHGF">Day Notes</a> root,
|
||||
<li>If the calendar is for the <a class="reference-link" href="#root/_help_l0tKav7yLHGF">Day Notes</a> root,
|
||||
clicking on the day number will open the popup for that day note.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>For <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_81SGnPGMk7Xc">Geo Map View</a>:
|
||||
<li>For <a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a>:
|
||||
<ul>
|
||||
<li>Clicking on a marker will open that marker, but only if the map is in
|
||||
read-only mode.</li>
|
||||
|
||||
43
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Feature Highlights.html
generated
vendored
43
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Feature Highlights.html
generated
vendored
@@ -5,23 +5,23 @@
|
||||
<ul>
|
||||
<li>v0.97.0:
|
||||
<ul>
|
||||
<li>Books are now <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_2FvYrpmOXm29">Table View</a> is
|
||||
<li>Books are now <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
<li><a class="reference-link" href="#root/_help_2FvYrpmOXm29">Table View</a> is
|
||||
a new collection type displaying notes and attributes in an editable grid.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_ZjLYv08Rp3qC">Quick edit</a> is
|
||||
<li><a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> is
|
||||
introduced, adding a new way to edit notes in a popup instead of opening
|
||||
a new tab. It also integrates well with <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
href="#root/_help_GTwFsgaA0lCt">Collections</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>v0.96.0:
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> gain
|
||||
<li><a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> gain
|
||||
premium features thanks to a collaboration with the CKEditor team:
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/gLt3vA97tMcp/_help_ZlN4nump6EbW">Slash Commands</a>
|
||||
<li><a class="reference-link" href="#root/_help_ZlN4nump6EbW">Slash Commands</a>
|
||||
</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/gLt3vA97tMcp/_help_pwc194wlRzcH">Text Snippets</a>
|
||||
<li><a class="reference-link" href="#root/_help_pwc194wlRzcH">Text Snippets</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -30,33 +30,33 @@
|
||||
<li>v0.95.0:
|
||||
<ul>
|
||||
<li>A more friendly theme was introduced for <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_R9pX4DGra2Vt">Sharing</a>,
|
||||
with search, expandable tree, night mode and more.</li>
|
||||
href="#root/_help_R9pX4DGra2Vt">Sharing</a>, with search, expandable tree,
|
||||
night mode and more.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>v0.94.0:
|
||||
<ul>
|
||||
<li>Added integration with <a class="reference-link" href="#root/pOsGYCXsbNQG/_help_LMAv4Uy3Wk6J">AI</a> (using
|
||||
<li>Added integration with <a class="reference-link" href="#root/_help_LMAv4Uy3Wk6J">AI</a> (using
|
||||
self-hosted LLMs such as Ollama or industry standards such as ChatGPT).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>v0.92.5:
|
||||
<ul>
|
||||
<li>Windows binaries are now signed.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/Otzi9La2YAUX/WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz">Multi-Factor Authentication</a> was
|
||||
<li><a class="reference-link" href="#root/_help_7DAiwaf8Z7Rz">Multi-Factor Authentication</a> was
|
||||
introduced.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>v0.92.4:
|
||||
<ul>
|
||||
<li>macOS binaries are now signed.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> notes
|
||||
can now have adjustable <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_veGu4faJErEM">Content language & Right-to-left support</a>.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_NRnIZmSMc5sj">Export as PDF</a>
|
||||
<li><a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> notes
|
||||
can now have adjustable <a class="reference-link" href="#root/_help_veGu4faJErEM">Content language & Right-to-left support</a>.</li>
|
||||
<li><a class="reference-link" href="#root/_help_NRnIZmSMc5sj">Export as PDF</a>
|
||||
</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/_help_rC3pL2aptaRE">Zen mode</a>
|
||||
<li><a class="reference-link" href="#root/_help_rC3pL2aptaRE">Zen mode</a>
|
||||
</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_xWbu3jpNWapp">Calendar View</a>,
|
||||
<li><a class="reference-link" href="#root/_help_xWbu3jpNWapp">Calendar View</a>,
|
||||
allowing notes to be displayed in a monthly grid based on start and end
|
||||
dates.</li>
|
||||
</ul>
|
||||
@@ -64,19 +64,20 @@
|
||||
<li>v0.91.5:
|
||||
<ul>
|
||||
<li>Significant improvements for mobile.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_AgjCISero73a">Footnotes</a> are
|
||||
now supported in <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> notes.</li>
|
||||
<li><a class="reference-link" href="#root/_help_AgjCISero73a">Footnotes</a> are
|
||||
now supported in <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> notes.</li>
|
||||
<li>Mermaid diagrams can now be inserted inline within <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> notes.</li>
|
||||
href="#root/_help_iPIMuisry3hd">Text</a> notes.</li>
|
||||
<li>The TriliumNext theme is introduced, bringing a more modern design to
|
||||
the application.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_81SGnPGMk7Xc">Geo Map View</a>,
|
||||
<li><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a>,
|
||||
displaying notes as markers on a geographical map for easy trip planning.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>v0.90.8:
|
||||
<ul>
|
||||
<li>A new note type was introduced: <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_gBbsAeiuUxI5">Mind Map</a> </li>
|
||||
<li>A new note type was introduced: <a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html
generated
vendored
@@ -84,7 +84,7 @@ style="width:100%;">
|
||||
kind of content, provided there is a script behind it to generate it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_GTwFsgaA0lCt">Collections</a>
|
||||
<td><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
|
||||
</td>
|
||||
<td>
|
||||
<p>Displays the children of the note either as a grid, a list, or for a more
|
||||
|
||||
42
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections.html
generated
vendored
42
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections.html
generated
vendored
@@ -4,51 +4,51 @@
|
||||
child notes into one continuous view. This makes it ideal for reading extensive
|
||||
information broken into smaller, manageable segments.</p>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_8QqnMzx393bx">Grid View</a> which
|
||||
<li><a class="reference-link" href="#root/_help_8QqnMzx393bx">Grid View</a> which
|
||||
is the default presentation method for child notes (see <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a>),
|
||||
where the notes are displayed as tiles with their title and content being
|
||||
visible. </li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_mULW0Q3VojwY">List View</a> is
|
||||
similar to <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_8QqnMzx393bx">Grid View</a>,
|
||||
href="#root/_help_0ESUbbAxVnoK">Note List</a>), where the notes are displayed
|
||||
as tiles with their title and content being visible.</li>
|
||||
<li><a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a> is
|
||||
similar to <a class="reference-link" href="#root/_help_8QqnMzx393bx">Grid View</a>,
|
||||
but it displays the notes one under the other with the content being expandable/collapsible,
|
||||
but also works recursively.</li>
|
||||
</ul>
|
||||
<p>More specialized collections were introduced, such as the:</p>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_xWbu3jpNWapp">Calendar View</a> which
|
||||
<li><a class="reference-link" href="#root/_help_xWbu3jpNWapp">Calendar View</a> which
|
||||
displays a week, month or year calendar with the notes being shown as events.
|
||||
New events can be added easily by dragging across the calendar. </li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_81SGnPGMk7Xc">Geo Map View</a> which
|
||||
New events can be added easily by dragging across the calendar.</li>
|
||||
<li><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a> which
|
||||
displays a geographical map in which the notes are represented as markers/pins
|
||||
on the map. New events can be easily added by pointing on the map.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/GTwFsgaA0lCt/_help_2FvYrpmOXm29">Table View</a> displays
|
||||
each note as a row in a table, with <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> being
|
||||
<li><a class="reference-link" href="#root/_help_2FvYrpmOXm29">Table View</a> displays
|
||||
each note as a row in a table, with <a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> being
|
||||
shown as well. This makes it easy to visualize attributes of notes, as
|
||||
well as making them easily editable.</li>
|
||||
</ul>
|
||||
<p>For a quick presentation of all the supported view types, see the child
|
||||
notes of this help page, including screenshots.</p>
|
||||
<h2>Configuration</h2>
|
||||
<p>To adjust the view type, see the dedicated <em>Collections </em>tab in
|
||||
the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>.</p>
|
||||
<p>To adjust the view type, see the dedicated <em>Collections</em> tab in the
|
||||
<a
|
||||
class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>.</p>
|
||||
<h2>Use in saved search</h2>
|
||||
<p>Since collections are based on the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a> mechanism,
|
||||
<p>Since collections are based on the <a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a> mechanism,
|
||||
it's possible to apply the same configuration to <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_m523cpzocqaD">Saved Search</a> to
|
||||
do advanced querying and presenting the result in an adequate matter such
|
||||
as a calendar, a table or even a map.</p>
|
||||
href="#root/_help_m523cpzocqaD">Saved Search</a> to do advanced querying
|
||||
and presenting the result in an adequate matter such as a calendar, a table
|
||||
or even a map.</p>
|
||||
<h2>Under the hood</h2>
|
||||
<p>Collections by themselves are simply notes with no content that rely on
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a> mechanism
|
||||
the <a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a> mechanism
|
||||
(the one that lists the children notes at the bottom of a note) to display
|
||||
information.</p>
|
||||
<p>By default, new collections use predefined <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_KC1HB96bqqHX">Templates</a> that
|
||||
are stored safely in the <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_2mUhVmZK8RF3">Hidden Notes</a> to
|
||||
href="#root/_help_KC1HB96bqqHX">Templates</a> that are stored safely
|
||||
in the <a class="reference-link" href="#root/_help_2mUhVmZK8RF3">Hidden Notes</a> to
|
||||
define some basic configuration such as the type of view, but also some
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> to make editing easier.</p>
|
||||
class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> to make editing easier.</p>
|
||||
<p>Collections don't store their configuration (e.g. the position on the
|
||||
map, the hidden columns in a table) in the content of the note itself,
|
||||
but as attachments.</p>
|
||||
4
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Links.html
generated
vendored
4
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Links.html
generated
vendored
@@ -1,8 +1,8 @@
|
||||
<p>There are two types of links:</p>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_3IDVtesTQ8ds">External links</a>,
|
||||
<li><a class="reference-link" href="#root/_help_3IDVtesTQ8ds">External links</a>,
|
||||
for standard hyperlinks to websites or other resources.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_hrZ1D00cLbal">Internal (reference) links</a> for
|
||||
<li><a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a> for
|
||||
links to other notes within Trilium.</li>
|
||||
</ul>
|
||||
<h2>Pasting links</h2>
|
||||
|
||||
@@ -43,7 +43,6 @@ interface MetricsData {
|
||||
*/
|
||||
function formatPrometheusMetrics(data: MetricsData): string {
|
||||
const lines: string[] = [];
|
||||
const timestamp = Math.floor(new Date(data.timestamp).getTime() / 1000);
|
||||
|
||||
// Helper function to add a metric
|
||||
const addMetric = (name: string, value: number | null, help: string, type: string = 'gauge', labels: Record<string, string> = {}) => {
|
||||
@@ -56,7 +55,7 @@ function formatPrometheusMetrics(data: MetricsData): string {
|
||||
? `{${Object.entries(labels).map(([k, v]) => `${k}="${v}"`).join(',')}}`
|
||||
: '';
|
||||
|
||||
lines.push(`${name}${labelStr} ${value} ${timestamp}`);
|
||||
lines.push(`${name}${labelStr} ${value}`);
|
||||
lines.push('');
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,18 @@ import type { Router } from "express";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { RESOURCE_DIR } from "../services/resource_dir";
|
||||
import rateLimit from "express-rate-limit";
|
||||
|
||||
const specPath = path.join(RESOURCE_DIR, "etapi.openapi.yaml");
|
||||
let spec: string | null = null;
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // limit each IP to 100 requests per windowMs
|
||||
});
|
||||
|
||||
function register(router: Router) {
|
||||
router.get("/etapi/etapi.openapi.yaml", (_, res) => {
|
||||
router.get("/etapi/etapi.openapi.yaml", limiter, (_, res) => {
|
||||
if (!spec) {
|
||||
spec = fs.readFileSync(specPath, "utf8");
|
||||
}
|
||||
|
||||
@@ -3,13 +3,25 @@ import becca from "../../becca/becca.js";
|
||||
import bulkActionService from "../../services/bulk_actions.js";
|
||||
|
||||
function execute(req: Request) {
|
||||
const { noteIds, includeDescendants } = req.body;
|
||||
const { noteIds, includeDescendants, actions } = req.body;
|
||||
if (!Array.isArray(noteIds)) {
|
||||
throw new Error("noteIds must be an array");
|
||||
}
|
||||
|
||||
const affectedNoteIds = getAffectedNoteIds(noteIds, includeDescendants);
|
||||
|
||||
const bulkActionNote = becca.getNoteOrThrow("_bulkAction");
|
||||
|
||||
bulkActionService.executeActions(bulkActionNote, affectedNoteIds);
|
||||
if (actions && actions.length > 0) {
|
||||
for (const action of actions) {
|
||||
if (!action.name) {
|
||||
throw new Error("Action must have a name");
|
||||
}
|
||||
}
|
||||
bulkActionService.executeActions(actions, affectedNoteIds);
|
||||
} else {
|
||||
bulkActionService.executeActionsFromNote(bulkActionNote, affectedNoteIds);
|
||||
}
|
||||
}
|
||||
|
||||
function getAffectedNoteCount(req: Request) {
|
||||
|
||||
@@ -110,7 +110,7 @@ function createNote(req: Request) {
|
||||
|
||||
const { target, targetBranchId } = req.query;
|
||||
|
||||
if (target !== "into" && target !== "after") {
|
||||
if (target !== "into" && target !== "after" && target !== "before") {
|
||||
throw new ValidationError("Invalid target type.");
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ function searchAndExecute(req: Request) {
|
||||
|
||||
const { searchResultNoteIds } = searchService.searchFromNote(note);
|
||||
|
||||
bulkActionService.executeActions(note, searchResultNoteIds);
|
||||
bulkActionService.executeActionsFromNote(note, searchResultNoteIds);
|
||||
}
|
||||
|
||||
function quickSearch(req: Request) {
|
||||
|
||||
@@ -5,25 +5,15 @@ import branchService from "./branches.js";
|
||||
import { randomString } from "./utils.js";
|
||||
import eraseService from "./erase.js";
|
||||
import type BNote from "../becca/entities/bnote.js";
|
||||
import { ActionHandlers, BulkAction, BulkActionData } from "@triliumnext/commons";
|
||||
|
||||
interface Action {
|
||||
labelName: string;
|
||||
labelValue: string;
|
||||
oldLabelName: string;
|
||||
newLabelName: string;
|
||||
type ActionHandler<T> = (action: T, note: BNote) => void;
|
||||
|
||||
relationName: string;
|
||||
oldRelationName: string;
|
||||
newRelationName: string;
|
||||
type ActionHandlerMap = {
|
||||
[K in keyof ActionHandlers]: ActionHandler<BulkActionData<K>>;
|
||||
};
|
||||
|
||||
targetNoteId: string;
|
||||
targetParentNoteId: string;
|
||||
newTitle: string;
|
||||
script: string;
|
||||
}
|
||||
type ActionHandler = (action: Action, note: BNote) => void;
|
||||
|
||||
const ACTION_HANDLERS: Record<string, ActionHandler> = {
|
||||
const ACTION_HANDLERS: ActionHandlerMap = {
|
||||
addLabel: (action, note) => {
|
||||
note.addLabel(action.labelName, action.labelValue);
|
||||
},
|
||||
@@ -125,7 +115,7 @@ const ACTION_HANDLERS: Record<string, ActionHandler> = {
|
||||
|
||||
note.save();
|
||||
}
|
||||
};
|
||||
} as const;
|
||||
|
||||
function getActions(note: BNote) {
|
||||
return note
|
||||
@@ -145,15 +135,23 @@ function getActions(note: BNote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return action;
|
||||
return action as BulkAction;
|
||||
})
|
||||
.filter((a) => !!a);
|
||||
}
|
||||
|
||||
function executeActions(note: BNote, searchResultNoteIds: string[] | Set<string>) {
|
||||
/**
|
||||
* Executes the bulk actions defined in the note against the provided search result note IDs.
|
||||
* @param note the note containing the bulk actions, read from the `action` label.
|
||||
* @param noteIds the IDs of the notes to apply the actions to.
|
||||
*/
|
||||
function executeActionsFromNote(note: BNote, noteIds: string[] | Set<string>) {
|
||||
const actions = getActions(note);
|
||||
return executeActions(actions, noteIds);
|
||||
}
|
||||
|
||||
for (const resultNoteId of searchResultNoteIds) {
|
||||
function executeActions(actions: BulkAction[], noteIds: string[] | Set<string>) {
|
||||
for (const resultNoteId of noteIds) {
|
||||
const resultNote = becca.getNote(resultNoteId);
|
||||
|
||||
if (!resultNote) {
|
||||
@@ -164,7 +162,8 @@ function executeActions(note: BNote, searchResultNoteIds: string[] | Set<string>
|
||||
try {
|
||||
log.info(`Applying action handler to note ${resultNote.noteId}: ${JSON.stringify(action)}`);
|
||||
|
||||
ACTION_HANDLERS[action.name](action, resultNote);
|
||||
const handler = ACTION_HANDLERS[action.name] as (a: typeof action, n: BNote) => void;
|
||||
handler(action, resultNote);
|
||||
} catch (e: any) {
|
||||
log.error(`ExecuteScript search action failed with ${e.message}`);
|
||||
}
|
||||
@@ -173,5 +172,6 @@ function executeActions(note: BNote, searchResultNoteIds: string[] | Set<string>
|
||||
}
|
||||
|
||||
export default {
|
||||
executeActions
|
||||
executeActions,
|
||||
executeActionsFromNote
|
||||
};
|
||||
|
||||
@@ -254,7 +254,7 @@ function createNewNote(params: NoteParams): {
|
||||
});
|
||||
}
|
||||
|
||||
function createNewNoteWithTarget(target: "into" | "after", targetBranchId: string | undefined, params: NoteParams) {
|
||||
function createNewNoteWithTarget(target: "into" | "after" | "before", targetBranchId: string | undefined, params: NoteParams) {
|
||||
if (!params.type) {
|
||||
const parentNote = becca.notes[params.parentNoteId];
|
||||
|
||||
@@ -277,6 +277,19 @@ function createNewNoteWithTarget(target: "into" | "after", targetBranchId: strin
|
||||
|
||||
entityChangesService.putNoteReorderingEntityChange(params.parentNoteId);
|
||||
|
||||
return retObject;
|
||||
} else if (target === "before" && targetBranchId) {
|
||||
const beforeBranch = becca.branches[targetBranchId];
|
||||
|
||||
// not updating utcDateModified to avoid having to sync whole rows
|
||||
sql.execute("UPDATE branches SET notePosition = notePosition - 10 WHERE parentNoteId = ? AND notePosition < ? AND isDeleted = 0", [params.parentNoteId, beforeBranch.notePosition]);
|
||||
|
||||
params.notePosition = beforeBranch.notePosition - 10;
|
||||
|
||||
const retObject = createNewNote(params);
|
||||
|
||||
entityChangesService.putNoteReorderingEntityChange(params.parentNoteId);
|
||||
|
||||
return retObject;
|
||||
} else {
|
||||
throw new Error(`Unknown target '${target}'`);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"eslint": "^9.18.0",
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
</main>
|
||||
|
||||
<footer class="container max-w-screen mx-0 w-full bg-white dark:bg-gray-900 mt-2 py-6 text-sm text-center text-gray-500">
|
||||
© 2024-2025 <a href="https://github.com/eliandoran" class="text-blue-500 hover:underline">Elian Doran</a> and the <a href="https://github.com/TriliumNext/Notes/graphs/contributors" class="text-blue-500 hover:underline">team</a>. <br/> © 2017-2024 <a href="https://github.com/zadam" class="text-blue-500 hover:underline">Adam Zivner</a>.
|
||||
© 2024-2025 <a href="https://github.com/eliandoran" class="text-blue-500 hover:underline">Elian Doran</a> and the <a href="https://github.com/TriliumNext/Notes/graphs/contributors" class="text-blue-500 hover:underline">team</a>. <br/> © 2017-2024 <a href="https://github.com/zadam" class="text-blue-500 hover:underline">zadam</a>.
|
||||
</footer>
|
||||
|
||||
2
docs/Developer Guide/!!!meta.json
vendored
2
docs/Developer Guide/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.96.0",
|
||||
"appVersion": "0.97.0",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
|
||||
120
docs/Release Notes/!!!meta.json
vendored
120
docs/Release Notes/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.96.0",
|
||||
"appVersion": "0.97.0",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
@@ -61,6 +61,58 @@
|
||||
"attachments": [],
|
||||
"dirFileName": "Release Notes",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "OtFZ6Nd9vM3n",
|
||||
"notePath": [
|
||||
"hD3V4hiu2VW4",
|
||||
"OtFZ6Nd9vM3n"
|
||||
],
|
||||
"title": "v0.97.1",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "template",
|
||||
"value": "wyurrlcDl416",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "v0.97.1.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "SJZ5PwfzHSQ1",
|
||||
"notePath": [
|
||||
"hD3V4hiu2VW4",
|
||||
"SJZ5PwfzHSQ1"
|
||||
],
|
||||
"title": "v0.97.0",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "template",
|
||||
"value": "wyurrlcDl416",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "v0.97.0.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "mYXFde3LuNR7",
|
||||
@@ -69,7 +121,7 @@
|
||||
"mYXFde3LuNR7"
|
||||
],
|
||||
"title": "v0.96.0",
|
||||
"notePosition": 10,
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -95,7 +147,7 @@
|
||||
"jthwbL0FdaeU"
|
||||
],
|
||||
"title": "v0.95.0",
|
||||
"notePosition": 20,
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -121,7 +173,7 @@
|
||||
"7HGYsJbLuhnv"
|
||||
],
|
||||
"title": "v0.94.1",
|
||||
"notePosition": 30,
|
||||
"notePosition": 50,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -147,7 +199,7 @@
|
||||
"Neq53ujRGBqv"
|
||||
],
|
||||
"title": "v0.94.0",
|
||||
"notePosition": 40,
|
||||
"notePosition": 60,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -173,7 +225,7 @@
|
||||
"VN3xnce1vLkX"
|
||||
],
|
||||
"title": "v0.93.0",
|
||||
"notePosition": 50,
|
||||
"notePosition": 70,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -191,7 +243,7 @@
|
||||
"WRaBfQqPr6qo"
|
||||
],
|
||||
"title": "v0.92.7",
|
||||
"notePosition": 60,
|
||||
"notePosition": 80,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -217,7 +269,7 @@
|
||||
"a2rwfKNmUFU1"
|
||||
],
|
||||
"title": "v0.92.6",
|
||||
"notePosition": 70,
|
||||
"notePosition": 90,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -235,7 +287,7 @@
|
||||
"fEJ8qErr0BKL"
|
||||
],
|
||||
"title": "v0.92.5-beta",
|
||||
"notePosition": 80,
|
||||
"notePosition": 100,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -253,7 +305,7 @@
|
||||
"kkkZQQGSXjwy"
|
||||
],
|
||||
"title": "v0.92.4",
|
||||
"notePosition": 90,
|
||||
"notePosition": 110,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -271,7 +323,7 @@
|
||||
"vAroNixiezaH"
|
||||
],
|
||||
"title": "v0.92.3-beta",
|
||||
"notePosition": 100,
|
||||
"notePosition": 120,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -289,7 +341,7 @@
|
||||
"mHEq1wxAKNZd"
|
||||
],
|
||||
"title": "v0.92.2-beta",
|
||||
"notePosition": 110,
|
||||
"notePosition": 130,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -307,7 +359,7 @@
|
||||
"IykjoAmBpc61"
|
||||
],
|
||||
"title": "v0.92.1-beta",
|
||||
"notePosition": 120,
|
||||
"notePosition": 140,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -325,7 +377,7 @@
|
||||
"dq2AJ9vSBX4Y"
|
||||
],
|
||||
"title": "v0.92.0-beta",
|
||||
"notePosition": 130,
|
||||
"notePosition": 150,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -343,7 +395,7 @@
|
||||
"3a8aMe4jz4yM"
|
||||
],
|
||||
"title": "v0.91.6",
|
||||
"notePosition": 140,
|
||||
"notePosition": 160,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -361,7 +413,7 @@
|
||||
"8djQjkiDGESe"
|
||||
],
|
||||
"title": "v0.91.5",
|
||||
"notePosition": 150,
|
||||
"notePosition": 170,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -379,7 +431,7 @@
|
||||
"OylxVoVJqNmr"
|
||||
],
|
||||
"title": "v0.91.4-beta",
|
||||
"notePosition": 160,
|
||||
"notePosition": 180,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -397,7 +449,7 @@
|
||||
"tANGQDvnyhrj"
|
||||
],
|
||||
"title": "v0.91.3-beta",
|
||||
"notePosition": 170,
|
||||
"notePosition": 190,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -415,7 +467,7 @@
|
||||
"hMoBfwSoj1SC"
|
||||
],
|
||||
"title": "v0.91.2-beta",
|
||||
"notePosition": 180,
|
||||
"notePosition": 200,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -433,7 +485,7 @@
|
||||
"a2XMSKROCl9z"
|
||||
],
|
||||
"title": "v0.91.1-beta",
|
||||
"notePosition": 190,
|
||||
"notePosition": 210,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -451,7 +503,7 @@
|
||||
"yqXFvWbLkuMD"
|
||||
],
|
||||
"title": "v0.90.12",
|
||||
"notePosition": 200,
|
||||
"notePosition": 220,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -469,7 +521,7 @@
|
||||
"veS7pg311yJP"
|
||||
],
|
||||
"title": "v0.90.11-beta",
|
||||
"notePosition": 210,
|
||||
"notePosition": 230,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -487,7 +539,7 @@
|
||||
"sq5W9TQxRqMq"
|
||||
],
|
||||
"title": "v0.90.10-beta",
|
||||
"notePosition": 220,
|
||||
"notePosition": 240,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -505,7 +557,7 @@
|
||||
"yFEGVCUM9tPx"
|
||||
],
|
||||
"title": "v0.90.9-beta",
|
||||
"notePosition": 230,
|
||||
"notePosition": 250,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -523,7 +575,7 @@
|
||||
"o4wAGqOQuJtV"
|
||||
],
|
||||
"title": "v0.90.8",
|
||||
"notePosition": 240,
|
||||
"notePosition": 260,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -556,7 +608,7 @@
|
||||
"i4A5g9iOg9I0"
|
||||
],
|
||||
"title": "v0.90.7-beta",
|
||||
"notePosition": 250,
|
||||
"notePosition": 270,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -574,7 +626,7 @@
|
||||
"ThNf2GaKgXUs"
|
||||
],
|
||||
"title": "v0.90.6-beta",
|
||||
"notePosition": 260,
|
||||
"notePosition": 280,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -592,7 +644,7 @@
|
||||
"G4PAi554kQUr"
|
||||
],
|
||||
"title": "v0.90.5-beta",
|
||||
"notePosition": 270,
|
||||
"notePosition": 290,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -619,7 +671,7 @@
|
||||
"zATRobGRCmBn"
|
||||
],
|
||||
"title": "v0.90.4",
|
||||
"notePosition": 280,
|
||||
"notePosition": 300,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -637,7 +689,7 @@
|
||||
"sCDLf8IKn3Iz"
|
||||
],
|
||||
"title": "v0.90.3",
|
||||
"notePosition": 290,
|
||||
"notePosition": 310,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -655,7 +707,7 @@
|
||||
"VqqyBu4AuTjC"
|
||||
],
|
||||
"title": "v0.90.2-beta",
|
||||
"notePosition": 300,
|
||||
"notePosition": 320,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -673,7 +725,7 @@
|
||||
"RX3Nl7wInLsA"
|
||||
],
|
||||
"title": "v0.90.1-beta",
|
||||
"notePosition": 310,
|
||||
"notePosition": 330,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -691,7 +743,7 @@
|
||||
"GyueACukPWjk"
|
||||
],
|
||||
"title": "v0.90.0-beta",
|
||||
"notePosition": 320,
|
||||
"notePosition": 340,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -709,7 +761,7 @@
|
||||
"wyurrlcDl416"
|
||||
],
|
||||
"title": "Release Template",
|
||||
"notePosition": 330,
|
||||
"notePosition": 350,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
|
||||
72
docs/Release Notes/Release Notes/v0.97.0.md
vendored
Normal file
72
docs/Release Notes/Release Notes/v0.97.0.md
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# v0.97.0
|
||||
> [!CAUTION]
|
||||
> **Important Security Update**
|
||||
>
|
||||
> This release addresses a security vulnerability that could make password-based attacks against your Trilium instance more feasible. We strongly recommend upgrading to this version as soon as possible, especially if your Trilium server is accessible over a network.
|
||||
>
|
||||
> For more details about this security fix, please see our published security advisory which will be available 14 days after this release.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
>
|
||||
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Notes) (top-right).
|
||||
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
|
||||
|
||||
## 💡 Key highlights
|
||||
|
||||
* “Books” have been renamed to Collections to better match their intentions.
|
||||
* **A new collection was introduced,** _**table.**_
|
||||
* See the in-app documentation for more information.
|
||||
* Custom table theme for Trilium by @adoriandoran
|
||||
* Geomap: The geomap was converted from a standalone note type to a collection.
|
||||
* The collections are not displayed directly in “Insert child” in the note tree with predefined configuration such as promoted attributes to make them easier to use (e.g. for calendar, geomap).
|
||||
* A new editing mechanism was introduced: quick edit. This opens notes for editing in a popup instead of a tab, allowing easy access. This is especially useful for collections, to edit notes without switching context.
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
* [Missing note meta. Can't export empty note and involved note tree](https://github.com/TriliumNext/Trilium/issues/6146)
|
||||
* [Mermaid notes sluggish](https://github.com/TriliumNext/Trilium/issues/5805)
|
||||
* [In-app help confusing due to ligatures](https://github.com/TriliumNext/Trilium/issues/6224)
|
||||
* Geo map: tooltip not showing.
|
||||
* [Nix flake support broke with electron 37 upgrade](https://github.com/TriliumNext/Trilium/issues/6217)
|
||||
* Signing on Windows did not work on the previous release.
|
||||
* [When editing a note in Linux, middle-clicking a note title in tree pane triggers a paste action](https://github.com/TriliumNext/Trilium/issues/5812)
|
||||
* Editor not focused after switching tabs.
|
||||
* PDF file preview: inconvenient 10px scrollable margin at the bottom.
|
||||
* Calendar view:
|
||||
* Subtree children not displayed when in calendar root.
|
||||
* Title changes to events not reflected.
|
||||
* [Attributes Dialogue Doesn't Display for existing attributes](https://github.com/TriliumNext/Trilium/issues/5718)
|
||||
* [Issues on Prometeus dashboard due to timestamps](https://github.com/TriliumNext/Trilium/issues/6354)
|
||||
* [Ckeditor (re)-creation likely causes important lagging when coming from code note](https://github.com/TriliumNext/Trilium/issues/6367)
|
||||
|
||||
## ✨ Improvements
|
||||
|
||||
* Export to ZIP:
|
||||
* Improve error handling
|
||||
* Improve handling of notes with empty title.
|
||||
* Tree context menu: reorder the note types of “Insert (child) note...” by @adoriandoran
|
||||
* [Note map: add attributes to include or exclude relations](https://github.com/TriliumNext/Trilium/pull/6104) by @kieranknowles1
|
||||
* [iframe sandbox allow popups](https://github.com/TriliumNext/Trilium/issues/5698)
|
||||
* [Badges for the note type context menu](https://github.com/TriliumNext/Trilium/pull/6229) by @adoriandoran
|
||||
* The “Book/Collection Properties" ribbon tab no longer focuses automatically.
|
||||
* Geomap improvements:
|
||||
* Geolocation now displayed in the context menu.
|
||||
* Context menu for empty spaces on the map, for quickly viewing the location or adding a new marker.
|
||||
* Adding markers by drag & dropping from note tree.
|
||||
* Read-only mode to prevent modification such as dragging.
|
||||
* Calendar View: Added options to hide weekends & display week numbers directly from the “Collection Properties” in the ribbon.
|
||||
* [Tree Context Menu: relocate the "Duplicate subtree" menu item](https://github.com/TriliumNext/Trilium/pull/6299) by @adoriandoran
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
* New features, table.
|
||||
* Updated collections.
|
||||
* Keyboard shortcuts for the note tree.
|
||||
|
||||
## 🛠️ Technical updates
|
||||
|
||||
* Updated to Electron 37.2.2.
|
||||
* Mindmap dependency (MindElixir) was updated to the latest major version.
|
||||
* Mermaid diagrams updated to the latest version (new diagram type tree map supported).
|
||||
* CKEditor updated to latest major version (46).
|
||||
77
docs/Release Notes/Release Notes/v0.97.1.md
vendored
Normal file
77
docs/Release Notes/Release Notes/v0.97.1.md
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
# v0.97.1
|
||||
> [!TIP]
|
||||
> This release is functionally identical to v0.97.0, but it fixes the version number not being correctly set in the release, causing some issues with the cache.
|
||||
|
||||
> [!CAUTION]
|
||||
> **Important Security Update**
|
||||
>
|
||||
> This release addresses a security vulnerability that could make password-based attacks against your Trilium instance more feasible. We strongly recommend upgrading to this version as soon as possible, especially if your Trilium server is accessible over a network.
|
||||
>
|
||||
> For more details about this security fix, please see our published security advisory which will be available 14 days after this release.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
>
|
||||
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Notes) (top-right).
|
||||
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
|
||||
|
||||
## Changes from v0.97.0
|
||||
|
||||
### 💡 Key highlights
|
||||
|
||||
* “Books” have been renamed to Collections to better match their intentions.
|
||||
* **A new collection was introduced,** _**table.**_
|
||||
* See the in-app documentation for more information.
|
||||
* Custom table theme for Trilium by @adoriandoran
|
||||
* Geomap: The geomap was converted from a standalone note type to a collection.
|
||||
* The collections are not displayed directly in “Insert child” in the note tree with predefined configuration such as promoted attributes to make them easier to use (e.g. for calendar, geomap).
|
||||
* A new editing mechanism was introduced: quick edit. This opens notes for editing in a popup instead of a tab, allowing easy access. This is especially useful for collections, to edit notes without switching context.
|
||||
|
||||
### 🐞 Bugfixes
|
||||
|
||||
* [Missing note meta. Can't export empty note and involved note tree](https://github.com/TriliumNext/Trilium/issues/6146)
|
||||
* [Mermaid notes sluggish](https://github.com/TriliumNext/Trilium/issues/5805)
|
||||
* [In-app help confusing due to ligatures](https://github.com/TriliumNext/Trilium/issues/6224)
|
||||
* Geo map: tooltip not showing.
|
||||
* [Nix flake support broke with electron 37 upgrade](https://github.com/TriliumNext/Trilium/issues/6217)
|
||||
* Signing on Windows did not work on the previous release.
|
||||
* [When editing a note in Linux, middle-clicking a note title in tree pane triggers a paste action](https://github.com/TriliumNext/Trilium/issues/5812)
|
||||
* Editor not focused after switching tabs.
|
||||
* PDF file preview: inconvenient 10px scrollable margin at the bottom.
|
||||
* Calendar view:
|
||||
* Subtree children not displayed when in calendar root.
|
||||
* Title changes to events not reflected.
|
||||
* [Attributes Dialogue Doesn't Display for existing attributes](https://github.com/TriliumNext/Trilium/issues/5718)
|
||||
* [Issues on Prometeus dashboard due to timestamps](https://github.com/TriliumNext/Trilium/issues/6354)
|
||||
* [Ckeditor (re)-creation likely causes important lagging when coming from code note](https://github.com/TriliumNext/Trilium/issues/6367)
|
||||
|
||||
### ✨ Improvements
|
||||
|
||||
* Export to ZIP:
|
||||
* Improve error handling
|
||||
* Improve handling of notes with empty title.
|
||||
* Tree context menu: reorder the note types of “Insert (child) note...” by @adoriandoran
|
||||
* [Note map: add attributes to include or exclude relations](https://github.com/TriliumNext/Trilium/pull/6104) by @kieranknowles1
|
||||
* [iframe sandbox allow popups](https://github.com/TriliumNext/Trilium/issues/5698)
|
||||
* [Badges for the note type context menu](https://github.com/TriliumNext/Trilium/pull/6229) by @adoriandoran
|
||||
* The “Book/Collection Properties" ribbon tab no longer focuses automatically.
|
||||
* Geomap improvements:
|
||||
* Geolocation now displayed in the context menu.
|
||||
* Context menu for empty spaces on the map, for quickly viewing the location or adding a new marker.
|
||||
* Adding markers by drag & dropping from note tree.
|
||||
* Read-only mode to prevent modification such as dragging.
|
||||
* Calendar View: Added options to hide weekends & display week numbers directly from the “Collection Properties” in the ribbon.
|
||||
* [Tree Context Menu: relocate the "Duplicate subtree" menu item](https://github.com/TriliumNext/Trilium/pull/6299) by @adoriandoran
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
* New features, table.
|
||||
* Updated collections.
|
||||
* Keyboard shortcuts for the note tree.
|
||||
|
||||
### 🛠️ Technical updates
|
||||
|
||||
* Updated to Electron 37.2.2.
|
||||
* Mindmap dependency (MindElixir) was updated to the latest major version.
|
||||
* Mermaid diagrams updated to the latest version (new diagram type tree map supported).
|
||||
* CKEditor updated to latest major version (46).
|
||||
607
docs/User Guide/!!!meta.json
vendored
607
docs/User Guide/!!!meta.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,24 @@
|
||||
|
||||
The table view displays information in a grid, where the rows are individual notes and the columns are <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a>. In addition, values are editable.
|
||||
|
||||
## How it works
|
||||
|
||||
The tabular structure is represented as such:
|
||||
|
||||
* Each child note is a row in the table.
|
||||
* If child rows also have children, they will be displayed under an expander (nested notes).
|
||||
* Each column is a [promoted attribute](../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md) that is defined on the Collection note.
|
||||
* Actually, both promoted and unpromoted attributes are supported, but it's a requirement to use a label/relation definition.
|
||||
* The promoted attributes are usually defined as inheritable in order to show up in the child notes, but it's not a requirement.
|
||||
* If there are multiple attribute definitions with the same `name`, only one will be displayed.
|
||||
|
||||
There are also a few predefined columns:
|
||||
|
||||
* The current item number, identified by the `#` symbol.
|
||||
* This simply counts the note and is affected by sorting.
|
||||
* <a class="reference-link" href="../../../Advanced%20Usage/Note%20ID.md">Note ID</a>, representing the unique ID used internally by Trilium
|
||||
* The title of the note.
|
||||
|
||||
## Interaction
|
||||
|
||||
### Creating a new table
|
||||
@@ -11,23 +29,44 @@ Right click the <a class="reference-link" href="../../UI%20Elements/Note%20Tree
|
||||
|
||||
### Adding columns
|
||||
|
||||
Each column is a [promoted attribute](../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md) that is defined on the Collection note. Ideally, the promoted attributes need to be inheritable in order to show up in the child notes.
|
||||
Each column is a [promoted or unpromoted attribute](../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md) that is defined on the Collection note.
|
||||
|
||||
To create a new column, simply press _Add new column_ at the bottom of the table.
|
||||
To create a new column, either:
|
||||
|
||||
There are also a few predefined columns:
|
||||
|
||||
* The current item number, identified by the `#` symbol. This simply counts the note and is affected by sorting.
|
||||
* <a class="reference-link" href="../../../Advanced%20Usage/Note%20ID.md">Note ID</a>, representing the unique ID used internally by Trilium
|
||||
* The title of the note.
|
||||
* Press _Add new column_ at the bottom of the table.
|
||||
* Right click on an existing column and select Add column to the left/right.
|
||||
* Right click on the empty space of the column header and select _Label_ or _Relation_ in the _New column_ section.
|
||||
|
||||
### Adding new rows
|
||||
|
||||
Each row is actually a note that is a child of the Collection note.
|
||||
|
||||
To create a new note, press _Add new row_ at the bottom of the table. By default it will try to edit the title of the newly created note.
|
||||
To create a new note, either:
|
||||
|
||||
Alternatively, the note can be created from the<a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a> or [scripting](../../../Scripting.md).
|
||||
* Press _Add new row_ at the bottom of the table.
|
||||
* Right click on an existing row and select _Insert row above, Insert child note_ or _Insert row below_.
|
||||
|
||||
By default it will try to edit the title of the newly created note.
|
||||
|
||||
Alternatively, the note can be created from the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a> or [scripting](../../../Scripting.md).
|
||||
|
||||
### Context menu
|
||||
|
||||
There are multiple menus:
|
||||
|
||||
* Right clicking on a column, allows:
|
||||
* Sorting by the selected column and resetting the sort.
|
||||
* Hiding the selected column or adjusting the visibility of every column.
|
||||
* Adding new columns to the left or the right of the column.
|
||||
* Editing the current column.
|
||||
* Deleting the current column.
|
||||
* Right clicking on the space to the right of the columns, allows:
|
||||
* Adjusting the visibility of every column.
|
||||
* Adding new columns.
|
||||
* Right clicking on a row, allows:
|
||||
* Opening the corresponding note of the row in a new tab, split, window or quick editing it.
|
||||
* Inserting rows above, below or as a child note.
|
||||
* Deleting the row.
|
||||
|
||||
### Editing data
|
||||
|
||||
@@ -35,16 +74,25 @@ Simply click on a cell within a row to change its value. The change will not onl
|
||||
|
||||
* 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.
|
||||
* It also possible to change the title of a note.
|
||||
* Editing relations is also possible, by using the note autocomplete.
|
||||
* Editing relations is also possible
|
||||
* Simply click on a relation and it will become editable. Enter the text to look for a note and click on it.
|
||||
* To remove a relation, remove the title of the note from the text box and click outside the cell.
|
||||
|
||||
### Editing columns
|
||||
|
||||
It is possible to edit a column by right clicking it and selecting _Edit column._ This will basically change the label/relation definition at the collection level.
|
||||
|
||||
If the _Name_ 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.
|
||||
|
||||
## Working with the data
|
||||
|
||||
### Sorting
|
||||
### Sorting by column
|
||||
|
||||
It is possible to sort the data by the values of a column:
|
||||
By default, the order of the notes matches the order in the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a>. However, it is possible to sort the data by the values of a column:
|
||||
|
||||
* To do so, simply click on a column.
|
||||
* To switch between ascending or descending sort, simply click again on the same column. The arrow next to the column will indicate the direction of the sort.
|
||||
* To disable sorting and fall back to the original order, right click any column on the header and select _Clear sorting._
|
||||
|
||||
### Reordering and hiding columns
|
||||
|
||||
@@ -53,19 +101,37 @@ It is possible to sort the data by the values of a column:
|
||||
|
||||
### Reordering rows
|
||||
|
||||
Notes can be dragged around to change their order. This will also change the order of the note in the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a>.
|
||||
Notes can be dragged around to change their order. To do so, move the mouse over the three vertical dots near the number row and drag the mouse to the desired position.
|
||||
|
||||
Currently, it's possible to reorder notes even if sorting is used, but the result might be inconsistent.
|
||||
This will also change the order of the note in the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a>.
|
||||
|
||||
Reordering does have some limitations:
|
||||
|
||||
* If the parent note has `#sorted`, reordering will be disabled.
|
||||
* If using nested tables, then reordering will also be disabled.
|
||||
* Currently, it's possible to reorder notes even if column sorting is used, but the result might be inconsistent.
|
||||
|
||||
### Nested trees
|
||||
|
||||
If the child notes of the collection also have their own child notes, then they will be displayed in a hierarchy.
|
||||
|
||||
Next to the title of each element there will be a button to expand or collapse. By default, all items are expanded.
|
||||
|
||||
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:
|
||||
|
||||
* Go to _Collection Properties_ in the <a class="reference-link" href="../../UI%20Elements/Ribbon.md">Ribbon</a> and look for the _Max nesting depth_ section.
|
||||
* To disable nesting, type 0 and press Enter.
|
||||
* To limit to a certain depth, type in the desired number (e.g. 2 to only display children and sub-children).
|
||||
* To re-enable unlimited nesting, remove the number and press Enter.
|
||||
* Manually set `maxNestingDepth` to the desired value.
|
||||
|
||||
Limitations:
|
||||
|
||||
* While in this mode, it's not possible to reorder notes.
|
||||
|
||||
## Limitations
|
||||
|
||||
The table functionality is still in its early stages, as such it faces quite a few important limitations:
|
||||
|
||||
1. As mentioned previously, the columns of the table are defined as <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a>.
|
||||
1. But only the promoted attributes that are defined at the level of the Collection note are actually taken into consideration.
|
||||
2. There are plans to recursively look for columns across the sub-hierarchy.
|
||||
2. Hierarchy is not yet supported, so the table will only show the items that are direct children of the _Collection_ note.
|
||||
3. Multiple labels and relations are not supported. If a <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a> is defined with a _Multi value_ specificity, they will be ignored.
|
||||
Multi-value labels and relations are not supported. If a <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a> is defined with a _Multi value_ specificity, they will be ignored.
|
||||
|
||||
## Use in search
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user