mirror of
https://github.com/zadam/trilium.git
synced 2025-12-16 21:29:56 +01:00
Compare commits
151 Commits
feat/impro
...
fix/try-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
055556891d | ||
|
|
a58cfbec05 | ||
|
|
3795be4750 | ||
|
|
3111738700 | ||
|
|
1fa0bada23 | ||
|
|
11eca7e58b | ||
|
|
4b50e2f14d | ||
|
|
63faba9603 | ||
|
|
c88ff07691 | ||
|
|
b53aa5cf6e | ||
|
|
2641b9b3fe | ||
|
|
3a2a73992c | ||
|
|
b82b17a701 | ||
|
|
a6202edcd1 | ||
|
|
6eac0cb75d | ||
|
|
83672d6138 | ||
|
|
51dadf72d0 | ||
|
|
0cbf61acb3 | ||
|
|
b192f43187 | ||
|
|
8cb8d1303c | ||
|
|
5237348975 | ||
|
|
72e2f6757e | ||
|
|
cf059e7f86 | ||
|
|
44d69216b6 | ||
|
|
9373d47e86 | ||
|
|
29350628c3 | ||
|
|
83d1a68879 | ||
|
|
f188408099 | ||
|
|
449ab3a798 | ||
|
|
21504d1417 | ||
|
|
3060b496e3 | ||
|
|
bd35539fa1 | ||
|
|
df6447e3ad | ||
|
|
24fd898f0d | ||
|
|
1aa6238288 | ||
|
|
c16c4788da | ||
|
|
0c35daab85 | ||
|
|
4a19639e92 | ||
|
|
36cceea677 | ||
|
|
b32a344a21 | ||
|
|
3896ab822f | ||
|
|
cfa4ba57d4 | ||
|
|
da051e0269 | ||
|
|
3eda77a91f | ||
|
|
5c2f4be5dd | ||
|
|
435b501db9 | ||
|
|
5a27ffef5f | ||
|
|
02256d9a45 | ||
|
|
7e069009d6 | ||
|
|
3c25cda4c0 | ||
|
|
70d7ad0b1a | ||
|
|
e793b2f661 | ||
|
|
a6e7dff61e | ||
|
|
86d1bbe8ff | ||
|
|
a10cb06f14 | ||
|
|
dd9a62818b | ||
|
|
c0e936675c | ||
|
|
b0b788b7dc | ||
|
|
18f0f3ecac | ||
|
|
e7d745ac94 | ||
|
|
24abf7f0ed | ||
|
|
9a08f6534b | ||
|
|
93c5413790 | ||
|
|
c97c66ed8a | ||
|
|
b581025bbe | ||
|
|
7bc5331747 | ||
|
|
2415976475 | ||
|
|
8d0d0f0449 | ||
|
|
16b00ed160 | ||
|
|
df73a420f9 | ||
|
|
1e4d57f275 | ||
|
|
19a238c8d3 | ||
|
|
5ffd8a79eb | ||
|
|
58e58c192f | ||
|
|
5939344378 | ||
|
|
349f19fef7 | ||
|
|
d5777a024e | ||
|
|
b7f4ee6171 | ||
|
|
a83c4e3970 | ||
|
|
5a767dae34 | ||
|
|
9f93d30b99 | ||
|
|
dff525edc6 | ||
|
|
26da431320 | ||
|
|
cde4622693 | ||
|
|
5ede7ecc69 | ||
|
|
513878dfef | ||
|
|
753d5529b2 | ||
|
|
4e755dc537 | ||
|
|
5351310a38 | ||
|
|
211ca43a82 | ||
|
|
e5235e7f22 | ||
|
|
e72298f0b4 | ||
|
|
3abf5c65c6 | ||
|
|
268acb0b88 | ||
|
|
196b3b873f | ||
|
|
4d9801a372 | ||
|
|
bd710ba665 | ||
|
|
afe369c876 | ||
|
|
206007bbce | ||
|
|
8ad05b92c0 | ||
|
|
735da2a855 | ||
|
|
980077f559 | ||
|
|
5daca270e4 | ||
|
|
e18813a4bf | ||
|
|
4aa7e211f3 | ||
|
|
419dc7edfb | ||
|
|
eaa84a6b39 | ||
|
|
1d0503d0e4 | ||
|
|
f7f98aa9a3 | ||
|
|
575d14261a | ||
|
|
9aab606deb | ||
|
|
2e11681b52 | ||
|
|
8cca6637f7 | ||
|
|
82e076378c | ||
|
|
94ddad3c49 | ||
|
|
d35dbca18b | ||
|
|
7468d6147a | ||
|
|
7c78d749de | ||
|
|
85dd99a3c4 | ||
|
|
0a9c0234e2 | ||
|
|
fad77ba5a0 | ||
|
|
12723f3216 | ||
|
|
a43140515f | ||
|
|
3e3cc8c541 | ||
|
|
d1538508e8 | ||
|
|
9b1da8c311 | ||
|
|
e4a8258acf | ||
|
|
5e88043c7b | ||
|
|
bedf9112fb | ||
|
|
03681d23c5 | ||
|
|
aa191e110c | ||
|
|
dd09907925 | ||
|
|
35e9508bde | ||
|
|
4c8da70ef3 | ||
|
|
ed5da5cd4a | ||
|
|
dc5fccdbcd | ||
|
|
91aea333c7 | ||
|
|
a0de01cff1 | ||
|
|
a41ed34193 | ||
|
|
49e8811c18 | ||
|
|
488563a82e | ||
|
|
a1b18c7f97 | ||
|
|
9958a6e1bf | ||
|
|
1fc6d8aca7 | ||
|
|
3e9ec2d943 | ||
|
|
1420def1c3 | ||
|
|
3b4184e765 | ||
|
|
eb27ec2234 | ||
|
|
b70e25d348 | ||
|
|
772c0bbe1a | ||
|
|
144021c053 |
@@ -54,7 +54,7 @@ The original Trilium developer ([Zadam](https://github.com/zadam)) has graciousl
|
||||
|
||||
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Trilium instance. Simply [install TriliumNext/Trilium](#-installation) as usual and it will use your existing database.
|
||||
|
||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest TriliumNext/Trilium version of [v0.63.7](https://github.com/TriliumNext/Trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration.
|
||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration.
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ fi
|
||||
VERSION=$1
|
||||
SERIES=${VERSION:0:4}-latest
|
||||
|
||||
docker push TriliumNext/Trilium:$VERSION
|
||||
docker push TriliumNext/Trilium:$SERIES
|
||||
docker push zadam/trilium:$VERSION
|
||||
docker push zadam/trilium:$SERIES
|
||||
|
||||
if [[ $1 != *"beta"* ]]; then
|
||||
docker push TriliumNext/Trilium:latest
|
||||
docker push zadam/trilium:latest
|
||||
fi
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.54.2",
|
||||
"@playwright/test": "1.55.0",
|
||||
"@stylistic/eslint-plugin": "5.2.3",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.17.2",
|
||||
"@types/node": "22.18.0",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.33.0",
|
||||
"eslint": "9.34.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
"jsdoc": "4.0.4",
|
||||
@@ -49,7 +49,7 @@
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"tslib": "2.8.1",
|
||||
"typedoc": "0.28.10",
|
||||
"typedoc": "0.28.11",
|
||||
"typedoc-plugin-missing-exports": "4.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.98.0",
|
||||
"version": "0.98.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.33.0",
|
||||
"@eslint/js": "9.34.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.19",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@triliumnext/highlightjs": "workspace:*",
|
||||
"@triliumnext/share-theme": "workspace:*",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.7",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
@@ -36,7 +36,7 @@
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.50.1",
|
||||
"globals": "16.3.0",
|
||||
"i18next": "25.3.6",
|
||||
"i18next": "25.4.2",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
@@ -47,12 +47,12 @@
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.2.0",
|
||||
"mermaid": "11.10.0",
|
||||
"mermaid": "11.10.1",
|
||||
"mind-elixir": "5.0.6",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.27.1",
|
||||
"react-i18next": "15.6.1",
|
||||
"react-i18next": "15.7.2",
|
||||
"split.js": "1.6.5",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
@@ -62,7 +62,7 @@
|
||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/jquery": "3.5.33",
|
||||
"@types/leaflet": "1.9.20",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/mark.js": "8.11.12",
|
||||
@@ -70,7 +70,7 @@
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "18.0.1",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.1"
|
||||
"vite-plugin-static-copy": "3.1.2"
|
||||
},
|
||||
"nx": {
|
||||
"name": "client",
|
||||
|
||||
@@ -834,7 +834,7 @@ class FNote {
|
||||
if (a.noteId === b.noteId) {
|
||||
return a.position < b.position ? -1 : 1;
|
||||
} else {
|
||||
// inherited promoted attributes should stay grouped: https://github.com/TriliumNext/Trilium/issues/3761
|
||||
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
|
||||
return a.noteId < b.noteId ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ export class WidgetsByParent {
|
||||
this.byParent[parentName]
|
||||
// previously, custom widgets were provided as a single instance, but that has the disadvantage
|
||||
// for splits where we actually need multiple instaces and thus having a class to instantiate is better
|
||||
// https://github.com/TriliumNext/Trilium/issues/4274
|
||||
// https://github.com/zadam/trilium/issues/4274
|
||||
.map((w: any) => (w.prototype ? new w() : w))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ async function copy(branchIds: string[]) {
|
||||
clipboardMode = "copy";
|
||||
|
||||
if (utils.isElectron()) {
|
||||
// https://github.com/TriliumNext/Trilium/issues/2401
|
||||
// https://github.com/zadam/trilium/issues/2401
|
||||
const { clipboard } = require("electron");
|
||||
const links: string[] = [];
|
||||
|
||||
|
||||
@@ -507,7 +507,7 @@ $(document).on("dblclick", "a", (e) => {
|
||||
$(document).on("mousedown", "a", (e) => {
|
||||
if (e.which === 2) {
|
||||
// prevent paste on middle click
|
||||
// https://github.com/TriliumNext/Trilium/issues/2995
|
||||
// https://github.com/zadam/trilium/issues/2995
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event#preventing_default_actions
|
||||
e.preventDefault();
|
||||
return false;
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface Suggestion {
|
||||
commandId?: string;
|
||||
commandDescription?: string;
|
||||
commandShortcut?: string;
|
||||
attributeSnippet?: string;
|
||||
highlightedAttributeSnippet?: string;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
@@ -323,7 +325,33 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
return `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`;
|
||||
// Add special class for search-notes action
|
||||
const actionClass = suggestion.action === "search-notes" ? "search-notes-action" : "";
|
||||
|
||||
// Choose appropriate icon based on action
|
||||
let iconClass = suggestion.icon ?? "bx bx-note";
|
||||
if (suggestion.action === "search-notes") {
|
||||
iconClass = "bx bx-search";
|
||||
} else if (suggestion.action === "create-note") {
|
||||
iconClass = "bx bx-plus";
|
||||
} else if (suggestion.action === "external-link") {
|
||||
iconClass = "bx bx-link-external";
|
||||
}
|
||||
|
||||
// Simplified HTML structure without nested divs
|
||||
let html = `<div class="note-suggestion ${actionClass}">`;
|
||||
html += `<span class="icon ${iconClass}"></span>`;
|
||||
html += `<span class="text">`;
|
||||
html += `<span class="search-result-title">${suggestion.highlightedNotePathTitle}</span>`;
|
||||
|
||||
// Add attribute snippet inline if available
|
||||
if (suggestion.highlightedAttributeSnippet) {
|
||||
html += `<span class="search-result-attributes">${suggestion.highlightedAttributeSnippet}</span>`;
|
||||
}
|
||||
|
||||
html += `</span>`;
|
||||
html += `</div>`;
|
||||
return html;
|
||||
}
|
||||
},
|
||||
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added
|
||||
|
||||
@@ -99,7 +99,7 @@ async function mouseEnterHandler(this: HTMLElement) {
|
||||
if ($link.filter(":hover").length > 0) {
|
||||
$link.tooltip({
|
||||
container: "body",
|
||||
// https://github.com/TriliumNext/Trilium/issues/2794 https://github.com/TriliumNext/Trilium/issues/2988
|
||||
// https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988
|
||||
// with bottom this flickering happens a bit less
|
||||
placement: "bottom",
|
||||
trigger: "manual",
|
||||
|
||||
@@ -79,7 +79,7 @@ body {
|
||||
height: unset !important;
|
||||
overflow: visible;
|
||||
position: unset;
|
||||
/* https://github.com/TriliumNext/Trilium/issues/3202 */
|
||||
/* https://github.com/zadam/trilium/issues/3202 */
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,28 @@
|
||||
--ck-mention-list-max-height: 500px;
|
||||
}
|
||||
|
||||
body#trilium-app.motion-disabled *,
|
||||
body#trilium-app.motion-disabled *::before,
|
||||
body#trilium-app.motion-disabled *::after {
|
||||
/* Disable transitions and animations */
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
body#trilium-app.shadows-disabled *,
|
||||
body#trilium-app.shadows-disabled *::before,
|
||||
body#trilium-app.shadows-disabled *::after {
|
||||
/* Disable shadows */
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
body#trilium-app.backdrop-effects-disabled *,
|
||||
body#trilium-app.backdrop-effects-disabled *::before,
|
||||
body#trilium-app.backdrop-effects-disabled *::after {
|
||||
/* Disable backdrop effects */
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
.table {
|
||||
--bs-table-bg: transparent !important;
|
||||
}
|
||||
@@ -40,7 +62,7 @@
|
||||
}
|
||||
|
||||
html {
|
||||
/* this fixes FF filter vs. position fixed bug: https://github.com/TriliumNext/Trilium/issues/233 */
|
||||
/* this fixes FF filter vs. position fixed bug: https://github.com/zadam/trilium/issues/233 */
|
||||
height: 100vh;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
@@ -355,7 +377,7 @@ body.desktop .tabulator-popup-container {
|
||||
|
||||
@supports (animation-fill-mode: forwards) {
|
||||
/* Delay the opening of submenus */
|
||||
body.desktop .dropdown-submenu .dropdown-menu {
|
||||
body.desktop:not(.motion-disabled) .dropdown-submenu .dropdown-menu {
|
||||
opacity: 0;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: var(--submenu-opening-delay);
|
||||
@@ -543,7 +565,7 @@ button.btn-sm {
|
||||
transform: translateX(7px);
|
||||
color: var(--muted-text-color);
|
||||
background-color: var(--main-background-color);
|
||||
/* Making this narrower because https://github.com/TriliumNext/Trilium/issues/502 (problem only in smaller font sizes) */
|
||||
/* Making this narrower because https://github.com/zadam/trilium/issues/502 (problem only in smaller font sizes) */
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
z-index: 1000;
|
||||
@@ -840,10 +862,34 @@ table.promoted-attributes-in-tooltip th {
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
padding: 6px 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .icon {
|
||||
display: inline-block;
|
||||
line-height: inherit;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .text {
|
||||
display: inline-block;
|
||||
width: calc(100% - 20px);
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .search-result-title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .search-result-attributes {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
color: var(--muted-text-color);
|
||||
opacity: 0.6;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -1117,7 +1163,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--accented-background-color);
|
||||
display: flex; /* see https://github.com/TriliumNext/Trilium/issues/1590 */
|
||||
display: flex; /* see https://github.com/zadam/trilium/issues/1590 */
|
||||
}
|
||||
|
||||
.include-note.ck-placeholder::before {
|
||||
@@ -1251,7 +1297,7 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu {
|
||||
left: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */
|
||||
margin-top: -10px;
|
||||
min-width: 15rem;
|
||||
/* to make submenu scrollable https://github.com/TriliumNext/Trilium/issues/3136 */
|
||||
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -1773,20 +1819,42 @@ textarea {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-dialog {
|
||||
max-width: 900px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-header {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-body {
|
||||
padding: 0;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-dropdown-menu {
|
||||
max-height: 40vh;
|
||||
max-height: calc(80vh - 200px);
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.jump-to-note-results {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-suggestions {
|
||||
padding: 1rem;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-dropdown-menu .aa-suggestion:hover,
|
||||
.jump-to-note-results .aa-dropdown-menu .aa-cursor {
|
||||
background-color: var(--hover-item-background-color, #f8f9fa);
|
||||
}
|
||||
|
||||
/* Command palette styling */
|
||||
@@ -1804,8 +1872,24 @@ textarea {
|
||||
|
||||
.jump-to-note-dialog .aa-cursor .command-suggestion,
|
||||
.jump-to-note-dialog .aa-suggestion:hover .command-suggestion {
|
||||
border-left-color: var(--link-color);
|
||||
background-color: var(--hover-background-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .show-in-full-search,
|
||||
.jump-to-note-results .show-in-full-search {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
padding-top: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-suggestion .search-notes-action {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-suggestion:has(.search-notes-action)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .command-icon {
|
||||
@@ -2262,7 +2346,8 @@ footer.webview-footer button {
|
||||
|
||||
/* Search result highlighting */
|
||||
.search-result-title b,
|
||||
.search-result-content b {
|
||||
.search-result-content b,
|
||||
.search-result-attributes b {
|
||||
font-weight: 900;
|
||||
color: var(--admonition-warning-accent-color);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
|
||||
--menu-text-color: #e3e3e3;
|
||||
--menu-background-color: #222222d9;
|
||||
--menu-background-color-no-backdrop: #1b1b1b;
|
||||
--menu-item-icon-color: #8c8c8c;
|
||||
--menu-item-disabled-opacity: 0.5;
|
||||
--menu-item-keyboard-shortcut-color: #ffffff8f;
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
|
||||
--menu-text-color: #272727;
|
||||
--menu-background-color: #ffffffd9;
|
||||
--menu-background-color-no-backdrop: #fdfdfd;
|
||||
--menu-item-icon-color: #727272;
|
||||
--menu-item-disabled-opacity: 0.6;
|
||||
--menu-item-keyboard-shortcut-color: #666666a8;
|
||||
|
||||
@@ -83,6 +83,12 @@
|
||||
--tab-note-icons: true;
|
||||
}
|
||||
|
||||
body.backdrop-effects-disabled {
|
||||
/* Backdrop effects are disabled, replace the menu background color with the
|
||||
* no-backdrop fallback color */
|
||||
--menu-background-color: var(--menu-background-color-no-backdrop);
|
||||
}
|
||||
|
||||
/*
|
||||
* MENUS
|
||||
*
|
||||
@@ -530,10 +536,9 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
||||
}
|
||||
|
||||
/* List item */
|
||||
.jump-to-note-dialog .aa-suggestions div,
|
||||
.note-detail-empty .aa-suggestions div {
|
||||
.jump-to-note-dialog .aa-suggestion,
|
||||
.note-detail-empty .aa-suggestion {
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
color: var(--menu-text-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -1871,7 +1871,12 @@
|
||||
"selected_provider": "已选提供商",
|
||||
"selected_provider_description": "选择用于聊天和补全功能的AI提供商",
|
||||
"select_model": "选择模型...",
|
||||
"select_provider": "选择提供商..."
|
||||
"select_provider": "选择提供商...",
|
||||
"ai_enabled": "已启用 AI 功能",
|
||||
"ai_disabled": "已禁用 AI 功能",
|
||||
"no_models_found_online": "找不到模型。请检查您的 API 密钥及设置。",
|
||||
"no_models_found_ollama": "找不到 Ollama 模型。请确认 Ollama 是否正在运行。",
|
||||
"error_fetching": "获取模型失败:{{error}}"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "编辑器"
|
||||
@@ -1999,5 +2004,21 @@
|
||||
"next_theme_message": "当前使用旧版主题,要试用新主题吗?",
|
||||
"next_theme_button": "试用新主题",
|
||||
"dismiss": "关闭"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "相关设置"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "文本笔记中代码块的色彩方案",
|
||||
"related_code_notes": "代码笔记的色彩方案"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "性能",
|
||||
"enable-motion": "启用过渡和动画",
|
||||
"enable-shadows": "启用阴影",
|
||||
"enable-backdrop-effects": "启用菜单、弹窗和面板的背景效果"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,11 +202,12 @@
|
||||
"okButton": "OK"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "Suche im Volltext"
|
||||
"search_button": "Suche im Volltext",
|
||||
"search_placeholder": "Suche nach Notiz anhand ihres Titels oder gib > ein für Kommandos..."
|
||||
},
|
||||
"markdown_import": {
|
||||
"dialog_title": "Markdown-Import",
|
||||
"modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“.",
|
||||
"modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“",
|
||||
"import_button": "Importieren",
|
||||
"import_success": "Markdown-Inhalt wurde in das Dokument importiert."
|
||||
},
|
||||
@@ -217,21 +218,26 @@
|
||||
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
|
||||
"move_button": "Zur ausgewählten Notiz wechseln",
|
||||
"error_no_path": "Kein Weg, auf den man sich bewegen kann.",
|
||||
"move_success_message": "Ausgewählte Notizen wurden verschoben"
|
||||
"move_success_message": "Ausgewählte Notizen wurden verschoben in "
|
||||
},
|
||||
"note_type_chooser": {
|
||||
"modal_title": "Wähle den Notiztyp aus",
|
||||
"modal_body": "Wähle den Notiztyp / die Vorlage der neuen Notiz:",
|
||||
"templates": "Vorlagen"
|
||||
"templates": "Vorlagen",
|
||||
"change_path_prompt": "Ändern wo die neue Notiz erzeugt wird:",
|
||||
"search_placeholder": "Durchsuche Pfad nach Namen (Standard falls leer)",
|
||||
"builtin_templates": "Eingebaute Vorlage"
|
||||
},
|
||||
"password_not_set": {
|
||||
"title": "Das Passwort ist nicht festgelegt",
|
||||
"body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt."
|
||||
"body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt.",
|
||||
"body2": "Um Notizen zu schützen, klicke den unteren Button um den Optionsdialog zu öffnen und dein Passwort festzulegen.",
|
||||
"go_to_password_options": "Gehe zu Passwortoptionen"
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Prompt",
|
||||
"title": "Eingabeaufforderung",
|
||||
"ok": "OK",
|
||||
"defaultTitle": "Prompt"
|
||||
"defaultTitle": "Eingabeaufforderung"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"modal_title": "Geschützte Sitzung",
|
||||
@@ -265,10 +271,12 @@
|
||||
"maximum_revisions": "Maximale Revisionen für aktuelle Notiz: {{number}}.",
|
||||
"settings": "Einstellungen für Notizrevisionen",
|
||||
"download_button": "Herunterladen",
|
||||
"mime": "MIME:",
|
||||
"mime": "MIME: ",
|
||||
"file_size": "Dateigröße:",
|
||||
"preview": "Vorschau:",
|
||||
"preview_not_available": "Für diesen Notiztyp ist keine Vorschau verfügbar."
|
||||
"preview_not_available": "Für diesen Notiztyp ist keine Vorschau verfügbar.",
|
||||
"restore_button": "Wiederherstellen",
|
||||
"delete_button": "Löschen"
|
||||
},
|
||||
"sort_child_notes": {
|
||||
"sort_children_by": "Unternotizen sortieren nach...",
|
||||
@@ -348,7 +356,7 @@
|
||||
"sorted": "Hält untergeordnete Notizen alphabetisch nach Titel sortiert",
|
||||
"sort_direction": "ASC (Standard) oder DESC",
|
||||
"sort_folders_first": "Ordner (Notizen mit Unternotizen) sollten oben sortiert werden",
|
||||
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen).",
|
||||
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen)",
|
||||
"hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden",
|
||||
"read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.",
|
||||
"auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst",
|
||||
@@ -371,10 +379,10 @@
|
||||
"inbox": "Standard-Inbox-Position für neue Notizen – wenn du eine Notiz über den \"Neue Notiz\"-Button in der Seitenleiste erstellst, wird die Notiz als untergeordnete Notiz der Notiz erstellt, die mit dem <code>#inbox</code>-Label markiert ist.",
|
||||
"workspace_inbox": "Standard-Posteingangsspeicherort für neue Notizen, wenn sie zu einem Vorgänger dieser Arbeitsbereichsnotiz verschoben werden",
|
||||
"sql_console_home": "Standardspeicherort der SQL-Konsolennotizen",
|
||||
"bookmark_folder": "Notizen mit dieser Bezeichnung werden in den Lesezeichen als Ordner angezeigt (und ermöglichen den Zugriff auf ihre untergeordneten Ordner).",
|
||||
"bookmark_folder": "Notizen mit dieser Bezeichnung werden in den Lesezeichen als Ordner angezeigt (und ermöglichen den Zugriff auf ihre untergeordneten Ordner)",
|
||||
"share_hidden_from_tree": "Diese Notiz ist im linken Navigationsbaum ausgeblendet, kann aber weiterhin über ihre URL aufgerufen werden",
|
||||
"share_external_link": "Die Notiz dient als Link zu einer externen Website im Freigabebaum",
|
||||
"share_alias": "Lege einen Alias fest, unter dem die Notiz unter https://your_trilium_host/share/[dein_alias] verfügbar sein wird.",
|
||||
"share_alias": "Lege einen Alias fest, mit dem die Notiz unter https://your_trilium_host/share/[dein_alias] verfügbar sein wird",
|
||||
"share_omit_default_css": "Das Standard-CSS für die Freigabeseite wird weggelassen. Verwende es, wenn du umfangreiche Stylingänderungen vornimmst.",
|
||||
"share_root": "Markiert eine Notiz, die im /share-Root bereitgestellt wird.",
|
||||
"share_description": "Definiere Text, der dem HTML-Meta-Tag zur Beschreibung hinzugefügt werden soll",
|
||||
@@ -390,7 +398,7 @@
|
||||
"color": "Definiert die Farbe der Notiz im Notizbaum, in Links usw. Verwende einen beliebigen gültigen CSS-Farbwert wie „rot“ oder #a13d5f",
|
||||
"keyboard_shortcut": "Definiert eine Tastenkombination, die sofort zu dieser Notiz springt. Beispiel: „Strg+Alt+E“. Erfordert ein Neuladen des Frontends, damit die Änderung wirksam wird.",
|
||||
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Unterbaum nicht angezeigt werden kann.",
|
||||
"execute_button": "Titel der Schaltfläche, die die aktuelle Codenotiz ausführt",
|
||||
"execute_button": "Titel der Schaltfläche, welche die aktuelle Codenotiz ausführt",
|
||||
"execute_description": "Längere Beschreibung der aktuellen Codenotiz, die zusammen mit der Schaltfläche „Ausführen“ angezeigt wird",
|
||||
"exclude_from_note_map": "Notizen mit dieser Bezeichnung werden in der Notizenkarte ausgeblendet",
|
||||
"new_notes_on_top": "Neue Notizen werden oben in der übergeordneten Notiz erstellt, nicht unten.",
|
||||
@@ -405,10 +413,10 @@
|
||||
"run_on_branch_change": "wird ausgeführt, wenn ein Zweig aktualisiert wird.",
|
||||
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. gelöscht. beim Verschieben der Notiz (alter Zweig/Link wird gelöscht).",
|
||||
"run_on_attribute_creation": "wird ausgeführt, wenn für die Notiz ein neues Attribut erstellt wird, das diese Beziehung definiert",
|
||||
"run_on_attribute_change": "wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
|
||||
"run_on_attribute_change": " wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
|
||||
"relation_template": "Die Attribute der Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Der Inhalt und der Unterbaum der Notiz werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
|
||||
"inherit": "Die Attribute einer Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributvererbung in der Dokumentation.",
|
||||
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll.",
|
||||
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll",
|
||||
"widget_relation": "Das Ziel dieser Beziehung wird ausgeführt und als Widget in der Seitenleiste gerendert",
|
||||
"share_css": "CSS-Hinweis, der in die Freigabeseite eingefügt wird. Die CSS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge auch die Verwendung von „share_hidden_from_tree“ und „share_omit_default_css“.",
|
||||
"share_js": "JavaScript-Hinweis, der in die Freigabeseite eingefügt wird. Die JS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge die Verwendung von „share_hidden_from_tree“.",
|
||||
@@ -418,7 +426,8 @@
|
||||
"other_notes_with_name": "Other notes with {{attributeType}} name \"{{attributeName}}\"",
|
||||
"and_more": "... und {{count}} mehr.",
|
||||
"print_landscape": "Beim Export als PDF, wird die Seitenausrichtung Querformat anstatt Hochformat verwendet.",
|
||||
"print_page_size": "Beim Export als PDF, wird die Größe der Seite angepasst. Unterstützte Größen: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>."
|
||||
"print_page_size": "Beim Export als PDF, wird die Größe der Seite angepasst. Unterstützte Größen: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
|
||||
"color_type": "Farbe"
|
||||
},
|
||||
"attribute_editor": {
|
||||
"help_text_body1": "Um ein Label hinzuzufügen, gebe einfach z.B. ein. <code>#rock</code> oder wenn du auch einen Wert hinzufügen möchten, dann z.B. <code>#year = 2024</code>",
|
||||
@@ -490,9 +499,9 @@
|
||||
"to": "nach",
|
||||
"target_parent_note": "Ziel-Übergeordnetenotiz",
|
||||
"on_all_matched_notes": "Auf allen übereinstimmenden Notizen",
|
||||
"move_note_new_parent": "Verschiebe die Notiz in die neue übergeordnete Notiz, wenn die Notiz nur eine übergeordnete Notiz hat (d. h. der alte Zweig wird entfernt und ein neuer Zweig in die neue übergeordnete Notiz erstellt).",
|
||||
"move_note_new_parent": "Verschiebe die Notiz in die neue übergeordnete Notiz, wenn die Notiz nur eine übergeordnete Notiz hat (d. h. der alte Zweig wird entfernt und ein neuer Zweig in die neue übergeordnete Notiz erstellt)",
|
||||
"clone_note_new_parent": "Notiz auf die neue übergeordnete Notiz klonen, wenn die Notiz mehrere Klone/Zweige hat (es ist nicht klar, welcher Zweig entfernt werden soll)",
|
||||
"nothing_will_happen": "Es passiert nichts, wenn die Notiz nicht zur Zielnotiz verschoben werden kann (d. h. dies würde einen Baumzyklus erzeugen)."
|
||||
"nothing_will_happen": "Es passiert nichts, wenn die Notiz nicht zur Zielnotiz verschoben werden kann (z.B. wenn dies einen Kreislauf in der Baumstruktur erzeugen würde)"
|
||||
},
|
||||
"rename_note": {
|
||||
"rename_note": "Notiz umbenennen",
|
||||
@@ -500,10 +509,10 @@
|
||||
"new_note_title": "neuer Notiztitel",
|
||||
"click_help_icon": "Klicke rechts auf das Hilfesymbol, um alle Optionen anzuzeigen",
|
||||
"evaluated_as_js_string": "Der angegebene Wert wird als JavaScript-String ausgewertet und kann somit über die injizierte <code>note</code>-Variable mit dynamischem Inhalt angereichert werden (Notiz wird umbenannt). Beispiele:",
|
||||
"example_note": "<code>Notiz</code> – alle übereinstimmenden Notizen werden in „Notiz“ umbenannt.",
|
||||
"example_note": "<code>Notiz</code> – alle übereinstimmenden Notizen werden in „Notiz“ umbenannt",
|
||||
"example_new_title": "<code>NEU: ${note.title}</code> – Übereinstimmende Notiztitel erhalten das Präfix „NEU:“",
|
||||
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> – übereinstimmende Notizen werden mit dem Erstellungsmonat und -datum der Notiz vorangestellt",
|
||||
"api_docs": "Siehe API-Dokumente für <a hrefu003d'https://zadam.github.io/trilium/backend_api/Note.html'>Notiz</a> und seinen <a hrefu003d'https://day.js.org/ docs/en/display/format'>dateCreatedObj / utcDateCreatedObj-Eigenschaften</a> für Details."
|
||||
"api_docs": "Siehe API-Dokumente für <a href='https://zadam.github.io/trilium/backend_api/Note.html'>Notiz</a> und seinen <a href='https://day.js.org/ docs/en/display/format'>dateCreatedObj / utcDateCreatedObj properties</a> für Details."
|
||||
},
|
||||
"add_relation": {
|
||||
"add_relation": "Beziehung hinzufügen",
|
||||
@@ -577,7 +586,8 @@
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
"december": "Dezember",
|
||||
"cannot_find_week_note": "Wochennotiz kann nicht gefunden werden"
|
||||
},
|
||||
"close_pane_button": {
|
||||
"close_this_pane": "Schließe diesen Bereich"
|
||||
@@ -699,14 +709,14 @@
|
||||
"zoom_out_title": "Herauszoomen"
|
||||
},
|
||||
"zpetne_odkazy": {
|
||||
"backlink": "{{count}} Backlink",
|
||||
"backlinks": "{{count}} Backlinks",
|
||||
"backlink": "{{count}} Rückverlinkung",
|
||||
"backlinks": "{{count}} Rückverlinkungen",
|
||||
"relation": "Beziehung"
|
||||
},
|
||||
"mobile_detail_menu": {
|
||||
"insert_child_note": "Untergeordnete Notiz einfügen",
|
||||
"delete_this_note": "Diese Notiz löschen",
|
||||
"error_cannot_get_branch_id": "BranchId für notePath „{{notePath}}“ kann nicht abgerufen werden.",
|
||||
"error_cannot_get_branch_id": "BranchId für notePath „{{notePath}}“ kann nicht abgerufen werden",
|
||||
"error_unrecognized_command": "Unbekannter Befehl {{command}}"
|
||||
},
|
||||
"note_icon": {
|
||||
@@ -718,7 +728,8 @@
|
||||
"basic_properties": {
|
||||
"note_type": "Notiztyp",
|
||||
"editable": "Bearbeitbar",
|
||||
"basic_properties": "Grundlegende Eigenschaften"
|
||||
"basic_properties": "Grundlegende Eigenschaften",
|
||||
"language": "Sprache"
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Ansichtstyp",
|
||||
@@ -729,7 +740,11 @@
|
||||
"collapse": "Einklappen",
|
||||
"expand": "Ausklappen",
|
||||
"invalid_view_type": "Ungültiger Ansichtstyp „{{type}}“",
|
||||
"calendar": "Kalender"
|
||||
"calendar": "Kalender",
|
||||
"book_properties": "Sammlungseigenschaften",
|
||||
"table": "Tabelle",
|
||||
"geo-map": "Weltkarte",
|
||||
"board": "Tafel"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
|
||||
@@ -805,7 +820,9 @@
|
||||
"unknown_label_type": "Unbekannter Labeltyp „{{type}}“",
|
||||
"unknown_attribute_type": "Unbekannter Attributtyp „{{type}}“",
|
||||
"add_new_attribute": "Neues Attribut hinzufügen",
|
||||
"remove_this_attribute": "Entferne dieses Attribut"
|
||||
"remove_this_attribute": "Entferne dieses Attribut",
|
||||
"unset-field-placeholder": "nicht gesetzt",
|
||||
"remove_color": "Entferne Farblabel"
|
||||
},
|
||||
"script_executor": {
|
||||
"query": "Abfrage",
|
||||
@@ -867,7 +884,7 @@
|
||||
"include_archived_notes": "Füge archivierte Notizen hinzu"
|
||||
},
|
||||
"limit": {
|
||||
"limit": "Limit",
|
||||
"limit": "Limitierung",
|
||||
"take_first_x_results": "Nehmen Sie nur die ersten X angegebenen Ergebnisse."
|
||||
},
|
||||
"order_by": {
|
||||
@@ -917,7 +934,7 @@
|
||||
"attachment_detail": {
|
||||
"open_help_page": "Hilfeseite zu Anhängen öffnen",
|
||||
"owning_note": "Eigentümernotiz: ",
|
||||
"you_can_also_open": ", Du kannst auch das öffnen",
|
||||
"you_can_also_open": ", Du kannst auch das öffnen ",
|
||||
"list_of_all_attachments": "Liste aller Anhänge",
|
||||
"attachment_deleted": "Dieser Anhang wurde gelöscht."
|
||||
},
|
||||
@@ -942,7 +959,8 @@
|
||||
"enter_workspace": "Betrete den Arbeitsbereich {{title}}"
|
||||
},
|
||||
"file": {
|
||||
"file_preview_not_available": "Für dieses Dateiformat ist keine Dateivorschau verfügbar."
|
||||
"file_preview_not_available": "Für dieses Dateiformat ist keine Dateivorschau verfügbar.",
|
||||
"too_big": "Die Vorschau zeigt aus Effizienzgründen nur die ersten {{maxNumChars}} Zeichen der Datei an. Lade die Datei herunter und öffne sie extern um den gesamten Inhalt zu sehen."
|
||||
},
|
||||
"protected_session": {
|
||||
"enter_password_instruction": "Um die geschützte Notiz anzuzeigen, musst du dein Passwort eingeben:",
|
||||
@@ -981,7 +999,7 @@
|
||||
"web_view": {
|
||||
"web_view": "Webansicht",
|
||||
"embed_websites": "Notiz vom Typ Web View ermöglicht das Einbetten von Websites in Trilium.",
|
||||
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
|
||||
"create_label": "Um zu beginnen, erstelle bitte ein Label mit einer URL-Adresse, die eingebettet werden soll, z. B. #webViewSrc=\"https://www.google.com\""
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Aktualisieren"
|
||||
@@ -1007,7 +1025,7 @@
|
||||
"error_creating_anonymized_database": "Die anonymisierte Datenbank konnte nicht erstellt werden. Überprüfe die Backend-Protokolle auf Details",
|
||||
"successfully_created_fully_anonymized_database": "Vollständig anonymisierte Datenbank in {{anonymizedFilePath}} erstellt",
|
||||
"successfully_created_lightly_anonymized_database": "Leicht anonymisierte Datenbank in {{anonymizedFilePath}} erstellt",
|
||||
"no_anonymized_database_yet": "Noch keine anonymisierte Datenbank"
|
||||
"no_anonymized_database_yet": "Noch keine anonymisierte Datenbank."
|
||||
},
|
||||
"database_integrity_check": {
|
||||
"title": "Datenbankintegritätsprüfung",
|
||||
@@ -1028,7 +1046,7 @@
|
||||
"failed": "Synchronisierung fehlgeschlagen: {{message}}"
|
||||
},
|
||||
"vacuum_database": {
|
||||
"title": "Vakuumdatenbank",
|
||||
"title": "Datenbank aufräumen",
|
||||
"description": "Dadurch wird die Datenbank neu erstellt, was normalerweise zu einer kleineren Datenbankdatei führt. Es werden keine Daten tatsächlich geändert.",
|
||||
"button_text": "Vakuumdatenbank",
|
||||
"vacuuming_database": "Datenbank wird geleert...",
|
||||
@@ -1063,7 +1081,8 @@
|
||||
"max_width_label": "Maximale Inhaltsbreite in Pixel",
|
||||
"apply_changes_description": "Um Änderungen an der Inhaltsbreite anzuwenden, klicke auf",
|
||||
"reload_button": "Frontend neu laden",
|
||||
"reload_description": "Änderungen an den Darstellungsoptionen"
|
||||
"reload_description": "Änderungen an den Darstellungsoptionen",
|
||||
"max_width_unit": "Pixel"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "Native Titelleiste (App-Neustart erforderlich)",
|
||||
@@ -1086,7 +1105,10 @@
|
||||
"layout-vertical-title": "Vertikal",
|
||||
"layout-horizontal-title": "Horizontal",
|
||||
"layout-vertical-description": "Startleiste ist auf der linken Seite (standard)",
|
||||
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert."
|
||||
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert.",
|
||||
"auto_theme": "Alt (Folge dem Farbschema des Systems)",
|
||||
"light_theme": "Alt (Hell)",
|
||||
"dark_theme": "Alt (Dunkel)"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"title": "Zoomfaktor (nur Desktop-Build)",
|
||||
@@ -1095,7 +1117,8 @@
|
||||
"code_auto_read_only_size": {
|
||||
"title": "Automatische schreibgeschützte Größe",
|
||||
"description": "Die automatische schreibgeschützte Notizgröße ist die Größe, ab der Notizen im schreibgeschützten Modus angezeigt werden (aus Leistungsgründen).",
|
||||
"label": "Automatische schreibgeschützte Größe (Codenotizen)"
|
||||
"label": "Automatische schreibgeschützte Größe (Codenotizen)",
|
||||
"unit": "Zeichen"
|
||||
},
|
||||
"code_mime_types": {
|
||||
"title": "Verfügbare MIME-Typen im Dropdown-Menü"
|
||||
@@ -1114,12 +1137,13 @@
|
||||
"download_images_description": "Eingefügter HTML-Code kann Verweise auf Online-Bilder enthalten. Trilium findet diese Verweise und lädt die Bilder herunter, sodass sie offline verfügbar sind.",
|
||||
"enable_image_compression": "Bildkomprimierung aktivieren",
|
||||
"max_image_dimensions": "Maximale Breite/Höhe eines Bildes in Pixel (die Größe des Bildes wird geändert, wenn es diese Einstellung überschreitet).",
|
||||
"jpeg_quality_description": "JPEG-Qualität (10 – schlechteste Qualität, 100 – beste Qualität, 50 – 85 wird empfohlen)"
|
||||
"jpeg_quality_description": "JPEG-Qualität (10 – schlechteste Qualität, 100 – beste Qualität, 50 – 85 wird empfohlen)",
|
||||
"max_image_dimensions_unit": "Pixel"
|
||||
},
|
||||
"attachment_erasure_timeout": {
|
||||
"attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen",
|
||||
"attachment_auto_deletion_description": "Anhänge werden automatisch gelöscht (und gelöscht), wenn sie nach einer definierten Zeitspanne nicht mehr in ihrer Notiz referenziert werden.",
|
||||
"erase_attachments_after": "Erase unused attachments after:",
|
||||
"erase_attachments_after": "Nicht verwendete Anhänge löschen nach:",
|
||||
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
|
||||
"erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen",
|
||||
"unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht."
|
||||
@@ -1130,7 +1154,7 @@
|
||||
},
|
||||
"note_erasure_timeout": {
|
||||
"note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung",
|
||||
"note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.",
|
||||
"note_erasure_description": "Gelöschte Notizen (und Attribute, Notizrevisionen...) werden zunächst nur als gelöscht markiert und können über den Dialog „Zuletzt verwendete Notizen” wiederhergestellt werden. Nach einer bestimmten Zeit werden gelöschte Notizen „gelöscht”, was bedeutet, dass ihr Inhalt nicht mehr wiederhergestellt werden kann. Mit dieser Einstellung können Sie die Zeitspanne zwischen dem Löschen und dem endgültigen Löschen der Notiz festlegen.",
|
||||
"erase_notes_after": "Notizen löschen nach:",
|
||||
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
|
||||
"erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen",
|
||||
@@ -1146,7 +1170,8 @@
|
||||
"note_revisions_snapshot_limit_description": "Das Limit für Notizrevision-Snapshots bezieht sich auf die maximale Anzahl von Revisionen, die für jede Notiz gespeichert werden können. Dabei bedeutet -1, dass es kein Limit gibt, und 0 bedeutet, dass alle Revisionen gelöscht werden. Du kannst das maximale Limit für Revisionen einer einzelnen Notiz über das Label #versioningLimit festlegen.",
|
||||
"snapshot_number_limit_label": "Limit der Notizrevision-Snapshots:",
|
||||
"erase_excess_revision_snapshots": "Überschüssige Revision-Snapshots jetzt löschen",
|
||||
"erase_excess_revision_snapshots_prompt": "Überschüssige Revision-Snapshots wurden gelöscht."
|
||||
"erase_excess_revision_snapshots_prompt": "Überschüssige Revision-Snapshots wurden gelöscht.",
|
||||
"snapshot_number_limit_unit": "Momentaufnahmen"
|
||||
},
|
||||
"search_engine": {
|
||||
"title": "Suchmaschine",
|
||||
@@ -1188,12 +1213,14 @@
|
||||
"title": "Inhaltsverzeichnis",
|
||||
"description": "Das Inhaltsverzeichnis wird in Textnotizen angezeigt, wenn die Notiz mehr als eine definierte Anzahl von Überschriften enthält. Du kannst diese Nummer anpassen:",
|
||||
"disable_info": "Du kannst diese Option auch verwenden, um TOC effektiv zu deaktivieren, indem du eine sehr hohe Zahl festlegst.",
|
||||
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Inhaltsverzeichnis) unter Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
|
||||
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Inhaltsverzeichnis) unter Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“).",
|
||||
"unit": "Überschriften"
|
||||
},
|
||||
"text_auto_read_only_size": {
|
||||
"title": "Automatische schreibgeschützte Größe",
|
||||
"description": "Die automatische schreibgeschützte Notizgröße ist die Größe, ab der Notizen im schreibgeschützten Modus angezeigt werden (aus Leistungsgründen).",
|
||||
"label": "Automatische schreibgeschützte Größe (Textnotizen)"
|
||||
"label": "Automatische schreibgeschützte Größe (Textnotizen)",
|
||||
"unit": "Zeichen"
|
||||
},
|
||||
"i18n": {
|
||||
"title": "Lokalisierung",
|
||||
@@ -1361,7 +1388,7 @@
|
||||
"duplicate": "Duplizieren",
|
||||
"export": "Exportieren",
|
||||
"import-into-note": "In Notiz importieren",
|
||||
"apply-bulk-actions": "Massenaktionen ausführen",
|
||||
"apply-bulk-actions": "Massenaktionen anwenden",
|
||||
"converted-to-attachments": "{{count}} Notizen wurden als Anhang konvertiert.",
|
||||
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?"
|
||||
},
|
||||
@@ -1377,7 +1404,7 @@
|
||||
"relation-map": "Beziehungskarte",
|
||||
"note-map": "Notizkarte",
|
||||
"render-note": "Render Notiz",
|
||||
"mermaid-diagram": "Mermaid Diagram",
|
||||
"mermaid-diagram": "Mermaid Diagramm",
|
||||
"canvas": "Canvas",
|
||||
"web-view": "Webansicht",
|
||||
"mind-map": "Mind Map",
|
||||
@@ -1387,7 +1414,7 @@
|
||||
"doc": "Dokument",
|
||||
"widget": "Widget",
|
||||
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
|
||||
"geo-map": "Geo Map",
|
||||
"geo-map": "Geo-Karte",
|
||||
"beta-feature": "Beta"
|
||||
},
|
||||
"protect_note": {
|
||||
@@ -1561,11 +1588,11 @@
|
||||
"label": "Format Toolbar",
|
||||
"floating": {
|
||||
"title": "Schwebend",
|
||||
"description": "Werkzeuge erscheinen in Cursornähe"
|
||||
"description": "Werkzeuge erscheinen in Cursornähe;"
|
||||
},
|
||||
"fixed": {
|
||||
"title": "Fixiert",
|
||||
"description": "Werkzeuge erscheinen im \"Format\" Tab"
|
||||
"description": "Werkzeuge erscheinen im \"Format\" Tab."
|
||||
},
|
||||
"multiline-toolbar": "Toolbar wenn nötig in mehreren Zeilen darstellen."
|
||||
}
|
||||
@@ -1631,5 +1658,170 @@
|
||||
},
|
||||
"modal": {
|
||||
"close": "Schließen"
|
||||
},
|
||||
"ai_llm": {
|
||||
"n_notes_queued": "{{ count }} Notiz zur Indizierung vorgemerkt",
|
||||
"n_notes_queued_plural": "{{ count }} Notizen zur Indizierung vorgemerkt",
|
||||
"notes_indexed": "{{ count }} Notiz indiziert",
|
||||
"notes_indexed_plural": "{{ count }} Notizen indiziert",
|
||||
"not_started": "Nicht gestartet",
|
||||
"title": "KI Einstellungen",
|
||||
"processed_notes": "Verarbeitete Notizen",
|
||||
"total_notes": "Gesamt Notizen",
|
||||
"progress": "Fortschritt",
|
||||
"queued_notes": "Eingereihte Notizen",
|
||||
"failed_notes": "Fehlgeschlagenen Notizen",
|
||||
"last_processed": "Zuletzt verarbeitet",
|
||||
"refresh_stats": "Statistiken neu laden",
|
||||
"enable_ai_features": "Aktiviere KI/LLM Funktionen",
|
||||
"enable_ai_description": "Aktiviere KI-Funktionen wie Notizzusammenfassungen, Inhaltserzeugung und andere LLM-Funktionen",
|
||||
"openai_tab": "OpenAI",
|
||||
"anthropic_tab": "Anthropic",
|
||||
"voyage_tab": "Voyage AI",
|
||||
"ollama_tab": "Ollama",
|
||||
"enable_ai": "Aktiviere KI/LLM Funktionen",
|
||||
"enable_ai_desc": "Aktiviere KI-Funktionen wie Notizzusammenfassungen, Inhaltserzeugung und andere LLM-Funktionen",
|
||||
"provider_configuration": "KI-Anbieterkonfiguration",
|
||||
"provider_precedence": "Anbieter Priorität",
|
||||
"provider_precedence_description": "Komma-getrennte Liste von Anbieter in der Reihenfolge ihrer Priorität (z.B. 'openai, anthropic,ollama')",
|
||||
"temperature": "Temperatur",
|
||||
"temperature_description": "Regelt die Zufälligkeit in Antworten (0 = deterministisch, 2 = maximale Zufälligkeit)",
|
||||
"system_prompt": "Systemaufforderung",
|
||||
"system_prompt_description": "Standard Systemaufforderung für alle KI-Interaktionen",
|
||||
"openai_configuration": "OpenAI Konfiguration",
|
||||
"openai_settings": "OpenAI Einstellungen",
|
||||
"api_key": "API Schlüssel",
|
||||
"url": "Basis-URL",
|
||||
"model": "Modell",
|
||||
"anthropic_settings": "Anthropic Einstellungen",
|
||||
"partial": "{{ percentage }}% verarbeitet",
|
||||
"anthropic_api_key_description": "Dein Anthropic API-Key für den Zugriff auf Claude Modelle",
|
||||
"anthropic_model_description": "Anthropic Claude Modell für Chat-Vervollständigung",
|
||||
"voyage_settings": "Einstellungen für Voyage AI",
|
||||
"ollama_url_description": "URL für die Ollama API (Standard: http://localhost:11434)",
|
||||
"ollama_model_description": "Ollama Modell für Chat-Vervollständigung",
|
||||
"anthropic_configuration": "Anthropic Konfiguration",
|
||||
"voyage_configuration": "Voyage AI Konfiguration",
|
||||
"voyage_url_description": "Standard: https://api.voyageai.com/v1",
|
||||
"ollama_configuration": "Ollama Konfiguration",
|
||||
"enable_ollama": "Aktiviere Ollama",
|
||||
"enable_ollama_description": "Aktiviere Ollama für lokale KI Modell Nutzung",
|
||||
"ollama_url": "Ollama URL",
|
||||
"ollama_model": "Ollama Modell",
|
||||
"refresh_models": "Aktualisiere Modelle",
|
||||
"refreshing_models": "Aktualisiere...",
|
||||
"enable_automatic_indexing": "Aktiviere automatische Indizierung",
|
||||
"rebuild_index": "Index neu aufbauen",
|
||||
"rebuild_index_error": "Fehler beim Neuaufbau des Index. Prüfe Log für mehr Informationen.",
|
||||
"retry_failed": "Fehler: Notiz konnte nicht erneut eingereiht werden",
|
||||
"max_notes_per_llm_query": "Max. Notizen je Abfrage",
|
||||
"max_notes_per_llm_query_description": "Maximale Anzahl ähnlicher Notizen zum Einbinden als KI Kontext",
|
||||
"active_providers": "Aktive Anbieter",
|
||||
"disabled_providers": "Inaktive Anbieter",
|
||||
"remove_provider": "Entferne Anbieter von Suche",
|
||||
"restore_provider": "Anbieter zur Suche wiederherstellen",
|
||||
"similarity_threshold": "Ähnlichkeitsschwelle",
|
||||
"similarity_threshold_description": "Mindestähnlichkeitswert (0-1) für Notizen, die im Kontext für LLM-Abfragen berücksichtigt werden sollen",
|
||||
"reprocess_index": "Suchindex neu erstellen",
|
||||
"reprocessing_index": "Neuerstellung...",
|
||||
"reprocess_index_started": "Suchindex-Optimierung wurde im Hintergrund gestartet",
|
||||
"reprocess_index_error": "Fehler beim Wiederaufbau des Suchindex",
|
||||
"index_rebuild_progress": "Fortschritt der Index-Neuerstellung",
|
||||
"index_rebuilding": "Optimierung Index ({{percentage}}%)",
|
||||
"index_rebuild_complete": "Index Optimierung abgeschlossen",
|
||||
"index_rebuild_status_error": "Fehler bei Überprüfung Status Index Neuerstellung",
|
||||
"never": "Niemals",
|
||||
"processing": "Verarbeitung ({{percentage}}%)",
|
||||
"refreshing": "Aktualisiere...",
|
||||
"incomplete": "Unvollständig ({{percentage}}%)",
|
||||
"complete": "Abgeschlossen (100%)",
|
||||
"auto_refresh_notice": "Auto-Aktualisierung alle {{seconds}} Sekunden",
|
||||
"note_queued_for_retry": "Notiz in Warteschlange für erneuten Versuch hinzugefügt",
|
||||
"failed_to_retry_note": "Wiederholungsversuch fehlgeschlagen für Notiz",
|
||||
"ai_settings": "KI Einstellungen",
|
||||
"agent": {
|
||||
"processing": "Verarbeite...",
|
||||
"thinking": "Nachdenken...",
|
||||
"loading": "Lade...",
|
||||
"generating": "Generiere..."
|
||||
},
|
||||
"name": "KI",
|
||||
"openai": "OpenAI",
|
||||
"use_enhanced_context": "Benutze verbesserten Kontext",
|
||||
"openai_api_key_description": "Dein OpenAPI-Key für den Zugriff auf den KI-Dienst",
|
||||
"default_model": "Standardmodell",
|
||||
"openai_model_description": "Beispiele: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
|
||||
"base_url": "Basis URL",
|
||||
"openai_url_description": "Standard: https://api.openai.com/v1",
|
||||
"anthropic_url_description": "Basis URL für Anthropic API (Standard: https://api.anthropic.com)",
|
||||
"ollama_settings": "Ollama Einstellungen",
|
||||
"note_title": "Notiz Titel",
|
||||
"error": "Fehler",
|
||||
"last_attempt": "Letzter Versuch",
|
||||
"actions": "Aktionen",
|
||||
"retry": "Erneut versuchen",
|
||||
"retry_queued": "Notiz für weiteren Versuch eingereiht",
|
||||
"empty_key_warning": {
|
||||
"anthropic": "Anthropic API-Key ist leer. Bitte gültigen API-Key eingeben.",
|
||||
"openai": "OpenAI API-Key ist leer. Bitte gültigen API-Key eingeben.",
|
||||
"voyage": "Voyage API-Key ist leer. Bitte gültigen API-Key eingeben.",
|
||||
"ollama": "Ollama API-Key ist leer. Bitte gültigen API-Key eingeben."
|
||||
},
|
||||
"api_key_tooltip": "API-Key für den Zugriff auf den Dienst",
|
||||
"failed_to_retry_all": "Wiederholungsversuch für Notizen fehlgeschlagen",
|
||||
"all_notes_queued_for_retry": "Alle fehlgeschlagenen Notizen wurden zur Wiederholung in die Warteschlange gestellt",
|
||||
"enhanced_context_description": "Versorgt die KI mit mehr Kontext aus der Notiz und den zugehörigen Notizen, um bessere Antworten zu ermöglichen",
|
||||
"show_thinking": "Zeige Denkprozess",
|
||||
"show_thinking_description": "Zeige den Denkprozess der KI",
|
||||
"enter_message": "Geben Sie Ihre Nachricht ein...",
|
||||
"error_contacting_provider": "Fehler beim Kontaktieren des KI-Anbieters. Bitte überprüfe die Einstellungen und die Internetverbindung.",
|
||||
"error_generating_response": "Fehler beim Generieren der KI Antwort",
|
||||
"index_all_notes": "Indiziere alle Notizen",
|
||||
"index_status": "Indizierungsstatus",
|
||||
"indexed_notes": "Indizierte Notizen",
|
||||
"indexing_stopped": "Indizierung gestoppt",
|
||||
"indexing_in_progress": "Indizierung in Bearbeitung...",
|
||||
"last_indexed": "Zuletzt Indiziert",
|
||||
"note_chat": "Notizen-Chat",
|
||||
"sources": "Quellen",
|
||||
"start_indexing": "Starte Indizierung",
|
||||
"use_advanced_context": "Benutze erweiterten Kontext",
|
||||
"ollama_no_url": "Ollama ist nicht konfiguriert. Bitte trage eine gültige URL ein.",
|
||||
"chat": {
|
||||
"root_note_title": "KI Chats",
|
||||
"root_note_content": "Diese Notiz enthält gespeicherte KI-Chat-Unterhaltungen.",
|
||||
"new_chat_title": "Neuer Chat",
|
||||
"create_new_ai_chat": "Erstelle neuen KI Chat"
|
||||
},
|
||||
"create_new_ai_chat": "Erstelle neuen KI Chat",
|
||||
"configuration_warnings": "Es wurden Probleme mit der KI Konfiguration festgestellt. Bitte überprüfe die Einstellungen.",
|
||||
"experimental_warning": "Die LLM-Funktionen sind aktuell experimentell - sei an dieser Stelle gewarnt.",
|
||||
"selected_provider": "Ausgewählter Anbieter",
|
||||
"selected_provider_description": "Wähle einen KI-Anbieter für Chat- und Vervollständigungsfunktionen",
|
||||
"select_model": "Wähle Modell...",
|
||||
"select_provider": "Wähle Anbieter...",
|
||||
"ai_enabled": "KI Funktionen aktiviert",
|
||||
"ai_disabled": "KI Funktionen deaktiviert",
|
||||
"no_models_found_online": "Keine Modelle gefunden. Bitte überprüfe den API-Key und die Einstellungen.",
|
||||
"no_models_found_ollama": "Kein Ollama Modell gefunden. Bitte prüfe, ob Ollama gerade läuft.",
|
||||
"error_fetching": "Fehler beim Abrufen der Modelle: {{error}}"
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Verlasse Zen Modus"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Leistung",
|
||||
"enable-motion": "Aktiviere Übergänge und Animationen",
|
||||
"enable-shadows": "Aktiviere Schatten",
|
||||
"enable-backdrop-effects": "Aktiviere Hintergrundeffekte für Menüs, Pop-up Fenster und Panele"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "Editor"
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "Benutzerdefiniertes Datums-/Zeitformat",
|
||||
"description": "Passe das Format des Datums und der Uhrzeit an, die über <shortcut /> oder die Symbolleiste eingefügt werden. Die verfügbaren Format-Tokens sind unter <doc>Day.js docs</doc> zu finden.",
|
||||
"format_string": "Format Zeichenfolge:",
|
||||
"formatted_time": "Formatiertes Datum/Uhrzeit:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,12 @@
|
||||
"layout-vertical-description": "launcher bar is on the left (default)",
|
||||
"layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width."
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Performance",
|
||||
"enable-motion": "Enable transitions and animations",
|
||||
"enable-shadows": "Enable shadows",
|
||||
"enable-backdrop-effects": "Enable background effects for menus, popups and panels"
|
||||
},
|
||||
"ai_llm": {
|
||||
"not_started": "Not started",
|
||||
"title": "AI Settings",
|
||||
|
||||
@@ -1680,6 +1680,16 @@
|
||||
"n_notes_queued_2": "",
|
||||
"notes_indexed_0": "{{ count }} note indexée",
|
||||
"notes_indexed_1": "{{ count }} notes indexées",
|
||||
"notes_indexed_2": ""
|
||||
"notes_indexed_2": "",
|
||||
"anthropic_url_description": "URL de base pour l'API Anthropic (par défaut : https ://api.anthropic.com)",
|
||||
"anthropic_model_description": "Modèles Anthropic Claude pour la complétion",
|
||||
"voyage_settings": "Réglages d'IA Voyage",
|
||||
"ollama_settings": "Réglages Ollama",
|
||||
"ollama_url_description": "URL pour l'API Ollama (par défaut: http://localhost:11434)",
|
||||
"ollama_model_description": "Model Ollama utilisé pour la complétion",
|
||||
"anthropic_configuration": "Configuration Anthropic",
|
||||
"voyage_configuration": "Configuration IA Voyage",
|
||||
"voyage_url_description": "Défaut: https://api.voyageai.com/v1",
|
||||
"ollama_configuration": "Configuration Ollama"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,7 +692,8 @@
|
||||
"placeholder_search": "ノート名で検索",
|
||||
"dialog_title": "埋め込みノート",
|
||||
"box_size_prompt": "埋め込みノート枠のサイズ:",
|
||||
"button_include": "埋め込みノート"
|
||||
"button_include": "埋め込みノート",
|
||||
"label_note": "ノート"
|
||||
},
|
||||
"ancestor": {
|
||||
"placeholder": "ノート名で検索"
|
||||
|
||||
29
apps/client/src/translations/ko/translation.json
Normal file
29
apps/client/src/translations/ko/translation.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Trilium Notes에 대해서",
|
||||
"homepage": "홈페이지:",
|
||||
"app_version": "앱 버전:",
|
||||
"db_version": "DB 버전:",
|
||||
"sync_version": "동기화 버전:",
|
||||
"build_date": "빌드 날짜:",
|
||||
"build_revision": "빌드 리비전:",
|
||||
"data_directory": "데이터 경로:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "심각한 오류",
|
||||
"message": "클라이언트 애플리케이션 시작 도중 심각한 오류가 발생했습니다:\n\n{{message}}\n\n이는 스크립트가 예기치 않게 실패하면서 발생한 것일 수 있습니다. 애플리케이션을 안전 모드로 시작한 뒤 문제를 해결해 보세요."
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "위젯 초기화 실패"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "링크 추가",
|
||||
"note": "노트",
|
||||
"search_note": "이름으로 노트 검색하기"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "저장"
|
||||
}
|
||||
}
|
||||
9
apps/client/src/translations/nl/translation.json
Normal file
9
apps/client/src/translations/nl/translation.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Over Trilium Notes",
|
||||
"homepage": "Homepagina:",
|
||||
"app_version": "App versie:",
|
||||
"db_version": "DB Versie:",
|
||||
"sync_version": "Sync Versie:"
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,121 @@
|
||||
"n_notes_queued_2": "{{ count }} notas enfileiradas para indexação",
|
||||
"notes_indexed_0": "{{ count }} nota indexada",
|
||||
"notes_indexed_1": "{{ count }} notas indexadas",
|
||||
"notes_indexed_2": "{{ count }} notas indexadas"
|
||||
"notes_indexed_2": "{{ count }} notas indexadas",
|
||||
"temperature": "Temperatura",
|
||||
"retry_queued": "Nota enfileirada para nova tentativa",
|
||||
"queued_notes": "Notas Enfileiradas",
|
||||
"empty_key_warning": {
|
||||
"voyage": "A chave de API da Voyage API está vazia. Por favor, digite uma chave de API válida.",
|
||||
"ollama": "A chave de API da Ollama API está vazia. Por favor, digite uma chave de API válida.",
|
||||
"anthropic": "A chave de API Anthropic está vazia. Por favor, digite uma chave de API válida.",
|
||||
"openai": "A chave de API OpenAI está vazia. Por favor, digite uma chave de API válida."
|
||||
},
|
||||
"not_started": "Não iniciado",
|
||||
"title": "Configurações de IA",
|
||||
"processed_notes": "Notas Processadas",
|
||||
"total_notes": "Total de Notas",
|
||||
"progress": "Andamento",
|
||||
"failed_notes": "Notas com Falha",
|
||||
"last_processed": "Últimas Processadas",
|
||||
"refresh_stats": "Atualizar Estatísticas",
|
||||
"enable_ai_features": "Ativar recurso IA/LLM",
|
||||
"enable_ai_description": "Ativar recursos IA como sumarização de notas, geração de conteúdo, e outras capacidades de LLM",
|
||||
"openai_tab": "OpenAI",
|
||||
"anthropic_tab": "Anthropic",
|
||||
"voyage_tab": "Voyage AI",
|
||||
"enable_ai": "Ativar recursos IA/LLM",
|
||||
"provider_configuration": "Configuração de Provedor de IA",
|
||||
"system_prompt": "Prompt de Sistema",
|
||||
"system_prompt_description": "Prompt padrão de sistema usado para todas as interações de IA",
|
||||
"openai_configuration": "Configuração OpenAI",
|
||||
"openai_settings": "Opções OpenAI",
|
||||
"api_key": "Chave de API",
|
||||
"url": "URL Base",
|
||||
"model": "Modelo",
|
||||
"openai_api_key_description": "Sua API Key da OpenAI para acessar os serviços de IA",
|
||||
"anthropic_api_key_description": "Sua API Key da Anthropic para acessar os modelos Claude",
|
||||
"default_model": "Modelo Padrão",
|
||||
"openai_model_description": "Exemplos: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
|
||||
"base_url": "URL Base",
|
||||
"openai_url_description": "Padrão: https://api.openai.com/v1",
|
||||
"anthropic_settings": "Configurações Anthropic",
|
||||
"anthropic_url_description": "URL Base da API Anthropic (padrão: https://api.anthropic.com)",
|
||||
"anthropic_model_description": "Modelos Claude da Anthropic para completar conversas",
|
||||
"voyage_settings": "Configurações Voyage AI",
|
||||
"retry": "Tentar novamente",
|
||||
"retry_failed": "Falha ao enfileirar nota para nova tentativa",
|
||||
"max_notes_per_llm_query": "Máximo de Notas por Consulta",
|
||||
"max_notes_per_llm_query_description": "Número máximo de notas similares para incluir no contexto da IA",
|
||||
"active_providers": "Provedores Ativos",
|
||||
"disabled_providers": "Provedores Desativados",
|
||||
"remove_provider": "Remover provedor da pesquisa",
|
||||
"restore_provider": "Restaurar provedor na pesquisa",
|
||||
"similarity_threshold": "Tolerância de Similaridade",
|
||||
"similarity_threshold_description": "Pontuação máxima de similaridade (0-1) para notas a serem incluídas no contexto das consultas de LLM",
|
||||
"reprocess_index": "Reconstruir Índice de Pesquisa",
|
||||
"reprocessing_index": "Reconstruindo…",
|
||||
"reprocess_index_started": "Otimiação do índice de pesquisa iniciado em plano de fundo",
|
||||
"reprocess_index_error": "Erro ao reconstruir índice de pesquisa",
|
||||
"index_rebuild_progress": "Andamento da Reconstrução do Índice",
|
||||
"index_rebuilding": "Otimizando índice ({{percentage}}%)",
|
||||
"index_rebuild_complete": "Otimização de índice finalizada",
|
||||
"index_rebuild_status_error": "Erro ao verificar o estado da reconstrução do índice",
|
||||
"never": "Nunca",
|
||||
"processing": "Processando ({{percentage}}%)",
|
||||
"incomplete": "Incompleto ({{percentage}}%)",
|
||||
"complete": "Completo (100%)",
|
||||
"refreshing": "Atualizando…",
|
||||
"auto_refresh_notice": "Atualizando automaticamente a cada {{seconds}} segundos",
|
||||
"note_queued_for_retry": "Nota enfileirada para nova tentativa",
|
||||
"failed_to_retry_note": "Falha ao retentar nota",
|
||||
"all_notes_queued_for_retry": "Todas as notas com falha enfileiradas para nova tentativa",
|
||||
"failed_to_retry_all": "Falha ao retentar notas",
|
||||
"ai_settings": "Configurações IA",
|
||||
"api_key_tooltip": "Chave de API para acessar o serviço",
|
||||
"agent": {
|
||||
"processing": "Processando…",
|
||||
"thinking": "Pensando…",
|
||||
"loading": "Carregando…",
|
||||
"generating": "Gerando…"
|
||||
},
|
||||
"name": "IA",
|
||||
"openai": "OpenAI",
|
||||
"use_enhanced_context": "Usar contexto aprimorado",
|
||||
"enhanced_context_description": "Alimentar IA com mais contexto sobre a nota e suas notas relacionadas para melhores respostas",
|
||||
"show_thinking": "Exibir pensamento",
|
||||
"enter_message": "Digite sua mensagem…",
|
||||
"error_contacting_provider": "Erro ao contatar o provedor de IA. Por favor, verifique suas configurações e sua conexão de internet.",
|
||||
"error_generating_response": "Erro ao gerar resposta da IA",
|
||||
"index_all_notes": "Indexar Todas as Notas",
|
||||
"index_status": "Estado do Índice",
|
||||
"indexed_notes": "Notas Indexadas",
|
||||
"indexing_stopped": "Indexação interrompida",
|
||||
"indexing_in_progress": "Indexação em andamento…",
|
||||
"last_indexed": "Última Indexada",
|
||||
"note_chat": "Conversa de Nota",
|
||||
"sources": "Origens",
|
||||
"start_indexing": "Iniciar Indexação",
|
||||
"use_advanced_context": "Usar Contexto Avançado",
|
||||
"ollama_no_url": "Ollama não está configurado. Por favor, digite uma URL válida.",
|
||||
"chat": {
|
||||
"root_note_title": "Conversas IA",
|
||||
"root_note_content": "Esta nota contém suas conversas com IA salvas.",
|
||||
"new_chat_title": "Nova Conversa",
|
||||
"create_new_ai_chat": "Criar nova Conversa IA"
|
||||
},
|
||||
"create_new_ai_chat": "Criar nova Conversa IA",
|
||||
"configuration_warnings": "Existem alguns problemas com sua configuração de IA. Por fovor, verifique suas configurações.",
|
||||
"experimental_warning": "O recurso de LLM atualmente é experimental - você foi avisado.",
|
||||
"selected_provider": "Provedor Selecionado",
|
||||
"selected_provider_description": "Escolha o provedor de IA para conversas e recursos de completar",
|
||||
"select_model": "Selecionar modelo…",
|
||||
"select_provider": "Selecionar provedor…",
|
||||
"ai_enabled": "Recursos de IA habilitados",
|
||||
"ai_disabled": "Recursos de IA desabilitados",
|
||||
"no_models_found_online": "Nenhum modelo encontrado. Por favor, verifique sua chave de API e as configurações.",
|
||||
"no_models_found_ollama": "Nenhum modelo Ollama encontrado. Por favor, verifique se o Ollama está em execução.",
|
||||
"error_fetching": "Erro ao obter modelos: {{error}}"
|
||||
},
|
||||
"confirm": {
|
||||
"confirmation": "Confirmação",
|
||||
@@ -405,10 +519,723 @@
|
||||
"share_index": "notas com este rótulo irão listar todas as raízes das notas compartilhadas",
|
||||
"display_relations": "nomes das relações separados por vírgula que devem ser exibidos. Todas as outras serão ocultadas.",
|
||||
"hide_relations": "nomes das relações separados por vírgula que devem ser ocultados. Todas as outras serão exibidas.",
|
||||
"title_template": "Título padrão das notas criadas como filhas desta nota. O valor é avaliado como uma string JavaScript e pode ser enriquecido com conteúdo dinâmico usando as variáveis injetadas <code>now</code> e <code>parentNote</code>. Exemplos:\n\n<ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n</ul>\n\nVeja a <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki com detalhes</a>, a documentação da API para <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> e para <a href=\"https://day.js.org/docs/en/display/format\">now</a> para mais informações.",
|
||||
"title_template": "título padrão das notas criadas como filhas desta nota. O valor é avaliado como uma string JavaScript \n e pode ser enriquecido com conteúdo dinâmico usando as variáveis injetadas <code>now</code> e <code>parentNote</code>. Exemplos:\n \n <ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Veja a <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki com detalhes</a>, a documentação da API para <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> e para <a href=\"https://day.js.org/docs/en/display/format\">now</a> para mais informações.",
|
||||
"template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota",
|
||||
"toc": "<code>#toc</code> ou <code>#toc=show</code> irá forçar a exibição do Sumário, <code>#toc=hide</code> irá forçar que ele fique oculto. Se o rótulo não existir, será considerado o ajuste global",
|
||||
"color": "define a cor da nota na árvore de notas, links etc. Use qualquer valor de cor CSS válido, como 'red' ou #a13d5f",
|
||||
"keyboard_shortcut": "Define um atalho de teclado que irá pular imediatamente para esta nota. Exemplo: 'ctrl+alt+e'. É necessário recarregar o frontend para que a alteração tenha efeito."
|
||||
"keyboard_shortcut": "Define um atalho de teclado que irá pular imediatamente para esta nota. Exemplo: 'ctrl+alt+e'. É necessário recarregar o frontend para que a alteração tenha efeito.",
|
||||
"hide_highlight_widget": "Ocultar o widget da lista de destaques",
|
||||
"keep_current_hoisting": "Abrir este link não alterará o destaque, mesmo que a nota não seja exibível na subárvore destacada atual.",
|
||||
"execute_button": "Titulo do botão que executará a nota de código atual",
|
||||
"exclude_from_note_map": "Notas com este rótulo ficarão ocultas no Mapa de Notas",
|
||||
"new_notes_on_top": "Novas notas serão criadas no topo da nota raiz, não na parte inferior.",
|
||||
"execute_description": "Descrição longa da nota de código atualmente exibida junto ao botão executar",
|
||||
"print_page_size": "Quando exportando para PDF, altera o tamanho da página. Valores suportados: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
|
||||
"and_more": "... e {{count}} mais.",
|
||||
"other_notes_with_name": "Outras notas com {{attributeType}} igual a \"{{attributeName}}\"",
|
||||
"color_type": "Cor",
|
||||
"run_on_note_creation": "executa quando a nota é criada no backend. Use esta relação se quiser executar o script para todas as notas criadas em uma subárvore específica. Neste caso, crie-a na nota raiz da subárvore e torne-a herdável. Uma nova nota criada dentro da subárvore (qualquer profundidade) irá acionar o script.",
|
||||
"run_on_child_note_creation": "executa quando uma nova nota é criada sob a nota onde esta relação está definida",
|
||||
"run_on_note_title_change": "executa quando o título da nota é alterado (inclusive na criação de nota)",
|
||||
"run_on_note_content_change": "executa quando o conteúdo da nota é alterado (inclusive na criação de nota).",
|
||||
"run_on_note_change": "executa quando a nota é alterada (inclusive na criação de nota). Não incluí alterações no conteúdo",
|
||||
"run_on_note_deletion": "executa quando a nota está sendo excluída",
|
||||
"run_on_branch_creation": "executa quando uma ramificação é criada. Ramificação é uma ligação entre nota pai e nota filha e é criado, por exemplo, ao clonar ou mover uma nota.",
|
||||
"run_on_branch_change": "executa quando uma remificação é atualizada.",
|
||||
"run_on_attribute_creation": "executa quando um novo atributo é criado para a nota que define esta relação",
|
||||
"run_on_attribute_change": " executa quando o atributo é alterado na nota que define esta relação. Também é disparado quando o atributo é excluído",
|
||||
"widget_relation": "o destino desta relação será executado e renderizado como um widget na barra lateral",
|
||||
"run_on_branch_deletion": "executa quando uma ramificação é excluída. Ramificação é um link entre a nota pai e a nota filha e é excluído, por exemplo, ao mover a nota (a ramificação/link antiga é excluída).",
|
||||
"relation_template": "os atributos da nota serão herdados mesmo sem um relacionamento pai-filho, o conteúdo e subárvore da nota serão adicionados às notas da instância se vazias. Veja a documentação para detalhes.",
|
||||
"inherit": "os atributos da nota serão herdados mesmo sem um relacionamento pai-filho. Veja relação de modelos para um conceito semelhante. Veja a herança de atributos na documentação.",
|
||||
"render_note": "notas do tipo \"nota de renderização HTML\" serão renderizadas usando uma nota de código (HTML ou script) e é necessário apontar usando esta relação qual nota deve ser renderizada",
|
||||
"share_css": "Nota CSS que será injetada na página de compartilhamento. A nota CSS também deve estar na subárvore compartilhada. Considere usar também 'share_hidden_from_tree' e 'share_omit_default_css'.",
|
||||
"share_js": "Nota JavaScript que será injetada na página de compartilhamento. A nota JS também deve estar na subárvore compartilhada. Considere usar 'share_hidden_from_tree'.",
|
||||
"share_template": "Nota JavaScript incorporada que será usada como modelo para exibir a nota compartilhada. Retorna ao modelo padrão. Considere usar 'share_hidden_from_tree'.",
|
||||
"share_favicon": "Nota Favicon que será usada na página compartilhada. Tipicamente você quer defini-la na raiz do compartilhamento e torná-lo herdável. A nota de Favicon também deve estar na subárvore compartilhada. Considere usar 'share_hidden_from_tree'.",
|
||||
"is_owned_by_note": "é propriedade da nota",
|
||||
"print_landscape": "Ao exportar para PDF, muda a orientação da página para paisagem em vez de retrato."
|
||||
},
|
||||
"attachments_actions": {
|
||||
"delete_attachment": "Excluir anexo",
|
||||
"open_externally": "Abrir externamente",
|
||||
"open_custom": "Abrir customizado",
|
||||
"download": "Baixar",
|
||||
"rename_attachment": "Renomear anexo",
|
||||
"upload_new_revision": "Enviar nova revisão",
|
||||
"copy_link_to_clipboard": "Copiar link para a área de transferência",
|
||||
"convert_attachment_into_note": "Converter anexo para nota",
|
||||
"upload_success": "Uma nova revisão de anexo foi enviada.",
|
||||
"upload_failed": "O envio de uma nova revisão de anexo falhou.",
|
||||
"delete_success": "O anexo '{{title}}' foi excluído.",
|
||||
"convert_success": "O anexo '{{title}}' foi convertido para uma nota.",
|
||||
"enter_new_name": "Por favor, digite o novo nome do anexo",
|
||||
"delete_confirm": "Tem certeza que deseja excluir o anexo '{{title}}'?",
|
||||
"convert_confirm": "Tem certeza que deseja converter o anexo '{{title}}' em uma nota separada?",
|
||||
"open_externally_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.",
|
||||
"open_custom_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.",
|
||||
"open_externally_detail_page": "A abertura de anexo externamente só está disponível através da página de detalhes. Por favor, primeiro clique nos detalhes do anexo e repita a ação.",
|
||||
"open_custom_client_only": "A abertura customizada de anexos só pode ser feita usando o cliente de desktop."
|
||||
},
|
||||
"attachment_detail": {
|
||||
"you_can_also_open": ", você também pode abrir o(a) ",
|
||||
"open_help_page": "Abrir página de ajuda nos anexos",
|
||||
"list_of_all_attachments": "Lista de todos os anexos",
|
||||
"attachment_deleted": "Este anexo foi excluído."
|
||||
},
|
||||
"ancestor": {
|
||||
"depth_gt": "é maior que {{count}}",
|
||||
"label": "Ancestral",
|
||||
"placeholder": "buscar notas pelo nome",
|
||||
"depth_label": "profundidade",
|
||||
"depth_doesnt_matter": "não importa",
|
||||
"depth_eq": "é exatamente {{count}}",
|
||||
"direct_children": "filho direto",
|
||||
"depth_lt": "é menor que {{count}}"
|
||||
},
|
||||
"add_relation": {
|
||||
"add_relation": "Adicionar relação",
|
||||
"allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"relation_name": "nome da relação",
|
||||
"to": "para",
|
||||
"target_note": "nota destino",
|
||||
"create_relation_on_all_matched_notes": "Crie a relação informada em todas as notas correspondentes."
|
||||
},
|
||||
"delete_label": {
|
||||
"label_name_placeholder": "nome da etiqueta",
|
||||
"label_name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"delete_label": "Excluir etiqueta"
|
||||
},
|
||||
"rename_label": {
|
||||
"rename_label": "Renomear etiqueta",
|
||||
"rename_label_from": "Renomear etiqueta de",
|
||||
"old_name_placeholder": "nome antigo",
|
||||
"to": "Para",
|
||||
"new_name_placeholder": "novo nome",
|
||||
"name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos."
|
||||
},
|
||||
"execute_script": {
|
||||
"example_1": "Por exemplo para anexar um texto ao título de uma nota, use este pequeno script:",
|
||||
"execute_script": "Executar script",
|
||||
"help_text": "Você pode executar scripts simples nas notas correspondentes.",
|
||||
"example_2": "Um exemplo mais complexo seria excluir todos os atributos das notas correspondentes:"
|
||||
},
|
||||
"attribute_editor": {
|
||||
"help_text_body1": "Para adicionar uma etiqueta, digite por exemplo <code>#rock</code> ou se você também quer adicionar um valor então por exemplo <code>#year = 2020</code>",
|
||||
"help_text_body2": "Para relação, digite <code>~author = @</code>, que deve ser exibido um autocompletar onde você pode encontrar a nota desejada.",
|
||||
"help_text_body3": "Alternativamente, você pode adicionar etiqueta e relação usando o botão <code>+</code> no lado direito.",
|
||||
"save_attributes": "Salvar atributos <enter>",
|
||||
"add_a_new_attribute": "Adicionar um novo atributo",
|
||||
"add_new_label": "Adicionar nova etiqueta <kbd data-command=\"addNewLabel\"></kbd>",
|
||||
"add_new_relation": "Adicionar nova relação <kbd data-command=\"addNewRelation\"></kbd>",
|
||||
"add_new_label_definition": "Adicionar nova definição de etiqueta",
|
||||
"add_new_relation_definition": "Adicionar nova definição de relação",
|
||||
"placeholder": "Digite as etiquetas e relações aqui"
|
||||
},
|
||||
"abstract_bulk_action": {
|
||||
"remove_this_search_action": "Remover esta ação de busca"
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "Adicionar etiqueta",
|
||||
"label_name_placeholder": "nome da etiqueta",
|
||||
"label_name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"to_value": "para o valor",
|
||||
"new_value_placeholder": "novo valor",
|
||||
"help_text": "Em todas as notas correspondentes:",
|
||||
"help_text_item1": "criar a etiqueta indicada se a nota ainda não tiver uma",
|
||||
"help_text_item2": "ou altere o valor da etiqueta existente",
|
||||
"help_text_note": "Você também pode chamar este método sem valor, neste caso a etiqueta será atribuída à nota sem valor."
|
||||
},
|
||||
"update_label_value": {
|
||||
"update_label_value": "Atualizar valor da etiqueta",
|
||||
"label_name_placeholder": "nome da etiqueta",
|
||||
"label_name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"new_value_placeholder": "novo valor",
|
||||
"to_value": "para o valor",
|
||||
"help_text": "Em todas as notas correspondentes, altera o valor da etiqueta existente.",
|
||||
"help_text_note": "Você também pode chamar este método sem um valor, neste caso a etiqueta será à nota sem um valor."
|
||||
},
|
||||
"delete_relation": {
|
||||
"allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"delete_relation": "Excluir relação",
|
||||
"relation_name": "nome da relação"
|
||||
},
|
||||
"rename_relation": {
|
||||
"allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"rename_relation": "Renomar relação",
|
||||
"rename_relation_from": "Renomear relação de",
|
||||
"old_name": "nome antigo",
|
||||
"to": "Para",
|
||||
"new_name": "novo nome"
|
||||
},
|
||||
"update_relation_target": {
|
||||
"allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.",
|
||||
"to": "para",
|
||||
"target_note": "nota destino",
|
||||
"on_all_matched_notes": "Em todas as notas correspondentes",
|
||||
"change_target_note": "alterar nota destino da relação existente",
|
||||
"update_relation_target": "Atualizar destino da relação",
|
||||
"update_relation": "Atualizar relação",
|
||||
"relation_name": "nome da relação"
|
||||
},
|
||||
"content_renderer": {
|
||||
"open_externally": "Abrir externamente"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Fechar"
|
||||
},
|
||||
"api_log": {
|
||||
"close": "Fechar"
|
||||
},
|
||||
"attachment_detail_2": {
|
||||
"will_be_deleted_in": "Este anexo será excluído automaticamente em {{time}}",
|
||||
"will_be_deleted_soon": "Este anexo será excluído automaticamente em breve",
|
||||
"deletion_reason": ", porque o anexo não está associado ao conteúdo da nota. Para evitar a exclusão, adicione o anexo novamente ao conteúdo ou converta o anexo em uma nota.",
|
||||
"role_and_size": "Regra: {{role}}, Tamanho: {{size}}",
|
||||
"link_copied": "Link do anexo copiado para a área de transferência.",
|
||||
"unrecognized_role": "Regra desconhecida de anexo '{{role}}'."
|
||||
},
|
||||
"bookmark_switch": {
|
||||
"bookmark": "Favorito",
|
||||
"bookmark_this_note": "Favoritar esta nota no painel da esquerda",
|
||||
"remove_bookmark": "Remover favorito"
|
||||
},
|
||||
"editability_select": {
|
||||
"auto": "Auto",
|
||||
"read_only": "Somente leitura",
|
||||
"always_editable": "Sempre Editável",
|
||||
"note_is_editable": "A nota é editável se não for muito longa.",
|
||||
"note_is_read_only": "A nota é somente leitura, mas pode ser editada com um clique no botão.",
|
||||
"note_is_always_editable": "A nota é sempre editável, independentemente do seu tamanho."
|
||||
},
|
||||
"note-map": {
|
||||
"button-link-map": "Mapa de Links",
|
||||
"button-tree-map": "Mapa em Árvore"
|
||||
},
|
||||
"tree-context-menu": {
|
||||
"open-in-a-new-tab": "Abrir em uma nova aba <kbd>Ctrl+Click</kbd>",
|
||||
"open-in-a-new-split": "Abrir em um novo painel dividido",
|
||||
"insert-note-after": "Inserir nota após",
|
||||
"insert-child-note": "Inserir nota filha",
|
||||
"delete": "Excluir",
|
||||
"search-in-subtree": "Buscar na subárvore"
|
||||
},
|
||||
"command_palette": {
|
||||
"search_subtree_title": "Buscar na Subárvore",
|
||||
"search_subtree_description": "Buscar dentro da subárvore atual",
|
||||
"search_history_title": "Exibir Histórico de Busca",
|
||||
"search_history_description": "Visualizar buscas anteriores",
|
||||
"configure_launch_bar_title": "Configurar Barra de Execução"
|
||||
},
|
||||
"delete_note": {
|
||||
"delete_note": "Excluir nota",
|
||||
"delete_matched_notes": "Excluir notas correspondentes",
|
||||
"delete_matched_notes_description": "Isso irá excluir as notas correspondentes.",
|
||||
"undelete_notes_instruction": "Depois da exclusão, é possível desfazer através da janela de Alterações Recentes.",
|
||||
"erase_notes_instruction": "Para apagar notas permanentemente, você pode fazer isso depois da exclusão indo em Opções -> Outros e clicar no botão \"Apagar notas excluídas agora\"."
|
||||
},
|
||||
"delete_revisions": {
|
||||
"delete_note_revisions": "Excluir revisões da nota",
|
||||
"all_past_note_revisions": "Todas as revisões anteriores das notas correspondentes serão excluídas. A nota em si será perservada. Ou seja, o histórico da nota será removido."
|
||||
},
|
||||
"move_note": {
|
||||
"move_note": "Mover nota",
|
||||
"to": "para",
|
||||
"target_parent_note": "nota pai destino",
|
||||
"on_all_matched_notes": "Em todas as notas correspondentes",
|
||||
"move_note_new_parent": "move a nota para o novo pai se a nota tem apenas um pai (ou seja, a antiga ramificação é removida e uma nova ramificação é criada para o novo pai)",
|
||||
"clone_note_new_parent": "clona a nota para o novo pai se a nota tem vários clones / ramificações (não é claro qual ramificação deve ser removida)",
|
||||
"nothing_will_happen": "nada acontecerá se a nota não puder ser movida para a nota de destino (por exemplo, se criaria um ciclo de árvore)"
|
||||
},
|
||||
"rename_note": {
|
||||
"rename_note": "Renomear nota",
|
||||
"rename_note_title_to": "Renomear título da nota para",
|
||||
"new_note_title": "novo título da nota",
|
||||
"click_help_icon": "Clique no ícone de ajuda a direita para ver todas as opções",
|
||||
"example_note": "<code>Nota</code> - todas as notas correspondentes serão renomeadas para 'Nota'",
|
||||
"example_new_title": "<code>NOVO: ${note.title}</code> - o título das notas correspondentes receberá o prefixo 'NOVO: '",
|
||||
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - notas correspondentes receberão um prefixo com o mês-dia da data de criação da nota",
|
||||
"api_docs": "Veja da documentação da API para <a href='https://zadam.github.io/trilium/backend_api/Note.html'>nota</a> e suas <a href='https://day.js.org/docs/en/display/format'>propriedades dateCreatedObj / utcDateCreatedObj</a> para detalhes.",
|
||||
"evaluated_as_js_string": "O valor digitado é avaliado como string JavaScript e, portanto, pode ser enriquecido com conteúdo dinâmico através da variável injetada <code>note</code> (nota sendo renomeada). Exemplos:"
|
||||
},
|
||||
"calendar": {
|
||||
"mon": "Seg",
|
||||
"tue": "Ter",
|
||||
"wed": "Qua",
|
||||
"thu": "Qui",
|
||||
"fri": "Sex",
|
||||
"sat": "Sáb",
|
||||
"sun": "Dom",
|
||||
"cannot_find_day_note": "Nota do dia não encontrada",
|
||||
"cannot_find_week_note": "Nota semanal não encontrada",
|
||||
"january": "Janeiro",
|
||||
"febuary": "Fevereiro",
|
||||
"march": "Março",
|
||||
"april": "Abril",
|
||||
"may": "Maio",
|
||||
"june": "Junho",
|
||||
"july": "Julho",
|
||||
"august": "Agosto",
|
||||
"september": "Setembro",
|
||||
"october": "Outubro",
|
||||
"november": "Novembro",
|
||||
"december": "Dezembro"
|
||||
},
|
||||
"close_pane_button": {
|
||||
"close_this_pane": "Fechar este painel"
|
||||
},
|
||||
"create_pane_button": {
|
||||
"create_new_split": "Criar nova divisão"
|
||||
},
|
||||
"edit_button": {
|
||||
"edit_this_note": "Editar esta nota"
|
||||
},
|
||||
"show_toc_widget_button": {
|
||||
"show_toc": "Mostrar Tabela de Conteúdo"
|
||||
},
|
||||
"show_highlights_list_widget_button": {
|
||||
"show_highlights_list": "Mostrar Lista de Destaques"
|
||||
},
|
||||
"global_menu": {
|
||||
"menu": "Menu",
|
||||
"options": "Opções",
|
||||
"open_new_window": "Abrir Nova Janela",
|
||||
"switch_to_mobile_version": "Alternar para Versão Mobile",
|
||||
"switch_to_desktop_version": "Alternar para Versão Desktop",
|
||||
"zoom": "Zoom",
|
||||
"toggle_fullscreen": "Alternar Tela Cheia",
|
||||
"zoom_out": "Reduzir",
|
||||
"reset_zoom_level": "Redefinir Zoom",
|
||||
"zoom_in": "Aumentar",
|
||||
"configure_launchbar": "Configurar Barra de Lançamento",
|
||||
"show_shared_notes_subtree": "Exibir Subárvore de Notas Compartilhadas",
|
||||
"advanced": "Avançado",
|
||||
"open_dev_tools": "Abrir Ferramentas de Desenvolvedor",
|
||||
"open_sql_console": "Abrir Console SQL",
|
||||
"open_sql_console_history": "Abrir Histórico de Console SQL",
|
||||
"open_search_history": "Abrir Histórico de Busca",
|
||||
"show_backend_log": "Abrir Log do Servidor",
|
||||
"reload_frontend": "Recarregar Frontend",
|
||||
"show_hidden_subtree": "Exibir Subárvore Oculta",
|
||||
"show_help": "Exibir Ajuda",
|
||||
"about": "Sobre o Trilium Notes",
|
||||
"logout": "Sair",
|
||||
"show-cheatsheet": "Exibir Cheatsheet",
|
||||
"toggle-zen-mode": "Modo Zen",
|
||||
"reload_hint": "Recarregar pode ajudar com alguns problemas visuais sem reiniciar toda a aplicação."
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Sair do Modo Zen"
|
||||
},
|
||||
"sync_status": {
|
||||
"in_progress": "Sincronização com o servidor em andamento.",
|
||||
"unknown": "<p>O estado da sincronização será conhecido assim que a próxima tentativa começar.</p><p>Clique para iniciar a sincronização agora.</p>",
|
||||
"connected_with_changes": "<p>Conectado ao servidor de sincronização.<br>Existem algumas alterações esperando para serem sincronizadas.</p><p>Clique para sincronizar.</p>",
|
||||
"connected_no_changes": "<p>Conectado ao servidor de sincronização.<br>Todas as alterações já foram sincronizadas.</p><p>Clique para sincronizar.</p>",
|
||||
"disconnected_with_changes": "<p>A conexão ao servidor de sincronização falhou.<br>Existem algumas alterações esperando para serem sincronizadas.</p><p>Clique para sincronizar.</p>",
|
||||
"disconnected_no_changes": "<p>A conexão ao servidor de sincronização falhou.<br>Todas as alterações já foram sincronizadas.</p><p>Clique para sincronizar.</p>"
|
||||
},
|
||||
"left_pane_toggle": {
|
||||
"show_panel": "Exibir painel",
|
||||
"hide_panel": "Esconder painel"
|
||||
},
|
||||
"move_pane_button": {
|
||||
"move_left": "Mover para a esquerda",
|
||||
"move_right": "Mover para a direita"
|
||||
},
|
||||
"note_actions": {
|
||||
"convert_into_attachment": "Converter para anexo",
|
||||
"re_render_note": "Renderizar nota novamente",
|
||||
"search_in_note": "Buscar na nota",
|
||||
"note_source": "Código Fonte da nota",
|
||||
"note_attachments": "Anexos da nota",
|
||||
"open_note_externally": "Abrir nota externamente",
|
||||
"open_note_custom": "Abrir nota de forma customizada",
|
||||
"import_files": "Importar arquivos",
|
||||
"export_note": "Exportar nota",
|
||||
"delete_note": "Excluir nota",
|
||||
"print_note": "Imprimir nota",
|
||||
"save_revision": "Salvar revisão",
|
||||
"convert_into_attachment_failed": "A conversão da nota '{{title}}' falhou.",
|
||||
"convert_into_attachment_successful": "A nota '{{title}}' foi convertida para anexo.",
|
||||
"print_pdf": "Exportar como PDF…",
|
||||
"open_note_externally_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.",
|
||||
"convert_into_attachment_prompt": "Você tem certeza que quer converter a nota '{{title}}' em um anexo da nota pai?"
|
||||
},
|
||||
"protected_session_status": {
|
||||
"inactive": "Clique para entrar na sessão protegida",
|
||||
"active": "Sessão protegida está ativada. Clique para deixar a sessão protegida."
|
||||
},
|
||||
"revisions_button": {
|
||||
"note_revisions": "Revisões da Nota"
|
||||
},
|
||||
"update_available": {
|
||||
"update_available": "Atualização disponível"
|
||||
},
|
||||
"code_buttons": {
|
||||
"execute_button_title": "Executar script",
|
||||
"trilium_api_docs_button_title": "Abrir documentação da Trilium API",
|
||||
"save_to_note_button_title": "Salvar para uma nota",
|
||||
"opening_api_docs_message": "Abrindo documentação da API…",
|
||||
"sql_console_saved_message": "Nota do Console SQL foi salva no caminho {{note_path}}"
|
||||
},
|
||||
"hide_floating_buttons_button": {
|
||||
"button_title": "Esconder botões"
|
||||
},
|
||||
"show_floating_buttons_button": {
|
||||
"button_title": "Exibir botões"
|
||||
},
|
||||
"svg_export_button": {
|
||||
"button_title": "Exportar diagrama como SVG"
|
||||
},
|
||||
"relation_map_buttons": {
|
||||
"zoom_in_title": "Aumentar",
|
||||
"zoom_out_title": "Reduzir",
|
||||
"create_child_note_title": "Criar nova nota filha e adicione neste mapa de relação",
|
||||
"reset_pan_zoom_title": "Redefinir pan & zoom para coordenadas e ampliação iniciais"
|
||||
},
|
||||
"zpetne_odkazy": {
|
||||
"backlink": "{{count}} Links Reversos",
|
||||
"backlinks": "{{count}} Links Reversos",
|
||||
"relation": "relação"
|
||||
},
|
||||
"mobile_detail_menu": {
|
||||
"insert_child_note": "Inserir nota filha",
|
||||
"delete_this_note": "Excluir essa nota",
|
||||
"error_unrecognized_command": "Comando não reconhecido {{command}}",
|
||||
"error_cannot_get_branch_id": "Não foi possível obter o branchId para o notePath '{{notePath}} '"
|
||||
},
|
||||
"note_icon": {
|
||||
"change_note_icon": "Alterar ícone da nota",
|
||||
"category": "Categoria:",
|
||||
"search": "Busca:",
|
||||
"reset-default": "Redefinir para o ícone padrão"
|
||||
},
|
||||
"basic_properties": {
|
||||
"note_type": "Tipo da nota",
|
||||
"editable": "Editável",
|
||||
"basic_properties": "Propriedades Básicas",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Tipo de visualização",
|
||||
"grid": "Grade",
|
||||
"list": "Lista",
|
||||
"collapse_all_notes": "Recolher todas as notas",
|
||||
"expand_all_children": "Expandir todos os filhos",
|
||||
"collapse": "Recolher",
|
||||
"expand": "Expandir",
|
||||
"book_properties": "Propriedades da Coleção",
|
||||
"invalid_view_type": "Tipo de visualização inválido '{{type}}'",
|
||||
"calendar": "Calendário",
|
||||
"table": "Tabela",
|
||||
"geo-map": "Geo Map",
|
||||
"board": "Quadro"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "Ainda não há nenhuma nota editada neste dia…",
|
||||
"title": "Notas Editadas",
|
||||
"deleted": "(excluído)"
|
||||
},
|
||||
"file_properties": {
|
||||
"note_id": "ID da Nota",
|
||||
"original_file_name": "Nome original do arquivo",
|
||||
"file_type": "Tipo do arquivo",
|
||||
"file_size": "Tamanho do arquivo",
|
||||
"download": "Baixar",
|
||||
"open": "Abrir",
|
||||
"upload_new_revision": "Enviar nova revisão",
|
||||
"upload_success": "Uma nova revisão de arquivo foi enviada.",
|
||||
"upload_failed": "O envio de uma nova revisão de arquivo falhou.",
|
||||
"title": "Arquivo"
|
||||
},
|
||||
"image_properties": {
|
||||
"original_file_name": "Nome original do arquivo",
|
||||
"file_type": "Tipo do arquivo",
|
||||
"file_size": "Tamanho do arquivo",
|
||||
"download": "Baixar",
|
||||
"open": "Abrir",
|
||||
"copy_reference_to_clipboard": "Copiar referência para a área de transferência",
|
||||
"upload_new_revision": "Enviar nova revisão",
|
||||
"upload_success": "Uma nova revisão de imagem foi enviado.",
|
||||
"upload_failed": "O envio de uma nova revisão de imagem falhou: {{message}}",
|
||||
"title": "Imagem"
|
||||
},
|
||||
"inherited_attribute_list": {
|
||||
"title": "Atributos Herdados",
|
||||
"no_inherited_attributes": "Nenhum atributo herdado."
|
||||
},
|
||||
"note_info_widget": {
|
||||
"note_id": "ID da Nota",
|
||||
"created": "Criado",
|
||||
"modified": "Editado",
|
||||
"type": "Tipo",
|
||||
"note_size": "Tamanho da nota",
|
||||
"calculate": "calcular",
|
||||
"title": "Informações da nota",
|
||||
"subtree_size": "(tamanho da subárvore: {{size}} em {{count}} notas)",
|
||||
"note_size_info": "O tamanho da nota fornece uma estimativa aproximada dos requisitos de armazenamento para esta nota. Leva em conta o conteúdo e o conteúdo de suas revisões de nota."
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "Expandir completamente",
|
||||
"collapse": "Recolher para tamanho normal",
|
||||
"title": "Mapa de Notas",
|
||||
"fix-nodes": "Fixar nós",
|
||||
"link-distance": "Distância do Link"
|
||||
},
|
||||
"note_paths": {
|
||||
"title": "Caminho das Notas",
|
||||
"clone_button": "Clonar nota para novo local…",
|
||||
"intro_placed": "Esta nova está localizada nos caminhos:",
|
||||
"intro_not_placed": "Esta nota ainda não está em nenhuma árvore de notas.",
|
||||
"archived": "Arquivado",
|
||||
"search": "Pesquisar",
|
||||
"outside_hoisted": "Este caminho está fora de uma nota fixada e você teria que desafixar."
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Esta nota foi originalmente obtida de:",
|
||||
"info": "Informações"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
"promoted_attributes": "Atributos Promovidos",
|
||||
"unset-field-placeholder": "não atribuído",
|
||||
"open_external_link": "Abrir link externo",
|
||||
"unknown_label_type": "Tipo de etiqueta desconhecido '{{type}}'",
|
||||
"unknown_attribute_type": "Tipo de atributo desconhecido '{{type}}'",
|
||||
"add_new_attribute": "Adicionar novo atributo",
|
||||
"remove_this_attribute": "Remover este atributo",
|
||||
"remove_color": "Remover a etiqueta de cor",
|
||||
"url_placeholder": "http://website..."
|
||||
},
|
||||
"script_executor": {
|
||||
"query": "Consulta",
|
||||
"script": "Script",
|
||||
"execute_query": "Executar Consulta",
|
||||
"execute_script": "Executar Script"
|
||||
},
|
||||
"search_definition": {
|
||||
"add_search_option": "Adicionar opção de pesquisa:",
|
||||
"search_string": "pesquisa de texto",
|
||||
"search_script": "pesquisa de script",
|
||||
"ancestor": "ancestral",
|
||||
"fast_search": "pesquisa rápida",
|
||||
"include_archived": "incluir arquivados",
|
||||
"order_by": "ordenar por",
|
||||
"limit": "limite",
|
||||
"limit_description": "Limitar número de resultados",
|
||||
"debug": "depurar",
|
||||
"action": "ação",
|
||||
"search_button": "Pesquisar <kbd>enter</kbd>",
|
||||
"search_execute": "Pesquisar & Executar ações",
|
||||
"save_to_note": "Salvar para nota",
|
||||
"search_parameters": "Parâmetros de Pesquisa",
|
||||
"unknown_search_option": "Opção de pesquisa desconhecida {{searchOptionName}}",
|
||||
"actions_executed": "As ações foram executadas.",
|
||||
"search_note_saved": "Nota de pesquisa foi salva em {{- notePathTitle}}",
|
||||
"fast_search_description": "A opção de pesquisa rápida desabilita a pesquisa de texto completo do conteúdo de nota, o que pode acelerar a pesquisa em grandes bancos de dados.",
|
||||
"include_archived_notes_description": "As notas arquivadas são por padrão excluídas dos resultados da pesquisa, com esta opção elas serão incluídas.",
|
||||
"debug_description": "A depuração irá imprimir informações adicionais no console para ajudar na depuração de consultas complexas"
|
||||
},
|
||||
"similar_notes": {
|
||||
"title": "Notas Similares",
|
||||
"no_similar_notes_found": "Nenhum nota similar encontrada."
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Remover esta opção de pesquisa",
|
||||
"failed_rendering": "A renderização da opção de busca falhou: {{dto}} com o erro: {{error}} {{stack}}"
|
||||
},
|
||||
"debug": {
|
||||
"debug": "Depurar",
|
||||
"debug_info": "A depuração irá imprimir informações adicionais no console para ajudar em depuração de consultas complexas.",
|
||||
"access_info": "Para acessar as informações de depuração, execute a consulta e clique em \"Exibir log do servidor\" no canto superior esquerdo."
|
||||
},
|
||||
"fast_search": {
|
||||
"fast_search": "Pesquisa rápida",
|
||||
"description": "A opção de pesquisa rápida desabilita a pesquisa de texto completo do conteúdo de nota, o que pode acelerar a pesquisa em grandes bancos de dados."
|
||||
},
|
||||
"include_archived_notes": {
|
||||
"include_archived_notes": "Incluir notas arquivadas"
|
||||
},
|
||||
"limit": {
|
||||
"limit": "Limite",
|
||||
"take_first_x_results": "Pegar apenas os X primeiros resultados."
|
||||
},
|
||||
"order_by": {
|
||||
"order_by": "Ordenar por",
|
||||
"relevancy": "Relevância (padrão)",
|
||||
"title": "Título",
|
||||
"date_created": "Data de criação",
|
||||
"date_modified": "Data da última modificação",
|
||||
"content_size": "Tamaho do conteúdo da nota",
|
||||
"content_and_attachments_size": "Tamanho do conteúdo da nota incluindo anexos",
|
||||
"content_and_attachments_and_revisions_size": "Tamanho do conteúdo da nota incluindo anexos e revisões",
|
||||
"revision_count": "Número de revisões",
|
||||
"children_count": "Número de notas filhas",
|
||||
"parent_count": "Número de clones",
|
||||
"owned_label_count": "Número de etiquetas",
|
||||
"owned_relation_count": "Número de relações",
|
||||
"target_relation_count": "Número de relações para esta nota",
|
||||
"random": "Ordem aleatória",
|
||||
"asc": "Crescente (padrão)",
|
||||
"desc": "Decrescente"
|
||||
},
|
||||
"search_script": {
|
||||
"title": "Buscar script:",
|
||||
"placeholder": "buscar notas pelo nome",
|
||||
"example_title": "Veja este exemplo:",
|
||||
"example_code": "// 1. pré-filtro usando pesquisa padrão\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. aplicando critérios de pesquisa customizados\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;",
|
||||
"description1": "O script de pesquisa permite definir os resultados da pesquisa executando um script. Isso proporciona flexibilidade máxima quando a busca padrão não é suficiente.",
|
||||
"description2": "O script de pesquisa deve ser do tipo \"código\" e subtipo \"JavaScript no servidor\". O script precisa retornar um array de noteIds ou de notas.",
|
||||
"note": "Note que o script de pesquisa e a pesquisa de texto não podem ser combinados entre si."
|
||||
},
|
||||
"search_string": {
|
||||
"title_column": "Buscar texto:",
|
||||
"search_syntax": "Sintaxe de pesquisa",
|
||||
"also_see": "veja também",
|
||||
"full_text_search": "Digite qualquer texto para busca por texto completo",
|
||||
"label_abc": "retorna notas com a etiqueta abc",
|
||||
"label_year": "corresponde notas com a etiqueta de ano 2019",
|
||||
"label_rock_pop": "corresponde notas que tenham tanto a etiqueta rock quando pop",
|
||||
"label_rock_or_pop": "apenas uma das etiquetas deve estar presente",
|
||||
"label_year_comparison": "comparação numérica (também >, >=, <).",
|
||||
"label_date_created": "notas criadas no último mês",
|
||||
"error": "Erro na busca: {{error}}",
|
||||
"search_prefix": "Busca:",
|
||||
"placeholder": "palavras-chave fulltext, #tag = valor..."
|
||||
},
|
||||
"attachment_list": {
|
||||
"open_help_page": "Abrir página de ajuda nos anexos",
|
||||
"upload_attachments": "Enviar anexos",
|
||||
"no_attachments": "Esta nota não possuí anexos."
|
||||
},
|
||||
"editable_code": {
|
||||
"placeholder": "Digite o conteúdo da sua nota de código aqui…"
|
||||
},
|
||||
"editable_text": {
|
||||
"placeholder": "Digite o conteúdo da sua nota aqui…"
|
||||
},
|
||||
"empty": {
|
||||
"search_placeholder": "buscar uma nota pelo nome",
|
||||
"enter_workspace": "Entrar no workspace {{title}}"
|
||||
},
|
||||
"file": {
|
||||
"file_preview_not_available": "Prévia não disponível para este formato de arquivo."
|
||||
},
|
||||
"protected_session": {
|
||||
"enter_password_instruction": "É necessário digitar sua senha para mostar notas protegidas:",
|
||||
"started": "A sessão protegida foi iniciada.",
|
||||
"wrong_password": "Senha incorreta.",
|
||||
"protecting-finished-successfully": "A proteção foi finalizada com sucesso.",
|
||||
"unprotecting-finished-successfully": "A remoção da proteção foi finalizada com sucesso.",
|
||||
"protecting-in-progress": "Proteções em andamento: {{count}}",
|
||||
"unprotecting-in-progress-count": "Remoções de proteção em andamento: {{count}}",
|
||||
"protecting-title": "Estado da proteção",
|
||||
"unprotecting-title": "Estado da remoção de proteção"
|
||||
},
|
||||
"relation_map": {
|
||||
"open_in_new_tab": "Abrir em nova aba",
|
||||
"remove_note": "Remover nota",
|
||||
"edit_title": "Editar título",
|
||||
"rename_note": "Renomear nota",
|
||||
"enter_new_title": "Digite o novo título da nota:",
|
||||
"remove_relation": "Remover relação",
|
||||
"confirm_remove_relation": "Tem certeza que deseja remover esta relação?",
|
||||
"connection_exists": "A conexão '{{name}}' já existe entre estas notas.",
|
||||
"note_not_found": "Nota {{noteId}} não encontrada!",
|
||||
"note_already_in_diagram": "A nota \"{{title}}\" já está no diagrama.",
|
||||
"enter_title_of_new_note": "Digite o título da nova nota",
|
||||
"default_new_note_title": "nova nota",
|
||||
"click_on_canvas_to_place_new_note": "Clique no quadro para incluir uma nova nota"
|
||||
},
|
||||
"web_view": {
|
||||
"web_view": "Web View"
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Recarregar"
|
||||
},
|
||||
"consistency_checks": {
|
||||
"title": "Chegagem de Consistência",
|
||||
"find_and_fix_button": "Encontrar e corrigir problemas de consistência",
|
||||
"finding_and_fixing_message": "Buscando e corrigindo problemas de consistência…",
|
||||
"issues_fixed_message": "Qualquer problema de consistência encontrado foi corrigido."
|
||||
},
|
||||
"database_integrity_check": {
|
||||
"check_button": "Verificar integridade do banco de dados",
|
||||
"checking_integrity": "Verificando integridade do banco de dados…",
|
||||
"integrity_check_succeeded": "Verificação de integridade bem sucedida - nenhum problema encontrado.",
|
||||
"integrity_check_failed": "Verificação de integridade falhou: {{results}}"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizar",
|
||||
"force_full_sync_button": "Forçar sincronização completa",
|
||||
"full_sync_triggered": "Sincronização completa iniciada",
|
||||
"finished-successfully": "Sincronização finalizada com sucesso.",
|
||||
"failed": "Sincronização falhou: {{message}}"
|
||||
},
|
||||
"vacuum_database": {
|
||||
"description": "Isso irá reconstruir o banco de dados, o que normalmente irá resultar em uma redução do arquivo do banco de dados. Nenhum dado será alterado."
|
||||
},
|
||||
"fonts": {
|
||||
"theme_defined": "Tema definido",
|
||||
"fonts": "Fontes",
|
||||
"main_font": "Fonte Principal",
|
||||
"font_family": "Família da fonte",
|
||||
"size": "Tamanho",
|
||||
"note_tree_font": "Fonte da Árvore de Notas",
|
||||
"note_detail_font": "Fonte Padrão da Nota",
|
||||
"monospace_font": "Fonte Monospace (código)",
|
||||
"not_all_fonts_available": "Nem todas as fontes listadas podem estar disponíveis em seu sistema.",
|
||||
"apply_font_changes": "Para aplicar as alterações de fonte, clique em",
|
||||
"reload_frontend": "recarregar frontend",
|
||||
"generic-fonts": "Fontes genéricas",
|
||||
"sans-serif-system-fonts": "Fontes sem serifa de sistema",
|
||||
"serif-system-fonts": "Fontes serifadas de sistema",
|
||||
"monospace-system-fonts": "Fontes monospace de sistema",
|
||||
"handwriting-system-fonts": "Fontes de escrita à mão de sistema",
|
||||
"serif": "Serifa",
|
||||
"sans-serif": "Sem Serifa",
|
||||
"monospace": "Monospace",
|
||||
"system-default": "Padrão do Sistema"
|
||||
},
|
||||
"max_content_width": {
|
||||
"title": "Largura do Conteúdo",
|
||||
"max_width_label": "Largura máxima do conteúdo",
|
||||
"max_width_unit": "pixels",
|
||||
"apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em",
|
||||
"reload_button": "recarregar frontend",
|
||||
"reload_description": "alterações de opções de aparência"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "Barra de Título Nativa (requer recarregar o app)",
|
||||
"enabled": "ativada",
|
||||
"disabled": "desativada"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Tema da Aplicação",
|
||||
"theme_label": "Tema",
|
||||
"override_theme_fonts_label": "Sobrepor fontes do tema",
|
||||
"auto_theme": "Legado (Seguir esquema de cor do sistema)",
|
||||
"light_theme": "Legado (Claro)",
|
||||
"dark_theme": "Legado (Escuro)",
|
||||
"triliumnext": "Trilium (Seguir esquema de cor do sistema)",
|
||||
"triliumnext-light": "Trilium (Claro)",
|
||||
"triliumnext-dark": "Trilium (Escuro)",
|
||||
"layout": "Layout",
|
||||
"layout-vertical-title": "Vertical",
|
||||
"layout-horizontal-title": "Horizontal",
|
||||
"layout-vertical-description": "barra de lançamento está a esquerda (padrão)",
|
||||
"layout-horizontal-description": "barra de lançamento está abaixo da barra de abas, a barra de abas agora tem a largura total."
|
||||
},
|
||||
"note_launcher": {
|
||||
"this_launcher_doesnt_define_target_note": "Este lançador não define uma nota destino."
|
||||
},
|
||||
"copy_image_reference_button": {
|
||||
"button_title": "Copiar referência da imagem para a área de transferência, pode ser colado em uma nota de texto."
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Componente de botão '{{componentId}}' não possui manipulador de clique definido"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Atributos próprios"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"add_relation": {
|
||||
"add_relation": "Adaugă relație",
|
||||
"allowed_characters": "Sunt permise doar caractere alfanumerice, underline și două puncte.",
|
||||
"create_relation_on_all_matched_notes": "Crează relația pentru toate notițele găsite",
|
||||
"create_relation_on_all_matched_notes": "Creează relația pentru toate notițele găsite.",
|
||||
"relation_name": "denumirea relației",
|
||||
"target_note": "notița destinație",
|
||||
"to": "către"
|
||||
@@ -76,9 +76,9 @@
|
||||
"attachment_erasure_timeout": {
|
||||
"attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.",
|
||||
"attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor",
|
||||
"erase_attachments_after": "Erase unused attachments after:",
|
||||
"erase_attachments_after": "Șterge atașamentele neutilizate după:",
|
||||
"erase_unused_attachments_now": "Elimină atașamentele șterse acum",
|
||||
"manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe",
|
||||
"manual_erasing_description": "Puteți șterge atașamentele nefolosite manual (fără a lua în considerare timpul de mai sus):",
|
||||
"unused_attachments_erased": "Atașamentele nefolosite au fost șterse."
|
||||
},
|
||||
"attachment_list": {
|
||||
@@ -141,7 +141,7 @@
|
||||
"hide_promoted_attributes": "Ascunde lista atributelor promovate pentru această notiță",
|
||||
"hide_relations": "lista denumirilor relațiilor ce trebuie ascunse, delimitate prin virgulă. Toate celelalte vor fi afișate.",
|
||||
"icon_class": "valoarea acestei etichete este adăugată ca o clasă CSS la iconița notiței din ierarhia notițelor, fapt ce poate ajuta la identificarea vizuală mai rapidă a notițelor. Un exemplu ar fi „bx bx-home” pentru iconițe preluate din boxicons. Poate fi folosită în notițe de tip șablon.",
|
||||
"inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței cu această etichetă.",
|
||||
"inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței marcată cu eticheta <code>#inbox</code>.",
|
||||
"inherit": "atributele acestei notițe vor fi moștenite chiar dacă nu există o relație părinte-copil între notițe. A se vedea relația de tip șablon pentru un concept similar. De asemenea, a se vedea moștenirea atributelor în documentație.",
|
||||
"inheritable": "Moștenibilă",
|
||||
"inheritable_title": "Atributele moștenibile vor fi moștenite de către toți descendenții acestei notițe.",
|
||||
@@ -177,7 +177,7 @@
|
||||
"render_note": "relație ce definește notița (de tip notiță de cod HTML sau script) ce trebuie randată pentru notițele de tip „Randare notiță HTML”",
|
||||
"run": "definește evenimentele la care să ruleze scriptul. Valori acceptate:\n<ul>\n<li>frontendStartup - când pornește interfața Trilium (sau este reîncărcată), dar nu pe mobil.</li>\n<li>mobileStartup - când pornește interfața Trilium (sau este reîncărcată), doar pe mobil.</li>\n<li>backendStartup - când pornește serverul Trilium</li>\n<li>hourly - o dată pe oră. Se poate utiliza adițional eticheta <code>runAtHour</code> pentru a specifica ora.</li>\n<li>daily - o dată pe zi</li>\n</ul>",
|
||||
"run_at_hour": "La ce oră ar trebui să ruleze. Trebuie folosit împreună cu <code>#run=hourly</code>. Poate fi definit de mai multe ori pentru a rula de mai multe ori în cadrul aceleași zile.",
|
||||
"run_on_attribute_change": "se execută atunci când atributele unei notițe care definește această relație se schimbă. Se apelează și atunci când un atribut este șters",
|
||||
"run_on_attribute_change": " se execută atunci când atributele unei notițe care definește această relație se schimbă. Se apelează și atunci când un atribut este șters",
|
||||
"run_on_attribute_creation": "se execută atunci când un nou atribut este creat pentru notița care definește această relație",
|
||||
"run_on_branch_change": "se execută atunci când o ramură este actualizată.",
|
||||
"run_on_branch_creation": "se execută când o ramură este creată. O ramură este o legătură dintre o notiță părinte și o notiță copil și este creată, spre exemplu, la clonarea sau mutarea unei notițe.",
|
||||
@@ -198,7 +198,7 @@
|
||||
"share_disallow_robot_indexing": "împiedică indexarea conținutului de către roboți utilizând antetul <code>X-Robots-Tag: noindex</code>",
|
||||
"share_external_link": "notița va funcționa drept o legătură către un site web extern în ierarhia de partajare",
|
||||
"share_favicon": "Notiță ce conține pictograma favicon pentru a fi setată în paginile partajate. De obicei se poate seta în rădăcina ierarhiei de partajare și se poate face moștenibilă. Notița ce conține favicon-ul trebuie să fie și ea în ierarhia de partajare. Considerați și utilizarea „share_hidden_from_tree”.",
|
||||
"share_hidden_from_tree": "notița este ascunsă din arborele de navigație din stânga, dar încă este accesibilă prin intermediul unui URL.",
|
||||
"share_hidden_from_tree": "notița este ascunsă din arborele de navigație din stânga, dar încă este accesibilă prin intermediul unui URL",
|
||||
"share_index": "notițele cu această etichetă vor afișa lista tuturor rădăcilor notițelor partajate",
|
||||
"share_js": "Notiță JavaScript ce va fi injectată în pagina de partajare. Notița respectivă trebuie să fie și ea în ierarhia de partajare. Considerați utilizarea 'share_hidden_from_tree'.",
|
||||
"share_omit_default_css": "CSS-ul implicit pentru pagina de partajare va fi omis. Se poate folosi atunci când se fac schimbări majore de stil la pagină.",
|
||||
@@ -214,7 +214,7 @@
|
||||
"target_note_title": "Relația este o conexiune numită dintre o notiță sursă și o notiță țintă.",
|
||||
"template": "Șablon",
|
||||
"text": "Text",
|
||||
"title_template": "titlul implicit al notițelor create în interiorul acestei notițe. Valoarea este evaluată ca un șir de caractere JavaScript\n și poate fi astfel îmbogățită cu un conținut dinamic prin intermediul variabilelow <code>now</code> și <code>parentNote</code>. Exemple:\n \n <ul>\n <li><code>Lucrările lui ${parentNote.getLabelValue('autor')}</code></li>\n <li><code>Jurnal pentru ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n A se vedea <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki-ul pentru detalii</a>, documentația API pentru <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> și <a href=\"https://day.js.org/docs/en/display/format\">now</a> pentru mai multe informații",
|
||||
"title_template": "titlul implicit al notițelor create în interiorul acestei notițe. Valoarea este evaluată ca un șir de caractere JavaScript\n și poate fi astfel îmbogățită cu un conținut dinamic prin intermediul variabilelor <code>now</code> și <code>parentNote</code>. Exemple:\n \n <ul>\n <li><code>Lucrările lui ${parentNote.getLabelValue('autor')}</code></li>\n <li><code>Jurnal pentru ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n A se vedea <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki-ul pentru detalii</a>, documentația API pentru <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> și <a href=\"https://day.js.org/docs/en/display/format\">now</a> pentru mai multe informații.",
|
||||
"toc": "<code>#toc</code> sau <code>#toc=show</code> forțează afișarea tabelei de conținut, <code>#toc=hide</code> forțează ascunderea ei. Dacă eticheta nu există, se utilizează setările globale",
|
||||
"top": "păstrează notița la începutul listei (se aplică doar pentru notițe sortate automat)",
|
||||
"url": "URL",
|
||||
@@ -369,7 +369,7 @@
|
||||
},
|
||||
"confirm": {
|
||||
"also_delete_note": "Șterge și notița",
|
||||
"are_you_sure_remove_note": "Doriți ștergerea notiței „{{title}}” din harta de relații?",
|
||||
"are_you_sure_remove_note": "Doriți ștergerea notiței „{{title}}” din harta de relații? ",
|
||||
"cancel": "Anulează",
|
||||
"confirmation": "Confirm",
|
||||
"if_you_dont_check": "Dacă această opțiune nu este bifată, notița va fi ștearsă doar din harta de relații.",
|
||||
@@ -519,8 +519,8 @@
|
||||
"export_status": "Starea exportului",
|
||||
"export_type_single": "Doar această notiță fără descendenții ei",
|
||||
"export_type_subtree": "Această notiță și toți descendenții ei",
|
||||
"format_html_zip": "HTML în arhivă ZIP - recomandat deoarece păstrează toată formatarea",
|
||||
"format_markdown": "Markdown - păstrează majoritatea formatării",
|
||||
"format_html_zip": "HTML în arhivă ZIP - recomandat deoarece păstrează toată formatarea.",
|
||||
"format_markdown": "Markdown - păstrează majoritatea formatării.",
|
||||
"format_opml": "OPML - format de interschimbare pentru editoare cu structură ierarhică (outline). Formatarea, imaginile și fișierele nu vor fi incluse.",
|
||||
"opml_version_1": "OPML v1.0 - text simplu",
|
||||
"opml_version_2": "OPML v2.0 - permite și HTML",
|
||||
@@ -640,7 +640,7 @@
|
||||
"newTabNoteLink": "pe o legătură către o notiță va deschide notița într-un tab nou",
|
||||
"notSet": "nesetat",
|
||||
"noteNavigation": "Navigarea printre notițe",
|
||||
"numberedList": "<kbd>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
|
||||
"numberedList": "<code>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
|
||||
"onlyInDesktop": "Doar pentru desktop (aplicația Electron)",
|
||||
"openEmptyTab": "deschide un tab nou",
|
||||
"other": "Altele",
|
||||
@@ -807,7 +807,7 @@
|
||||
"dialog_title": "Mută notițele în...",
|
||||
"error_no_path": "Nicio cale la care să poată fi mutate.",
|
||||
"move_button": "Mută la notița selectată",
|
||||
"move_success_message": "Notițele selectate au fost mutate în",
|
||||
"move_success_message": "Notițele selectate au fost mutate în ",
|
||||
"notes_to_move": "Notițe de mutat",
|
||||
"search_placeholder": "căutați notița după denumirea ei",
|
||||
"target_parent_note": "Notița părinte destinație"
|
||||
@@ -1058,7 +1058,7 @@
|
||||
"download_button": "Descarcă",
|
||||
"file_size": "Dimensiune fișier:",
|
||||
"help_title": "Informații despre reviziile notițelor",
|
||||
"mime": "MIME:",
|
||||
"mime": "MIME: ",
|
||||
"no_revisions": "Nu există încă nicio revizie pentru această notiță...",
|
||||
"note_revisions": "Revizii ale notiței",
|
||||
"preview": "Previzualizare:",
|
||||
@@ -1193,7 +1193,7 @@
|
||||
"enable": "Activează corectorul ortografic",
|
||||
"language_code_label": "Codurile de limbă",
|
||||
"language_code_placeholder": "de exemplu „en-US”, „de-AT”",
|
||||
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\".",
|
||||
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\". ",
|
||||
"title": "Corector ortografic",
|
||||
"restart-required": "Schimbările asupra setărilor corectorului ortografic vor fi aplicate după restartarea aplicației."
|
||||
},
|
||||
@@ -1286,7 +1286,7 @@
|
||||
"update_relation_target": {
|
||||
"allowed_characters": "Sunt permise doar caractere alfanumerice, underline și două puncte.",
|
||||
"change_target_note": "schimbă notița-țintă a unei relații existente",
|
||||
"on_all_matched_notes": "Pentru toate notițele găsite:",
|
||||
"on_all_matched_notes": "Pentru toate notițele găsite",
|
||||
"relation_name": "denumirea relației",
|
||||
"target_note": "notița destinație",
|
||||
"to": "la",
|
||||
@@ -1314,7 +1314,7 @@
|
||||
"use_vim_keybindings_in_code_notes": "Combinații de taste Vim"
|
||||
},
|
||||
"web_view": {
|
||||
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
|
||||
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
|
||||
"embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.",
|
||||
"web_view": "Vizualizare web"
|
||||
},
|
||||
@@ -1863,11 +1863,16 @@
|
||||
},
|
||||
"create_new_ai_chat": "Crează o nouă discuție cu AI-ul",
|
||||
"configuration_warnings": "Sunt câteva probleme la configurația AI-ului. Verificați setările.",
|
||||
"experimental_warning": "Funcția LLM este experimentală!",
|
||||
"experimental_warning": "Funcția LLM este experimentală.",
|
||||
"selected_provider": "Furnizor selectat",
|
||||
"selected_provider_description": "Selectați furnizorul de AI pentru funcțiile de discuție și completare",
|
||||
"select_model": "Selectați modelul...",
|
||||
"select_provider": "Selectați furnizorul..."
|
||||
"select_provider": "Selectați furnizorul...",
|
||||
"ai_enabled": "Funcționalitățile AI au fost activate",
|
||||
"ai_disabled": "Funcționalitățile AI au fost dezactivate",
|
||||
"no_models_found_online": "Nu s-a găsit niciun model. Verificați cheia API și configurația.",
|
||||
"no_models_found_ollama": "Nu s-a găsit niciun model Ollama. Verificați dacă Ollama rulează.",
|
||||
"error_fetching": "Eroare la obținerea modelelor: {{error}}"
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "Format dată/timp personalizat",
|
||||
@@ -1998,6 +2003,26 @@
|
||||
"call_to_action": {
|
||||
"background_effects_title": "Efectele de fundal sunt acum stabile",
|
||||
"background_effects_message": "Pe dispozitive cu Windows, efectele de fundal sunt complet stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei. Această tehnică este folosită și în alte aplicații precum Windows Explorer.",
|
||||
"background_effects_button": "Activează efectele de fundal"
|
||||
"background_effects_button": "Activează efectele de fundal",
|
||||
"next_theme_title": "Încercați noua temă Trilium",
|
||||
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
|
||||
"next_theme_button": "Testează noua temă",
|
||||
"dismiss": "Treci peste"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Setări de performanță",
|
||||
"enable-motion": "Activează tranzițiile și animațiile",
|
||||
"enable-shadows": "Activează umbrirea elementelor",
|
||||
"enable-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Setări similare"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Tema de culori pentru blocuri de cod în notițe de tip text",
|
||||
"related_code_notes": "Tema de culori pentru notițele de tip cod"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,8 @@
|
||||
"snapshot_number_limit_unit": "снимков",
|
||||
"note_revisions_snapshot_limit_title": "Максимальное количество снимков заметок",
|
||||
"snapshot_number_limit_label": "Максимальное количество снимков:",
|
||||
"erase_excess_revision_snapshots_prompt": "Удалить лишние снимки."
|
||||
"erase_excess_revision_snapshots_prompt": "Удалить лишние снимки.",
|
||||
"erase_excess_revision_snapshots": "Удалить лишние снимки версий"
|
||||
},
|
||||
"password": {
|
||||
"alert_message": "Пожалуйста, запомните новый пароль. Пароль используется для входа в веб-интерфейс и шифрования защищённых заметок. Если вы забудете пароль, все ваши защищённые заметки будут потеряны навсегда.",
|
||||
@@ -115,10 +116,14 @@
|
||||
"protected_session_timeout": "Тайм-аут защищенного сеанса",
|
||||
"protected_session_timeout_label": "Тайм-аут защищенного сеанса:",
|
||||
"protected_session_timeout_description": "Тайм-аут защищенного сеанса - это период времени, по истечении которого защищенный сеанс удаляется из памяти браузера. Он отсчитывается с момента последнего взаимодействия с защищенными заметками. См",
|
||||
"for_more_info": "для получения более подробной информации."
|
||||
"for_more_info": "для получения более подробной информации.",
|
||||
"reset_confirmation": "Сбросив пароль, вы навсегда потеряете доступ ко всем своим защищённым заметкам. Вы действительно хотите сбросить пароль?",
|
||||
"password_changed_success": "Пароль изменён. Trilium будет перезагружен после нажатия кнопки «ОК».",
|
||||
"password_mismatch": "Новые пароли не совпадают.",
|
||||
"reset_success_message": "Пароль был сброшен. Пожалуйста, установите новый пароль"
|
||||
},
|
||||
"content_language": {
|
||||
"description": "Выберите один или несколько языков, которые должны отображаться в разделе «Основные свойства» текстовой заметки, доступной только для чтения или редактируемой. Это позволит реализовать такие функции, как проверка орфографии и поддержка письма справа налево.",
|
||||
"description": "Выберите один или несколько языков, которые должны отображаться в разделе «Общее» текстовой заметки, доступной только для чтения или редактируемой. Это позволит реализовать такие функции, как проверка орфографии и поддержка письма справа налево.",
|
||||
"title": "Языки контента"
|
||||
},
|
||||
"theme": {
|
||||
@@ -133,7 +138,9 @@
|
||||
"layout-horizontal-title": "Горизонтальный",
|
||||
"auto_theme": "Legacy (следует системной цветовой схеме)",
|
||||
"light_theme": "Legacy (светлая)",
|
||||
"dark_theme": "Legacy (темная)"
|
||||
"dark_theme": "Legacy (темная)",
|
||||
"layout-horizontal-description": "панель запуска находится под панелью вкладок, панель вкладок теперь занимает всю ширину.",
|
||||
"layout-vertical-description": "панель запуска находится слева (по умолчанию)"
|
||||
},
|
||||
"tasks": {
|
||||
"due": {
|
||||
@@ -154,7 +161,8 @@
|
||||
"reopen_last_tab": "Повторно открыть последнюю закрытую вкладку",
|
||||
"close_all_tabs": "Закрыть все вкладки",
|
||||
"close_other_tabs": "Закрыть остальные вкладки",
|
||||
"add_new_tab": "Добавить новую вкладку"
|
||||
"add_new_tab": "Добавить новую вкладку",
|
||||
"close_right_tabs": "Закрыть вкладки справа"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "Новая строка",
|
||||
@@ -182,7 +190,10 @@
|
||||
"label_name_title": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.",
|
||||
"new_value_placeholder": "новое значение",
|
||||
"to_value": "на значение",
|
||||
"help_text": "На всех совпадающих заметках:"
|
||||
"help_text": "На всех совпадающих заметках:",
|
||||
"help_text_note": "Вы также можете вызвать этот метод без значения, в таком случае метка будет присвоена заметке без значения.",
|
||||
"help_text_item1": "создать заданную метку, если у заметки ее еще нет",
|
||||
"help_text_item2": "или изменить значение существующей метки"
|
||||
},
|
||||
"delete_label": {
|
||||
"delete_label": "Удалить метку",
|
||||
@@ -273,16 +284,17 @@
|
||||
"selectNote": "выбрать заметку",
|
||||
"copyNotes": "скопировать активную заметку (или выделение) в буфер обмер (используется для <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">клонирования</a>)",
|
||||
"createEditLink": "создать/редактировать внешнюю ссылку",
|
||||
"headings": "<code>##</code>, <code>###</code>, <code>####</code> и т. д., за которыми следует пробел для заголовков.",
|
||||
"headings": "<code>##</code>, <code>###</code>, <code>####</code> и т. д., за которыми следует пробел для заголовков",
|
||||
"bulletList": "<code>*</code> или <code>-</code> с последующим пробелом для маркированного списка",
|
||||
"numberedList": "<code>1.</code> или <code>1)</code> с последующим пробелом для нумерованного списка",
|
||||
"blockQuote": "начните строку с <code>></code>, а затем пробела для блока цитаты",
|
||||
"quickSearch": "сфокусироваться на полее ввода быстрого поиска",
|
||||
"editNoteTitle": "В области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. <kbd>Ctrl+.</kbd> переключит обратно с редактора на область дерева.",
|
||||
"editNoteTitle": "в области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. <kbd>Ctrl+.</kbd> переключит обратно с редактора на область дерева.",
|
||||
"title": "Справка"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Закрыть"
|
||||
"close": "Закрыть",
|
||||
"help_title": "Показать больше информации об этом экране"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "Импортировать в заметку",
|
||||
@@ -327,7 +339,7 @@
|
||||
"password_not_set": {
|
||||
"title": "Пароль не установлен",
|
||||
"body1": "Защищенные заметки шифруются с помощью пароля пользователя, но пароль еще не установлен.",
|
||||
"body2": "Чтобы иметь возможность защищать заметки, нажмите <a class=\"open-password-options-button\" href=\"javascript:\">здесь</a>, чтобы установить пароль.",
|
||||
"body2": "Чтобы защитить заметки, нажмите кнопку ниже, чтобы открыть диалоговое окно «Параметры» и установить пароль.",
|
||||
"go_to_password_options": "Перейти к параметрам пароля"
|
||||
},
|
||||
"protected_session_password": {
|
||||
@@ -339,7 +351,7 @@
|
||||
},
|
||||
"recent_changes": {
|
||||
"title": "Последние изменения",
|
||||
"erase_notes_button": "Удалить заметки, помеченные на удаление сейчас",
|
||||
"erase_notes_button": "Стереть удаленные заметки сейчас",
|
||||
"undelete_link": "восстановить",
|
||||
"no_changes_message": "Еще нет изменений...",
|
||||
"deleted_notes_message": "Удаленные заметки были стерты окончательно.",
|
||||
@@ -389,7 +401,7 @@
|
||||
"upload_attachments": {
|
||||
"upload_attachments_to_note": "Загрузить вложения к заметке",
|
||||
"choose_files": "Выберите файлы",
|
||||
"files_will_be_uploaded": "Файлы будут загружены как приложения в",
|
||||
"files_will_be_uploaded": "Файлы будут загружены как приложения в {{noteTitle}}",
|
||||
"options": "Параметры",
|
||||
"shrink_images": "Сжать изображения",
|
||||
"tooltip": "Если этот параметр включен, Trilium попытается уменьшить размер загружаемых изображений путём масштабирования и оптимизации, что может повлиять на воспринимаемое качество изображения. Если этот параметр не включен, изображения будут загружаться без изменений.",
|
||||
@@ -425,7 +437,7 @@
|
||||
"target_note_title": "Отношение — это именованная связь между исходной и целевой заметками.",
|
||||
"promoted_title": "Выделенный атрибут отображается в заметке явно.",
|
||||
"promoted": "Выделенный",
|
||||
"promoted_alias_title": "Название, которое будет отображаться в интерфейсе выделенных атрибутов.",
|
||||
"promoted_alias_title": "Название, которое будет отображаться в интерфейсе продвигаемых атрибутов.",
|
||||
"multiplicity_title": "Множественность определяет, сколько атрибутов с одним и тем же именем можно создать — максимум 1 или более 1.",
|
||||
"label_type_title": "Тип метки поможет Trilium выбрать подходящий интерфейс для ввода значения метки.",
|
||||
"precision_title": "Какое количество цифр после плавающей запятой должно быть доступно в интерфейсе настройки значения.",
|
||||
@@ -446,10 +458,10 @@
|
||||
"run_at_hour": "В какой час это должно выполняться? Следует использовать вместе с <code>#run=hourly</code>. Можно задать несколько раз для большего количества запусков в течение дня.",
|
||||
"disable_inclusion": "скрипты с этой меткой не будут включены в выполнение родительского скрипта.",
|
||||
"sorted": "сохраняет алфавитную сортировку дочерних заметок",
|
||||
"sort_direction": "ASC (по умолчанию) или DESC",
|
||||
"sort_direction": "ASC (по возрастани, по умолчанию) или DESC (по убыванию)",
|
||||
"sort_folders_first": "Папки (заметки, включая дочерние) должны быть отсортированы вверх",
|
||||
"top": "закрепить заданную заметку наверху в ее родителе (применяется только к отсортированным родительским заметкам)",
|
||||
"hide_promoted_attributes": "Скрыть выделенные атрибуты в этой заметке",
|
||||
"hide_promoted_attributes": "Скрыть продвигаемых атрибуты в этой заметке",
|
||||
"read_only": "редактор находится в режиме только для чтения. Работает только с текстом и заметками типа \"код\".",
|
||||
"auto_read_only_disabled": "текстовые/заметки с кодом могут автоматически переводиться в режим чтения, если они слишком большие. Вы можете отключить это поведение для каждой заметки, добавив к ней соответствующую метку",
|
||||
"app_css": "отмечает заметки CSS, которые загружаются в приложение Trilium и, таким образом, могут использоваться для изменения внешнего вида Trilium.",
|
||||
@@ -461,10 +473,64 @@
|
||||
"workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если она будет перемещена в рабочую область, содержащую этот шаблон",
|
||||
"workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки при перемещении их к какому-либо предку этой заметки рабочей области",
|
||||
"workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства",
|
||||
"hide_highlight_widget": "Скрыть виджет «Список выделенного»",
|
||||
"hide_highlight_widget": "Скрыть виджет «Выделенное»",
|
||||
"is_owned_by_note": "принадлежит записке",
|
||||
"and_more": "... и ещё {{count}}.",
|
||||
"app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium."
|
||||
"app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium.",
|
||||
"title_template": "Заголовок по умолчанию для заметок, создаваемых как дочерние элементы данной заметки. Значение вычисляется как строка JavaScript\n и, таким образом, может быть дополнено динамическим контентом с помощью внедренных переменных <code>now</code> и <code>parentNote</code>. Примеры:\n \n <ul>\n <li><code>Литературные произведения ${parentNote.getLabelValue('authorName')}</code></li>\n <li><code>Лог для ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Подробности см. в <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">вики</a>, документации API для <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> и <a href=\"https://day.js.org/docs/en/display/format\">now</a>.",
|
||||
"icon_class": "значение этой метки добавляется в виде CSS-класса к значку в дереве, что помогает визуально различать заметки в дереве. Примером может служить bx bx-home — значки берутся из boxicons. Может использоваться в шаблонах заметок.",
|
||||
"share_favicon": "Заметка о фавиконе должна быть размещена на странице общего доступа. Обычно её назначают корневой папке общего доступа и делают наследуемой. Заметка о фавиконе также должна находиться в поддереве общего доступа. Рассмотрите возможность использования атрибута 'share_hidden_from_tree'.",
|
||||
"inbox": "расположение папки «Входящие» по умолчанию для новых заметок — при создании заметки с помощью кнопки «Новая заметка» на боковой панели заметки будут созданы как дочерние заметки в заметке, помеченной меткой <code>#inbox</code>.",
|
||||
"share_css": "CSS-заметка, которая будет добавлена на страницу общего доступа. CSS-заметка также должна находиться в общем поддереве. Также рассмотрите возможность использования 'share_hidden_from_tree' и 'share_omit_default_css'.",
|
||||
"run_on_branch_deletion": "выполняется при удалении ветви. Ветка — это связь между родительской и дочерней заметками и удаляется, например, при перемещении заметки (старая ветвь/ссылка удаляется).",
|
||||
"share_template": "Встроенная заметка JavaScript, которая будет использоваться в качестве шаблона для отображения общей заметки. Возвращается к шаблону по умолчанию. Рекомендуется использовать 'share_hidden_from_tree'.",
|
||||
"print_page_size": "При экспорте в PDF изменяет размер страницы. Поддерживаемые значения: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
|
||||
"keyboard_shortcut": "Определяет сочетание клавиш для немедленного перехода к этой заметке. Пример: Ctrl+Alt+E. Для вступления изменений в силу требуется перезагрузка интерфейса.",
|
||||
"new_notes_on_top": "Новые заметки будут создаваться вверху родительской заметки, а не внизу.",
|
||||
"print_landscape": "При экспорте в PDF изменяет ориентацию страницы с книжной на альбомную.",
|
||||
"hide_relations": "имена отношений, которые следует скрыть, разделённые запятыми. Все остальные будут отображены.",
|
||||
"run_on_note_change": "выполняется при изменении заметки (включая создание заметки). Не включает изменения содержимого",
|
||||
"display_relations": "названия отношений, разделённые запятыми, которые следует отобразить. Все остальные будут скрыты.",
|
||||
"template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки",
|
||||
"execute_button": "Название кнопки, которая выполнит текущую заметку типа \"Код\"",
|
||||
"page_size": "количество элементов на странице в списке заметок",
|
||||
"custom_request_handler": "см. <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Пользовательский обработчик запросов</a>",
|
||||
"custom_resource_provider": "см. <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Пользовательский обработчик запросов</a>",
|
||||
"widget": "отмечает эту заметку как пользовательский виджет, который будет добавлен в дерево компонентов Trilium",
|
||||
"search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки",
|
||||
"workspace_inbox": "расположение в папке «Входящие» по умолчанию для новых заметок при перемещении их в некую родственную папку этой заметки в рабочей области",
|
||||
"sql_console_home": "расположение заметок консоли SQL по умолчанию",
|
||||
"css_class": "значение этой метки затем добавляется как CSS-класс к узлу, представляющему данную заметку в дереве. Это может быть полезно для изменения внешнего вида заметки. Может использоваться в шаблонах заметок.",
|
||||
"bookmark_folder": "заметка с этой меткой появится в закладках как папка (с предоставлением доступа к ее дочерним элементам)",
|
||||
"share_hidden_from_tree": "эта заметка скрыта в левом навигационном дереве, но по-прежнему доступна по ее URL-адресу",
|
||||
"share_external_link": "заметка будет действовать как ссылка на внешний веб-сайт в дереве общего доступа",
|
||||
"share_alias": "определить псевдоним, с помощью которого заметка будет доступна по адресу https://ссылка_на_ваш_trilium/share/[ваш_псевдоним]",
|
||||
"share_omit_default_css": "CSS-код страницы общего доступа по умолчанию будет пропущен. Используйте его при внесении существенных изменений в стили.",
|
||||
"share_root": "помечает заметку, которая будет выступать корневой страницей /share общедоступного сайта.",
|
||||
"share_description": "определение текста, который будет добавлен в HTML-тег meta для описания",
|
||||
"share_raw": "заметка будет передана в исходном виде, без HTML-обертки",
|
||||
"share_disallow_robot_indexing": "запретит индексацию этой заметки роботами через заголовок <code>X-Robots-Tag: noindex</code>",
|
||||
"share_credentials": "для доступа к этой общедоступной заметке требуются учётные данные. Значение должно быть в формате 'имя пользователя:пароль'. Не забудьте сделать этот атрибут наследуемым для применения к дочерним заметкам/изображениям.",
|
||||
"share_index": "заметка с этой меткой будет содержать список всех корневых узлов общедоступных заметок",
|
||||
"toc": "<code>#toc</code> или <code>#toc=show</code> принудительно отобразят оглавление, <code>#toc=hide</code> — скроют его. Если метка отсутствует, применяется глобальная настройка",
|
||||
"color": "определяет цвет заметки в дереве заметок, ссылках и т. д. Используйте любое допустимое значение цвета CSS, например «red» или #a13d5f",
|
||||
"keep_current_hoisting": "Открытие этой ссылки не изменит закрепление, даже если заметка не отображается в текущем закрепленном поддереве.",
|
||||
"execute_description": "Более подробное описание текущей заметки типа \"Код\", отображаемое вместе с кнопкой \"Выполнить\"",
|
||||
"run_on_note_creation": "выполняется при создании заметки на сервере. Используйте это отношение, если хотите запустить скрипт для всех заметок, созданных в определённом поддереве. В этом случае создайте его в корневой заметке поддерева и сделайте его наследуемым. Новая заметка, созданная в поддереве (любой глубины), запустит скрипт.",
|
||||
"run_on_child_note_creation": "выполняется, когда создается новая заметка под заметкой, в которой определено это отношение",
|
||||
"run_on_note_title_change": "выполняется при изменении заголовка заметки (включая создание заметки)",
|
||||
"run_on_note_content_change": "выполняется при изменении содержимого заметки (включая создание заметки).",
|
||||
"run_on_note_deletion": "выполняется при удалении заметки",
|
||||
"run_on_branch_creation": "выполняется при создании ветви. Ветвь — это связующее звено между родительской и дочерней заметками и создаётся, например, при клонировании или перемещении заметки.",
|
||||
"run_on_branch_change": "выполняется при обновлении ветки.",
|
||||
"run_on_attribute_creation": "выполняется, когда создается новый атрибут для заметка, определяющей это отношение",
|
||||
"run_on_attribute_change": " выполняется при изменении атрибута заметки, определяющей это отношение. Также срабатывает при удалении атрибута",
|
||||
"relation_template": "атрибуты заметки будут унаследованы даже без родительско-дочерних отношений. Содержимое заметки и её поддерево будут добавлены к экземпляру заметки, если оно пустое. Подробности см. в документации.",
|
||||
"inherit": "атрибуты заметки будут унаследованы даже без родительско-дочерних отношений. См. описание шаблонных отношений для получения аналогичной информации. См. раздел «Наследование атрибутов» в документации.",
|
||||
"render_note": "заметки типа «Рендер HTML» будут отображаться с использованием кодовой заметки (HTML или скрипта), и необходимо указать с помощью этой связи, какую заметку следует отобразить",
|
||||
"widget_relation": "заметка, на которую ссылается отношение будет выполнена и отображена как виджет на боковой панели",
|
||||
"share_js": "JavaScript-заметка, которая будет добавлена на страницу общего доступа. JavaScript-заметка также должна находиться в общем поддереве. Рекомендуется использовать 'share_hidden_from_tree'.",
|
||||
"other_notes_with_name": "Другие заметки с {{attributeType}} названием \"{{attributeName}}\""
|
||||
},
|
||||
"command_palette": {
|
||||
"configure_launch_bar_description": "Откройте конфигурацию панели запуска, чтобы добавить или удалить элементы.",
|
||||
@@ -542,7 +608,8 @@
|
||||
"not_set": "Не установлен"
|
||||
},
|
||||
"time_selector": {
|
||||
"invalid_input": "Введенное значение времени не является допустимым числом."
|
||||
"invalid_input": "Введенное значение времени не является допустимым числом.",
|
||||
"minimum_input": "Введенное значение времени должно быть не менее {{minimumSeconds}} секунд."
|
||||
},
|
||||
"share": {
|
||||
"share_root_not_found": "Заметка с меткой #shareRoot не найдена",
|
||||
@@ -550,7 +617,10 @@
|
||||
"redirect_bare_domain_description": "Перенаправлять анонимных пользователей на страницу общедоступных заметок вместо отображения страницы входа",
|
||||
"show_login_link": "Показать ссылку для аутентификации в интерфейсе общедоступных заметок",
|
||||
"show_login_link_description": "Добавить ссылку для аутентификации в нижний колонтитул интерфейса общедоступных заметок",
|
||||
"title": "Настройки общего доступа"
|
||||
"title": "Настройки общего доступа",
|
||||
"check_share_root": "Проверка состояния корневой заметки для общедоступного сайта",
|
||||
"share_root_not_shared": "Заметка '{{noteTitle}}' имеет метку #shareRoot, но не является общедоступной",
|
||||
"share_root_found": "Заметка корня общедоступного сайта '{{noteTitle}}' готова"
|
||||
},
|
||||
"duration": {
|
||||
"days": "Дни",
|
||||
@@ -564,7 +634,9 @@
|
||||
"open-location": "Открыть местоположение"
|
||||
},
|
||||
"geo-map": {
|
||||
"unable-to-load-map": "Не удалось загрузить карту."
|
||||
"unable-to-load-map": "Не удалось загрузить карту.",
|
||||
"create-child-note-instruction": "Щелкните по карте, чтобы создать новую заметку в этом месте, или нажмите Escape, чтобы закрыть ее.",
|
||||
"create-child-note-title": "Создать новую дочернюю заметку и добавить ее на карту"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"quick-edit": "Быстрое редактирование",
|
||||
@@ -574,7 +646,9 @@
|
||||
"full-text-search": "Полнотекстовый поиск",
|
||||
"show-recent-notes": "Показать последние заметки",
|
||||
"search-for": "Поиск \"{{term}}\"",
|
||||
"clear-text-field": "Очистить текстовое поле"
|
||||
"clear-text-field": "Очистить текстовое поле",
|
||||
"insert-external-link": "Вставить внешнюю ссылку \"{{term}}\"",
|
||||
"create-note": "Создать и связать дочернюю заметку \"{{term}}\""
|
||||
},
|
||||
"electron_integration": {
|
||||
"zoom-factor": "Коэффициент масштабирования",
|
||||
@@ -592,7 +666,8 @@
|
||||
"open_note_in_new_split": "Открыть заметку в новой панели"
|
||||
},
|
||||
"image_context_menu": {
|
||||
"copy_image_to_clipboard": "Копировать изображение в буфер обмена"
|
||||
"copy_image_to_clipboard": "Копировать изображение в буфер обмена",
|
||||
"copy_reference_to_clipboard": "Скопировать ссылку в буфер обмена"
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"paste-as-plain-text": "Вставить как обычный текст",
|
||||
@@ -600,7 +675,8 @@
|
||||
"copy-link": "Скопировать ссылку",
|
||||
"copy": "Скопировать",
|
||||
"cut": "Вырезать",
|
||||
"search_online": "Поиск \"{{term}}\" в {{searchEngine}}"
|
||||
"search_online": "Поиск \"{{term}}\" в {{searchEngine}}",
|
||||
"add-term-to-dictionary": "Добавить \"{{term}}\" в словарь"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
@@ -631,7 +707,8 @@
|
||||
},
|
||||
"highlighting": {
|
||||
"color-scheme": "Цветовая схема",
|
||||
"title": "Блоки кода"
|
||||
"title": "Блоки кода",
|
||||
"description": "Управляет подсветкой синтаксиса для блоков кода внутри текстовых заметок. Заметки с типом \"Код\" не будут затронуты."
|
||||
},
|
||||
"editable-text": {
|
||||
"auto-detect-language": "Определен автоматически"
|
||||
@@ -642,7 +719,11 @@
|
||||
"add-custom-widget": "Добавить пользовательский виджет",
|
||||
"move-to-visible-launchers": "Переместить к видимым лаунчерам",
|
||||
"move-to-available-launchers": "Переместить к доступным лаунчерам",
|
||||
"delete": "Удалить <kbd data-command=\"deleteNotes\"></kbd>"
|
||||
"delete": "Удалить <kbd data-command=\"deleteNotes\"></kbd>",
|
||||
"add-note-launcher": "Добавить лаунчер заметки",
|
||||
"add-script-launcher": "Добавить лаунчер скрипта",
|
||||
"duplicate-launcher": "Создать копию лаунчера <kbd data-command=\"duplicateSubtree\">",
|
||||
"reset_launcher_confirm": "Вы действительно хотите сбросить \"{{title}}\"? Все данные/настройки в этой заметке (и её дочерних заметках) будут потеряны, а панель запуска будет возвращена в исходное местоположение."
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Оглавление",
|
||||
@@ -660,13 +741,15 @@
|
||||
"create-child-note": "Создать дочернюю заметку",
|
||||
"save-changes": "Сохранить и применить изменения",
|
||||
"saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.",
|
||||
"refresh-saved-search-results": "Обновить сохраненные результаты поиска"
|
||||
"refresh-saved-search-results": "Обновить сохраненные результаты поиска",
|
||||
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве."
|
||||
},
|
||||
"quick-search": {
|
||||
"no-results": "Результаты не найдены",
|
||||
"placeholder": "Быстрый поиск",
|
||||
"searching": "Поиск...",
|
||||
"show-in-full-search": "Расширенный поиск"
|
||||
"show-in-full-search": "Расширенный поиск",
|
||||
"more-results": "... и еще {{number}} результатов."
|
||||
},
|
||||
"find": {
|
||||
"replace_all": "Заменить все",
|
||||
@@ -678,7 +761,8 @@
|
||||
},
|
||||
"template_switch": {
|
||||
"template": "Шаблон",
|
||||
"toggle-off-hint": "Удалить заметку как шаблон"
|
||||
"toggle-off-hint": "Удалить заметку как шаблон",
|
||||
"toggle-on-hint": "Сделать заметку шаблоном"
|
||||
},
|
||||
"note_types": {
|
||||
"collections": "Коллекции",
|
||||
@@ -702,7 +786,8 @@
|
||||
"mind-map": "Mind Map",
|
||||
"geo-map": "Географическая карта",
|
||||
"ai-chat": "ИИ Чат",
|
||||
"task-list": "Список задач"
|
||||
"task-list": "Список задач",
|
||||
"confirm-change": "Не рекомендуется менять тип заметки, если её содержимое не пустое. Вы всё равно хотите продолжить?"
|
||||
},
|
||||
"tree-context-menu": {
|
||||
"open-in-popup": "Быстрое редактирование",
|
||||
@@ -732,7 +817,10 @@
|
||||
"edit-branch-prefix": "Изменить префикс ветки",
|
||||
"convert-to-attachment": "Преобразовать в приложение",
|
||||
"apply-bulk-actions": "Применить массовые действия",
|
||||
"recent-changes-in-subtree": "Последние изменения в поддереве"
|
||||
"recent-changes-in-subtree": "Последние изменения в поддереве",
|
||||
"copy-note-path-to-clipboard": "Копировать путь к заметке в буфер обмена",
|
||||
"convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок?",
|
||||
"converted-to-attachments": "{{count}} заметок были преобразованы во вложения."
|
||||
},
|
||||
"info": {
|
||||
"closeButton": "Закрыть",
|
||||
@@ -770,14 +858,17 @@
|
||||
"to": "в",
|
||||
"add_relation": "Добавить отношение",
|
||||
"relation_name": "название отношения",
|
||||
"target_note": "целевая заметка"
|
||||
"target_note": "целевая заметка",
|
||||
"allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.",
|
||||
"create_relation_on_all_matched_notes": "Для всех соответствующих заметок создать заданную связь."
|
||||
},
|
||||
"rename_relation": {
|
||||
"to": "В",
|
||||
"rename_relation": "Переименовать отношение",
|
||||
"old_name": "старое наименование",
|
||||
"new_name": "новое наименование",
|
||||
"rename_relation_from": "Переименовать отношение из"
|
||||
"rename_relation_from": "Переименовать отношение из",
|
||||
"allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие."
|
||||
},
|
||||
"update_relation_target": {
|
||||
"to": "в",
|
||||
@@ -785,7 +876,9 @@
|
||||
"relation_name": "название отношения",
|
||||
"target_note": "целевая заметка",
|
||||
"update_relation_target": "Обновить целевой элемент отношения",
|
||||
"on_all_matched_notes": "На всех совпадающих заметках"
|
||||
"on_all_matched_notes": "На всех совпадающих заметках",
|
||||
"allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.",
|
||||
"change_target_note": "изменить целевую заметку существующего отношения"
|
||||
},
|
||||
"attachments_actions": {
|
||||
"download": "Скачать",
|
||||
@@ -799,7 +892,14 @@
|
||||
"open_custom_title": "Файл будет открыт во внешнем приложении и отслеживаться на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.",
|
||||
"open_externally_title": "Файл будет открыт во внешнем приложении и отслеживаться на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.",
|
||||
"copy_link_to_clipboard": "Копировать ссылку в буфер обмена",
|
||||
"convert_attachment_into_note": "Преобразовать вложение в заметку"
|
||||
"convert_attachment_into_note": "Преобразовать вложение в заметку",
|
||||
"delete_success": "Вложение \"{{title}}\" удалено.",
|
||||
"enter_new_name": "Введите новое название вложения",
|
||||
"upload_success": "Загружена новая версия вложения.",
|
||||
"upload_failed": "Не удалось загрузить новую версию вложения.",
|
||||
"delete_confirm": "Вы уверены, что хотите удалить вложение '{{title}}'?",
|
||||
"convert_confirm": "Вы уверены, что хотите преобразовать вложение '{{title}}' в отдельную заметку?",
|
||||
"convert_success": "Вложение '{{title}}' преобразовано в заметку."
|
||||
},
|
||||
"calendar": {
|
||||
"mon": "Пн",
|
||||
@@ -867,7 +967,7 @@
|
||||
"editable": "Изменяемое",
|
||||
"language": "Язык",
|
||||
"note_type": "Тип",
|
||||
"basic_properties": "Общие параметры"
|
||||
"basic_properties": "Общее"
|
||||
},
|
||||
"book_properties": {
|
||||
"grid": "Сетка",
|
||||
@@ -886,8 +986,8 @@
|
||||
},
|
||||
"edited_notes": {
|
||||
"deleted": "(удалено)",
|
||||
"title": "Отредактированные заметки",
|
||||
"no_edited_notes_found": "Пока нет отредактированных заметок за этот день..."
|
||||
"title": "Измененные заметки",
|
||||
"no_edited_notes_found": "Пока нет измененных заметок за этот день..."
|
||||
},
|
||||
"file_properties": {
|
||||
"download": "Скачать",
|
||||
@@ -898,7 +998,8 @@
|
||||
"file_size": "Размер файла",
|
||||
"file_type": "Тип файла",
|
||||
"original_file_name": "Исходное имя файла",
|
||||
"note_id": "ID заметки"
|
||||
"note_id": "ID заметки",
|
||||
"upload_failed": "Загрузка новой версии файла не удалась."
|
||||
},
|
||||
"image_properties": {
|
||||
"download": "Скачать",
|
||||
@@ -919,7 +1020,9 @@
|
||||
"note_id": "ID заметки",
|
||||
"note_size": "Размер заметки",
|
||||
"title": "Информация",
|
||||
"calculate": "подсчитать"
|
||||
"calculate": "подсчитать",
|
||||
"note_size_info": "Размер заметки позволяет приблизительно оценить требования к объёму хранилища для данной заметки. Он учитывает её содержание и содержание её сохраненных версий.",
|
||||
"subtree_size": "(размер поддерева: {{size}} в {{count}} заметках)"
|
||||
},
|
||||
"note_paths": {
|
||||
"search": "Поиск",
|
||||
@@ -927,11 +1030,12 @@
|
||||
"clone_button": "Клонировать заметку в новое место...",
|
||||
"intro_placed": "Эта заметка размещена по следующим путям:",
|
||||
"intro_not_placed": "Эта заметка еще не помещена в дерево заметок.",
|
||||
"outside_hoisted": "Этот путь находится за пределами выделенный заметки, и вам придется снять выделение.",
|
||||
"outside_hoisted": "Этот путь находится за пределами закрепленной заметки, и вам придется снять закрепление.",
|
||||
"archived": "Архивировано"
|
||||
},
|
||||
"note_properties": {
|
||||
"info": "Информация"
|
||||
"info": "Информация",
|
||||
"this_note_was_originally_taken_from": "Эта заметка была первоначально взята из:"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
"url_placeholder": "http://website...",
|
||||
@@ -989,7 +1093,8 @@
|
||||
"access_info": "Чтобы получить доступ к отладочной информации, выполните запрос и нажмите «Показать лог бэкенда» в левом верхнем углу."
|
||||
},
|
||||
"limit": {
|
||||
"limit": "Ограничение"
|
||||
"limit": "Ограничение",
|
||||
"take_first_x_results": "Взять только первые X указанных результатов."
|
||||
},
|
||||
"order_by": {
|
||||
"title": "Названию",
|
||||
@@ -1005,7 +1110,10 @@
|
||||
"owned_label_count": "Количество меток",
|
||||
"owned_relation_count": "Количество отношений",
|
||||
"date_modified": "Дата последнего изменения",
|
||||
"children_count": "Количество дочерних заметок"
|
||||
"children_count": "Количество дочерних заметок",
|
||||
"content_and_attachments_size": "Размер содержимого заметки, включая вложения",
|
||||
"content_and_attachments_and_revisions_size": "Размер содержимого заметки, включая вложения и версии.",
|
||||
"target_relation_count": "Количество отношений, направленных на заметку"
|
||||
},
|
||||
"search_string": {
|
||||
"search_prefix": "Поиск:",
|
||||
@@ -1020,7 +1128,8 @@
|
||||
"label_rock_or_pop": "должна присутствовать только одна из vtnjr",
|
||||
"label_year_comparison": "числовое сравнение (также >, >=, <).",
|
||||
"label_date_created": "заметки, созданные за последний месяц",
|
||||
"error": "Ошибка поиска: {{error}}"
|
||||
"error": "Ошибка поиска: {{error}}",
|
||||
"placeholder": "полнотекстовые ключевые слова, #tag = value..."
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Обновить"
|
||||
@@ -1032,7 +1141,8 @@
|
||||
"full_sync_triggered": "Полная синхронизация запущена",
|
||||
"finished-successfully": "Синхронизация успешно завершена.",
|
||||
"failed": "Синхронизация не удалась: {{message}}",
|
||||
"sync_rows_filled_successfully": "Строки синхронизации успешно заполнены"
|
||||
"sync_rows_filled_successfully": "Строки синхронизации успешно заполнены",
|
||||
"filling_entity_changes": "Заполнение строк изменений сущностей..."
|
||||
},
|
||||
"fonts": {
|
||||
"fonts": "Шрифты",
|
||||
@@ -1052,7 +1162,10 @@
|
||||
"sans-serif-system-fonts": "Системные шрифты без засечек",
|
||||
"serif-system-fonts": "Системные шрифты с засечками",
|
||||
"monospace-system-fonts": "Моноширинные системные шрифты",
|
||||
"handwriting-system-fonts": "Шрифты системы рукописного ввода"
|
||||
"handwriting-system-fonts": "Шрифты системы рукописного ввода",
|
||||
"note_tree_and_detail_font_sizing": "Обратите внимание, что размер шрифта дерева и детальной страницы зависит от настройки размера основного шрифта.",
|
||||
"apply_font_changes": "Чтобы применить изменения шрифта, нажмите",
|
||||
"not_all_fonts_available": "Не все перечисленные шрифты могут быть доступны в вашей системе."
|
||||
},
|
||||
"max_content_width": {
|
||||
"max_width_unit": "пикселей",
|
||||
@@ -1129,13 +1242,14 @@
|
||||
"index_status": "Статус индексирования",
|
||||
"indexed_notes": "Проиндексированные заметки",
|
||||
"indexing_stopped": "Индексирование остановлено",
|
||||
"last_indexed": "Последние проиндексированные",
|
||||
"last_indexed": "Индексировано в последний раз",
|
||||
"note_chat": "Чат по заметке",
|
||||
"start_indexing": "Начать индексирование",
|
||||
"chat": {
|
||||
"root_note_title": "Чаты с AI",
|
||||
"new_chat_title": "Новый чат",
|
||||
"create_new_ai_chat": "Создать новый чат с ИИ"
|
||||
"create_new_ai_chat": "Создать новый чат с ИИ",
|
||||
"root_note_content": "В этой заметке содержатся сохраненные вами разговоры в чате ИИ."
|
||||
},
|
||||
"selected_provider": "Выбранный провайдер",
|
||||
"select_model": "Выбрать модель...",
|
||||
@@ -1151,7 +1265,10 @@
|
||||
"temperature_description": "Контролирует случайность ответов (0 = детерминированный, 2 = максимальная случайность)",
|
||||
"system_prompt_description": "Системный промпт по умолчанию, используемый для всех взаимодействий с ИИ",
|
||||
"empty_key_warning": {
|
||||
"openai": "Ключ API OpenAI пуст. Введите действительный ключ API."
|
||||
"openai": "Ключ API OpenAI пуст. Введите действительный ключ API.",
|
||||
"ollama": "API-ключ Ollama пуст. Введите действительный API-ключ.",
|
||||
"voyage": "Ключ API Voyage пуст. Введите действительный ключ API.",
|
||||
"anthropic": "Ключ API Anthropic пуст. Введите действительный ключ API."
|
||||
},
|
||||
"openai_api_key_description": "Ваш ключ API OpenAI для доступа к их службам ИИ",
|
||||
"provider_precedence_description": "Список провайдеров, разделенных запятыми, в порядке приоритета (например, \"openai,anthropic,ollama\")",
|
||||
@@ -1181,7 +1298,37 @@
|
||||
"failed_to_retry_note": "Не удалось повторить попытку",
|
||||
"failed_to_retry_all": "Не удалось повторить попытку",
|
||||
"error_generating_response": "Ошибка генерации ответа ИИ",
|
||||
"create_new_ai_chat": "Создать новый чат с ИИ"
|
||||
"create_new_ai_chat": "Создать новый чат с ИИ",
|
||||
"ai_enabled": "Возможности ИИ активны",
|
||||
"ai_disabled": "Возможности ИИ неактивны",
|
||||
"restore_provider": "Восстановить значение провайдера",
|
||||
"error_fetching": "Ошибка получения списка моделей: {{error}}",
|
||||
"index_rebuild_status_error": "Ошибка проверки статуса перестроения индекса",
|
||||
"enhanced_context_description": "Предоставляет ИИ больше контекста из заметки и связанных с ней заметок для более точных ответов",
|
||||
"n_notes_queued_0": "{{ count }} заметка в очереди на индексирование",
|
||||
"n_notes_queued_1": "{{ count }} заметки в очереди на индексирование",
|
||||
"n_notes_queued_2": "{{ count }} заметок в очереди на индексирование",
|
||||
"no_models_found_ollama": "Модели Ollama не найдены. Проверьте, запущена ли Ollama.",
|
||||
"no_models_found_online": "Модели не найдены. Проверьте ваш ключ API и настройки.",
|
||||
"experimental_warning": "Функция LLM в настоящее время является экспериментальной — вы предупреждены.",
|
||||
"ollama_no_url": "Ollama не настроена. Введите корректный URL-адрес.",
|
||||
"notes_indexed_0": "{{ count }} заметка проиндексирована",
|
||||
"notes_indexed_1": "{{ count }} заметки проиндексировано",
|
||||
"notes_indexed_2": "{{ count }} заметок проиндексировано",
|
||||
"show_thinking_description": "Показать цепочку мыслительного процесса ИИ",
|
||||
"api_key_tooltip": "API-ключ для доступа к сервису",
|
||||
"all_notes_queued_for_retry": "Все неудачные заметки поставлены в очередь на повторную попытку",
|
||||
"reprocess_index_started": "Оптимизация поискового индекса запущена в фоновом режиме",
|
||||
"similarity_threshold_description": "Минимальный показатель сходства (similarity score, 0–1) для заметок, которые следует включить в контекст запросов LLM",
|
||||
"max_notes_per_llm_query_description": "Максимальное количество похожих заметок для включения в контекст ИИ",
|
||||
"retry_failed": "Не удалось поставить заметку в очередь для повторной попытки",
|
||||
"rebuild_index_error": "Ошибка при запуске перестроения индекса. Подробности смотрите в логах.",
|
||||
"enable_ollama_description": "Включить Ollama для использования локальной модели ИИ",
|
||||
"anthropic_model_description": "Модели Anthropic Claude для автодополнения чата",
|
||||
"anthropic_url_description": "Базовый URL для Anthropic API (по умолчанию: https://api.anthropic.com)",
|
||||
"anthropic_api_key_description": "Ваш ключ Anthropic API для доступа к моделям Claude",
|
||||
"enable_ai_desc": "Включить функции ИИ, такие как резюмирование заметок, генерация контента и другие возможности LLM",
|
||||
"enable_ai_description": "Включить функции ИИ, такие как резюмирование заметок, генерация контента и другие возможности LLM"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "Редактор"
|
||||
@@ -1245,7 +1392,7 @@
|
||||
},
|
||||
"backup": {
|
||||
"path": "Путь",
|
||||
"backup_now": "Резервное копирование сейчас",
|
||||
"backup_now": "Принудительное резервное копирование",
|
||||
"existing_backups": "Существующие резервные копии",
|
||||
"automatic_backup": "Автоматическое резервное копирование",
|
||||
"automatic_backup_description": "Trilium может автоматически создавать резервную копию базы данных:",
|
||||
@@ -1254,7 +1401,9 @@
|
||||
"enable_monthly_backup": "Включить ежемесячное резервное копирование",
|
||||
"backup_database_now": "Создать резервную копию",
|
||||
"date-and-time": "Дата и время",
|
||||
"no_backup_yet": "нет резервных копий"
|
||||
"no_backup_yet": "нет резервных копий",
|
||||
"database_backed_up_to": "Резервная копия базы данных создана в {{backupFilePath}}",
|
||||
"backup_recommendation": "Рекомендуется держать резервное копирование включенным, но это может замедлить запуск приложений при использовании больших баз данных и/или медленных устройств хранения."
|
||||
},
|
||||
"etapi": {
|
||||
"title": "ETAPI",
|
||||
@@ -1272,7 +1421,14 @@
|
||||
"swagger_ui": "Пользовательский интерфейс ETAPI Swagger",
|
||||
"new_token_title": "Новый токен ETAPI",
|
||||
"token_created_title": "Создан токен ETAPI",
|
||||
"rename_token": "Переименовать этот токен"
|
||||
"rename_token": "Переименовать этот токен",
|
||||
"new_token_message": "Введите название нового токена",
|
||||
"error_empty_name": "Имя токена не может быть пустым",
|
||||
"delete_token": "Удалить/деактивировать этот токен",
|
||||
"rename_token_message": "Пожалуйста, введите имя нового токена",
|
||||
"token_created_message": "Скопируйте созданный токен в буфер обмена. Trilium сохранит хеш токена, и вы его больше не сможете увидеть.",
|
||||
"delete_token_confirmation": "Вы уверены, что хотите удалить токен ETAPI \"{{name}}\"?",
|
||||
"no_tokens_yet": "Токенов пока нет. Нажмите кнопку выше, чтобы создать токен."
|
||||
},
|
||||
"multi_factor_authentication": {
|
||||
"oauth_title": "OAuth/OpenID",
|
||||
@@ -1296,7 +1452,16 @@
|
||||
"totp_title": "Одноразовый пароль с ограничением по времени (TOTP)",
|
||||
"recovery_keys_title": "Ключи восстановления единого входа",
|
||||
"recovery_keys_error": "Ошибка генерации кодов восстановления",
|
||||
"recovery_keys_no_key_set": "Коды восстановления не установлены"
|
||||
"recovery_keys_no_key_set": "Коды восстановления не установлены",
|
||||
"recovery_keys_unused": "Код восстановления {{index}} не используется",
|
||||
"description": "Многофакторная аутентификация (MFA) добавляет дополнительный уровень безопасности вашей учётной записи. Вместо простого ввода пароля для входа MFA требует предоставить один или несколько дополнительных документов для подтверждения вашей личности. Таким образом, даже если кто-то узнает ваш пароль, он всё равно не сможет получить доступ к вашей учётной записи без второго элемента данных. Это похоже на установку дополнительного замка на вашу дверь, значительно усложняя взлом.<br><br>Следуйте инструкциям ниже, чтобы включить MFA. Если вы настроите её неправильно, для входа будет использоваться только пароль.",
|
||||
"totp_description": "TOTP (одноразовый пароль с ограничением по времени) — это функция безопасности, которая генерирует уникальный временный код, который меняется каждые 30 секунд. Вы используете этот код вместе с паролем для входа в свою учётную запись, что значительно затрудняет доступ к ней посторонним лицам.",
|
||||
"recovery_keys_description_warning": "Ключи восстановления больше не будут отображаться после выхода со страницы, сохраните их в надежном и безопасном месте.<br>После использования ключа восстановления его нельзя будет использовать повторно.",
|
||||
"totp_secret_regenerate_confirm": "Вы уверены, что хотите восстановить секрет TOTP? Это аннулирует предыдущий секрет TOTP и все существующие коды восстановления.",
|
||||
"totp_secret_description_warning": "После создания нового секрета TOTP вам потребуется снова войти в систему, используя новый секрет TOTP.",
|
||||
"recovery_keys_description": "Ключи восстановления единого входа используются для входа в систему, даже если вы не можете получить доступ к своим кодам аутентификатора.",
|
||||
"totp_secret_warning": "Сохраните сгенерированный секретный ключ в безопасном месте. Он больше не будет показан.",
|
||||
"no_totp_secret_warning": "Чтобы включить TOTP, вам сначала нужно сгенерировать секрет TOTP."
|
||||
},
|
||||
"shortcuts": {
|
||||
"shortcuts": "Сочетания клавиш",
|
||||
@@ -1305,7 +1470,11 @@
|
||||
"action_name": "Название действия",
|
||||
"default_shortcuts": "Сочетания клавиш по умолчанию",
|
||||
"multiple_shortcuts": "Несколько сочетаний клавиш для одного и того же действия можно разделить запятой.",
|
||||
"electron_documentation": "Информацию о доступных модификаторах и кодах клавиш см. в <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\">документации Electron</a>."
|
||||
"electron_documentation": "Информацию о доступных модификаторах и кодах клавиш см. в <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\">документации Electron</a>.",
|
||||
"type_text_to_filter": "Введите текст для фильтрации сочетаний клавиш...",
|
||||
"reload_app": "Перезагрузить приложение, чтобы применить изменения",
|
||||
"confirm_reset": "Вы действительно хотите сбросить все сочетания клавиш до значений по умолчанию?",
|
||||
"set_all_to_default": "Установить все сочетания клавиш по умолчанию"
|
||||
},
|
||||
"sync_2": {
|
||||
"timeout_unit": "миллисекунд",
|
||||
@@ -1320,28 +1489,35 @@
|
||||
"timeout": "Тайм-аут синхронизации",
|
||||
"test_description": "Это проверит подключение и подтверждение связи с сервером синхронизации. Если сервер синхронизации не инициализирован, он будет настроен на синхронизацию с локальным документом.",
|
||||
"test_title": "Тест синхронизации",
|
||||
"test_button": "Проверка синхронизации"
|
||||
"test_button": "Проверка синхронизации",
|
||||
"handshake_failed": "Синхронизация с сервером не удалась, ошибка: {{message}}"
|
||||
},
|
||||
"api_log": {
|
||||
"close": "Закрыть"
|
||||
},
|
||||
"bookmark_switch": {
|
||||
"bookmark": "В закладки",
|
||||
"remove_bookmark": "Удалить закладку"
|
||||
"remove_bookmark": "Удалить закладку",
|
||||
"bookmark_this_note": "Добавить эту заметку в закладки на левой боковой панели"
|
||||
},
|
||||
"editability_select": {
|
||||
"auto": "Авто",
|
||||
"read_only": "Только для чтения",
|
||||
"always_editable": "Всегда доступно для редактирования"
|
||||
"always_editable": "Всегда доступно для редактирования",
|
||||
"note_is_always_editable": "Заметку всегда можно редактировать, независимо от ее длины.",
|
||||
"note_is_read_only": "Заметка доступна только для чтения, но ее можно редактировать одним нажатием кнопки.",
|
||||
"note_is_editable": "Заметку можно редактировать, если она не слишком длинная."
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "Общий доступ",
|
||||
"toggle-on-title": "Сделать заметку общедоступной",
|
||||
"toggle-off-title": "Отменить общий доступ к заметке"
|
||||
"toggle-off-title": "Отменить общий доступ к заметке",
|
||||
"shared-branch": "Эта заметка существует только как общая, и если отменить общий доступ, она будет удалена. Хотите продолжить и удалить эту заметку?",
|
||||
"inherited": "Заметка не может быть убрана из общего доступа в данном случае, поскольку общий доступ передан по наследству от предка."
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"options": "Параметры",
|
||||
"title": "Список выделений"
|
||||
"title": "Список выделенного"
|
||||
},
|
||||
"include_note": {
|
||||
"dialog_title": "Вставить заметку",
|
||||
@@ -1354,26 +1530,42 @@
|
||||
"box_size_prompt": "Размер рамки вставленной заметки:"
|
||||
},
|
||||
"execute_script": {
|
||||
"execute_script": "Выполнить скрипт"
|
||||
"execute_script": "Выполнить скрипт",
|
||||
"help_text": "Вы можете выполнять простые скрипты на соответствующих заметках.",
|
||||
"example_1": "Например, чтобы добавить строку к заголовку заметки, используйте этот небольшой скрипт:",
|
||||
"example_2": "Более сложным примером будет удаление всех соответствующих атрибутов заметки:"
|
||||
},
|
||||
"update_label_value": {
|
||||
"label_name_placeholder": "название метки",
|
||||
"new_value_placeholder": "новое значение",
|
||||
"update_label_value": "Обновить значение метки",
|
||||
"to_value": "на значение"
|
||||
"to_value": "на значение",
|
||||
"help_text_note": "Вы также можете вызвать этот метод без значения, в таком случае метка будет присвоена заметке без значения.",
|
||||
"label_name_title": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.",
|
||||
"help_text": "На всех соответствующих заметках изменить значение существующей метки."
|
||||
},
|
||||
"delete_note": {
|
||||
"delete_note": "Удалить заметку",
|
||||
"delete_matched_notes": "Удалить совпадающие заметки"
|
||||
"delete_matched_notes": "Удалить совпадающие заметки",
|
||||
"delete_matched_notes_description": "Это приведет к удалению соответствующих заметок.",
|
||||
"erase_notes_instruction": "Чтобы навсегда стереть заметки, после удаления перейдите в раздел «Параметры» -> «Другие» и нажмите кнопку «Стереть удаленные заметки сейчас».",
|
||||
"undelete_notes_instruction": "После удаления их можно восстановить из диалогового окна «Последние изменения»."
|
||||
},
|
||||
"rename_note": {
|
||||
"rename_note": "Переименовать заметку",
|
||||
"new_note_title": "название новой заметки",
|
||||
"rename_note_title_to": "Переименовать заголовок заметки на"
|
||||
"rename_note_title_to": "Переименовать заголовок заметки на",
|
||||
"click_help_icon": "Нажмите значок справки справа, чтобы увидеть все параметры",
|
||||
"evaluated_as_js_string": "Указанное значение обрабатывается как строка JavaScript и, таким образом, может быть дополнено динамическим содержимым через внедренную переменную <code>note</code> (при этом заметка переименовывается). Примеры:",
|
||||
"example_note": "<code>Note</code> — все соответствующие примечания переименовываются в \"Note\"",
|
||||
"example_new_title": "<code>NEW: ${note.title}</code> — заголовки соответствующих заметок начинаются с префикса \"NEW:\"",
|
||||
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> — соответствующие заметки имеют префикс в виде месяца и даты создания заметки",
|
||||
"api_docs": "Подробную информацию см. в документации API для <a href='https://zadam.github.io/trilium/backend_api/Note.html'>note</a> и его <a href='https://day.js.org/docs/en/display/format'>свойства dateCreatedObj / utcDateCreatedObj</a>."
|
||||
},
|
||||
"delete_relation": {
|
||||
"delete_relation": "Удалить отношение",
|
||||
"relation_name": "название отношения"
|
||||
"relation_name": "название отношения",
|
||||
"allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие."
|
||||
},
|
||||
"left_pane_toggle": {
|
||||
"show_panel": "Показать панель",
|
||||
@@ -1394,7 +1586,7 @@
|
||||
"save_revision": "Сохранить версию",
|
||||
"convert_into_attachment": "Конвертировать во вложение",
|
||||
"search_in_note": "Поиск в заметке",
|
||||
"print_pdf": "Экспорт в PDF",
|
||||
"print_pdf": "Экспорт в PDF...",
|
||||
"convert_into_attachment_prompt": "Вы уверены, что хотите преобразовать заметку '{{title}}' во вложение родительской заметки?",
|
||||
"convert_into_attachment_successful": "Примечание '{{title}}' преобразовано во вложение.",
|
||||
"convert_into_attachment_failed": "Не удалось преобразовать заметку '{{title}}'.",
|
||||
@@ -1457,7 +1649,9 @@
|
||||
},
|
||||
"attachment_list": {
|
||||
"upload_attachments": "Загрузка вложений",
|
||||
"owning_note": "Заметка-владелец: "
|
||||
"owning_note": "Заметка-владелец: ",
|
||||
"open_help_page": "Открыть справку по вложениям",
|
||||
"no_attachments": "Заметка не содержит вложений."
|
||||
},
|
||||
"protected_session": {
|
||||
"wrong_password": "Неверный пароль.",
|
||||
@@ -1467,7 +1661,9 @@
|
||||
"unprotecting-finished-successfully": "Снятие защиты успешно завершено.",
|
||||
"start_session_button": "Начать защищенный сеанс <kbd>enter</kbd>",
|
||||
"protecting-in-progress": "Защита в процессе: {{count}}",
|
||||
"unprotecting-in-progress-count": "Снятие защиты в процессе: {{count}}"
|
||||
"unprotecting-in-progress-count": "Снятие защиты в процессе: {{count}}",
|
||||
"started": "Защищенный сеанс запущен.",
|
||||
"enter_password_instruction": "Для отображения защищенной заметки требуется ввести пароль:"
|
||||
},
|
||||
"relation_map": {
|
||||
"remove_note": "Удалить заметку",
|
||||
@@ -1479,7 +1675,13 @@
|
||||
"confirm_remove_relation": "Вы уверены, что хотите удалить отношение?",
|
||||
"enter_new_title": "Введите новое название заметки:",
|
||||
"note_not_found": "Заметка {{noteId}} не найдена!",
|
||||
"cannot_match_transform": "Невозможно сопоставить преобразование: {{transform}}"
|
||||
"cannot_match_transform": "Невозможно сопоставить преобразование: {{transform}}",
|
||||
"enter_title_of_new_note": "Введите название новой заметки",
|
||||
"click_on_canvas_to_place_new_note": "Щелкните по холсту, чтобы разместить новую заметку",
|
||||
"note_already_in_diagram": "Заметка \"{{title}}\" уже есть на диаграмме.",
|
||||
"connection_exists": "Связь '{{name}}' между этими заметками уже существует.",
|
||||
"specify_new_relation_name": "Укажите новое имя отношения (допустимые символы: буквы, цифры, двоеточие и подчеркивание):",
|
||||
"start_dragging_relations": "Начните перетягивать отношения отсюда на другую заметку."
|
||||
},
|
||||
"vacuum_database": {
|
||||
"title": "Сжатие базы данных",
|
||||
@@ -1501,22 +1703,22 @@
|
||||
"enable_tray": "Включить отображение иконки в системном трее (чтобы изменения вступили в силу, необходимо перезапустить Trilium)"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Список выделений",
|
||||
"title": "Список выделенного",
|
||||
"bold": "Жирный текст",
|
||||
"italic": "Наклонный текст",
|
||||
"underline": "Подчеркнутый текст",
|
||||
"color": "Цветной текст",
|
||||
"description": "Вы можете настроить список выделений, отображаемый на правой панели:",
|
||||
"description": "Вы можете настроить список выделенного, отображаемый на правой панели:",
|
||||
"bg_color": "Текст с заливкой фона",
|
||||
"visibility_title": "Видимость списка выделений",
|
||||
"visibility_description": "Вы можете скрыть виджет списка выделений, добавив атрибут #hideHighlightWidget к заметке.",
|
||||
"shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделений) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")."
|
||||
"visibility_description": "Вы можете скрыть виджет списка выделенного, добавив атрибут #hideHighlightWidget к заметке.",
|
||||
"shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделенного) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")."
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"format_string": "Строка форматирования:",
|
||||
"formatted_time": "Пример форматирования:",
|
||||
"title": "Пользовательский формат даты и времени",
|
||||
"description": "Настройте формат даты и времени, вставляемых с помощью <kbd>Alt+T</kbd> или панели инструментов. Доступные токены формата см. в <a href=\"https://day.js.org/docs/en/display/format\" target=\"_blank\" rel=\"noopener noreferrer\">документации Day.js</a>."
|
||||
"description": "Настройте формат даты и времени, вставляемых с помощью <shortcut /> или панели инструментов. Доступные токены форматирования см. в <doc>документации Day.js</doc>."
|
||||
},
|
||||
"spellcheck": {
|
||||
"title": "Проверка орфографии",
|
||||
@@ -1525,13 +1727,20 @@
|
||||
"multiple_languages_info": "Несколько языков можно разделять запятой, например, \"en-US, de-DE, cs\". ",
|
||||
"available_language_codes_label": "Доступные коды языков:",
|
||||
"restart-required": "Изменения параметров проверки орфографии вступят в силу после перезапуска приложения.",
|
||||
"language_code_placeholder": "например \"en-US\", \"de-AT\""
|
||||
"language_code_placeholder": "например \"en-US\", \"de-AT\"",
|
||||
"description": "Эти параметры применимы только для десктопных сборок, браузеры будут использовать собственную встроенную проверку орфографии."
|
||||
},
|
||||
"attribute_editor": {
|
||||
"save_attributes": "Сохранить атрибуты <enter>",
|
||||
"add_a_new_attribute": "Добавить новый атрибут",
|
||||
"add_new_label_definition": "Добавить новое определение метки",
|
||||
"add_new_relation_definition": "Добавить новое определение отношения"
|
||||
"add_new_relation_definition": "Добавить новое определение отношения",
|
||||
"add_new_label": "Добавить новую метку <kbd data-command=\"addNewLabel\"></kbd>",
|
||||
"add_new_relation": "Добавить новое отношение <kbd data-command=\"addNewRelation\"></kbd>",
|
||||
"help_text_body1": "Чтобы добавить метку, просто введите, например, <code>#rock</code> или, если вы хотите добавить также значение, то, например, <code>#year = 2020</code>",
|
||||
"help_text_body2": "Для отношения введите <code>~author = @</code>, после чего должно появиться окно автозаполнения, где вы сможете найти нужную заметку.",
|
||||
"help_text_body3": "В качестве альтернативы вы можете добавить метку и отношение, используя кнопку <code>+</code> с правой стороны.",
|
||||
"placeholder": "Введите здесь метки и отношения"
|
||||
},
|
||||
"delete_revisions": {
|
||||
"delete_note_revisions": "Удалить версии заметки",
|
||||
@@ -1547,7 +1756,7 @@
|
||||
"edit_this_note": "Редактировать заметку"
|
||||
},
|
||||
"show_highlights_list_widget_button": {
|
||||
"show_highlights_list": "Показать список выделений"
|
||||
"show_highlights_list": "Показать список выделенного"
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Покинуть режим \"дзен\""
|
||||
@@ -1587,7 +1796,9 @@
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Защитить заметку",
|
||||
"toggle-off": "Снять защиту с заметки"
|
||||
"toggle-off": "Снять защиту с заметки",
|
||||
"toggle-off-hint": "Заметка защищена. Щелкните, чтобы снять защиту",
|
||||
"toggle-on-hint": "Заметка не защищена. Щелкните, чтобы установить защиту"
|
||||
},
|
||||
"note-map": {
|
||||
"button-link-map": "Карта связей",
|
||||
@@ -1604,15 +1815,21 @@
|
||||
"consistency_checks": {
|
||||
"find_and_fix_button": "Найти и устранить проблемы целостности",
|
||||
"finding_and_fixing_message": "Поиск и устранение проблем целостности...",
|
||||
"title": "Проверки целостности"
|
||||
"title": "Проверки целостности",
|
||||
"issues_fixed_message": "Все обнаруженные проблемы с согласованностью теперь устранены."
|
||||
},
|
||||
"call_to_action": {
|
||||
"next_theme_message": "В настоящее время вы используете старую тему оформления. Хотите попробовать новую тему?",
|
||||
"dismiss": "Отклонить",
|
||||
"background_effects_button": "Включить эффекты фона"
|
||||
"background_effects_button": "Включить эффекты фона",
|
||||
"next_theme_button": "Попробовать новую тему",
|
||||
"background_effects_message": "На устройствах Windows фоновые эффекты теперь полностью стабильны. Они добавляют цвет в пользовательский интерфейс, размывая фон за ним. Этот приём также используется в других приложениях, например, в проводнике Windows.",
|
||||
"background_effects_title": "Фоновые эффекты теперь стабильны",
|
||||
"next_theme_title": "Попробуйте новую тему Trilium"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"description": "Масштабированием также можно управлять с помощью сочетаний клавиш CTRL+- и CTRL+=."
|
||||
"description": "Масштабированием также можно управлять с помощью сочетаний клавиш CTRL+- и CTRL+=.",
|
||||
"title": "Коэффициент масштабирования (только для настольной версии)"
|
||||
},
|
||||
"show_toc_widget_button": {
|
||||
"show_toc": "Показать оглавление"
|
||||
@@ -1621,7 +1838,8 @@
|
||||
"title": "Доступные типы в выпадающем списке"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "По заданным параметрам поиска заметки не найдены."
|
||||
"no_notes_found": "По заданным параметрам поиска заметки не найдены.",
|
||||
"search_not_executed": "Поиск ещё не выполнен. Нажмите кнопку «Поиск» выше, чтобы увидеть результаты."
|
||||
},
|
||||
"empty": {
|
||||
"search_placeholder": "поиск заметки по ее названию",
|
||||
@@ -1631,7 +1849,11 @@
|
||||
"search_script": {
|
||||
"placeholder": "поиск заметки по ее названию",
|
||||
"title": "Скрипт поиска:",
|
||||
"example_title": "См. этот пример:"
|
||||
"example_title": "См. этот пример:",
|
||||
"description1": "Скрипт поиска позволяет определить результаты поиска, запустив его. Это обеспечивает максимальную гибкость, когда стандартного поиска недостаточно.",
|
||||
"description2": "Скрипт поиска должен иметь тип «Код» и подтип «JavaScript backend». Скрипт должен возвращать массив идентификаторов заметок или заметок.",
|
||||
"note": "Обратите внимание, что скрипт поиска и строка поиска не могут быть объединены друг с другом.",
|
||||
"example_code": "// 1. Предварительная фильтрация с использованием стандартного поиска\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. Применение пользовательских критериев поиска\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;"
|
||||
},
|
||||
"note_erasure_timeout": {
|
||||
"note_erasure_timeout_title": "Срок окончательного удаления заметок",
|
||||
@@ -1645,21 +1867,25 @@
|
||||
"erase_unused_attachments_now": "Удалить неиспользуемые вложения прямо сейчас",
|
||||
"attachment_auto_deletion_description": "Вложения автоматически удаляются, если в заметке на них больше не ссылаются по истечении определенного времени.",
|
||||
"attachment_erasure_timeout": "Тайм-аут удаления вложения",
|
||||
"erase_attachments_after": "Удалять неиспользуемые вложения через:"
|
||||
"erase_attachments_after": "Удалять неиспользуемые вложения через:",
|
||||
"unused_attachments_erased": "Неиспользуемые вложения были удалены.",
|
||||
"manual_erasing_description": "Вы также можете запустить стирание вручную (без учета тайм-аута, определенного выше):"
|
||||
},
|
||||
"revisions_snapshot_interval": {
|
||||
"note_revisions_snapshot_interval_title": "Интервал создания снимка версии заметки",
|
||||
"note_revisions_snapshot_description": "Интервал создания снимка версии заметки - это время, по истечении которого для неё будет создана новая версия. Подробнее см. в <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a>.",
|
||||
"note_revisions_snapshot_description": "Интервал между снимками редакции заметки — это время, по истечении которого для заметки будет создана новая редакция. Подробнее см. <doc>wiki</doc>.",
|
||||
"snapshot_time_interval_label": "Интервал создания снимка версии заметки:"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Закрепить окно"
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Удалить эту опцию поиска"
|
||||
"remove_this_search_option": "Удалить эту опцию поиска",
|
||||
"failed_rendering": "Не удалось выполнить рендеринг опции поиска: {{dto}} с ошибкой: {{error}} {{stack}}"
|
||||
},
|
||||
"image": {
|
||||
"copied-to-clipboard": "Ссылка на изображение скопирована в буфер обмена. Её можно вставить в любую текстовую заметку."
|
||||
"copied-to-clipboard": "Ссылка на изображение скопирована в буфер обмена. Её можно вставить в любую текстовую заметку.",
|
||||
"cannot-copy": "Не удалось скопировать ссылку на изображение в буфер обмена."
|
||||
},
|
||||
"abstract_bulk_action": {
|
||||
"remove_this_search_action": "Удалить это действие поиска"
|
||||
@@ -1668,26 +1894,38 @@
|
||||
"cannot-move-notes-here": "Невозможно переместить заметки сюда.",
|
||||
"delete-status": "Статус удаления",
|
||||
"delete-finished-successfully": "Удаление успешно завершено.",
|
||||
"undeleting-notes-finished-successfully": "Восстановление заметок успешно завершено."
|
||||
"undeleting-notes-finished-successfully": "Восстановление заметок успешно завершено.",
|
||||
"delete-notes-in-progress": "Удаление заметок в процессе: {{count}}",
|
||||
"undeleting-notes-in-progress": "Идет восстановление заметок: {{count}}"
|
||||
},
|
||||
"attachment_detail": {
|
||||
"owning_note": "Заметка-владелец: ",
|
||||
"list_of_all_attachments": "Список всех вложений"
|
||||
"list_of_all_attachments": "Список всех вложений",
|
||||
"open_help_page": "Открыть справку по вложениям",
|
||||
"attachment_deleted": "Это вложение было удалено.",
|
||||
"you_can_also_open": ", вы также можете открыть "
|
||||
},
|
||||
"web_view": {
|
||||
"web_view": "Веб-страница"
|
||||
"web_view": "Веб-страница",
|
||||
"create_label": "Для начала создайте метку с URL-адресом, который вы хотите встроить, например, #webViewSrc=\"https://www.google.com\"",
|
||||
"embed_websites": "Заметки типа \"Веб-страница\" позволяет встраивать веб-сайты в Trilium."
|
||||
},
|
||||
"ribbon": {
|
||||
"widgets": "Виджеты ленты"
|
||||
"widgets": "Виджеты ленты",
|
||||
"promoted_attributes_message": "Вкладка \"Продвигаемые атрибуты\" будет автоматически открыта, если таковые атрибуты установлены у заметки",
|
||||
"edited_notes_message": "Вкладка ленты «Измененные заметки» будет автоматически открываться в заметках дня"
|
||||
},
|
||||
"options_widget": {
|
||||
"options_status": "Статус опций"
|
||||
"options_status": "Статус опций",
|
||||
"options_change_saved": "Изменения параметров сохранены."
|
||||
},
|
||||
"spacer": {
|
||||
"configure_launchbar": "Конфигурация лаунчбара"
|
||||
},
|
||||
"entrypoints": {
|
||||
"note-executed": "Заметка выполнена."
|
||||
"note-executed": "Заметка выполнена.",
|
||||
"note-revision-created": "Снимок версии заметки создан успешно.",
|
||||
"sql-error": "Произошла ошибка при выполнении SQL-запроса: {{message}}"
|
||||
},
|
||||
"include_archived_notes": {
|
||||
"include_archived_notes": "Включить архивные заметки"
|
||||
@@ -1695,19 +1933,96 @@
|
||||
"open-help-page": "Открыть страницу справки",
|
||||
"watched_file_update_status": {
|
||||
"upload_modified_file": "Загрузить измененный файл",
|
||||
"ignore_this_change": "Игнорировать это изменение"
|
||||
"ignore_this_change": "Игнорировать это изменение",
|
||||
"file_last_modified": "Файл <code class=\"file-path\"></code> был последний раз изменен <span class=\"file-last-modified\"></span>."
|
||||
},
|
||||
"clipboard": {
|
||||
"copy_success": "Скопировано в буфер обмена."
|
||||
"copy_success": "Скопировано в буфер обмена.",
|
||||
"copy_failed": "Невозможно скопировать в буфер обмена из-за проблем с правами доступа.",
|
||||
"copied": "Заметки скопированы в буфер обмена.",
|
||||
"cut": "Заметки вырезаны в буфер обмена."
|
||||
},
|
||||
"ws": {
|
||||
"sync-check-failed": "Проверка синхронизации не удалась!"
|
||||
"sync-check-failed": "Проверка синхронизации не удалась!",
|
||||
"encountered-error": "Обнаружена ошибка \"{{message}}\", проверьте консоль.",
|
||||
"consistency-checks-failed": "Проверка целостности не пройдена! Подробности смотрите в логах."
|
||||
},
|
||||
"attachment_detail_2": {
|
||||
"role_and_size": "Роль: {{role}}, Размер: {{size}}",
|
||||
"unrecognized_role": "Нераспознанная роль вложения '{{role}}'."
|
||||
"unrecognized_role": "Нераспознанная роль вложения '{{role}}'.",
|
||||
"link_copied": "Ссылка на вложение скопирована в буфер обмена.",
|
||||
"will_be_deleted_soon": "Это вложение скоро будет автоматически удалено",
|
||||
"will_be_deleted_in": "Это вложение будет автоматически удалено через {{time}}",
|
||||
"deletion_reason": ", поскольку вложение не связано с содержимым заметки. Чтобы предотвратить удаление, добавьте ссылку на вложение обратно в содержимое или преобразуйте вложение в заметку."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "введите здесь название заметки..."
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Связанные настройки"
|
||||
},
|
||||
"content_widget": {
|
||||
"unknown_widget": "Неизвестный виджет \"{{id}}\"."
|
||||
},
|
||||
"wrap_lines": {
|
||||
"wrap_lines_in_code_notes": "Переносить строки в заметках типа \"Код\"",
|
||||
"enable_line_wrap": "Включить перенос строк (для вступления изменений в силу может потребоваться перезагрузка интерфейса)"
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Для получения справки посетите <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вики</a>.",
|
||||
"shared_locally": "Заметка общедоступна локально в",
|
||||
"shared_publicly": "Заметка общедоступна публично в"
|
||||
},
|
||||
"note_create": {
|
||||
"duplicated": "Создан дубль заметки \"{{title}}\"."
|
||||
},
|
||||
"help-button": {
|
||||
"title": "Открыть соответствующую страницу справки"
|
||||
},
|
||||
"render": {
|
||||
"note_detail_render_help_2": "Тип заметки «Рендер HTML» используется для <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">скриптинга</a>. Если коротко, у вас есть заметка с HTML-кодом (возможно, с добавлением JavaScript), и эта заметка её отобразит. Для этого необходимо определить <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">отношение</a> с именем «renderNote», указывающее на HTML-заметку для отрисовки.",
|
||||
"note_detail_render_help_1": "Эта справочная заметка отображается, поскольку эта справка типа Render HTML не имеет необходимой связи для правильной работы."
|
||||
},
|
||||
"file": {
|
||||
"too_big": "В целях повышения производительности в режиме предварительного просмотра отображаются только первые {{maxNumChars}} символов файла. Загрузите файл и откройте его во внешнем браузере, чтобы увидеть всё содержимое.",
|
||||
"file_preview_not_available": "Предварительный просмотр файла недоступен для этого файла."
|
||||
},
|
||||
"app_context": {
|
||||
"please_wait_for_save": "Подождите несколько секунд, пока сохранение завершится, затем попробуйте еще раз."
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Цветовая схема для блоков кода в текстовых заметках",
|
||||
"related_code_notes": "Цветовая схема для заметок типа \"Код\""
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "По этому запросу не возвращено ни одной строки"
|
||||
},
|
||||
"editable_code": {
|
||||
"placeholder": "Введите содержимое для заметки с кодом..."
|
||||
},
|
||||
"editable_text": {
|
||||
"placeholder": "Введите содержимое для заметки..."
|
||||
},
|
||||
"hoisted_note": {
|
||||
"confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять закрепление. Открепить заметку?"
|
||||
},
|
||||
"frontend_script_api": {
|
||||
"sync_warning": "Вы передаете синхронную функцию в `api.runAsyncOnBackendWithManualTransactionHandling()`, \\nхотя вместо этого вам, скорее всего, следует использовать `api.runOnBackend()`.",
|
||||
"async_warning": "Вы передаете асинхронную функцию в `api.runOnBackend()`, которая, скорее всего, не будет работать так, как вы предполагали.\\nЛибо сделайте функцию синхронной (удалив ключевое слово `async`), либо используйте `api.runAsyncOnBackendWithManualTransactionHandling()`."
|
||||
},
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'"
|
||||
},
|
||||
"book": {
|
||||
"no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. на <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a>."
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Производительность",
|
||||
"enable-motion": "Включить визуальные эффекты и анимации",
|
||||
"enable-shadows": "Включить тени",
|
||||
"enable-backdrop-effects": "Включить эффекты размытия фона меню, всплывающих окон и панелей"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1643,13 +1643,13 @@
|
||||
"failed_notes": "失敗筆記",
|
||||
"last_processed": "最後處理時間",
|
||||
"refresh_stats": "更新統計資料",
|
||||
"enable_ai_features": "啟用 AI / LLM 功能",
|
||||
"enable_ai_features": "啟用 AI/LLM 功能",
|
||||
"enable_ai_description": "啟用筆記摘要、內容生成等 AI 功能及其他 LLM 能力",
|
||||
"openai_tab": "OpenAI",
|
||||
"anthropic_tab": "Anthropic",
|
||||
"voyage_tab": "Voyage AI",
|
||||
"ollama_tab": "Ollama",
|
||||
"enable_ai": "啟用 AI / LLM 功能",
|
||||
"enable_ai": "啟用 AI/LLM 功能",
|
||||
"enable_ai_desc": "啟用筆記摘要、內容生成等 AI 功能及其他 LLM 能力",
|
||||
"provider_configuration": "AI 提供者設定",
|
||||
"provider_precedence": "提供者優先級",
|
||||
@@ -1771,7 +1771,12 @@
|
||||
"selected_provider": "已選提供者",
|
||||
"selected_provider_description": "選擇用於聊天和補全功能的 AI 提供者",
|
||||
"select_model": "選擇模型…",
|
||||
"select_provider": "選擇提供者…"
|
||||
"select_provider": "選擇提供者…",
|
||||
"ai_enabled": "已啟用 AI 功能",
|
||||
"ai_disabled": "已禁用 AI 功能",
|
||||
"no_models_found_online": "找不到模型。請檢查您的 API 金鑰及設定。",
|
||||
"no_models_found_ollama": "找不到 Ollama 模型。請確認 Ollama 是否正在執行。",
|
||||
"error_fetching": "獲取模型失敗:{{error}}"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "編輯器"
|
||||
@@ -1999,5 +2004,21 @@
|
||||
"next_theme_message": "您正在使用舊版主題,要試用新主題嗎?",
|
||||
"next_theme_button": "試用新主題",
|
||||
"dismiss": "關閉"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "相關設定"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "文字筆記中程式碼區塊的配色方案",
|
||||
"related_code_notes": "程式碼筆記的配色方案"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "效能",
|
||||
"enable-motion": "啟用轉場與動畫",
|
||||
"enable-shadows": "啟用陰影",
|
||||
"enable-backdrop-effects": "啟用選單、彈出視窗和面板的背景特效"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -364,7 +364,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.$inputName = this.$widget.find(".attr-input-name");
|
||||
this.$inputName.on("input", (ev) => {
|
||||
if (!(ev.originalEvent as KeyboardEvent)?.isComposing) {
|
||||
// https://github.com/TriliumNext/Trilium/pull/3812
|
||||
// https://github.com/zadam/trilium/pull/3812
|
||||
this.userEditedAttribute();
|
||||
}
|
||||
});
|
||||
@@ -383,7 +383,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.$inputValue = this.$widget.find(".attr-input-value");
|
||||
this.$inputValue.on("input", (ev) => {
|
||||
if (!(ev.originalEvent as KeyboardEvent)?.isComposing) {
|
||||
// https://github.com/TriliumNext/Trilium/pull/3812
|
||||
// https://github.com/zadam/trilium/pull/3812
|
||||
this.userEditedAttribute();
|
||||
}
|
||||
});
|
||||
@@ -421,7 +421,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.$inputInverseRelation = this.$widget.find(".attr-input-inverse-relation");
|
||||
this.$inputInverseRelation.on("input", (ev) => {
|
||||
if (!(ev.originalEvent as KeyboardEvent)?.isComposing) {
|
||||
// https://github.com/TriliumNext/Trilium/pull/3812
|
||||
// https://github.com/zadam/trilium/pull/3812
|
||||
this.userEditedAttribute();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -173,7 +173,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
this.attributeDetailWidget.hide();
|
||||
});
|
||||
|
||||
this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/TriliumNext/Trilium/issues/4160
|
||||
this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160
|
||||
|
||||
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
|
||||
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
|
||||
@@ -282,7 +282,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
|
||||
async save() {
|
||||
if (this.lastUpdatedNoteId !== this.noteId) {
|
||||
// https://github.com/TriliumNext/Trilium/issues/3090
|
||||
// https://github.com/zadam/trilium/issues/3090
|
||||
console.warn("Ignoring blur event because a different note is loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import utils from "../../services/utils.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import { EventData } from "../../components/app_context.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import options from "../../services/options.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
/**
|
||||
* The root container is the top-most widget/container, from which the entire layout derives.
|
||||
@@ -27,15 +29,45 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
window.visualViewport?.addEventListener("resize", () => this.#onMobileResize());
|
||||
}
|
||||
|
||||
this.#setMotion(options.is("motionEnabled"));
|
||||
this.#setShadows(options.is("shadowsEnabled"));
|
||||
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
|
||||
|
||||
return super.render();
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isOptionReloaded("motionEnabled")) {
|
||||
this.#setMotion(options.is("motionEnabled"));
|
||||
}
|
||||
|
||||
if (loadResults.isOptionReloaded("shadowsEnabled")) {
|
||||
this.#setShadows(options.is("shadowsEnabled"));
|
||||
}
|
||||
|
||||
if (loadResults.isOptionReloaded("backdropEffectsEnabled")) {
|
||||
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
|
||||
}
|
||||
}
|
||||
|
||||
#onMobileResize() {
|
||||
const currentViewportHeight = getViewportHeight();
|
||||
const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight);
|
||||
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
|
||||
}
|
||||
|
||||
#setMotion(enabled: boolean) {
|
||||
document.body.classList.toggle("motion-disabled", !enabled);
|
||||
jQuery.fx.off = !enabled;
|
||||
}
|
||||
|
||||
#setShadows(enabled: boolean) {
|
||||
document.body.classList.toggle("shadows-disabled", !enabled);
|
||||
}
|
||||
|
||||
#setBackdropEffects(enabled: boolean) {
|
||||
document.body.classList.toggle("backdrop-effects-disabled", !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
function getViewportHeight() {
|
||||
|
||||
@@ -77,7 +77,7 @@ function DirectoryLink({ directory, style }: { directory: string, style?: CSSPro
|
||||
openService.openDirectory(directory);
|
||||
};
|
||||
|
||||
return <a className="tn-link" href="#" onClick={onClick} style={style}></a>
|
||||
return <a className="tn-link" href="#" onClick={onClick} style={style}>{directory}</a>
|
||||
} else {
|
||||
return <span style={style}>{directory}</span>;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,14 @@ function AddLinkDialogComponent() {
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSelection) {
|
||||
setLinkType("hyper-link");
|
||||
} else {
|
||||
setLinkType("reference-link");
|
||||
}
|
||||
}, [ hasSelection ])
|
||||
|
||||
async function setDefaultLinkTitle(noteId: string) {
|
||||
const noteTitle = await tree.getNoteTitle(noteId);
|
||||
setLinkTitle(noteTitle);
|
||||
|
||||
@@ -9,6 +9,7 @@ import appContext from "../../components/app_context";
|
||||
import commandRegistry from "../../services/command_registry";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import shortcutService from "../../services/shortcuts";
|
||||
|
||||
const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120;
|
||||
|
||||
@@ -83,6 +84,27 @@ function JumpToNoteDialogComponent() {
|
||||
$autoComplete
|
||||
.trigger("focus")
|
||||
.trigger("select");
|
||||
|
||||
// Add keyboard shortcut for full search
|
||||
shortcutService.bindElShortcut($autoComplete, "ctrl+return", () => {
|
||||
if (!isCommandMode) {
|
||||
showInFullSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function showInFullSearch() {
|
||||
try {
|
||||
setShown(false);
|
||||
const searchString = actualText.current?.trim();
|
||||
if (searchString && !searchString.startsWith(">")) {
|
||||
await appContext.triggerCommand("searchNotes", {
|
||||
searchString
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to trigger full search:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -108,7 +130,12 @@ function JumpToNoteDialogComponent() {
|
||||
/>}
|
||||
onShown={onShown}
|
||||
onHidden={() => setShown(false)}
|
||||
footer={!isCommandMode && <Button className="show-in-full-text-button" text={t("jump_to_note.search_button")} keyboardShortcut="Ctrl+Enter" />}
|
||||
footer={!isCommandMode && <Button
|
||||
className="show-in-full-text-button"
|
||||
text={t("jump_to_note.search_button")}
|
||||
keyboardShortcut="Ctrl+Enter"
|
||||
onClick={showInFullSearch}
|
||||
/>}
|
||||
show={shown}
|
||||
>
|
||||
<div className="algolia-autocomplete-container jump-to-note-results" ref={containerRef}></div>
|
||||
|
||||
@@ -192,7 +192,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
* sets full height of container that contains note content for a subset of note-types
|
||||
*/
|
||||
checkFullHeight() {
|
||||
// https://github.com/TriliumNext/Trilium/issues/2522
|
||||
// https://github.com/zadam/trilium/issues/2522
|
||||
const isBackendNote = this.noteContext?.noteId === "_backendLog";
|
||||
const isSqlNote = this.mime === "text/x-sqlite;schema=trilium";
|
||||
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "mermaid", "file"].includes(this.type ?? "");
|
||||
|
||||
@@ -81,7 +81,7 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
);
|
||||
|
||||
// there seems to be a race condition on Firefox which triggers the observer only before the widget is visible
|
||||
// (intersection is false). https://github.com/TriliumNext/Trilium/issues/4165
|
||||
// (intersection is false). https://github.com/zadam/trilium/issues/4165
|
||||
setTimeout(() => observer.observe(this.$widget[0]), 10);
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ const TPL = /*html*/`
|
||||
const MAX_SEARCH_RESULTS_IN_TREE = 100;
|
||||
|
||||
// this has to be hanged on the actual elements to effectively intercept and stop click event
|
||||
const cancelClickPropagation: JQuery.TypeEventHandler<unknown, unknown, unknown, unknown, any> = (e) => e.stopPropagation();
|
||||
const cancelClickPropagation: (e: JQuery.ClickEvent) => void = (e) => e.stopPropagation();
|
||||
|
||||
// TODO: Fix once we remove Node.js API from public
|
||||
type Timeout = NodeJS.Timeout | string | number | undefined;
|
||||
@@ -312,7 +312,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
setupNoteTitleTooltip() {
|
||||
// the following will dynamically set tree item's tooltip if the whole item's text is not currently visible
|
||||
// if the whole text is visible then no tooltip is show since that's unnecessarily distracting
|
||||
// see https://github.com/TriliumNext/Trilium/pull/1120 for discussion
|
||||
// see https://github.com/zadam/trilium/pull/1120 for discussion
|
||||
|
||||
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
|
||||
const isEnclosing = ($container: JQuery<HTMLElement>, $sub: JQuery<HTMLElement>) => {
|
||||
@@ -952,7 +952,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
await this.filterHoistedBranch(true);
|
||||
|
||||
// don't activate the active note, see discussion in https://github.com/TriliumNext/Trilium/issues/3664
|
||||
// don't activate the active note, see discussion in https://github.com/zadam/trilium/issues/3664
|
||||
}
|
||||
|
||||
async expandTree(node: Fancytree.FancytreeNode | null = null) {
|
||||
@@ -1181,7 +1181,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
/*
|
||||
* We're collapsing notes after a period of inactivity to "cleanup" the tree - users rarely
|
||||
* collapse the notes and the tree becomes unusuably large.
|
||||
* Some context: https://github.com/TriliumNext/Trilium/issues/1192
|
||||
* Some context: https://github.com/zadam/trilium/issues/1192
|
||||
*/
|
||||
|
||||
const noteIdsToKeepExpanded = new Set(
|
||||
@@ -1429,7 +1429,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
if (activeNodeFocused) {
|
||||
// needed by Firefox: https://github.com/TriliumNext/Trilium/issues/1865
|
||||
// needed by Firefox: https://github.com/zadam/trilium/issues/1865
|
||||
this.tree.$container.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
|
||||
if (a.noteId === b.noteId) {
|
||||
return a.position - b.position;
|
||||
} else {
|
||||
// inherited attributes should stay grouped: https://github.com/TriliumNext/Trilium/issues/3761
|
||||
// inherited attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
|
||||
return a.noteId < b.noteId ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ const TPL = /*html*/`
|
||||
<div class="promoted-attributes-widget">
|
||||
<style>
|
||||
body.mobile .promoted-attributes-widget {
|
||||
/* https://github.com/TriliumNext/Trilium/issues/4468 */
|
||||
/* https://github.com/zadam/trilium/issues/4468 */
|
||||
flex-shrink: 0.4;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class SharedInfoWidget extends NoteContextAwareWidget {
|
||||
let host = location.host;
|
||||
if (host.endsWith("/")) {
|
||||
// seems like IE has trailing slash
|
||||
// https://github.com/TriliumNext/Trilium/issues/3782
|
||||
// https://github.com/zadam/trilium/issues/3782
|
||||
host = host.substr(0, host.length - 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -360,7 +360,7 @@ export default class TocWidget extends RightPanelWidget {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce indent if a larger headings are not being used: https://github.com/TriliumNext/Trilium/issues/4363
|
||||
* Reduce indent if a larger headings are not being used: https://github.com/zadam/trilium/issues/4363
|
||||
*/
|
||||
pullLeft($toc: JQuery<HTMLElement>) {
|
||||
while (true) {
|
||||
@@ -390,7 +390,7 @@ export default class TocWidget extends RightPanelWidget {
|
||||
// temporarily" (ie "edit this note" button) without any
|
||||
// intervening events, do the readonly calculation at navigation
|
||||
// time and not at outline creation time
|
||||
// See https://github.com/TriliumNext/Trilium/issues/2828
|
||||
// See https://github.com/zadam/trilium/issues/2828
|
||||
const isDocNote = this.note.type === "doc";
|
||||
const isReadOnly = await this.noteContext.isReadOnly();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const TPL = /*html*/`
|
||||
}
|
||||
|
||||
/* Conflict between excalidraw and bootstrap classes keeps the menu hidden */
|
||||
/* https://github.com/TriliumNext/Trilium/issues/3780 */
|
||||
/* https://github.com/zadam/trilium/issues/3780 */
|
||||
/* https://github.com/excalidraw/excalidraw/issues/6567 */
|
||||
.excalidraw .dropdown-menu {
|
||||
display: block;
|
||||
|
||||
@@ -88,6 +88,7 @@ export default function AppearanceSettings() {
|
||||
<ApplicationTheme />
|
||||
{overrideThemeFonts === "true" && <Fonts />}
|
||||
{isElectron() && <ElectronIntegration /> }
|
||||
<Performance />
|
||||
<MaxContentWidth />
|
||||
<RelatedSettings items={[
|
||||
{
|
||||
@@ -245,6 +246,30 @@ function ElectronIntegration() {
|
||||
)
|
||||
}
|
||||
|
||||
function Performance() {
|
||||
const [ motionEnabled, setMotionEnabled ] = useTriliumOptionBool("motionEnabled");
|
||||
const [ shadowsEnabled, setShadowsEnabled ] = useTriliumOptionBool("shadowsEnabled");
|
||||
const [ backdropEffectsEnabled, setBackdropEffectsEnabled ] = useTriliumOptionBool("backdropEffectsEnabled");
|
||||
|
||||
return <OptionsSection title={t("ui-performance.title")}>
|
||||
<FormCheckbox
|
||||
label={t("ui-performance.enable-motion")}
|
||||
currentValue={motionEnabled} onChange={setMotionEnabled}
|
||||
/>
|
||||
|
||||
<FormCheckbox
|
||||
label={t("ui-performance.enable-shadows")}
|
||||
currentValue={shadowsEnabled} onChange={setShadowsEnabled}
|
||||
/>
|
||||
|
||||
<FormCheckbox
|
||||
label={t("ui-performance.enable-backdrop-effects")}
|
||||
currentValue={backdropEffectsEnabled} onChange={setBackdropEffectsEnabled}
|
||||
/>
|
||||
</OptionsSection>
|
||||
}
|
||||
|
||||
|
||||
function MaxContentWidth() {
|
||||
const [ maxContentWidth, setMaxContentWidth ] = useTriliumOption("maxContentWidth");
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
async doRefresh(note: FNote) {
|
||||
// we load CKEditor also for read only notes because they contain content styles required for correct rendering of even read only notes
|
||||
// we could load just ckeditor-content.css but that causes CSS conflicts when both build CSS and this content CSS is loaded at the same time
|
||||
// (see https://github.com/TriliumNext/Trilium/issues/1590 for example of such conflict)
|
||||
// (see https://github.com/zadam/trilium/issues/1590 for example of such conflict)
|
||||
await import("@triliumnext/ckeditor5");
|
||||
|
||||
this.onLanguageChanged();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Calendar, DateSelectArg, DatesSetArg, EventChangeArg, EventDropArg, EventInput, EventSourceFunc, EventSourceFuncArg, EventSourceInput, PluginDef } from "@fullcalendar/core";
|
||||
import type { Calendar, DateSelectArg, DatesSetArg, EventChangeArg, EventDropArg, EventInput, EventSourceFunc, EventSourceFuncArg, EventSourceInput, LocaleInput, PluginDef } from "@fullcalendar/core";
|
||||
import froca from "../../services/froca.js";
|
||||
import ViewMode, { type ViewModeArgs } from "./view_mode.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
@@ -15,6 +15,22 @@ import type { EventImpl } from "@fullcalendar/core/internal";
|
||||
import debounce, { type DebouncedFunction } from "debounce";
|
||||
import type { TouchBarItem } from "../../components/touch_bar.js";
|
||||
import type { SegmentedControlSegment } from "electron";
|
||||
import { LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
// Here we hard-code the imports in order to ensure that they are embedded by webpack without having to load all the languages.
|
||||
const LOCALE_MAPPINGS: Record<LOCALE_IDS, (() => Promise<{ default: LocaleInput }>) | null> = {
|
||||
de: () => import("@fullcalendar/core/locales/de"),
|
||||
es: () => import("@fullcalendar/core/locales/es"),
|
||||
fr: () => import("@fullcalendar/core/locales/fr"),
|
||||
cn: () => import("@fullcalendar/core/locales/zh-cn"),
|
||||
tw: () => import("@fullcalendar/core/locales/zh-tw"),
|
||||
ro: () => import("@fullcalendar/core/locales/ro"),
|
||||
ru: () => import("@fullcalendar/core/locales/ru"),
|
||||
ja: () => import("@fullcalendar/core/locales/ja"),
|
||||
"pt_br": () => import("@fullcalendar/core/locales/pt-br"),
|
||||
uk: () => import("@fullcalendar/core/locales/uk"),
|
||||
en: null
|
||||
};
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="calendar-view">
|
||||
@@ -657,27 +673,11 @@ export default class CalendarView extends ViewMode<{}> {
|
||||
|
||||
}
|
||||
|
||||
export async function getFullCalendarLocale(locale: string) {
|
||||
// Here we hard-code the imports in order to ensure that they are embedded by webpack without having to load all the languages.
|
||||
switch (locale) {
|
||||
case "de":
|
||||
return (await import("@fullcalendar/core/locales/de")).default;
|
||||
case "es":
|
||||
return (await import("@fullcalendar/core/locales/es")).default;
|
||||
case "fr":
|
||||
return (await import("@fullcalendar/core/locales/fr")).default;
|
||||
case "cn":
|
||||
return (await import("@fullcalendar/core/locales/zh-cn")).default;
|
||||
case "tw":
|
||||
return (await import("@fullcalendar/core/locales/zh-tw")).default;
|
||||
case "ro":
|
||||
return (await import("@fullcalendar/core/locales/ro")).default;
|
||||
case "ru":
|
||||
return (await import("@fullcalendar/core/locales/ru")).default;
|
||||
case "ja":
|
||||
return (await import("@fullcalendar/core/locales/ja")).default;
|
||||
case "en":
|
||||
default:
|
||||
return undefined;
|
||||
export async function getFullCalendarLocale(locale: LOCALE_IDS) {
|
||||
const correspondingLocale = LOCALE_MAPPINGS[locale];
|
||||
if (correspondingLocale) {
|
||||
return (await correspondingLocale()).default;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop",
|
||||
"version": "0.98.0",
|
||||
"version": "0.98.1",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "main.cjs",
|
||||
|
||||
@@ -22,7 +22,7 @@ async function main() {
|
||||
electronDebug();
|
||||
electronDl({ saveAs: true });
|
||||
|
||||
// needed for excalidraw export https://github.com/TriliumNext/Trilium/issues/4271
|
||||
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
|
||||
electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features");
|
||||
electron.app.commandLine.appendSwitch("lang", options.getOptionOrNull("formattingLocale") ?? "en");
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function decrypt(key: any, cipherText: any) {
|
||||
|
||||
try {
|
||||
const cipherTextBufferWithIv = Buffer.from(cipherText.toString(), "base64");
|
||||
// old encrypted data can have IV of length 13, see some details here: https://github.com/TriliumNext/Trilium/issues/3017
|
||||
// old encrypted data can have IV of length 13, see some details here: https://github.com/zadam/trilium/issues/3017
|
||||
const ivLength = cipherTextBufferWithIv.length % 16 === 0 ? 16 : 13;
|
||||
const iv = cipherTextBufferWithIv.slice(0, ivLength);
|
||||
|
||||
@@ -48,7 +48,7 @@ function decrypt(key: any, cipherText: any) {
|
||||
|
||||
return payload;
|
||||
} catch (e: any) {
|
||||
// recovery from https://github.com/TriliumNext/Trilium/issues/510
|
||||
// recovery from https://github.com/zadam/trilium/issues/510
|
||||
if (e.message?.includes("WRONG_FINAL_BLOCK_LENGTH") || e.message?.includes("wrong final block length")) {
|
||||
console.log("Caught WRONG_FINAL_BLOCK_LENGTH, returning cipherText instead");
|
||||
return cipherText;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<h1 data-trilium-h1>Journal</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<p>You can read some explanation on how this journal works here: <a href="https://github.com/TriliumNext/Trilium/wiki/Day-notes">https://github.com/TriliumNext/Trilium/wiki/Day-notes</a>
|
||||
<p>You can read some explanation on how this journal works here: <a href="https://github.com/zadam/trilium/wiki/Day-notes">https://github.com/zadam/trilium/wiki/Day-notes</a>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ return api.res.send(404);
|
||||
* To test this, execute the following curl request: curl -X POST http://localhost:37740/custom/create-note -H "Content-Type: application/json" -d "{ \"secret\": \"secret-password\", \"title\": \"hello\", \"content\": \"world\" }"
|
||||
* (host and port might have to be adjusted based on your setup)
|
||||
*
|
||||
* See https://github.com/TriliumNext/Trilium/wiki/Custom-request-handler for details.
|
||||
* See https://github.com/zadam/trilium/wiki/Custom-request-handler for details.
|
||||
*/
|
||||
|
||||
const {req, res} = api;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<div class="ck-content">
|
||||
<p>This is a simple TODO/Task manager. You can see some description and explanation
|
||||
here: <a href="https://github.com/TriliumNext/Trilium/wiki/Task-manager">https://github.com/TriliumNext/Trilium/wiki/Task-manager</a>
|
||||
here: <a href="https://github.com/zadam/trilium/wiki/Task-manager">https://github.com/zadam/trilium/wiki/Task-manager</a>
|
||||
|
||||
</p>
|
||||
<p>Please note that this is meant as scripting example only and feature/bug
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div style="padding: 20px">
|
||||
<strong>See explanation <a href="https://github.com/TriliumNext/Trilium/wiki/Weight-tracker" target="_blank">here</a></strong>.
|
||||
<strong>See explanation <a href="https://github.com/zadam/trilium/wiki/Weight-tracker" target="_blank">here</a></strong>.
|
||||
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
* This is a demo of how you can create custom theme for Trilium. You can activate it by going
|
||||
* into options in first tab "Appearance".
|
||||
*
|
||||
* You can read some details on theming here: http://github.com/TriliumNext/Trilium/wiki/Themes
|
||||
* You can read some details on theming here: http://github.com/zadam/trilium/wiki/Themes
|
||||
*/
|
||||
|
||||
@font-face { /* This will be used as main UI font (see below) */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/server",
|
||||
"version": "0.98.0",
|
||||
"version": "0.98.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": {
|
||||
@@ -74,7 +74,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.3.6",
|
||||
"i18next": "25.4.2",
|
||||
"i18next-fs-backend": "2.6.0",
|
||||
"image-type": "6.0.0",
|
||||
"ini": "5.0.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,720 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ETAPI Complete Guide</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { color: #2c3e50; border-bottom: 3px solid #e67e22; padding-bottom: 10px; }
|
||||
h2 { color: #34495e; margin-top: 40px; border-bottom: 2px solid #ecf0f1; padding-bottom: 5px; }
|
||||
h3 { color: #7f8c8d; margin-top: 30px; }
|
||||
h4 { color: #95a5a6; margin-top: 25px; }
|
||||
pre {
|
||||
background: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
border-left: 4px solid #e67e22;
|
||||
}
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 3px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', Monaco, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.endpoint-box {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #e67e22;
|
||||
}
|
||||
.method-get { border-left-color: #28a745; }
|
||||
.method-post { border-left-color: #007bff; }
|
||||
.method-put { border-left-color: #ffc107; }
|
||||
.method-patch { border-left-color: #fd7e14; }
|
||||
.method-delete { border-left-color: #dc3545; }
|
||||
.example-box {
|
||||
background: #f1f3f4;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #34495e;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
tr:nth-child(even) { background: #f8f9fa; }
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #74c0fc;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.success {
|
||||
background: #d4edda;
|
||||
border: 1px solid #51cf66;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.toc {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.toc ul {
|
||||
list-style-type: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.toc a {
|
||||
text-decoration: none;
|
||||
color: #495057;
|
||||
}
|
||||
.toc a:hover {
|
||||
color: #e67e22;
|
||||
}
|
||||
.auth-example {
|
||||
background: #e8f5e8;
|
||||
border-left: 4px solid #28a745;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ETAPI Complete Guide</h1>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Table of Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#introduction">Introduction</a></li>
|
||||
<li><a href="#authentication-setup">Authentication Setup</a></li>
|
||||
<li><a href="#api-endpoints">API Endpoints</a></li>
|
||||
<li><a href="#common-use-cases">Common Use Cases</a></li>
|
||||
<li><a href="#client-library-examples">Client Library Examples</a></li>
|
||||
<li><a href="#rate-limiting-and-best-practices">Rate Limiting and Best Practices</a></li>
|
||||
<li><a href="#migration-from-internal-api">Migration from Internal API</a></li>
|
||||
<li><a href="#error-handling">Error Handling</a></li>
|
||||
<li><a href="#performance-considerations">Performance Considerations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
<p>ETAPI (External Trilium API) is the recommended REST API for external integrations with Trilium Notes. It provides a secure, stable interface for programmatic access to notes, attributes, branches, and attachments.</p>
|
||||
|
||||
<div class="success">
|
||||
<h4>Key Features</h4>
|
||||
<ul>
|
||||
<li>RESTful design with predictable endpoints</li>
|
||||
<li>Token-based authentication</li>
|
||||
<li>Comprehensive CRUD operations</li>
|
||||
<li>Search functionality</li>
|
||||
<li>Import/export capabilities</li>
|
||||
<li>Calendar and special note access</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<strong>Base URL:</strong> <code>http://localhost:8080/etapi</code>
|
||||
</div>
|
||||
|
||||
<h2 id="authentication-setup">Authentication Setup</h2>
|
||||
|
||||
<h3>Method 1: Token Authentication</h3>
|
||||
|
||||
<div class="auth-example">
|
||||
<h4>Step 1: Generate ETAPI Token</h4>
|
||||
<ol>
|
||||
<li>Open Trilium Notes</li>
|
||||
<li>Navigate to <strong>Options</strong> → <strong>ETAPI</strong></li>
|
||||
<li>Click "Create new ETAPI token"</li>
|
||||
<li>Copy the generated token</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h4>Step 2: Use Token in Requests</h4>
|
||||
|
||||
<div class="endpoint-box">
|
||||
<strong>HTTP Header:</strong>
|
||||
<pre><code>Authorization: <your-token></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>cURL Example:</strong>
|
||||
<pre><code>curl -X GET http://localhost:8080/etapi/notes/root \
|
||||
-H "Authorization: myEtapiToken123"</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Python Example:</strong>
|
||||
<pre><code>import requests
|
||||
|
||||
headers = {
|
||||
'Authorization': 'myEtapiToken123'
|
||||
}
|
||||
|
||||
response = requests.get('http://localhost:8080/etapi/notes/root', headers=headers)
|
||||
print(response.json())</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Method 2: Basic Authentication</h3>
|
||||
|
||||
<p>Use the ETAPI token as the password with any username:</p>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>curl -X GET http://localhost:8080/etapi/notes/root \
|
||||
-u "trilium:myEtapiToken123"</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Method 3: Programmatic Login</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>import requests
|
||||
|
||||
# Login to get token
|
||||
login_data = {'password': 'your-trilium-password'}
|
||||
response = requests.post('http://localhost:8080/etapi/auth/login', json=login_data)
|
||||
token = response.json()['authToken']
|
||||
|
||||
# Use token for subsequent requests
|
||||
headers = {'Authorization': token}
|
||||
notes = requests.get('http://localhost:8080/etapi/notes/root', headers=headers)</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="api-endpoints">API Endpoints</h2>
|
||||
|
||||
<h3>Notes</h3>
|
||||
|
||||
<h4>Create Note</h4>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/etapi/create-note</code>
|
||||
<p>Creates a new note and places it in the tree.</p>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Request Body:</strong>
|
||||
<pre><code>{
|
||||
"parentNoteId": "root",
|
||||
"title": "My New Note",
|
||||
"type": "text",
|
||||
"content": "<p>This is the note content</p>",
|
||||
"notePosition": 10,
|
||||
"prefix": "📝",
|
||||
"isExpanded": true
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Response (201 Created):</strong>
|
||||
<pre><code>{
|
||||
"note": {
|
||||
"noteId": "evnnmvHTCgIn",
|
||||
"title": "My New Note",
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"isProtected": false,
|
||||
"dateCreated": "2024-01-15 10:30:00.000+0100",
|
||||
"dateModified": "2024-01-15 10:30:00.000+0100",
|
||||
"utcDateCreated": "2024-01-15 09:30:00.000Z",
|
||||
"utcDateModified": "2024-01-15 09:30:00.000Z"
|
||||
},
|
||||
"branch": {
|
||||
"branchId": "ibhg4WxTdULk",
|
||||
"noteId": "evnnmvHTCgIn",
|
||||
"parentNoteId": "root",
|
||||
"prefix": "📝",
|
||||
"notePosition": 10,
|
||||
"isExpanded": true,
|
||||
"utcDateModified": "2024-01-15 09:30:00.000Z"
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Python Example:</strong>
|
||||
<pre><code>import requests
|
||||
import json
|
||||
|
||||
def create_note(parent_id, title, content, note_type="text"):
|
||||
url = "http://localhost:8080/etapi/create-note"
|
||||
headers = {
|
||||
'Authorization': 'your-token',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
data = {
|
||||
"parentNoteId": parent_id,
|
||||
"title": title,
|
||||
"type": note_type,
|
||||
"content": content
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Failed to create note: {response.text}")
|
||||
|
||||
# Usage
|
||||
new_note = create_note("root", "Meeting Notes", "<p>Discussion points:</p><ul><li>Item 1</li></ul>")
|
||||
print(f"Created note with ID: {new_note['note']['noteId']}")</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Get Note by ID</h4>
|
||||
|
||||
<div class="endpoint-box method-get">
|
||||
<strong>GET</strong> <code>/etapi/notes/{noteId}</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>cURL Example:</strong>
|
||||
<pre><code>curl -X GET http://localhost:8080/etapi/notes/evnnmvHTCgIn \
|
||||
-H "Authorization: your-token"</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Response:</strong>
|
||||
<pre><code>{
|
||||
"noteId": "evnnmvHTCgIn",
|
||||
"title": "My Note",
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"isProtected": false,
|
||||
"attributes": [
|
||||
{
|
||||
"attributeId": "abc123",
|
||||
"noteId": "evnnmvHTCgIn",
|
||||
"type": "label",
|
||||
"name": "todo",
|
||||
"value": "",
|
||||
"position": 10,
|
||||
"isInheritable": false
|
||||
}
|
||||
],
|
||||
"parentNoteIds": ["root"],
|
||||
"childNoteIds": ["child1", "child2"],
|
||||
"dateCreated": "2024-01-15 10:30:00.000+0100",
|
||||
"dateModified": "2024-01-15 14:20:00.000+0100",
|
||||
"utcDateCreated": "2024-01-15 09:30:00.000Z",
|
||||
"utcDateModified": "2024-01-15 13:20:00.000Z"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Update Note</h4>
|
||||
|
||||
<div class="endpoint-box method-patch">
|
||||
<strong>PATCH</strong> <code>/etapi/notes/{noteId}</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Request Body:</strong>
|
||||
<pre><code>{
|
||||
"title": "Updated Title",
|
||||
"type": "text",
|
||||
"mime": "text/html"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>JavaScript Example:</strong>
|
||||
<pre><code>async function updateNote(noteId, updates) {
|
||||
const response = await fetch(`http://localhost:8080/etapi/notes/${noteId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': 'your-token',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(updates)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to update note: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Usage
|
||||
updateNote('evnnmvHTCgIn', { title: 'New Title' })
|
||||
.then(note => console.log('Updated note:', note))
|
||||
.catch(err => console.error('Error:', err));</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Search</h3>
|
||||
|
||||
<h4>Search Notes</h4>
|
||||
|
||||
<div class="endpoint-box method-get">
|
||||
<strong>GET</strong> <code>/etapi/notes</code>
|
||||
<p>Search for notes using Trilium's search syntax.</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>search</code></td>
|
||||
<td>Yes</td>
|
||||
<td>Search query string</td>
|
||||
<td>#todo, "exact phrase"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fastSearch</code></td>
|
||||
<td>No</td>
|
||||
<td>Enable fast search (default: false)</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>includeArchivedNotes</code></td>
|
||||
<td>No</td>
|
||||
<td>Include archived notes (default: false)</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ancestorNoteId</code></td>
|
||||
<td>No</td>
|
||||
<td>Search within subtree</td>
|
||||
<td>root</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>orderBy</code></td>
|
||||
<td>No</td>
|
||||
<td>Property to order by</td>
|
||||
<td>dateModified</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>orderDirection</code></td>
|
||||
<td>No</td>
|
||||
<td>"asc" or "desc"</td>
|
||||
<td>desc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>No</td>
|
||||
<td>Maximum number of results</td>
|
||||
<td>10</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Python Examples:</strong>
|
||||
<pre><code># Full-text search
|
||||
def search_notes(query, token, **kwargs):
|
||||
url = "http://localhost:8080/etapi/notes"
|
||||
headers = {'Authorization': token}
|
||||
params = {'search': query, **kwargs}
|
||||
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
return response.json()
|
||||
|
||||
# Search for keyword
|
||||
results = search_notes("project management", token)
|
||||
|
||||
# Search with label
|
||||
results = search_notes("#todo", token)
|
||||
|
||||
# Search for exact phrase
|
||||
results = search_notes('"exact phrase"', token)
|
||||
|
||||
# Complex search with ordering and limit
|
||||
results = search_notes(
|
||||
"type:text #important",
|
||||
token,
|
||||
orderBy="dateModified",
|
||||
orderDirection="desc",
|
||||
limit=10
|
||||
)</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="common-use-cases">Common Use Cases</h2>
|
||||
|
||||
<h3>1. Daily Journal Entry</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>from datetime import date
|
||||
import requests
|
||||
|
||||
class TriliumJournal:
|
||||
def __init__(self, base_url, token):
|
||||
self.base_url = base_url
|
||||
self.headers = {'Authorization': token}
|
||||
|
||||
def create_journal_entry(self, content, tags=[]):
|
||||
# Get today's day note
|
||||
today = date.today().strftime('%Y-%m-%d')
|
||||
day_note_url = f"{self.base_url}/calendar/days/{today}"
|
||||
day_note = requests.get(day_note_url, headers=self.headers).json()
|
||||
|
||||
# Create entry
|
||||
entry_data = {
|
||||
"parentNoteId": day_note['noteId'],
|
||||
"title": f"Entry - {date.today().strftime('%H:%M')}",
|
||||
"type": "text",
|
||||
"content": content
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{self.base_url}/create-note",
|
||||
headers={**self.headers, 'Content-Type': 'application/json'},
|
||||
json=entry_data
|
||||
)
|
||||
|
||||
entry = response.json()
|
||||
|
||||
# Add tags
|
||||
for tag in tags:
|
||||
self.add_tag(entry['note']['noteId'], tag)
|
||||
|
||||
return entry
|
||||
|
||||
def add_tag(self, note_id, tag_name):
|
||||
attr_data = {
|
||||
"noteId": note_id,
|
||||
"type": "label",
|
||||
"name": tag_name,
|
||||
"value": ""
|
||||
}
|
||||
|
||||
requests.post(
|
||||
f"{self.base_url}/attributes",
|
||||
headers={**self.headers, 'Content-Type': 'application/json'},
|
||||
json=attr_data
|
||||
)
|
||||
|
||||
# Usage
|
||||
journal = TriliumJournal("http://localhost:8080/etapi", "your-token")
|
||||
entry = journal.create_journal_entry(
|
||||
"<p>Today's meeting went well. Key decisions:</p><ul><li>Item 1</li></ul>",
|
||||
tags=["meeting", "important"]
|
||||
)</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="error-handling">Error Handling</h2>
|
||||
|
||||
<h3>Common Error Codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Code</th>
|
||||
<th>Description</th>
|
||||
<th>Resolution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>400</td>
|
||||
<td>BAD_REQUEST</td>
|
||||
<td>Invalid request format</td>
|
||||
<td>Check request body and parameters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>401</td>
|
||||
<td>UNAUTHORIZED</td>
|
||||
<td>Invalid or missing token</td>
|
||||
<td>Verify authentication token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>404</td>
|
||||
<td>NOTE_NOT_FOUND</td>
|
||||
<td>Note doesn't exist</td>
|
||||
<td>Check note ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>400</td>
|
||||
<td>NOTE_IS_PROTECTED</td>
|
||||
<td>Cannot modify protected note</td>
|
||||
<td>Unlock protected session first</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>429</td>
|
||||
<td>TOO_MANY_REQUESTS</td>
|
||||
<td>Rate limit exceeded</td>
|
||||
<td>Wait before retrying</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Error Response Format</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>{
|
||||
"status": 400,
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Note title cannot be empty"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Handling Errors in Code</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>class ETAPIError(Exception):
|
||||
def __init__(self, status, code, message):
|
||||
self.status = status
|
||||
self.code = code
|
||||
self.message = message
|
||||
super().__init__(f"{code}: {message}")
|
||||
|
||||
def handle_api_response(response):
|
||||
if response.status_code >= 400:
|
||||
try:
|
||||
error = response.json()
|
||||
raise ETAPIError(
|
||||
error.get('status'),
|
||||
error.get('code'),
|
||||
error.get('message')
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
raise ETAPIError(
|
||||
response.status_code,
|
||||
'UNKNOWN_ERROR',
|
||||
response.text
|
||||
)
|
||||
|
||||
return response.json() if response.content else None
|
||||
|
||||
# Usage
|
||||
try:
|
||||
response = requests.get(
|
||||
'http://localhost:8080/etapi/notes/invalid',
|
||||
headers={'Authorization': 'token'}
|
||||
)
|
||||
note = handle_api_response(response)
|
||||
except ETAPIError as e:
|
||||
if e.code == 'NOTE_NOT_FOUND':
|
||||
print("Note doesn't exist")
|
||||
else:
|
||||
print(f"API Error: {e.message}")</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="rate-limiting-and-best-practices">Rate Limiting and Best Practices</h2>
|
||||
|
||||
<div class="warning">
|
||||
<h4>Rate Limiting</h4>
|
||||
<p>ETAPI implements rate limiting for authentication endpoints:</p>
|
||||
<ul>
|
||||
<li><strong>Login endpoint</strong>: Maximum 10 requests per IP per hour</li>
|
||||
<li><strong>Other endpoints</strong>: No specific rate limits, but excessive requests may be throttled</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Best Practices</h3>
|
||||
|
||||
<div class="success">
|
||||
<h4>1. Connection Pooling</h4>
|
||||
<p>Reuse HTTP connections for better performance:</p>
|
||||
<pre><code>import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.util.retry import Retry
|
||||
|
||||
session = requests.Session()
|
||||
retry = Retry(
|
||||
total=3,
|
||||
backoff_factor=0.3,
|
||||
status_forcelist=[500, 502, 503, 504]
|
||||
)
|
||||
adapter = HTTPAdapter(max_retries=retry)
|
||||
session.mount('http://', adapter)
|
||||
session.mount('https://', adapter)</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="success">
|
||||
<h4>2. Error Handling</h4>
|
||||
<p>Implement robust error handling with specific exception types for different error conditions.</p>
|
||||
</div>
|
||||
|
||||
<div class="success">
|
||||
<h4>3. Caching</h4>
|
||||
<p>Cache frequently accessed data to reduce API calls and improve performance.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="performance-considerations">Performance Considerations</h2>
|
||||
|
||||
<h3>1. Minimize API Calls</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code># Bad: Multiple calls (N+1 problem)
|
||||
note = api.get_note(note_id)
|
||||
for child_id in note['childNoteIds']:
|
||||
child = api.get_note(child_id) # N+1 problem
|
||||
process(child)
|
||||
|
||||
# Good: Batch processing
|
||||
note = api.get_note(note_id)
|
||||
children = api.search_notes(
|
||||
f"note.parents.noteId={note_id}",
|
||||
limit=1000
|
||||
)
|
||||
for child in children:
|
||||
process(child)</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>2. Use Appropriate Search Depth</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code># Limit search depth for better performance
|
||||
results = api.search_notes(
|
||||
"keyword",
|
||||
ancestor_note_id="root",
|
||||
ancestor_depth="lt3" # Only search 3 levels deep
|
||||
)</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<h4>Additional Resources</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/TriliumNext/Trilium">Trilium GitHub Repository</a></li>
|
||||
<li><a href="/apps/server/src/assets/etapi.openapi.yaml">OpenAPI Specification</a></li>
|
||||
<li><a href="https://triliumnext.github.io/Docs/Wiki/search.html">Trilium Search Documentation</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,810 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Internal API Reference</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; }
|
||||
h2 { color: #34495e; margin-top: 40px; border-bottom: 2px solid #ecf0f1; padding-bottom: 5px; }
|
||||
h3 { color: #7f8c8d; margin-top: 30px; }
|
||||
h4 { color: #95a5a6; margin-top: 25px; }
|
||||
pre {
|
||||
background: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 3px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', Monaco, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.endpoint-box {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
.method-get { border-left-color: #28a745; }
|
||||
.method-post { border-left-color: #007bff; }
|
||||
.method-put { border-left-color: #ffc107; }
|
||||
.method-patch { border-left-color: #fd7e14; }
|
||||
.method-delete { border-left-color: #dc3545; }
|
||||
.example-box {
|
||||
background: #f1f3f4;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #34495e;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
tr:nth-child(even) { background: #f8f9fa; }
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #f39c12;
|
||||
}
|
||||
.info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #74c0fc;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
.danger {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f1aeb5;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
.toc {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.toc ul {
|
||||
list-style-type: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.toc a {
|
||||
text-decoration: none;
|
||||
color: #495057;
|
||||
}
|
||||
.toc a:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
.websocket-box {
|
||||
background: #e8f4f8;
|
||||
border-left: 4px solid #9b59b6;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Internal API Reference</h1>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Table of Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#introduction">Introduction</a></li>
|
||||
<li><a href="#authentication-and-session-management">Authentication and Session Management</a></li>
|
||||
<li><a href="#core-api-endpoints">Core API Endpoints</a></li>
|
||||
<li><a href="#websocket-real-time-updates">WebSocket Real-time Updates</a></li>
|
||||
<li><a href="#file-operations">File Operations</a></li>
|
||||
<li><a href="#import-export-operations">Import/Export Operations</a></li>
|
||||
<li><a href="#when-to-use-internal-vs-etapi">When to Use Internal vs ETAPI</a></li>
|
||||
<li><a href="#security-considerations">Security Considerations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
<p>The Internal API is the primary interface used by the Trilium Notes client application to communicate with the server. While powerful and feature-complete, this API is primarily designed for internal use.</p>
|
||||
|
||||
<div class="danger">
|
||||
<h4>Important Notice</h4>
|
||||
<p><strong>For external integrations, please use <a href="ETAPI%20Complete%20Guide.html">ETAPI</a> instead.</strong> The Internal API:</p>
|
||||
<ul>
|
||||
<li>May change between versions without notice</li>
|
||||
<li>Requires session-based authentication with CSRF protection</li>
|
||||
<li>Is tightly coupled with the frontend application</li>
|
||||
<li>Has limited documentation and stability guarantees</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<strong>Base URL:</strong> <code>http://localhost:8080/api</code>
|
||||
</div>
|
||||
|
||||
<h3>Key Characteristics</h3>
|
||||
<ul>
|
||||
<li>Session-based authentication with cookies</li>
|
||||
<li>CSRF token protection for state-changing operations</li>
|
||||
<li>WebSocket support for real-time updates</li>
|
||||
<li>Full feature parity with the Trilium UI</li>
|
||||
<li>Complex request/response formats optimized for the client</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="authentication-and-session-management">Authentication and Session Management</h2>
|
||||
|
||||
<h3>Password Login</h3>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/api/login</code>
|
||||
<p>Authenticates user with password and creates a session.</p>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Request:</strong>
|
||||
<pre><code>const formData = new URLSearchParams();
|
||||
formData.append('password', 'your-password');
|
||||
|
||||
const response = await fetch('http://localhost:8080/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData,
|
||||
credentials: 'include' // Important for cookie handling
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Response:</strong>
|
||||
<pre><code>{
|
||||
"success": true,
|
||||
"message": "Login successful"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<p>The server sets a session cookie (<code>trilium.sid</code>) that must be included in subsequent requests.</p>
|
||||
|
||||
<h3>TOTP Authentication (2FA)</h3>
|
||||
|
||||
<p>If 2FA is enabled, include the TOTP token:</p>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>formData.append('password', 'your-password');
|
||||
formData.append('totpToken', '123456');</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Token Authentication</h3>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/api/login/token</code>
|
||||
<p>Generate an API token for programmatic access:</p>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const response = await fetch('http://localhost:8080/api/login/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: 'your-password',
|
||||
tokenName: 'My Integration'
|
||||
})
|
||||
});
|
||||
|
||||
const { authToken } = await response.json();
|
||||
// Use this token in Authorization header for future requests</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Protected Session</h3>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/api/login/protected</code>
|
||||
<p>Enter protected session to access encrypted notes:</p>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>await fetch('http://localhost:8080/api/login/protected', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: 'your-password'
|
||||
}),
|
||||
credentials: 'include'
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="core-api-endpoints">Core API Endpoints</h2>
|
||||
|
||||
<h3>Notes</h3>
|
||||
|
||||
<h4>Get Note</h4>
|
||||
|
||||
<div class="endpoint-box method-get">
|
||||
<strong>GET</strong> <code>/api/notes/{noteId}</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const response = await fetch('http://localhost:8080/api/notes/root', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const note = await response.json();</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<strong>Response:</strong>
|
||||
<pre><code>{
|
||||
"noteId": "root",
|
||||
"title": "Trilium Notes",
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"isProtected": false,
|
||||
"isDeleted": false,
|
||||
"dateCreated": "2024-01-01 00:00:00.000+0000",
|
||||
"dateModified": "2024-01-15 10:30:00.000+0000",
|
||||
"utcDateCreated": "2024-01-01 00:00:00.000Z",
|
||||
"utcDateModified": "2024-01-15 10:30:00.000Z",
|
||||
"parentBranches": [
|
||||
{
|
||||
"branchId": "root_root",
|
||||
"parentNoteId": "none",
|
||||
"prefix": null,
|
||||
"notePosition": 10
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"cssClass": "",
|
||||
"iconClass": "bx bx-folder"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Create Note</h4>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/api/notes/{parentNoteId}/children</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const response = await fetch('http://localhost:8080/api/notes/root/children', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'New Note',
|
||||
type: 'text',
|
||||
content: '<p>Note content</p>',
|
||||
isProtected: false
|
||||
}),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const { note, branch } = await response.json();</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Update Note</h4>
|
||||
|
||||
<div class="endpoint-box method-put">
|
||||
<strong>PUT</strong> <code>/api/notes/{noteId}</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>await fetch(`http://localhost:8080/api/notes/${noteId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'Updated Title',
|
||||
type: 'text',
|
||||
mime: 'text/html'
|
||||
}),
|
||||
credentials: 'include'
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Get Note Content</h4>
|
||||
|
||||
<div class="endpoint-box method-get">
|
||||
<strong>GET</strong> <code>/api/notes/{noteId}/content</code>
|
||||
<p>Returns the actual content of the note:</p>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const response = await fetch(`http://localhost:8080/api/notes/${noteId}/content`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const content = await response.text();</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Save Note Content</h4>
|
||||
|
||||
<div class="endpoint-box method-put">
|
||||
<strong>PUT</strong> <code>/api/notes/{noteId}/content</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>await fetch(`http://localhost:8080/api/notes/${noteId}/content`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: '<p>Updated content</p>',
|
||||
credentials: 'include'
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Tree Operations</h3>
|
||||
|
||||
<h4>Move Note</h4>
|
||||
|
||||
<div class="endpoint-box method-put">
|
||||
<strong>PUT</strong> <code>/api/branches/{branchId}/move</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>await fetch(`http://localhost:8080/api/branches/${branchId}/move`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
parentNoteId: 'newParentId',
|
||||
beforeNoteId: 'siblingNoteId' // optional, for positioning
|
||||
}),
|
||||
credentials: 'include'
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Clone Note</h4>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/api/notes/{noteId}/clone</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const response = await fetch(`http://localhost:8080/api/notes/${noteId}/clone`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
parentNoteId: 'targetParentId',
|
||||
prefix: 'Copy of '
|
||||
}),
|
||||
credentials: 'include'
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Search</h3>
|
||||
|
||||
<h4>Search Notes</h4>
|
||||
|
||||
<div class="endpoint-box method-get">
|
||||
<strong>GET</strong> <code>/api/search</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const params = new URLSearchParams({
|
||||
query: '#todo OR #task',
|
||||
fastSearch: 'false',
|
||||
includeArchivedNotes: 'false',
|
||||
ancestorNoteId: 'root',
|
||||
orderBy: 'relevancy',
|
||||
orderDirection: 'desc',
|
||||
limit: '50'
|
||||
});
|
||||
|
||||
const response = await fetch(`http://localhost:8080/api/search?${params}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const { results } = await response.json();</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="websocket-real-time-updates">WebSocket Real-time Updates</h2>
|
||||
|
||||
<div class="websocket-box">
|
||||
<p>The Internal API provides WebSocket connections for real-time synchronization and updates.</p>
|
||||
</div>
|
||||
|
||||
<h3>Connection Setup</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>class TriliumWebSocket {
|
||||
constructor() {
|
||||
this.ws = null;
|
||||
this.reconnectInterval = 5000;
|
||||
this.shouldReconnect = true;
|
||||
}
|
||||
|
||||
connect() {
|
||||
// WebSocket URL same as base URL but with ws:// protocol
|
||||
const wsUrl = 'ws://localhost:8080';
|
||||
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
this.sendPing();
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
this.handleMessage(message);
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('WebSocket disconnected');
|
||||
if (this.shouldReconnect) {
|
||||
setTimeout(() => this.connect(), this.reconnectInterval);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
switch (message.type) {
|
||||
case 'sync':
|
||||
this.handleSync(message.data);
|
||||
break;
|
||||
case 'entity-changes':
|
||||
this.handleEntityChanges(message.data);
|
||||
break;
|
||||
case 'refresh-tree':
|
||||
this.refreshTree();
|
||||
break;
|
||||
case 'create-note':
|
||||
this.handleNoteCreated(message.data);
|
||||
break;
|
||||
case 'update-note':
|
||||
this.handleNoteUpdated(message.data);
|
||||
break;
|
||||
case 'delete-note':
|
||||
this.handleNoteDeleted(message.data);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
|
||||
sendPing() {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify({ type: 'ping' }));
|
||||
setTimeout(() => this.sendPing(), 30000); // Ping every 30 seconds
|
||||
}
|
||||
}
|
||||
|
||||
send(type, data) {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify({ type, data }));
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Message Types</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Direction</th>
|
||||
<th>Description</th>
|
||||
<th>Data Format</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>sync</code></td>
|
||||
<td>Incoming</td>
|
||||
<td>Synchronization data</td>
|
||||
<td><code>{ entityChanges: [], lastSyncedPush: number }</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>entity-changes</code></td>
|
||||
<td>Incoming</td>
|
||||
<td>Entity modifications</td>
|
||||
<td><code>[{ entityName, entityId, action }]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>refresh-tree</code></td>
|
||||
<td>Incoming</td>
|
||||
<td>Tree structure changed</td>
|
||||
<td><code>None</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ping</code></td>
|
||||
<td>Outgoing</td>
|
||||
<td>Keep connection alive</td>
|
||||
<td><code>None</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>log-error</code></td>
|
||||
<td>Outgoing</td>
|
||||
<td>Log client error</td>
|
||||
<td><code>{ error, stack }</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="file-operations">File Operations</h2>
|
||||
|
||||
<h3>Upload File</h3>
|
||||
|
||||
<div class="endpoint-box method-post">
|
||||
<strong>POST</strong> <code>/api/notes/{noteId}/attachments/upload</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
|
||||
const response = await fetch(`/api/notes/${noteId}/attachments/upload`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: formData,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const attachment = await response.json();</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Download Attachment</h3>
|
||||
|
||||
<div class="endpoint-box method-get">
|
||||
<strong>GET</strong> <code>/api/attachments/{attachmentId}/download</code>
|
||||
</div>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>const response = await fetch(`/api/attachments/${attachmentId}/download`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'attachment.pdf';
|
||||
a.click();</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="when-to-use-internal-vs-etapi">When to Use Internal vs ETAPI</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Use Internal API When</th>
|
||||
<th>Use ETAPI When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Building custom Trilium clients</td>
|
||||
<td>Building external integrations</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Needing WebSocket real-time updates</td>
|
||||
<td>Creating automation scripts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Requiring full feature parity with the UI</td>
|
||||
<td>Developing third-party applications</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Working within the Trilium frontend environment</td>
|
||||
<td>Needing stable, documented API</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Accessing advanced features not available in ETAPI</td>
|
||||
<td>Working with different programming languages</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Feature Comparison</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Internal API</th>
|
||||
<th>ETAPI</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Authentication</strong></td>
|
||||
<td>Session/Cookie</td>
|
||||
<td>Token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>CSRF Protection</strong></td>
|
||||
<td>Required</td>
|
||||
<td>Not needed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>WebSocket</strong></td>
|
||||
<td>Yes</td>
|
||||
<td>No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Stability</strong></td>
|
||||
<td>May change</td>
|
||||
<td>Stable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Documentation</strong></td>
|
||||
<td>Limited</td>
|
||||
<td>Comprehensive</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Real-time updates</strong></td>
|
||||
<td>Yes</td>
|
||||
<td>No</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="security-considerations">Security Considerations</h2>
|
||||
|
||||
<h3>CSRF Protection</h3>
|
||||
|
||||
<p>All state-changing operations require a CSRF token:</p>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>// Get CSRF token from meta tag or API
|
||||
async function getCsrfToken() {
|
||||
const response = await fetch('/api/csrf-token', {
|
||||
credentials: 'include'
|
||||
});
|
||||
const { token } = await response.json();
|
||||
return token;
|
||||
}
|
||||
|
||||
// Use in requests
|
||||
const csrfToken = await getCsrfToken();
|
||||
|
||||
await fetch('/api/notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Session Management</h3>
|
||||
|
||||
<div class="example-box">
|
||||
<pre><code>class TriliumSession {
|
||||
constructor() {
|
||||
this.isAuthenticated = false;
|
||||
this.csrfToken = null;
|
||||
}
|
||||
|
||||
async login(password) {
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('password', password);
|
||||
|
||||
const response = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
this.isAuthenticated = true;
|
||||
this.csrfToken = await this.getCsrfToken();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async getCsrfToken() {
|
||||
const response = await fetch('/api/csrf-token', {
|
||||
credentials: 'include'
|
||||
});
|
||||
const { token } = await response.json();
|
||||
return token;
|
||||
}
|
||||
|
||||
async request(url, options = {}) {
|
||||
if (!this.isAuthenticated) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const headers = {
|
||||
...options.headers
|
||||
};
|
||||
|
||||
if (options.method && options.method !== 'GET') {
|
||||
headers['X-CSRF-Token'] = this.csrfToken;
|
||||
}
|
||||
|
||||
return fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="warning">
|
||||
<h4>Important Security Notes</h4>
|
||||
<ul>
|
||||
<li>Always include <code>credentials: 'include'</code> for session-based requests</li>
|
||||
<li>Use CSRF tokens for all state-changing operations</li>
|
||||
<li>Handle protected notes with proper authentication</li>
|
||||
<li>Validate all user input before sending to the API</li>
|
||||
<li>Use HTTPS in production environments</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<h4>Related Documentation</h4>
|
||||
<ul>
|
||||
<li><a href="ETAPI%20Complete%20Guide.html">ETAPI Complete Guide</a></li>
|
||||
<li><a href="WebSocket%20API.html">WebSocket API Documentation</a></li>
|
||||
<li><a href="Script%20API%20Cookbook.html">Script API Cookbook</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,937 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Script API Cookbook</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { color: #2c3e50; border-bottom: 3px solid #8e44ad; padding-bottom: 10px; }
|
||||
h2 { color: #34495e; margin-top: 40px; border-bottom: 2px solid #ecf0f1; padding-bottom: 5px; }
|
||||
h3 { color: #7f8c8d; margin-top: 30px; }
|
||||
h4 { color: #95a5a6; margin-top: 25px; }
|
||||
pre {
|
||||
background: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
border-left: 4px solid #8e44ad;
|
||||
}
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 3px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', Monaco, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.recipe-box {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #8e44ad;
|
||||
}
|
||||
.backend-script { border-left-color: #e74c3c; }
|
||||
.frontend-script { border-left-color: #3498db; }
|
||||
.example-box {
|
||||
background: #f1f3f4;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #8e44ad;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
tr:nth-child(even) { background: #f8f9fa; }
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #f39c12;
|
||||
}
|
||||
.info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #74c0fc;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
.success {
|
||||
background: #d4edda;
|
||||
border: 1px solid #51cf66;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
.toc {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.toc ul {
|
||||
list-style-type: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.toc a {
|
||||
text-decoration: none;
|
||||
color: #495057;
|
||||
}
|
||||
.toc a:hover {
|
||||
color: #8e44ad;
|
||||
}
|
||||
.use-case {
|
||||
background: #f8f4ff;
|
||||
border-left: 4px solid #8e44ad;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Script API Cookbook</h1>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Table of Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#introduction">Introduction</a></li>
|
||||
<li><a href="#backend-script-recipes">Backend Script Recipes</a></li>
|
||||
<li><a href="#frontend-script-recipes">Frontend Script Recipes</a></li>
|
||||
<li><a href="#common-patterns">Common Patterns</a></li>
|
||||
<li><a href="#note-manipulation">Note Manipulation</a></li>
|
||||
<li><a href="#attribute-operations">Attribute Operations</a></li>
|
||||
<li><a href="#search-and-filtering">Search and Filtering</a></li>
|
||||
<li><a href="#automation-examples">Automation Examples</a></li>
|
||||
<li><a href="#integration-with-external-services">Integration with External Services</a></li>
|
||||
<li><a href="#best-practices">Best Practices</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
<p>Trilium's Script API provides powerful automation capabilities through JavaScript code that runs either on the backend (Node.js) or frontend (browser). This cookbook contains practical recipes and patterns for common scripting tasks.</p>
|
||||
|
||||
<h3>Script Types</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Environment</th>
|
||||
<th>Access</th>
|
||||
<th>Use Cases</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Backend Script</strong></td>
|
||||
<td>Node.js</td>
|
||||
<td>Full database, file system, network</td>
|
||||
<td>Automation, data processing, integrations</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Frontend Script</strong></td>
|
||||
<td>Browser</td>
|
||||
<td>UI manipulation, user interaction</td>
|
||||
<td>Custom widgets, UI enhancements</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Custom Widget</strong></td>
|
||||
<td>Browser</td>
|
||||
<td>Widget lifecycle, note context</td>
|
||||
<td>Interactive components, visualizations</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Basic Script Structure</h3>
|
||||
|
||||
<div class="recipe-box backend-script">
|
||||
<h4>Backend Script:</h4>
|
||||
<pre><code>// Access to api object is automatic
|
||||
const note = await api.getNoteWithLabel('todoList');
|
||||
const children = await note.getChildNotes();
|
||||
|
||||
// Return value becomes script output
|
||||
return {
|
||||
noteTitle: note.title,
|
||||
childCount: children.length
|
||||
};</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="recipe-box frontend-script">
|
||||
<h4>Frontend Script:</h4>
|
||||
<pre><code>// Access to api object is automatic
|
||||
api.showMessage('Script executed!');
|
||||
|
||||
// Manipulate UI
|
||||
const $button = $('<button>').text('Click Me').click(() => {
|
||||
api.showMessage('Button clicked!');
|
||||
});
|
||||
|
||||
$('body').append($button);</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="backend-script-recipes">Backend Script Recipes</h2>
|
||||
|
||||
<h3>1. Daily Note Generator</h3>
|
||||
|
||||
<div class="use-case">
|
||||
<strong>Use Case:</strong> Automatically create daily notes with template content
|
||||
</div>
|
||||
|
||||
<div class="recipe-box backend-script">
|
||||
<pre><code>// #run=hourly
|
||||
|
||||
async function createDailyNote() {
|
||||
const today = api.dayjs().format('YYYY-MM-DD');
|
||||
const dayNote = await api.getDayNote(today);
|
||||
|
||||
// Check if content already exists
|
||||
const content = await dayNote.getContent();
|
||||
if (content && content.length > 100) {
|
||||
return; // Already has content
|
||||
}
|
||||
|
||||
// Get template
|
||||
const template = await api.getNoteWithLabel('dailyTemplate');
|
||||
if (!template) {
|
||||
await dayNote.setContent(`
|
||||
<h2>📅 ${api.dayjs().format('dddd, MMMM D, YYYY')}</h2>
|
||||
|
||||
<h3>☀️ Morning Routine</h3>
|
||||
<ul>
|
||||
<li>[ ] Morning meditation</li>
|
||||
<li>[ ] Exercise</li>
|
||||
<li>[ ] Review daily goals</li>
|
||||
</ul>
|
||||
|
||||
<h3>📋 Today's Tasks</h3>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
|
||||
<h3>📝 Notes</h3>
|
||||
<p></p>
|
||||
|
||||
<h3>🌙 Evening Reflection</h3>
|
||||
<p></p>
|
||||
`);
|
||||
} else {
|
||||
const templateContent = await template.getContent();
|
||||
await dayNote.setContent(templateContent);
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
await dayNote.setLabel('type', 'daily');
|
||||
await dayNote.setLabel('created', api.dayjs().format());
|
||||
|
||||
api.log(`Daily note created for ${today}`);
|
||||
}
|
||||
|
||||
await createDailyNote();</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>2. Note Statistics Collector</h3>
|
||||
|
||||
<div class="use-case">
|
||||
<strong>Use Case:</strong> Collect and display statistics about your notes
|
||||
</div>
|
||||
|
||||
<div class="recipe-box backend-script">
|
||||
<pre><code>async function collectStatistics() {
|
||||
const stats = {
|
||||
totalNotes: 0,
|
||||
notesByType: {},
|
||||
notesByMonth: {},
|
||||
largestNotes: [],
|
||||
recentlyModified: [],
|
||||
tagCloud: {}
|
||||
};
|
||||
|
||||
// Get all notes
|
||||
const notes = await api.searchForNotes('');
|
||||
stats.totalNotes = notes.length;
|
||||
|
||||
for (const note of notes) {
|
||||
// Count by type
|
||||
stats.notesByType[note.type] = (stats.notesByType[note.type] || 0) + 1;
|
||||
|
||||
// Count by creation month
|
||||
const month = api.dayjs(note.utcDateCreated).format('YYYY-MM');
|
||||
stats.notesByMonth[month] = (stats.notesByMonth[month] || 0) + 1;
|
||||
|
||||
// Track largest notes
|
||||
const content = await note.getContent();
|
||||
if (content) {
|
||||
stats.largestNotes.push({
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
size: content.length
|
||||
});
|
||||
}
|
||||
|
||||
// Collect labels for tag cloud
|
||||
const labels = await note.getLabels();
|
||||
for (const label of labels) {
|
||||
if (!label.name.startsWith('child:')) {
|
||||
stats.tagCloud[label.name] = (stats.tagCloud[label.name] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort largest notes
|
||||
stats.largestNotes.sort((a, b) => b.size - a.size);
|
||||
stats.largestNotes = stats.largestNotes.slice(0, 10);
|
||||
|
||||
// Create or update statistics note
|
||||
let statsNote = await api.getNoteWithLabel('statistics');
|
||||
if (!statsNote) {
|
||||
statsNote = await api.createTextNote('root', 'Statistics', '');
|
||||
await statsNote.setLabel('statistics');
|
||||
}
|
||||
|
||||
// Generate report
|
||||
const report = `
|
||||
<h1>📊 Note Statistics</h1>
|
||||
<p>Generated: ${api.dayjs().format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<ul>
|
||||
<li>Total Notes: <strong>${stats.totalNotes}</strong></li>
|
||||
<li>Note Types: ${Object.entries(stats.notesByType)
|
||||
.map(([type, count]) => `${type} (${count})`)
|
||||
.join(', ')}</li>
|
||||
</ul>
|
||||
|
||||
<h2>Largest Notes</h2>
|
||||
<ol>
|
||||
${stats.largestNotes.map(n =>
|
||||
`<li><a href="#root/${n.noteId}">${n.title}</a> - ${(n.size / 1024).toFixed(1)} KB</li>`
|
||||
).join('')}
|
||||
</ol>
|
||||
|
||||
<h2>Top Tags</h2>
|
||||
<div class="tag-cloud">
|
||||
${Object.entries(stats.tagCloud)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 20)
|
||||
.map(([tag, count]) =>
|
||||
`<span style="font-size: ${Math.min(200, 100 + count * 5)}%">#${tag} (${count})</span>`
|
||||
).join(' ')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
await statsNote.setContent(report);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
return await collectStatistics();</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>3. Task Aggregator</h3>
|
||||
|
||||
<div class="use-case">
|
||||
<strong>Use Case:</strong> Collect all tasks from different notes and create a dashboard
|
||||
</div>
|
||||
|
||||
<div class="recipe-box backend-script">
|
||||
<pre><code>async function aggregateTasks() {
|
||||
// Find all notes with todos
|
||||
const todoNotes = await api.searchForNotes('#todo OR #task OR content:"[ ]" OR content:"[x]"');
|
||||
|
||||
const tasks = {
|
||||
pending: [],
|
||||
completed: [],
|
||||
overdue: []
|
||||
};
|
||||
|
||||
for (const note of todoNotes) {
|
||||
const content = await note.getContent();
|
||||
if (!content) continue;
|
||||
|
||||
// Parse checkbox tasks
|
||||
const checkboxRegex = /\[([ x])\]\s*(.+?)(?=\n|\<|$)/gi;
|
||||
let match;
|
||||
|
||||
while ((match = checkboxRegex.exec(content)) !== null) {
|
||||
const isCompleted = match[1] === 'x';
|
||||
const taskText = match[2].replace(/<[^>]*>/g, ''); // Strip HTML
|
||||
|
||||
const task = {
|
||||
noteId: note.noteId,
|
||||
noteTitle: note.title,
|
||||
text: taskText,
|
||||
completed: isCompleted
|
||||
};
|
||||
|
||||
// Check for due date
|
||||
const dueDateLabel = await note.getLabel('dueDate');
|
||||
if (dueDateLabel) {
|
||||
task.dueDate = dueDateLabel.value;
|
||||
const dueDate = api.dayjs(dueDateLabel.value);
|
||||
if (!isCompleted && dueDate.isBefore(api.dayjs())) {
|
||||
tasks.overdue.push(task);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCompleted) {
|
||||
tasks.completed.push(task);
|
||||
} else {
|
||||
tasks.pending.push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update task dashboard
|
||||
let dashboard = await api.getNoteWithLabel('taskDashboard');
|
||||
if (!dashboard) {
|
||||
dashboard = await api.createTextNote('root', '📋 Task Dashboard', '');
|
||||
await dashboard.setLabel('taskDashboard');
|
||||
}
|
||||
|
||||
const dashboardContent = `
|
||||
<h1>📋 Task Dashboard</h1>
|
||||
<p>Last updated: ${api.dayjs().format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
|
||||
<h2>⚠️ Overdue (${tasks.overdue.length})</h2>
|
||||
<ul>
|
||||
${tasks.overdue.map(t =>
|
||||
`<li style="color: red;">
|
||||
<strong>${t.text}</strong>
|
||||
(Due: ${api.dayjs(t.dueDate).format('MMM D')})
|
||||
- <a href="#root/${t.noteId}">${t.noteTitle}</a>
|
||||
</li>`
|
||||
).join('')}
|
||||
</ul>
|
||||
|
||||
<h2>📌 Pending (${tasks.pending.length})</h2>
|
||||
<ul>
|
||||
${tasks.pending.slice(0, 20).map(t =>
|
||||
`<li>
|
||||
${t.text}
|
||||
${t.dueDate ? `(Due: ${api.dayjs(t.dueDate).format('MMM D')})` : ''}
|
||||
- <a href="#root/${t.noteId}">${t.noteTitle}</a>
|
||||
</li>`
|
||||
).join('')}
|
||||
</ul>
|
||||
${tasks.pending.length > 20 ? `<p><em>...and ${tasks.pending.length - 20} more</em></p>` : ''}
|
||||
|
||||
<h2>✅ Recently Completed (${tasks.completed.length})</h2>
|
||||
<ul>
|
||||
${tasks.completed.slice(0, 10).map(t =>
|
||||
`<li style="text-decoration: line-through; opacity: 0.7;">
|
||||
${t.text} - <a href="#root/${t.noteId}">${t.noteTitle}</a>
|
||||
</li>`
|
||||
).join('')}
|
||||
</ul>
|
||||
`;
|
||||
|
||||
await dashboard.setContent(dashboardContent);
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
return await aggregateTasks();</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="frontend-script-recipes">Frontend Script Recipes</h2>
|
||||
|
||||
<h3>4. Quick Note Creator</h3>
|
||||
|
||||
<div class="use-case">
|
||||
<strong>Use Case:</strong> Add a floating button to quickly create notes from anywhere in the UI
|
||||
</div>
|
||||
|
||||
<div class="recipe-box frontend-script">
|
||||
<pre><code>// Create floating button
|
||||
const $button = $(`
|
||||
<div id="quick-note-btn" style="
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #4CAF50;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
z-index: 10000;
|
||||
color: white;
|
||||
font-size: 30px;
|
||||
">+</div>
|
||||
`);
|
||||
|
||||
// Create modal
|
||||
const $modal = $(`
|
||||
<div id="quick-note-modal" style="
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
||||
z-index: 10001;
|
||||
min-width: 400px;
|
||||
">
|
||||
<h3>Quick Note</h3>
|
||||
<input type="text" id="quick-note-title" placeholder="Title..." style="
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
">
|
||||
<textarea id="quick-note-content" placeholder="Content..." style="
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
resize: vertical;
|
||||
"></textarea>
|
||||
<div>
|
||||
<button id="quick-note-save" style="
|
||||
padding: 10px 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
">Save</button>
|
||||
<button id="quick-note-cancel" style="
|
||||
padding: 10px 20px;
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Add to page
|
||||
$('body').append($button, $modal);
|
||||
|
||||
// Handle button click
|
||||
$button.click(() => {
|
||||
$modal.show();
|
||||
$('#quick-note-title').focus();
|
||||
});
|
||||
|
||||
// Handle save
|
||||
$('#quick-note-save').click(async () => {
|
||||
const title = $('#quick-note-title').val() || 'Quick Note';
|
||||
const content = $('#quick-note-content').val() || '';
|
||||
|
||||
// Get current note or use inbox
|
||||
const currentNote = api.getActiveContextNote();
|
||||
const parentNoteId = currentNote ? currentNote.noteId : (await api.getDayNote()).noteId;
|
||||
|
||||
// Create note
|
||||
const { note } = await api.runOnBackend(async (parentId, noteTitle, noteContent) => {
|
||||
const parent = await api.getNote(parentId);
|
||||
const newNote = await api.createNote(parent, noteTitle, noteContent, 'text');
|
||||
return { note: newNote.getPojo() };
|
||||
}, [parentNoteId, title, `<h2>${title}</h2><p>${content}</p>`]);
|
||||
|
||||
api.showMessage(`Note "${title}" created!`);
|
||||
|
||||
// Clear and close
|
||||
$('#quick-note-title').val('');
|
||||
$('#quick-note-content').val('');
|
||||
$modal.hide();
|
||||
|
||||
// Navigate to new note
|
||||
await api.activateNewNote(note.noteId);
|
||||
});
|
||||
|
||||
// Handle cancel
|
||||
$('#quick-note-cancel').click(() => {
|
||||
$modal.hide();
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
$(document).keydown((e) => {
|
||||
// Ctrl+Shift+N to open quick note
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'N') {
|
||||
e.preventDefault();
|
||||
$button.click();
|
||||
}
|
||||
|
||||
// Escape to close
|
||||
if (e.key === 'Escape' && $modal.is(':visible')) {
|
||||
$modal.hide();
|
||||
}
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>5. Markdown Preview Toggle</h3>
|
||||
|
||||
<div class="use-case">
|
||||
<strong>Use Case:</strong> Add live markdown preview for text notes
|
||||
</div>
|
||||
|
||||
<div class="recipe-box frontend-script">
|
||||
<pre><code>// Create preview pane
|
||||
const $previewPane = $(`
|
||||
<div id="markdown-preview" style="
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 10px;
|
||||
width: 45%;
|
||||
height: calc(100% - 60px);
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
z-index: 100;
|
||||
">
|
||||
<div id="preview-content"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Create toggle button
|
||||
const $toggleBtn = $(`
|
||||
<button id="preview-toggle" style="
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 8px 15px;
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
z-index: 101;
|
||||
">
|
||||
<i class="bx bx-show"></i> Preview
|
||||
</button>
|
||||
`);
|
||||
|
||||
// Add to note detail
|
||||
$('.note-detail-text').css('position', 'relative').append($previewPane, $toggleBtn);
|
||||
|
||||
let previewVisible = false;
|
||||
|
||||
// Load markdown library (simplified conversion)
|
||||
const convertToMarkdown = (html) => {
|
||||
return html
|
||||
.replace(/<h1[^>]*>(.*?)<\/h1>/g, '# $1\n')
|
||||
.replace(/<h2[^>]*>(.*?)<\/h2>/g, '## $1\n')
|
||||
.replace(/<h3[^>]*>(.*?)<\/h3>/g, '### $1\n')
|
||||
.replace(/<p[^>]*>(.*?)<\/p>/g, '$1\n\n')
|
||||
.replace(/<strong[^>]*>(.*?)<\/strong>/g, '**$1**')
|
||||
.replace(/<em[^>]*>(.*?)<\/em>/g, '*$1*')
|
||||
.replace(/<code[^>]*>(.*?)<\/code>/g, '`$1`')
|
||||
.replace(/<ul[^>]*>/g, '')
|
||||
.replace(/<\/ul>/g, '\n')
|
||||
.replace(/<li[^>]*>(.*?)<\/li>/g, '- $1\n')
|
||||
.replace(/<br[^>]*>/g, '\n')
|
||||
.replace(/<[^>]+>/g, ''); // Remove remaining HTML tags
|
||||
};
|
||||
|
||||
// Toggle preview
|
||||
$toggleBtn.click(() => {
|
||||
previewVisible = !previewVisible;
|
||||
|
||||
if (previewVisible) {
|
||||
$previewPane.show();
|
||||
$('.note-detail-text .note-detail-editable').css('width', '50%');
|
||||
$toggleBtn.html('<i class="bx bx-hide"></i> Hide');
|
||||
updatePreview();
|
||||
} else {
|
||||
$previewPane.hide();
|
||||
$('.note-detail-text .note-detail-editable').css('width', '100%');
|
||||
$toggleBtn.html('<i class="bx bx-show"></i> Preview');
|
||||
}
|
||||
});
|
||||
|
||||
// Update preview function
|
||||
async function updatePreview() {
|
||||
if (!previewVisible) return;
|
||||
|
||||
const content = await api.getActiveContextTextEditor().getContent();
|
||||
const markdown = convertToMarkdown(content);
|
||||
|
||||
// Simple markdown to HTML conversion
|
||||
const html = markdown
|
||||
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>')
|
||||
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
|
||||
.replace(/\n\n/g, '</p><p>')
|
||||
.replace(/^(?!<[h|u])(.+)$/gm, '<p>$1</p>');
|
||||
|
||||
$('#preview-content').html(html);
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="integration-with-external-services">Integration with External Services</h2>
|
||||
|
||||
<h3>6. GitHub Integration</h3>
|
||||
|
||||
<div class="use-case">
|
||||
<strong>Use Case:</strong> Sync GitHub issues with Trilium notes
|
||||
</div>
|
||||
|
||||
<div class="recipe-box backend-script">
|
||||
<pre><code>// Requires axios library
|
||||
const axios = require('axios');
|
||||
|
||||
class GitHubSync {
|
||||
constructor(token, repo) {
|
||||
this.token = token;
|
||||
this.repo = repo; // format: "owner/repo"
|
||||
this.apiBase = 'https://api.github.com';
|
||||
}
|
||||
|
||||
async getIssues(state = 'open') {
|
||||
const response = await axios.get(`${this.apiBase}/repos/${this.repo}/issues`, {
|
||||
headers: {
|
||||
'Authorization': `token ${this.token}`,
|
||||
'Accept': 'application/vnd.github.v3+json'
|
||||
},
|
||||
params: { state }
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async syncIssuesToNotes() {
|
||||
// Get or create GitHub folder
|
||||
let githubFolder = await api.getNoteWithLabel('githubSync');
|
||||
if (!githubFolder) {
|
||||
githubFolder = await api.createTextNote('root', 'GitHub Issues', '');
|
||||
await githubFolder.setLabel('githubSync');
|
||||
}
|
||||
|
||||
const issues = await this.getIssues();
|
||||
const syncedNotes = [];
|
||||
|
||||
for (const issue of issues) {
|
||||
// Check if issue note already exists
|
||||
let issueNote = await api.getNoteWithLabel(`github:issue:${issue.number}`);
|
||||
|
||||
const content = `
|
||||
<h1>${issue.title}</h1>
|
||||
|
||||
<table>
|
||||
<tr><th>Issue #</th><td>${issue.number}</td></tr>
|
||||
<tr><th>State</th><td>${issue.state}</td></tr>
|
||||
<tr><th>Author</th><td>${issue.user.login}</td></tr>
|
||||
<tr><th>Created</th><td>${api.dayjs(issue.created_at).format('YYYY-MM-DD HH:mm')}</td></tr>
|
||||
<tr><th>Labels</th><td>${issue.labels.map(l => l.name).join(', ')}</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>Description</h2>
|
||||
<div style="background: #f5f5f5; padding: 10px; border-radius: 5px;">
|
||||
${issue.body || 'No description'}
|
||||
</div>
|
||||
|
||||
<h2>Links</h2>
|
||||
<ul>
|
||||
<li><a href="${issue.html_url}">View on GitHub</a></li>
|
||||
<li><a href="${issue.url}">API URL</a></li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
if (!issueNote) {
|
||||
// Create new note
|
||||
issueNote = await api.createNote(
|
||||
githubFolder,
|
||||
`#${issue.number}: ${issue.title}`,
|
||||
content
|
||||
);
|
||||
await issueNote.setLabel(`github:issue:${issue.number}`);
|
||||
} else {
|
||||
// Update existing note
|
||||
await issueNote.setContent(content);
|
||||
}
|
||||
|
||||
// Set labels based on issue state and labels
|
||||
await issueNote.setLabel('githubIssue');
|
||||
await issueNote.setLabel('state', issue.state);
|
||||
|
||||
for (const label of issue.labels) {
|
||||
await issueNote.setLabel(`gh:${label.name}`);
|
||||
}
|
||||
|
||||
syncedNotes.push({
|
||||
noteId: issueNote.noteId,
|
||||
issueNumber: issue.number,
|
||||
title: issue.title
|
||||
});
|
||||
}
|
||||
|
||||
api.log(`Synced ${syncedNotes.length} GitHub issues`);
|
||||
return syncedNotes;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const github = new GitHubSync(
|
||||
process.env.GITHUB_TOKEN || 'your-token',
|
||||
'your-org/your-repo'
|
||||
);
|
||||
|
||||
// Sync issues to notes
|
||||
const synced = await github.syncIssuesToNotes();
|
||||
return synced;</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="best-practices">Best Practices</h2>
|
||||
|
||||
<h3>Error Handling</h3>
|
||||
|
||||
<div class="success">
|
||||
<p>Always wrap scripts in try-catch blocks:</p>
|
||||
<pre><code>async function safeScriptExecution() {
|
||||
try {
|
||||
// Your script code here
|
||||
const result = await riskyOperation();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result
|
||||
};
|
||||
} catch (error) {
|
||||
api.log(`Error in script: ${error.message}`, 'error');
|
||||
|
||||
// Create error report note
|
||||
const errorNote = await api.createTextNote(
|
||||
'root',
|
||||
`Script Error - ${api.dayjs().format('YYYY-MM-DD HH:mm:ss')}`,
|
||||
`
|
||||
<h1>Script Error</h1>
|
||||
<p><strong>Error:</strong> ${error.message}</p>
|
||||
<p><strong>Stack:</strong></p>
|
||||
<pre>${error.stack}</pre>
|
||||
<p><strong>Script:</strong> ${api.currentNote.title}</p>
|
||||
`
|
||||
);
|
||||
|
||||
await errorNote.setLabel('scriptError');
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return await safeScriptExecution();</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Performance Optimization</h3>
|
||||
|
||||
<div class="success">
|
||||
<p>Use batch operations and caching:</p>
|
||||
<pre><code>class OptimizedNoteProcessor {
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
async processNotes(noteIds) {
|
||||
// Batch fetch notes
|
||||
const notes = await Promise.all(
|
||||
noteIds.map(id => this.getCachedNote(id))
|
||||
);
|
||||
|
||||
// Process in chunks to avoid memory issues
|
||||
const chunkSize = 100;
|
||||
const results = [];
|
||||
|
||||
for (let i = 0; i < notes.length; i += chunkSize) {
|
||||
const chunk = notes.slice(i, i + chunkSize);
|
||||
const chunkResults = await Promise.all(
|
||||
chunk.map(note => this.processNote(note))
|
||||
);
|
||||
results.push(...chunkResults);
|
||||
|
||||
// Allow other operations
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getCachedNote(noteId) {
|
||||
if (!this.cache.has(noteId)) {
|
||||
const note = await api.getNote(noteId);
|
||||
this.cache.set(noteId, note);
|
||||
}
|
||||
return this.cache.get(noteId);
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="warning">
|
||||
<h4>Key Takeaways</h4>
|
||||
<ol>
|
||||
<li><strong>Use Backend Scripts</strong> for data processing, automation, and integrations</li>
|
||||
<li><strong>Use Frontend Scripts</strong> for UI enhancements and user interactions</li>
|
||||
<li><strong>Always handle errors</strong> gracefully and provide meaningful feedback</li>
|
||||
<li><strong>Optimize performance</strong> with caching and batch operations</li>
|
||||
<li><strong>Organize complex scripts</strong> into modules for reusability</li>
|
||||
<li><strong>Test your scripts</strong> to ensure reliability</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<h4>Related Documentation</h4>
|
||||
<ul>
|
||||
<li><a href="https://triliumnext.github.io/Docs/api/Backend_Script_API.html">Backend Script API Reference</a></li>
|
||||
<li><a href="https://triliumnext.github.io/Docs/api/Frontend_Script_API.html">Frontend Script API Reference</a></li>
|
||||
<li><a href="Custom%20Widget%20Development.html">Custom Widget Development</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,122 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Trilium Architecture Documentation</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
||||
h1 { color: #2c3e50; }
|
||||
h2 { color: #34495e; margin-top: 30px; }
|
||||
ul { line-height: 1.8; }
|
||||
a { color: #3498db; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.overview { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }
|
||||
.quick-start { background: #e8f5e9; padding: 20px; border-radius: 8px; margin: 20px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Trilium Architecture Documentation</h1>
|
||||
|
||||
<p>This comprehensive guide documents the architecture of Trilium Notes, providing developers with detailed information about the system's core components, data flow, and design patterns.</p>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
<ol>
|
||||
<li><a href="Three-Layer-Cache-System.html">Three-Layer Cache System</a></li>
|
||||
<li><a href="Entity-System.html">Entity System</a></li>
|
||||
<li><a href="Widget-Based-UI-Architecture.html">Widget-Based UI Architecture</a></li>
|
||||
<li><a href="API-Architecture.html">API Architecture</a></li>
|
||||
<li><a href="Monorepo-Structure.html">Monorepo Structure</a></li>
|
||||
</ol>
|
||||
|
||||
<div class="overview">
|
||||
<h2>Overview</h2>
|
||||
<p>Trilium Notes is built as a TypeScript monorepo using NX, featuring a sophisticated architecture that balances performance, flexibility, and maintainability. The system is designed around several key architectural patterns:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Three-layer caching system</strong> for optimal performance across backend, frontend, and shared content</li>
|
||||
<li><strong>Entity-based data model</strong> supporting hierarchical note structures with multiple parent relationships</li>
|
||||
<li><strong>Widget-based UI architecture</strong> enabling modular and extensible interface components</li>
|
||||
<li><strong>Multiple API layers</strong> for internal operations, external integrations, and real-time synchronization</li>
|
||||
<li><strong>Monorepo structure</strong> facilitating code sharing and consistent development patterns</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="quick-start">
|
||||
<h2>Quick Start for Developers</h2>
|
||||
|
||||
<p>If you're new to Trilium development, start with these sections:</p>
|
||||
|
||||
<ol>
|
||||
<li><a href="Monorepo-Structure.html">Monorepo Structure</a> - Understand the project organization</li>
|
||||
<li><a href="Entity-System.html">Entity System</a> - Learn about the core data model</li>
|
||||
<li><a href="Three-Layer-Cache-System.html">Three-Layer Cache System</a> - Understand data flow and caching</li>
|
||||
</ol>
|
||||
|
||||
<p>For UI development, refer to:</p>
|
||||
<ul>
|
||||
<li><a href="Widget-Based-UI-Architecture.html">Widget-Based UI Architecture</a></li>
|
||||
</ul>
|
||||
|
||||
<p>For API integration, see:</p>
|
||||
<ul>
|
||||
<li><a href="API-Architecture.html">API Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Architecture Principles</h2>
|
||||
|
||||
<h3>Performance First</h3>
|
||||
<ul>
|
||||
<li>Lazy loading of note content</li>
|
||||
<li>Efficient caching at multiple layers</li>
|
||||
<li>Optimized database queries with prepared statements</li>
|
||||
</ul>
|
||||
|
||||
<h3>Flexibility</h3>
|
||||
<ul>
|
||||
<li>Support for multiple note types</li>
|
||||
<li>Extensible through scripting</li>
|
||||
<li>Plugin architecture for UI widgets</li>
|
||||
</ul>
|
||||
|
||||
<h3>Data Integrity</h3>
|
||||
<ul>
|
||||
<li>Transactional database operations</li>
|
||||
<li>Revision history for all changes</li>
|
||||
<li>Synchronization conflict resolution</li>
|
||||
</ul>
|
||||
|
||||
<h3>Security</h3>
|
||||
<ul>
|
||||
<li>Per-note encryption</li>
|
||||
<li>Protected sessions</li>
|
||||
<li>API authentication tokens</li>
|
||||
</ul>
|
||||
|
||||
<h2>Development Workflow</h2>
|
||||
|
||||
<ol>
|
||||
<li><strong>Setup Development Environment</strong>
|
||||
<pre><code>pnpm install
|
||||
pnpm run server:start</code></pre>
|
||||
</li>
|
||||
|
||||
<li><strong>Make Changes</strong>
|
||||
<ul>
|
||||
<li>Backend changes in <code>apps/server/src/</code></li>
|
||||
<li>Frontend changes in <code>apps/client/src/</code></li>
|
||||
<li>Shared code in <code>packages/</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Test Your Changes</strong>
|
||||
<pre><code>pnpm test:all
|
||||
pnpm nx run <project>:lint</code></pre>
|
||||
</li>
|
||||
|
||||
<li><strong>Build for Production</strong>
|
||||
<pre><code>pnpm nx build server
|
||||
pnpm nx build client</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,367 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>API Architecture</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; }
|
||||
h1 { color: #2c3e50; }
|
||||
h2 { color: #34495e; margin-top: 30px; }
|
||||
h3 { color: #7f8c8d; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
.api-layer { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }
|
||||
.internal-api { border-left: 4px solid #3498db; }
|
||||
.etapi { border-left: 4px solid #e67e22; }
|
||||
.websocket { border-left: 4px solid #9b59b6; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
||||
th { background: #f8f9fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>API Architecture</h1>
|
||||
|
||||
<p>Trilium provides multiple API layers for different use cases: Internal API for frontend-backend communication, ETAPI for external integrations, and WebSocket for real-time synchronization.</p>
|
||||
|
||||
<h2>API Layers Overview</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>API Layer</th>
|
||||
<th>Purpose</th>
|
||||
<th>Authentication</th>
|
||||
<th>Primary Users</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Internal API</td>
|
||||
<td>Frontend-backend communication</td>
|
||||
<td>Session-based</td>
|
||||
<td>Web/Desktop clients</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ETAPI</td>
|
||||
<td>External integrations</td>
|
||||
<td>Token-based</td>
|
||||
<td>Third-party apps, scripts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WebSocket</td>
|
||||
<td>Real-time sync</td>
|
||||
<td>Session/Token</td>
|
||||
<td>All clients</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="api-layer internal-api">
|
||||
<h2>Internal API</h2>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/routes/api/</code></p>
|
||||
|
||||
<p>The Internal API handles communication between Trilium's frontend and backend, providing full access to application functionality.</p>
|
||||
|
||||
<h3>Key Endpoints</h3>
|
||||
|
||||
<h4>Note Operations</h4>
|
||||
<pre><code>// Get note with content
|
||||
GET /api/notes/:noteId
|
||||
|
||||
// Update note
|
||||
PUT /api/notes/:noteId
|
||||
Body: {
|
||||
title?: string,
|
||||
content?: string,
|
||||
type?: string,
|
||||
mime?: string
|
||||
}
|
||||
|
||||
// Create note
|
||||
POST /api/notes/:parentNoteId/children
|
||||
Body: {
|
||||
title: string,
|
||||
type: string,
|
||||
content?: string,
|
||||
position?: number
|
||||
}
|
||||
|
||||
// Delete note
|
||||
DELETE /api/notes/:noteId</code></pre>
|
||||
|
||||
<h4>Tree Operations</h4>
|
||||
<pre><code>// Get tree structure
|
||||
GET /api/tree
|
||||
Query: {
|
||||
subTreeNoteId?: string,
|
||||
includeAttributes?: boolean
|
||||
}
|
||||
|
||||
// Move branch
|
||||
PUT /api/branches/:branchId/move
|
||||
Body: {
|
||||
parentNoteId: string,
|
||||
position: number
|
||||
}</code></pre>
|
||||
|
||||
<h4>Search Operations</h4>
|
||||
<pre><code>// Execute search
|
||||
GET /api/search
|
||||
Query: {
|
||||
query: string,
|
||||
fastSearch?: boolean,
|
||||
includeArchivedNotes?: boolean
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="api-layer etapi">
|
||||
<h2>ETAPI (External API)</h2>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/etapi/</code></p>
|
||||
|
||||
<p>ETAPI provides a stable, versioned API for external applications and scripts to interact with Trilium.</p>
|
||||
|
||||
<h3>Authentication</h3>
|
||||
<pre><code>// Creating ETAPI token
|
||||
POST /etapi/auth/login
|
||||
Body: {
|
||||
username: string,
|
||||
password: string
|
||||
}
|
||||
Response: {
|
||||
authToken: string
|
||||
}
|
||||
|
||||
// Using token in requests
|
||||
GET /etapi/notes/:noteId
|
||||
Headers: {
|
||||
Authorization: "authToken"
|
||||
}</code></pre>
|
||||
|
||||
<h3>Key Endpoints</h3>
|
||||
|
||||
<h4>Note CRUD Operations</h4>
|
||||
<pre><code>// Create note
|
||||
POST /etapi/notes
|
||||
Body: {
|
||||
parentNoteId: string,
|
||||
title: string,
|
||||
type: string,
|
||||
content?: string
|
||||
}
|
||||
|
||||
// Get note
|
||||
GET /etapi/notes/:noteId
|
||||
|
||||
// Update note content
|
||||
PUT /etapi/notes/:noteId/content
|
||||
Body: string | Buffer
|
||||
|
||||
// Delete note
|
||||
DELETE /etapi/notes/:noteId</code></pre>
|
||||
|
||||
<h4>Search</h4>
|
||||
<pre><code>// Search notes
|
||||
GET /etapi/notes/search
|
||||
Query: {
|
||||
search: string,
|
||||
limit?: number,
|
||||
orderBy?: string
|
||||
}</code></pre>
|
||||
|
||||
<h3>Client Example (JavaScript)</h3>
|
||||
<pre><code>class EtapiClient {
|
||||
constructor(serverUrl, authToken) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
async getNote(noteId) {
|
||||
const response = await fetch(
|
||||
`${this.serverUrl}/etapi/notes/${noteId}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': this.authToken
|
||||
}
|
||||
}
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async createNote(parentNoteId, title, content) {
|
||||
const response = await fetch(
|
||||
`${this.serverUrl}/etapi/notes`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': this.authToken,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
parentNoteId,
|
||||
title,
|
||||
type: 'text',
|
||||
content
|
||||
})
|
||||
}
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="api-layer websocket">
|
||||
<h2>WebSocket Real-time Synchronization</h2>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/services/ws.ts</code></p>
|
||||
|
||||
<p>WebSocket connections provide real-time updates and synchronization between clients.</p>
|
||||
|
||||
<h3>Message Types</h3>
|
||||
<ul>
|
||||
<li><code>entity-changes</code> - Entity updates</li>
|
||||
<li><code>sync</code> - Sync events</li>
|
||||
<li><code>note-content-change</code> - Content updates</li>
|
||||
<li><code>refresh-tree</code> - Tree structure changes</li>
|
||||
<li><code>options-changed</code> - Configuration updates</li>
|
||||
</ul>
|
||||
|
||||
<h3>Connection Example</h3>
|
||||
<pre><code>// Client connection
|
||||
const ws = new WebSocket('wss://server/ws');
|
||||
|
||||
ws.on('open', () => {
|
||||
// Authenticate
|
||||
ws.send(JSON.stringify({
|
||||
type: 'auth',
|
||||
token: sessionToken
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const message = JSON.parse(data);
|
||||
handleWSMessage(message);
|
||||
});
|
||||
|
||||
// Handle messages
|
||||
function handleWSMessage(message) {
|
||||
switch (message.type) {
|
||||
case 'entity-changes':
|
||||
handleEntityChanges(message.data);
|
||||
break;
|
||||
case 'refresh-tree':
|
||||
froca.loadInitialTree();
|
||||
break;
|
||||
case 'note-content-change':
|
||||
handleContentChange(message.data);
|
||||
break;
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>API Security</h2>
|
||||
|
||||
<h3>Authentication Methods</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>API</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Session-based</td>
|
||||
<td>Internal API</td>
|
||||
<td>Cookie-based sessions for web clients</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Token-based</td>
|
||||
<td>ETAPI</td>
|
||||
<td>Bearer tokens for external apps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WebSocket auth</td>
|
||||
<td>WebSocket</td>
|
||||
<td>Initial auth message after connection</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Rate Limiting</h3>
|
||||
<pre><code>// Global rate limit
|
||||
const globalLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 1000 // limit each IP to 1000 requests
|
||||
});
|
||||
|
||||
// Strict limit for authentication
|
||||
const authLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 5,
|
||||
message: 'Too many authentication attempts'
|
||||
});</code></pre>
|
||||
|
||||
<h2>Performance Optimization</h2>
|
||||
|
||||
<h3>Batch Operations</h3>
|
||||
<pre><code>// Batch API endpoint
|
||||
router.post('/api/batch', async (req, res) => {
|
||||
const operations = req.body.operations;
|
||||
const results = [];
|
||||
|
||||
await sql.transactional(async () => {
|
||||
for (const op of operations) {
|
||||
const result = await executeOperation(op);
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ results });
|
||||
});</code></pre>
|
||||
|
||||
<h3>Streaming Responses</h3>
|
||||
<pre><code>// Stream large data
|
||||
router.get('/api/export', (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/x-ndjson',
|
||||
'Transfer-Encoding': 'chunked'
|
||||
});
|
||||
|
||||
const noteStream = createNoteExportStream();
|
||||
|
||||
noteStream.on('data', (note) => {
|
||||
res.write(JSON.stringify(note) + '\n');
|
||||
});
|
||||
|
||||
noteStream.on('end', () => {
|
||||
res.end();
|
||||
});
|
||||
});</code></pre>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>API Design</h3>
|
||||
<ol>
|
||||
<li><strong>RESTful conventions:</strong> Use appropriate HTTP methods and status codes</li>
|
||||
<li><strong>Consistent naming:</strong> Use camelCase for JSON properties</li>
|
||||
<li><strong>Versioning:</strong> Version the API to maintain compatibility</li>
|
||||
<li><strong>Documentation:</strong> Keep OpenAPI spec up to date</li>
|
||||
</ol>
|
||||
|
||||
<h3>Security</h3>
|
||||
<ol>
|
||||
<li><strong>Authentication:</strong> Always verify user identity</li>
|
||||
<li><strong>Authorization:</strong> Check permissions for each operation</li>
|
||||
<li><strong>Validation:</strong> Validate all input data</li>
|
||||
<li><strong>Rate limiting:</strong> Prevent abuse with appropriate limits</li>
|
||||
</ol>
|
||||
|
||||
<h3>Performance</h3>
|
||||
<ol>
|
||||
<li><strong>Pagination:</strong> Limit response sizes with pagination</li>
|
||||
<li><strong>Caching:</strong> Cache frequently accessed data</li>
|
||||
<li><strong>Batch operations:</strong> Support bulk operations</li>
|
||||
<li><strong>Async processing:</strong> Use queues for long-running tasks</li>
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,268 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Entity System Architecture</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; }
|
||||
h1 { color: #2c3e50; }
|
||||
h2 { color: #34495e; margin-top: 30px; }
|
||||
h3 { color: #7f8c8d; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
.entity-box { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #3498db; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
||||
th { background: #f8f9fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Entity System Architecture</h1>
|
||||
|
||||
<p>The Entity System forms the core data model of Trilium Notes, providing a flexible and powerful structure for organizing information. This document details the entities, their relationships, and usage patterns.</p>
|
||||
|
||||
<h2>Core Entities Overview</h2>
|
||||
|
||||
<div class="entity-box">
|
||||
<h3>BNote - Notes with Content and Metadata</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/becca/entities/bnote.ts</code></p>
|
||||
|
||||
<p>Notes are the fundamental unit of information in Trilium. Each note can contain different types of content and maintain relationships with other notes.</p>
|
||||
|
||||
<h4>Properties</h4>
|
||||
<ul>
|
||||
<li><code>noteId</code> - Unique identifier</li>
|
||||
<li><code>title</code> - Display title</li>
|
||||
<li><code>type</code> - Content type (text, code, file, etc.)</li>
|
||||
<li><code>mime</code> - MIME type for content</li>
|
||||
<li><code>isProtected</code> - Encryption flag</li>
|
||||
<li><code>dateCreated</code> - Creation timestamp</li>
|
||||
<li><code>dateModified</code> - Last modification</li>
|
||||
</ul>
|
||||
|
||||
<h4>Note Types</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>text</code></td>
|
||||
<td>Rich text with HTML formatting</td>
|
||||
<td>General notes, documentation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>code</code></td>
|
||||
<td>Source code with syntax highlighting</td>
|
||||
<td>Code snippets, scripts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file</code></td>
|
||||
<td>Binary file attachment</td>
|
||||
<td>PDFs, documents, archives</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>image</code></td>
|
||||
<td>Image with preview</td>
|
||||
<td>Pictures, diagrams</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>search</code></td>
|
||||
<td>Saved search query</td>
|
||||
<td>Dynamic note collections</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>canvas</code></td>
|
||||
<td>Drawing canvas (Excalidraw)</td>
|
||||
<td>Diagrams, sketches</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mermaid</code></td>
|
||||
<td>Mermaid diagram</td>
|
||||
<td>Flowcharts, graphs</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="entity-box">
|
||||
<h3>BBranch - Hierarchical Relationships</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/becca/entities/bbranch.ts</code></p>
|
||||
|
||||
<p>Branches define the parent-child relationships between notes, allowing a note to have multiple parents (cloning).</p>
|
||||
|
||||
<h4>Key Features</h4>
|
||||
<ul>
|
||||
<li><strong>Multiple Parents:</strong> Notes can appear in multiple locations</li>
|
||||
<li><strong>Ordering:</strong> Explicit positioning among siblings</li>
|
||||
<li><strong>Prefixes:</strong> Optional labels for context (e.g., "Chapter 1:")</li>
|
||||
<li><strong>UI State:</strong> Expansion state persisted per branch</li>
|
||||
</ul>
|
||||
|
||||
<h4>Usage Example</h4>
|
||||
<pre><code>// Create parent-child relationship
|
||||
const branch = new BBranch({
|
||||
noteId: childNote.noteId,
|
||||
parentNoteId: parentNote.noteId,
|
||||
notePosition: 10
|
||||
});
|
||||
branch.save();
|
||||
|
||||
// Clone note to another parent
|
||||
const cloneBranch = childNote.cloneTo(otherParent.noteId);</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="entity-box">
|
||||
<h3>BAttribute - Key-Value Metadata</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/becca/entities/battribute.ts</code></p>
|
||||
|
||||
<p>Attributes provide flexible metadata and relationships between notes.</p>
|
||||
|
||||
<h4>Types</h4>
|
||||
<ol>
|
||||
<li><strong>Labels:</strong> Key-value pairs for metadata</li>
|
||||
<li><strong>Relations:</strong> References to other notes</li>
|
||||
</ol>
|
||||
|
||||
<h4>Common Patterns</h4>
|
||||
<pre><code>// Add label
|
||||
note.addLabel("status", "active");
|
||||
note.addLabel("priority", "high");
|
||||
|
||||
// Add relation
|
||||
note.addRelation("template", templateNoteId);
|
||||
note.addRelation("renderNote", renderNoteId);
|
||||
|
||||
// Query by attributes
|
||||
const todos = becca.findAttributes("label", "todoItem");</code></pre>
|
||||
|
||||
<h4>System Attributes</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Type</th>
|
||||
<th>Purpose</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#hidePromotedAttributes</code></td>
|
||||
<td>Label</td>
|
||||
<td>Hide promoted attributes in UI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#readOnly</code></td>
|
||||
<td>Label</td>
|
||||
<td>Prevent note editing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#workspace</code></td>
|
||||
<td>Label</td>
|
||||
<td>Workspace organization</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~template</code></td>
|
||||
<td>Relation</td>
|
||||
<td>Note template reference</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~renderNote</code></td>
|
||||
<td>Relation</td>
|
||||
<td>Custom rendering</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="entity-box">
|
||||
<h3>BRevision - Version History</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/becca/entities/brevision.ts</code></p>
|
||||
|
||||
<p>Revisions provide version history and recovery capabilities.</p>
|
||||
|
||||
<h4>Revision Strategy</h4>
|
||||
<ul>
|
||||
<li>Created automatically on significant changes</li>
|
||||
<li>Configurable retention period</li>
|
||||
<li>Day/week/month/year retention rules</li>
|
||||
<li>Protected note revisions are encrypted</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="entity-box">
|
||||
<h3>BOption - Application Configuration</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/becca/entities/boption.ts</code></p>
|
||||
|
||||
<p>Options store application and user preferences.</p>
|
||||
|
||||
<h4>Common Options</h4>
|
||||
<pre><code>// Theme settings
|
||||
setOption("theme", "dark");
|
||||
|
||||
// Protected session timeout
|
||||
setOption("protectedSessionTimeout", "600");
|
||||
|
||||
// Sync settings
|
||||
setOption("syncServerHost", "https://sync.server");</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Entity Relationships</h2>
|
||||
|
||||
<h3>Parent-Child Hierarchy</h3>
|
||||
<pre><code>// Single parent
|
||||
childNote.setParent(parentNote.noteId);
|
||||
|
||||
// Multiple parents (cloning)
|
||||
childNote.cloneTo(parent1.noteId);
|
||||
childNote.cloneTo(parent2.noteId);
|
||||
|
||||
// Get parents
|
||||
const parents = childNote.getParentNotes();
|
||||
|
||||
// Get children
|
||||
const children = parentNote.getChildNotes();</code></pre>
|
||||
|
||||
<h3>Attribute Relationships</h3>
|
||||
<pre><code>// Direct relations
|
||||
note.addRelation("author", authorNote.noteId);
|
||||
|
||||
// Bidirectional relations
|
||||
note1.addRelation("related", note2.noteId);
|
||||
note2.addRelation("related", note1.noteId);
|
||||
|
||||
// Get related notes
|
||||
const related = note.getRelations("related");</code></pre>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>Entity Creation</h3>
|
||||
<pre><code>// Always use transactions for multiple operations
|
||||
sql.transactional(() => {
|
||||
const note = new BNote({...});
|
||||
note.save();
|
||||
|
||||
note.addLabel("status", "draft");
|
||||
note.addRelation("template", templateId);
|
||||
});</code></pre>
|
||||
|
||||
<h3>Entity Updates</h3>
|
||||
<pre><code>// Check existence before update
|
||||
const note = becca.getNote(noteId);
|
||||
if (note) {
|
||||
note.title = "Updated";
|
||||
note.save();
|
||||
}</code></pre>
|
||||
|
||||
<h3>Querying</h3>
|
||||
<pre><code>// Use indexed queries
|
||||
const attrs = becca.findAttributes("label", "task");
|
||||
|
||||
// Avoid N+1 queries
|
||||
const noteIds = [...];
|
||||
const notes = becca.getNotes(noteIds); // Single batch</code></pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,325 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Monorepo Structure</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; }
|
||||
h1 { color: #2c3e50; }
|
||||
h2 { color: #34495e; margin-top: 30px; }
|
||||
h3 { color: #7f8c8d; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
.directory-tree { background: #2c3e50; color: #ecf0f1; padding: 20px; border-radius: 8px; margin: 20px 0; }
|
||||
.app-section { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #3498db; }
|
||||
.package-section { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #27ae60; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
||||
th { background: #f8f9fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Monorepo Structure</h1>
|
||||
|
||||
<p>Trilium is organized as a TypeScript monorepo using NX, facilitating code sharing, consistent tooling, and efficient build processes.</p>
|
||||
|
||||
<h2>Project Organization</h2>
|
||||
|
||||
<div class="directory-tree">
|
||||
<pre>TriliumNext/Trilium/
|
||||
├── apps/ # Runnable applications
|
||||
│ ├── client/ # Frontend web application
|
||||
│ ├── server/ # Node.js backend server
|
||||
│ ├── desktop/ # Electron desktop application
|
||||
│ ├── web-clipper/ # Browser extension
|
||||
│ ├── db-compare/ # Database comparison tool
|
||||
│ ├── dump-db/ # Database dump utility
|
||||
│ └── edit-docs/ # Documentation editor
|
||||
├── packages/ # Shared libraries
|
||||
│ ├── commons/ # Shared interfaces and utilities
|
||||
│ ├── ckeditor5/ # Rich text editor
|
||||
│ ├── codemirror/ # Code editor
|
||||
│ ├── highlightjs/ # Syntax highlighting
|
||||
│ └── ckeditor5-*/ # CKEditor plugins
|
||||
├── docs/ # Documentation
|
||||
├── nx.json # NX workspace configuration
|
||||
├── package.json # Root package configuration
|
||||
├── pnpm-workspace.yaml # PNPM workspace configuration
|
||||
└── tsconfig.base.json # Base TypeScript configuration</pre>
|
||||
</div>
|
||||
|
||||
<h2>Applications</h2>
|
||||
|
||||
<div class="app-section">
|
||||
<h3>Client (/apps/client)</h3>
|
||||
<p>The frontend application shared by both server and desktop versions.</p>
|
||||
|
||||
<h4>Structure</h4>
|
||||
<pre>apps/client/
|
||||
├── src/
|
||||
│ ├── components/ # Core UI components
|
||||
│ ├── entities/ # Frontend entities
|
||||
│ ├── services/ # Business logic
|
||||
│ ├── widgets/ # UI widgets system
|
||||
│ └── desktop.ts # Entry point
|
||||
├── package.json
|
||||
└── vite.config.ts # Vite configuration</pre>
|
||||
|
||||
<h4>Key Files</h4>
|
||||
<ul>
|
||||
<li><code>desktop.ts</code> - Main application initialization</li>
|
||||
<li><code>services/froca.ts</code> - Frontend cache implementation</li>
|
||||
<li><code>widgets/basic_widget.ts</code> - Base widget class</li>
|
||||
<li><code>services/server.ts</code> - API communication layer</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="app-section">
|
||||
<h3>Server (/apps/server)</h3>
|
||||
<p>The Node.js backend providing API, database, and business logic.</p>
|
||||
|
||||
<h4>Structure</h4>
|
||||
<pre>apps/server/
|
||||
├── src/
|
||||
│ ├── becca/ # Backend cache system
|
||||
│ ├── routes/ # Express routes
|
||||
│ ├── etapi/ # External API
|
||||
│ ├── services/ # Business services
|
||||
│ ├── share/ # Note sharing
|
||||
│ └── main.ts # Server entry point
|
||||
├── package.json
|
||||
└── webpack.config.js # Webpack configuration</pre>
|
||||
|
||||
<h4>Key Services</h4>
|
||||
<ul>
|
||||
<li><code>services/sql.ts</code> - Database access layer</li>
|
||||
<li><code>services/sync.ts</code> - Synchronization logic</li>
|
||||
<li><code>services/ws.ts</code> - WebSocket server</li>
|
||||
<li><code>services/protected_session.ts</code> - Encryption handling</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="app-section">
|
||||
<h3>Desktop (/apps/desktop)</h3>
|
||||
<p>Electron wrapper for the desktop application.</p>
|
||||
|
||||
<h4>Key Components</h4>
|
||||
<ul>
|
||||
<li><code>main.ts</code> - Electron main process</li>
|
||||
<li><code>preload.ts</code> - Preload script</li>
|
||||
<li><code>electron-builder.yml</code> - Build configuration</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Packages</h2>
|
||||
|
||||
<div class="package-section">
|
||||
<h3>Commons (/packages/commons)</h3>
|
||||
<p>Shared TypeScript interfaces and utilities used across applications.</p>
|
||||
|
||||
<pre><code>// packages/commons/src/types.ts
|
||||
export interface NoteRow {
|
||||
noteId: string;
|
||||
title: string;
|
||||
type: string;
|
||||
mime: string;
|
||||
isProtected: boolean;
|
||||
dateCreated: string;
|
||||
dateModified: string;
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="package-section">
|
||||
<h3>CKEditor5 (/packages/ckeditor5)</h3>
|
||||
<p>Custom CKEditor5 build with Trilium-specific plugins.</p>
|
||||
|
||||
<h4>Custom Plugins</h4>
|
||||
<ul>
|
||||
<li><strong>Admonition:</strong> Note boxes with icons</li>
|
||||
<li><strong>Footnotes:</strong> Reference footnotes</li>
|
||||
<li><strong>Math:</strong> LaTeX equation rendering</li>
|
||||
<li><strong>Mermaid:</strong> Diagram integration</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Build System</h2>
|
||||
|
||||
<h3>Development Commands</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Command</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>pnpm install</code></td>
|
||||
<td>Install dependencies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pnpm run server:start</code></td>
|
||||
<td>Start development server</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pnpm nx run desktop:serve</code></td>
|
||||
<td>Start desktop app</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pnpm test:all</code></td>
|
||||
<td>Run all tests</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pnpm nx build server</code></td>
|
||||
<td>Build server</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Build Commands</h3>
|
||||
<pre><code># Build specific project
|
||||
pnpm nx build server
|
||||
pnpm nx build client
|
||||
|
||||
# Build all projects
|
||||
pnpm nx run-many --target=build --all
|
||||
|
||||
# Production build
|
||||
pnpm nx build server --configuration=production
|
||||
|
||||
# Build only affected projects
|
||||
pnpm nx affected:build --base=main</code></pre>
|
||||
|
||||
<h2>Development Workflow</h2>
|
||||
|
||||
<h3>Initial Setup</h3>
|
||||
<pre><code># Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Enable corepack for pnpm
|
||||
corepack enable
|
||||
|
||||
# Build all packages
|
||||
pnpm nx run-many --target=build --all</code></pre>
|
||||
|
||||
<h3>Testing</h3>
|
||||
<pre><code># Run all tests
|
||||
pnpm test:all
|
||||
|
||||
# Run tests for specific project
|
||||
pnpm nx test server
|
||||
|
||||
# Run tests in watch mode
|
||||
pnpm nx test server --watch
|
||||
|
||||
# Generate coverage
|
||||
pnpm nx test server --coverage</code></pre>
|
||||
|
||||
<h3>Linting and Type Checking</h3>
|
||||
<pre><code># Lint specific project
|
||||
pnpm nx lint server
|
||||
|
||||
# Type check
|
||||
pnpm nx run server:typecheck
|
||||
|
||||
# Fix lint issues
|
||||
pnpm nx lint server --fix</code></pre>
|
||||
|
||||
<h2>TypeScript Configuration</h2>
|
||||
|
||||
<h3>Base Configuration</h3>
|
||||
<pre><code>{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022", "dom"],
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@triliumnext/commons": ["packages/commons/src/index.ts"]
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Build Optimization</h2>
|
||||
|
||||
<h3>NX Features</h3>
|
||||
<ul>
|
||||
<li><strong>Build Caching:</strong> Speeds up subsequent builds</li>
|
||||
<li><strong>Affected Commands:</strong> Build/test only changed code</li>
|
||||
<li><strong>Parallel Execution:</strong> Run tasks in parallel</li>
|
||||
<li><strong>Dependency Graph:</strong> Visualize project dependencies</li>
|
||||
</ul>
|
||||
|
||||
<h3>Optimization Commands</h3>
|
||||
<pre><code># Show project graph
|
||||
pnpm nx graph
|
||||
|
||||
# Clear cache
|
||||
pnpm nx reset
|
||||
|
||||
# Profile build performance
|
||||
pnpm nx build server --profile
|
||||
|
||||
# Run with cache disabled
|
||||
pnpm nx build server --skip-nx-cache</code></pre>
|
||||
|
||||
<h2>Production Builds</h2>
|
||||
|
||||
<h3>Docker Build</h3>
|
||||
<pre><code>FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
RUN pnpm nx build server --configuration=production
|
||||
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist/apps/server ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
CMD ["node", "main.js"]</code></pre>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>Project Structure</h3>
|
||||
<ol>
|
||||
<li><strong>Keep packages focused:</strong> Each package should have a single, clear purpose</li>
|
||||
<li><strong>Minimize circular dependencies:</strong> Use dependency graph to identify issues</li>
|
||||
<li><strong>Share common code:</strong> Extract shared logic to packages/commons</li>
|
||||
</ol>
|
||||
|
||||
<h3>Development</h3>
|
||||
<ol>
|
||||
<li><strong>Use NX generators:</strong> Generate consistent code structure</li>
|
||||
<li><strong>Leverage caching:</strong> Don't skip-nx-cache unless debugging</li>
|
||||
<li><strong>Run affected commands:</strong> Save time by only building/testing changed code</li>
|
||||
</ol>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Issues</h3>
|
||||
|
||||
<h4>Build Cache Issues</h4>
|
||||
<pre><code># Clear NX cache
|
||||
pnpm nx reset
|
||||
|
||||
# Clear node_modules and reinstall
|
||||
rm -rf node_modules
|
||||
pnpm install</code></pre>
|
||||
|
||||
<h4>Dependency Conflicts</h4>
|
||||
<pre><code># Check for duplicate packages
|
||||
pnpm list --depth=0
|
||||
|
||||
# Update all dependencies
|
||||
pnpm update --recursive</code></pre>
|
||||
|
||||
<h4>Debug Commands</h4>
|
||||
<pre><code># Verbose output
|
||||
pnpm nx build server --verbose
|
||||
|
||||
# Show affected projects
|
||||
pnpm nx print-affected --type=app --select=projects</code></pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,266 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Three-Layer Cache System Architecture</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; }
|
||||
h1 { color: #2c3e50; }
|
||||
h2 { color: #34495e; margin-top: 30px; }
|
||||
h3 { color: #7f8c8d; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
.cache-layer { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }
|
||||
.becca { background: #e1f5fe; }
|
||||
.froca { background: #fff3e0; }
|
||||
.shaca { background: #f3e5f5; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
||||
th { background: #f8f9fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Three-Layer Cache System Architecture</h1>
|
||||
|
||||
<p>Trilium implements a sophisticated three-layer caching system to optimize performance and reduce database load. This architecture ensures fast access to frequently used data while maintaining consistency across different application contexts.</p>
|
||||
|
||||
<h2>Overview</h2>
|
||||
|
||||
<p>The three cache layers are:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Becca</strong> (Backend Cache) - Server-side entity cache</li>
|
||||
<li><strong>Froca</strong> (Frontend Cache) - Client-side mirror of backend data</li>
|
||||
<li><strong>Shaca</strong> (Share Cache) - Optimized cache for shared/published notes</li>
|
||||
</ol>
|
||||
|
||||
<div class="cache-layer becca">
|
||||
<h2>Becca (Backend Cache)</h2>
|
||||
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/becca/</code></p>
|
||||
|
||||
<p>Becca is the authoritative cache layer that maintains all notes, branches, attributes, and options in server memory.</p>
|
||||
|
||||
<h3>Key Components</h3>
|
||||
|
||||
<h4>Becca Interface</h4>
|
||||
<pre><code>export default class Becca {
|
||||
loaded: boolean;
|
||||
notes: Record<string, BNote>;
|
||||
branches: Record<string, BBranch>;
|
||||
childParentToBranch: Record<string, BBranch>;
|
||||
attributes: Record<string, BAttribute>;
|
||||
attributeIndex: Record<string, BAttribute[]>;
|
||||
options: Record<string, BOption>;
|
||||
etapiTokens: Record<string, BEtapiToken>;
|
||||
allNoteSetCache: NoteSet | null;
|
||||
}</code></pre>
|
||||
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li><strong>In-memory storage:</strong> All active entities are kept in memory for fast access</li>
|
||||
<li><strong>Lazy loading:</strong> Related entities (revisions, attachments) loaded on demand</li>
|
||||
<li><strong>Index structures:</strong> Optimized lookups via childParentToBranch and attributeIndex</li>
|
||||
<li><strong>Cache invalidation:</strong> Automatic cache updates on entity changes</li>
|
||||
<li><strong>Protected note decryption:</strong> On-demand decryption of encrypted content</li>
|
||||
</ul>
|
||||
|
||||
<h3>Usage Example</h3>
|
||||
<pre><code>import becca from "./becca/becca.js";
|
||||
|
||||
// Get a note
|
||||
const note = becca.getNote("noteId");
|
||||
|
||||
// Find attributes by type and name
|
||||
const labels = becca.findAttributes("label", "todoItem");
|
||||
|
||||
// Get branch relationships
|
||||
const branch = becca.getBranchFromChildAndParent(childId, parentId);</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cache-layer froca">
|
||||
<h2>Froca (Frontend Cache)</h2>
|
||||
|
||||
<p><strong>Location:</strong> <code>/apps/client/src/services/froca.ts</code></p>
|
||||
|
||||
<p>Froca is the frontend mirror of Becca, maintaining a subset of backend data for client-side operations.</p>
|
||||
|
||||
<h3>Key Components</h3>
|
||||
|
||||
<pre><code>class FrocaImpl implements Froca {
|
||||
notes: Record<string, FNote>;
|
||||
branches: Record<string, FBranch>;
|
||||
attributes: Record<string, FAttribute>;
|
||||
attachments: Record<string, FAttachment>;
|
||||
blobPromises: Record<string, Promise<FBlob | null> | null>;
|
||||
}</code></pre>
|
||||
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li><strong>Lazy loading:</strong> Notes loaded on-demand with their immediate context</li>
|
||||
<li><strong>Subtree loading:</strong> Efficient loading of note hierarchies</li>
|
||||
<li><strong>Real-time updates:</strong> WebSocket synchronization with backend changes</li>
|
||||
<li><strong>Search note support:</strong> Virtual branches for search results</li>
|
||||
<li><strong>Promise-based blob loading:</strong> Asynchronous content loading</li>
|
||||
</ul>
|
||||
|
||||
<h3>Loading Strategy</h3>
|
||||
<pre><code>// Initial load - loads root and immediate children
|
||||
await froca.loadInitialTree();
|
||||
|
||||
// Load subtree on demand
|
||||
const note = await froca.loadSubTree(noteId);
|
||||
|
||||
// Reload specific notes
|
||||
await froca.reloadNotes([noteId1, noteId2]);</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="cache-layer shaca">
|
||||
<h2>Shaca (Share Cache)</h2>
|
||||
|
||||
<p><strong>Location:</strong> <code>/apps/server/src/share/shaca/</code></p>
|
||||
|
||||
<p>Shaca is a specialized cache for publicly shared notes, optimized for read-only access.</p>
|
||||
|
||||
<h3>Key Components</h3>
|
||||
|
||||
<pre><code>export default class Shaca {
|
||||
notes: Record<string, SNote>;
|
||||
branches: Record<string, SBranch>;
|
||||
childParentToBranch: Record<string, SBranch>;
|
||||
attributes: Record<string, SAttribute>;
|
||||
attachments: Record<string, SAttachment>;
|
||||
aliasToNote: Record<string, SNote>;
|
||||
shareRootNote: SNote | null;
|
||||
shareIndexEnabled: boolean;
|
||||
}</code></pre>
|
||||
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li><strong>Read-only optimization:</strong> Streamlined for public access</li>
|
||||
<li><strong>Alias support:</strong> URL-friendly note access via aliases</li>
|
||||
<li><strong>Share index:</strong> Optional indexing of all shared subtrees</li>
|
||||
<li><strong>Minimal memory footprint:</strong> Only shared content cached</li>
|
||||
<li><strong>Security isolation:</strong> Separate from main application cache</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Cache Interaction and Data Flow</h2>
|
||||
|
||||
<h3>Create/Update Flow</h3>
|
||||
<ol>
|
||||
<li>Client sends update request to API</li>
|
||||
<li>API updates Becca cache</li>
|
||||
<li>Becca persists change to database</li>
|
||||
<li>API pushes update to Froca via WebSocket</li>
|
||||
<li>Froca updates UI components</li>
|
||||
</ol>
|
||||
|
||||
<h3>Read Flow</h3>
|
||||
<ol>
|
||||
<li>Client requests note from Froca</li>
|
||||
<li>If cached: Return immediately</li>
|
||||
<li>If not cached: Fetch from API</li>
|
||||
<li>API retrieves from Becca</li>
|
||||
<li>Froca caches and returns data</li>
|
||||
</ol>
|
||||
|
||||
<h2>Performance Considerations</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cache Layer</th>
|
||||
<th>Memory Usage</th>
|
||||
<th>Loading Strategy</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Becca</td>
|
||||
<td>100-500MB typical</td>
|
||||
<td>Full load on startup</td>
|
||||
<td>Server operations</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Froca</td>
|
||||
<td>Variable (on-demand)</td>
|
||||
<td>Progressive loading</td>
|
||||
<td>Client UI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Shaca</td>
|
||||
<td>Minimal</td>
|
||||
<td>Lazy loading</td>
|
||||
<td>Public sharing</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>When to Use Each Cache</h3>
|
||||
|
||||
<p><strong>Use Becca when:</strong></p>
|
||||
<ul>
|
||||
<li>Implementing server-side business logic</li>
|
||||
<li>Performing bulk operations</li>
|
||||
<li>Handling synchronization</li>
|
||||
<li>Managing protected notes</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Use Froca when:</strong></p>
|
||||
<ul>
|
||||
<li>Building UI components</li>
|
||||
<li>Handling user interactions</li>
|
||||
<li>Displaying note content</li>
|
||||
<li>Managing client state</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Use Shaca when:</strong></p>
|
||||
<ul>
|
||||
<li>Serving public content</li>
|
||||
<li>Building share pages</li>
|
||||
<li>Implementing read-only access</li>
|
||||
<li>Creating public APIs</li>
|
||||
</ul>
|
||||
|
||||
<h3>Cache Invalidation</h3>
|
||||
<pre><code>// Becca - automatic on entity save
|
||||
note.save(); // Cache updated automatically
|
||||
|
||||
// Froca - manual reload when needed
|
||||
await froca.reloadNotes([noteId]);
|
||||
|
||||
// Shaca - rebuild on share changes
|
||||
shaca.reset();
|
||||
shaca.load();</code></pre>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Issues</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Cache Inconsistency</strong>
|
||||
<ul>
|
||||
<li>Symptom: UI shows outdated data</li>
|
||||
<li>Solution: Force reload with <code>froca.reloadNotes()</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Memory Growth</strong>
|
||||
<ul>
|
||||
<li>Symptom: Server memory usage increases</li>
|
||||
<li>Solution: Check for memory leaks in custom scripts</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Slow Initial Load</strong>
|
||||
<ul>
|
||||
<li>Symptom: Long startup time</li>
|
||||
<li>Solution: Optimize database queries, add indexes</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,286 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Widget-Based UI Architecture</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; }
|
||||
h1 { color: #2c3e50; }
|
||||
h2 { color: #34495e; margin-top: 30px; }
|
||||
h3 { color: #7f8c8d; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
.widget-class { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #27ae60; }
|
||||
.example-box { background: #ecf0f1; padding: 15px; border-radius: 5px; margin: 15px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Widget-Based UI Architecture</h1>
|
||||
|
||||
<p>Trilium's frontend is built on a modular widget system that provides flexibility, reusability, and maintainability. This architecture enables dynamic UI composition and extensibility through custom widgets.</p>
|
||||
|
||||
<h2>Widget System Overview</h2>
|
||||
|
||||
<p>The widget hierarchy follows an inheritance pattern where each level adds specific functionality:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Component</strong> - Base class for all UI components</li>
|
||||
<li><strong>BasicWidget</strong> - UI foundation with DOM manipulation</li>
|
||||
<li><strong>NoteContextAwareWidget</strong> - Note-aware components</li>
|
||||
<li><strong>RightPanelWidget</strong> - Side panel widgets</li>
|
||||
<li><strong>TypeWidgets</strong> - Note type specific widgets</li>
|
||||
<li><strong>CustomWidgets</strong> - User-created widgets</li>
|
||||
</ol>
|
||||
|
||||
<div class="widget-class">
|
||||
<h2>Core Widget Classes</h2>
|
||||
|
||||
<h3>BasicWidget</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/client/src/widgets/basic_widget.ts</code></p>
|
||||
|
||||
<p>Base class for all UI widgets, providing DOM manipulation and styling capabilities.</p>
|
||||
|
||||
<h4>Key Methods</h4>
|
||||
<ul>
|
||||
<li><code>id(id: string)</code> - Set widget ID</li>
|
||||
<li><code>class(className: string)</code> - Add CSS class</li>
|
||||
<li><code>css(name: string, value: string)</code> - Set CSS property</li>
|
||||
<li><code>child(...components)</code> - Add child widgets</li>
|
||||
<li><code>doRender()</code> - Render widget HTML</li>
|
||||
</ul>
|
||||
|
||||
<div class="example-box">
|
||||
<h4>Usage Example</h4>
|
||||
<pre><code>class MyWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div>')
|
||||
.addClass('my-widget')
|
||||
.append($('<h3>').text('Widget Title'));
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.$widget.find('h3').text(note.title);
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-class">
|
||||
<h3>NoteContextAwareWidget</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/client/src/widgets/note_context_aware_widget.ts</code></p>
|
||||
|
||||
<p>Base class for widgets that respond to note context changes.</p>
|
||||
|
||||
<h4>Lifecycle Methods</h4>
|
||||
<ul>
|
||||
<li><code>refreshWithNote(note)</code> - Called when note context changes</li>
|
||||
<li><code>noteSwitched()</code> - Called when user switches notes</li>
|
||||
<li><code>activeContextChanged()</code> - Called on context change</li>
|
||||
<li><code>noteTypeMimeChanged()</code> - React to note type changes</li>
|
||||
</ul>
|
||||
|
||||
<div class="example-box">
|
||||
<h4>Context Management Example</h4>
|
||||
<pre><code>class MyNoteWidget extends NoteContextAwareWidget {
|
||||
async refreshWithNote(note) {
|
||||
// Called when note context changes
|
||||
this.$widget.find('.note-title').text(note.title);
|
||||
this.$widget.find('.note-type').text(note.type);
|
||||
|
||||
// Access note attributes
|
||||
const labels = note.getLabels();
|
||||
const relations = note.getRelations();
|
||||
}
|
||||
|
||||
async noteSwitched() {
|
||||
// Called when user switches to different note
|
||||
console.log(`Switched to note: ${this.noteId}`);
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-class">
|
||||
<h3>RightPanelWidget</h3>
|
||||
<p><strong>Location:</strong> <code>/apps/client/src/widgets/right_panel_widget.ts</code></p>
|
||||
|
||||
<p>Base class for widgets displayed in the right sidebar panel.</p>
|
||||
|
||||
<h4>Required Methods</h4>
|
||||
<ul>
|
||||
<li><code>getTitle()</code> - Widget title</li>
|
||||
<li><code>getIcon()</code> - Widget icon</li>
|
||||
<li><code>getPosition()</code> - Display order</li>
|
||||
<li><code>doRenderBody()</code> - Render widget content</li>
|
||||
</ul>
|
||||
|
||||
<div class="example-box">
|
||||
<h4>Right Panel Widget Example</h4>
|
||||
<pre><code>class InfoWidget extends RightPanelWidget {
|
||||
getTitle() { return "Note Info"; }
|
||||
getIcon() { return "info"; }
|
||||
getPosition() { return 100; }
|
||||
|
||||
async doRenderBody() {
|
||||
return $('<div class="info-widget">')
|
||||
.append($('<div class="created">'))
|
||||
.append($('<div class="modified">'))
|
||||
.append($('<div class="word-count">'));
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.$body.find('.created').text(`Created: ${note.dateCreated}`);
|
||||
this.$body.find('.modified').text(`Modified: ${note.dateModified}`);
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Type-Specific Widgets</h2>
|
||||
|
||||
<p><strong>Location:</strong> <code>/apps/client/src/widgets/type_widgets/</code></p>
|
||||
|
||||
<p>Each note type has a specialized widget for rendering and editing:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>TextTypeWidget</strong> - Rich text editor using CKEditor</li>
|
||||
<li><strong>CodeTypeWidget</strong> - Code editor using CodeMirror</li>
|
||||
<li><strong>FileTypeWidget</strong> - File attachment viewer</li>
|
||||
<li><strong>ImageTypeWidget</strong> - Image viewer with editing</li>
|
||||
<li><strong>CanvasTypeWidget</strong> - Excalidraw integration</li>
|
||||
<li><strong>MermaidTypeWidget</strong> - Mermaid diagram renderer</li>
|
||||
</ul>
|
||||
|
||||
<h2>Widget Communication</h2>
|
||||
|
||||
<h3>Event System</h3>
|
||||
<pre><code>// Publishing events
|
||||
class PublisherWidget extends BasicWidget {
|
||||
async handleClick() {
|
||||
// Local event
|
||||
this.trigger('itemSelected', { itemId: '123' });
|
||||
|
||||
// Global event
|
||||
appContext.triggerEvent('noteChanged', { noteId: this.noteId });
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribing to events
|
||||
class SubscriberWidget extends BasicWidget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Local event subscription
|
||||
this.on('itemSelected', (event) => {
|
||||
console.log('Item selected:', event.itemId);
|
||||
});
|
||||
|
||||
// Global event subscription
|
||||
appContext.addEventListener('noteChanged', (event) => {
|
||||
this.handleNoteChange(event.noteId);
|
||||
});
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Custom Widget Development</h2>
|
||||
|
||||
<h3>Creating Custom Widgets</h3>
|
||||
<pre><code>// 1. Define widget class
|
||||
class TaskListWidget extends NoteContextAwareWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div class="task-list-widget">');
|
||||
this.$list = $('<ul>').appendTo(this.$widget);
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
const tasks = await this.loadTasks(note);
|
||||
|
||||
this.$list.empty();
|
||||
for (const task of tasks) {
|
||||
$('<li>')
|
||||
.text(task.title)
|
||||
.toggleClass('completed', task.completed)
|
||||
.appendTo(this.$list);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadTasks(note) {
|
||||
// Load task data from note attributes
|
||||
const taskLabels = note.getLabels('task');
|
||||
return taskLabels.map(label => JSON.parse(label.value));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Register widget
|
||||
api.addWidget(TaskListWidget);</code></pre>
|
||||
|
||||
<h2>Performance Optimization</h2>
|
||||
|
||||
<h3>Lazy Loading</h3>
|
||||
<pre><code>class LazyWidget extends BasicWidget {
|
||||
private contentLoaded = false;
|
||||
|
||||
async becomeVisible() {
|
||||
if (!this.contentLoaded) {
|
||||
await this.loadContent();
|
||||
this.contentLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadContent() {
|
||||
// Heavy content loading
|
||||
const data = await server.get('expensive-data');
|
||||
this.renderContent(data);
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Debouncing Updates</h3>
|
||||
<pre><code>class DebouncedWidget extends NoteContextAwareWidget {
|
||||
private refreshDebounced = utils.debounce(
|
||||
() => this.doRefresh(),
|
||||
500
|
||||
);
|
||||
|
||||
async refreshWithNote(note) {
|
||||
// Debounce rapid updates
|
||||
this.refreshDebounced();
|
||||
}
|
||||
|
||||
private async doRefresh() {
|
||||
// Actual refresh logic
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>Widget Design</h3>
|
||||
<ol>
|
||||
<li><strong>Single Responsibility:</strong> Each widget should have one clear purpose</li>
|
||||
<li><strong>Composition over Inheritance:</strong> Use composition for complex UIs</li>
|
||||
<li><strong>Lazy Initialization:</strong> Load resources only when needed</li>
|
||||
<li><strong>Event Cleanup:</strong> Remove event listeners in cleanup()</li>
|
||||
</ol>
|
||||
|
||||
<h3>Error Handling</h3>
|
||||
<pre><code>class ResilientWidget extends BasicWidget {
|
||||
async refreshWithNote(note) {
|
||||
try {
|
||||
await this.loadData(note);
|
||||
} catch (error) {
|
||||
this.showError('Failed to load data');
|
||||
console.error('Widget error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private showError(message) {
|
||||
this.$widget.html(`
|
||||
<div class="alert alert-danger">
|
||||
${message}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}</code></pre>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,828 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Custom Widget Development Guide - Trilium Developer Guide</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.content {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 3px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
h2 {
|
||||
color: #34495e;
|
||||
margin-top: 30px;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
h3 {
|
||||
color: #555;
|
||||
margin-top: 25px;
|
||||
}
|
||||
pre {
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
code {
|
||||
background: #f3f4f6;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
}
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid #3498db;
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
ul, ol {
|
||||
margin: 16px 0;
|
||||
padding-left: 30px;
|
||||
}
|
||||
li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #17a2b8;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Custom Widget Development Guide</h1>
|
||||
|
||||
<p>This guide provides comprehensive instructions for creating custom widgets in Trilium Notes. Widgets are fundamental UI components that enable you to extend Trilium's functionality with custom interfaces and behaviors.</p>
|
||||
|
||||
<h2>Prerequisites</h2>
|
||||
|
||||
<p>Before developing custom widgets, ensure you have:
|
||||
- Basic knowledge of TypeScript/JavaScript
|
||||
- Understanding of jQuery and DOM manipulation
|
||||
- Familiarity with Trilium's note structure
|
||||
- A development environment with Trilium running locally</p>
|
||||
|
||||
<h2>Understanding Widget Architecture</h2>
|
||||
|
||||
<h3>Widget Hierarchy</h3>
|
||||
|
||||
<p>Trilium's widget system follows a hierarchical structure:</p>
|
||||
|
||||
<pre><code>Component (base class)
|
||||
└── BasicWidget
|
||||
├── NoteContextAwareWidget
|
||||
│ ├── TypeWidget (for note type widgets)
|
||||
│ └── RightPanelWidget
|
||||
└── Custom widgets (buttons, containers, etc.)
|
||||
</code></pre>
|
||||
|
||||
<h3>Core Widget Classes</h3>
|
||||
|
||||
<p>#### BasicWidget
|
||||
The foundation class for all widgets. Provides basic rendering, positioning, and visibility management.</p>
|
||||
|
||||
<pre><code class="language-typescript">import BasicWidget from "../widgets/basic_widget.js";
|
||||
|
||||
<p>class MyCustomWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div class="my-widget">Hello Widget</div>');
|
||||
}
|
||||
}
|
||||
</code></pre></p>
|
||||
|
||||
<p>#### NoteContextAwareWidget
|
||||
Extends BasicWidget to respond to note changes. Use this when your widget needs to update based on the active note.</p>
|
||||
|
||||
<pre><code class="language-typescript">import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
|
||||
|
||||
<p>class NoteInfoWidget extends NoteContextAwareWidget {
|
||||
async refreshWithNote(note) {
|
||||
if (!note) return;
|
||||
|
||||
this.$widget.find('.note-title').text(note.title);
|
||||
this.$widget.find('.note-type').text(note.type);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(<code>
|
||||
<div class="note-info-widget">
|
||||
<div class="note-title"></div>
|
||||
<div class="note-type"></div>
|
||||
</div>
|
||||
</code>);
|
||||
}
|
||||
}
|
||||
</code></pre></p>
|
||||
|
||||
<p>#### RightPanelWidget
|
||||
Specialized widget for rendering panels in the right sidebar with a consistent card layout.</p>
|
||||
|
||||
<pre><code class="language-typescript">import RightPanelWidget from "../widgets/right_panel_widget.js";
|
||||
|
||||
<p>class StatisticsWidget extends RightPanelWidget {
|
||||
get widgetTitle() {
|
||||
return "Note Statistics";
|
||||
}
|
||||
|
||||
async doRenderBody() {
|
||||
this.$body.html(<code>
|
||||
<div class="stats-container">
|
||||
<div class="word-count">Words: <span>0</span></div>
|
||||
<div class="char-count">Characters: <span>0</span></div>
|
||||
</div>
|
||||
</code>);
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
const content = await note.getContent();
|
||||
const wordCount = content.split(/\s+/).length;
|
||||
const charCount = content.length;
|
||||
|
||||
this.$body.find('.word-count span').text(wordCount);
|
||||
this.$body.find('.char-count span').text(charCount);
|
||||
}
|
||||
}
|
||||
</code></pre></p>
|
||||
|
||||
<h2>Widget Lifecycle</h2>
|
||||
|
||||
<h3>Initialization Phase</h3>
|
||||
<li><strong>Constructor</strong>: Set up initial state and child widgets</li>
|
||||
<li><strong>render()</strong>: Called to create the widget's DOM structure</li>
|
||||
<li><strong>doRender()</strong>: Override this to create your widget's HTML</li>
|
||||
|
||||
<h3>Update Phase</h3>
|
||||
<li><strong>refresh()</strong>: Called when widget needs updating</li>
|
||||
<li><strong>refreshWithNote()</strong>: Called for NoteContextAwareWidget when note changes</li>
|
||||
<li><strong>Event handlers</strong>: Respond to various Trilium events</li>
|
||||
|
||||
<h3>Cleanup Phase</h3>
|
||||
<li><strong>cleanup()</strong>: Override to clean up resources, event listeners, etc.</li>
|
||||
<li><strong>remove()</strong>: Removes widget from DOM</li>
|
||||
|
||||
<h2>Event Handling</h2>
|
||||
|
||||
<h3>Subscribing to Events</h3>
|
||||
|
||||
<p>Widgets can listen to Trilium's event system:</p>
|
||||
|
||||
<pre><code class="language-typescript">class EventAwareWidget extends NoteContextAwareWidget {
|
||||
constructor() {
|
||||
super();
|
||||
// Events are automatically subscribed based on method names
|
||||
}
|
||||
|
||||
// Called when entities are reloaded
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
console.log('Entities reloaded');
|
||||
await this.refresh();
|
||||
}
|
||||
|
||||
// Called when note content changes
|
||||
async noteContentChangedEvent({ noteId }) {
|
||||
if (this.noteId === noteId) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when active context changes
|
||||
async activeContextChangedEvent({ noteContext }) {
|
||||
this.noteContext = noteContext;
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Common Events</h3>
|
||||
|
||||
<p>- <code>noteSwitched</code>: Active note changed
|
||||
- <code>activeContextChanged</code>: Active tab/context changed
|
||||
- <code>entitiesReloaded</code>: Notes, branches, or attributes reloaded
|
||||
- <code>noteContentChanged</code>: Note content modified
|
||||
- <code>noteTypeMimeChanged</code>: Note type or MIME changed
|
||||
- <code>frocaReloaded</code>: Frontend cache reloaded</p>
|
||||
|
||||
<h2>State Management</h2>
|
||||
|
||||
<h3>Local State</h3>
|
||||
Store widget-specific state in instance properties:
|
||||
|
||||
<pre><code class="language-typescript">class StatefulWidget extends BasicWidget {
|
||||
constructor() {
|
||||
super();
|
||||
this.isExpanded = false;
|
||||
this.cachedData = null;
|
||||
}
|
||||
|
||||
toggleExpanded() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
this.$widget.toggleClass('expanded', this.isExpanded);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Persistent State</h3>
|
||||
Use options or attributes for persistent state:
|
||||
|
||||
<pre><code class="language-typescript">class PersistentWidget extends NoteContextAwareWidget {
|
||||
async saveState(state) {
|
||||
await server.put('options', {
|
||||
name: 'widgetState',
|
||||
value: JSON.stringify(state)
|
||||
});
|
||||
}
|
||||
|
||||
async loadState() {
|
||||
const option = await server.get('options/widgetState');
|
||||
return option ? JSON.parse(option.value) : {};
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Accessing Trilium APIs</h2>
|
||||
|
||||
<h3>Frontend Services</h3>
|
||||
|
||||
<pre><code class="language-typescript">import froca from "../services/froca.js";
|
||||
import server from "../services/server.js";
|
||||
import linkService from "../services/link.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
|
||||
<p>class ApiWidget extends NoteContextAwareWidget {
|
||||
async doRenderBody() {
|
||||
// Access notes
|
||||
const note = await froca.getNote(this.noteId);
|
||||
|
||||
// Get attributes
|
||||
const attributes = note.getAttributes();
|
||||
|
||||
// Create links
|
||||
const $link = await linkService.createLink(note.noteId);
|
||||
|
||||
// Show notifications
|
||||
toastService.showMessage("Widget loaded");
|
||||
|
||||
// Open dialogs
|
||||
const result = await dialogService.confirm("Continue?");
|
||||
}
|
||||
}
|
||||
</code></pre></p>
|
||||
|
||||
<h3>Server Communication</h3>
|
||||
|
||||
<pre><code class="language-typescript">class ServerWidget extends BasicWidget {
|
||||
async loadData() {
|
||||
// GET request
|
||||
const data = await server.get('custom-api/data');
|
||||
|
||||
// POST request
|
||||
const result = await server.post('custom-api/process', {
|
||||
noteId: this.noteId,
|
||||
action: 'analyze'
|
||||
});
|
||||
|
||||
// PUT request
|
||||
await server.put(<code>notes/${this.noteId}</code>, {
|
||||
title: 'Updated Title'
|
||||
});
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Styling Widgets</h2>
|
||||
|
||||
<h3>Inline Styles</h3>
|
||||
<pre><code class="language-typescript">class StyledWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div>');
|
||||
this.css('padding', '10px')
|
||||
.css('background-color', '#f0f0f0')
|
||||
.css('border-radius', '4px');
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>CSS Classes</h3>
|
||||
<pre><code class="language-typescript">class ClassedWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div>');
|
||||
this.class('custom-widget')
|
||||
.class('bordered');
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>CSS Blocks</h3>
|
||||
<pre><code class="language-typescript">class CSSBlockWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div class="my-widget">Content</div>');
|
||||
|
||||
this.cssBlock(<code>
|
||||
.my-widget {
|
||||
padding: 15px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.my-widget:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
</code>);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Performance Optimization</h2>
|
||||
|
||||
<h3>Lazy Loading</h3>
|
||||
<pre><code class="language-typescript">class LazyWidget extends NoteContextAwareWidget {
|
||||
constructor() {
|
||||
super();
|
||||
this.dataLoaded = false;
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
if (!this.isVisible()) {
|
||||
return; // Don't load if not visible
|
||||
}
|
||||
|
||||
if (!this.dataLoaded) {
|
||||
await this.loadExpensiveData();
|
||||
this.dataLoaded = true;
|
||||
}
|
||||
|
||||
this.updateDisplay();
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Debouncing Updates</h3>
|
||||
<pre><code class="language-typescript">import SpacedUpdate from "../services/spaced_update.js";
|
||||
|
||||
<p>class DebouncedWidget extends NoteContextAwareWidget {
|
||||
constructor() {
|
||||
super();
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
await this.performUpdate();
|
||||
}, 500); // 500ms delay
|
||||
}
|
||||
|
||||
async handleInput(value) {
|
||||
await this.spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
}
|
||||
</code></pre></p>
|
||||
|
||||
<h3>Caching</h3>
|
||||
<pre><code class="language-typescript">class CachedWidget extends NoteContextAwareWidget {
|
||||
constructor() {
|
||||
super();
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
async getProcessedData(noteId) {
|
||||
if (!this.cache.has(noteId)) {
|
||||
const data = await this.processExpensiveOperation(noteId);
|
||||
this.cache.set(noteId, data);
|
||||
}
|
||||
return this.cache.get(noteId);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Debugging Widgets</h2>
|
||||
|
||||
<h3>Console Logging</h3>
|
||||
<pre><code class="language-typescript">class DebugWidget extends BasicWidget {
|
||||
doRender() {
|
||||
console.log('Widget rendering', this.componentId);
|
||||
console.time('render');
|
||||
|
||||
this.$widget = $('<div>');
|
||||
|
||||
console.timeEnd('render');
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Error Handling</h3>
|
||||
<pre><code class="language-typescript">class SafeWidget extends NoteContextAwareWidget {
|
||||
async refreshWithNote(note) {
|
||||
try {
|
||||
await this.riskyOperation();
|
||||
} catch (error) {
|
||||
console.error('Widget error:', error);
|
||||
this.logRenderingError(error);
|
||||
this.$widget.html('<div class="error">Failed to load</div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Development Tools</h3>
|
||||
<pre><code class="language-typescript">class DevWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $('<div>');
|
||||
|
||||
// Add debug information in development
|
||||
if (window.glob.isDev) {
|
||||
this.$widget.attr('data-debug', 'true');
|
||||
this.$widget.append(<code>
|
||||
<div class="debug-info">
|
||||
Component ID: ${this.componentId}
|
||||
Position: ${this.position}
|
||||
</div>
|
||||
</code>);
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Complete Example: Note Statistics Widget</h2>
|
||||
|
||||
<p>Here's a complete example implementing a custom note statistics widget:</p>
|
||||
|
||||
<pre><code class="language-typescript">import RightPanelWidget from "../widgets/right_panel_widget.js";
|
||||
import server from "../services/server.js";
|
||||
import froca from "../services/froca.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import SpacedUpdate from "../services/spaced_update.js";
|
||||
|
||||
<p>class NoteStatisticsWidget extends RightPanelWidget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Initialize state
|
||||
this.statistics = {
|
||||
words: 0,
|
||||
characters: 0,
|
||||
paragraphs: 0,
|
||||
readingTime: 0,
|
||||
links: 0,
|
||||
images: 0
|
||||
};
|
||||
|
||||
// Debounce updates for performance
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
await this.calculateStatistics();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
get widgetTitle() {
|
||||
return "Note Statistics";
|
||||
}
|
||||
|
||||
get help() {
|
||||
return {
|
||||
title: "Note Statistics",
|
||||
text: "Displays various statistics about the current note including word count, reading time, and more."
|
||||
};
|
||||
}
|
||||
|
||||
async doRenderBody() {
|
||||
this.$body.html(<code>
|
||||
<div class="note-statistics">
|
||||
<div class="stat-group">
|
||||
<h5>Content</h5>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Words:</span>
|
||||
<span class="stat-value" data-stat="words">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Characters:</span>
|
||||
<span class="stat-value" data-stat="characters">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Paragraphs:</span>
|
||||
<span class="stat-value" data-stat="paragraphs">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-group">
|
||||
<h5>Reading</h5>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Reading time:</span>
|
||||
<span class="stat-value" data-stat="readingTime">0 min</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-group">
|
||||
<h5>Elements</h5>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Links:</span>
|
||||
<span class="stat-value" data-stat="links">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Images:</span>
|
||||
<span class="stat-value" data-stat="images">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-actions">
|
||||
<button class="btn btn-sm refresh-stats">Refresh</button>
|
||||
<button class="btn btn-sm export-stats">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
</code>);
|
||||
|
||||
this.cssBlock(<code>
|
||||
.note-statistics {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stat-group {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.stat-group:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.stat-group h5 {
|
||||
margin: 0 0 10px 0;
|
||||
color: var(--muted-text-color);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.stat-actions {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stat-actions .btn {
|
||||
flex: 1;
|
||||
}
|
||||
</code>);
|
||||
|
||||
// Bind events
|
||||
this.$body.on('click', '.refresh-stats', () => this.handleRefresh());
|
||||
this.$body.on('click', '.export-stats', () => this.handleExport());
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
if (!note) {
|
||||
this.clearStatistics();
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule statistics calculation
|
||||
await this.spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
async calculateStatistics() {
|
||||
try {
|
||||
const note = this.note;
|
||||
if (!note) return;
|
||||
|
||||
const content = await note.getContent();
|
||||
|
||||
if (note.type === 'text') {
|
||||
// Parse HTML content
|
||||
const $content = $('<div>').html(content);
|
||||
const textContent = $content.text();
|
||||
|
||||
// Calculate statistics
|
||||
this.statistics.words = this.countWords(textContent);
|
||||
this.statistics.characters = textContent.length;
|
||||
this.statistics.paragraphs = $content.find('p').length;
|
||||
this.statistics.readingTime = Math.ceil(this.statistics.words / 200);
|
||||
this.statistics.links = $content.find('a').length;
|
||||
this.statistics.images = $content.find('img').length;
|
||||
} else if (note.type === 'code') {
|
||||
// For code notes, count lines and characters
|
||||
const lines = content.split('\n');
|
||||
this.statistics.words = lines.length; // Show lines instead of words
|
||||
this.statistics.characters = content.length;
|
||||
this.statistics.paragraphs = 0;
|
||||
this.statistics.readingTime = 0;
|
||||
this.statistics.links = 0;
|
||||
this.statistics.images = 0;
|
||||
}
|
||||
|
||||
this.updateDisplay();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to calculate statistics:', error);
|
||||
toastService.showError("Failed to calculate statistics");
|
||||
}
|
||||
}
|
||||
|
||||
countWords(text) {
|
||||
const words = text.match(/\b\w+\b/g);
|
||||
return words ? words.length : 0;
|
||||
}
|
||||
|
||||
clearStatistics() {
|
||||
this.statistics = {
|
||||
words: 0,
|
||||
characters: 0,
|
||||
paragraphs: 0,
|
||||
readingTime: 0,
|
||||
links: 0,
|
||||
images: 0
|
||||
};
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
this.$body.find('[data-stat="words"]').text(this.statistics.words);
|
||||
this.$body.find('[data-stat="characters"]').text(this.statistics.characters);
|
||||
this.$body.find('[data-stat="paragraphs"]').text(this.statistics.paragraphs);
|
||||
this.$body.find('[data-stat="readingTime"]').text(<code>${this.statistics.readingTime} min</code>);
|
||||
this.$body.find('[data-stat="links"]').text(this.statistics.links);
|
||||
this.$body.find('[data-stat="images"]').text(this.statistics.images);
|
||||
}
|
||||
|
||||
async handleRefresh() {
|
||||
await this.calculateStatistics();
|
||||
toastService.showMessage("Statistics refreshed");
|
||||
}
|
||||
|
||||
async handleExport() {
|
||||
const note = this.note;
|
||||
if (!note) return;
|
||||
|
||||
const exportData = {
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
statistics: this.statistics,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Create a CSV
|
||||
const csv = [
|
||||
'Metric,Value',
|
||||
<code>Words,${this.statistics.words}</code>,
|
||||
<code>Characters,${this.statistics.characters}</code>,
|
||||
<code>Paragraphs,${this.statistics.paragraphs}</code>,
|
||||
<code>Reading Time,${this.statistics.readingTime} minutes</code>,
|
||||
<code>Links,${this.statistics.links}</code>,
|
||||
<code>Images,${this.statistics.images}</code>
|
||||
].join('\n');
|
||||
|
||||
// Download CSV
|
||||
const blob = new Blob([csv], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = <code>statistics-${note.noteId}.csv</code>;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toastService.showMessage("Statistics exported");
|
||||
}
|
||||
|
||||
async noteContentChangedEvent({ noteId }) {
|
||||
if (this.noteId === noteId) {
|
||||
await this.spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.$body.off('click');
|
||||
this.spacedUpdate = null;
|
||||
}
|
||||
}</p>
|
||||
|
||||
<p>export default NoteStatisticsWidget;
|
||||
</code></pre></p>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>1. Memory Management</h3>
|
||||
- Clean up event listeners in <code>cleanup()</code>
|
||||
- Clear caches and timers when widget is destroyed
|
||||
- Avoid circular references
|
||||
|
||||
<h3>2. Performance</h3>
|
||||
- Use debouncing for frequent updates
|
||||
- Implement lazy loading for expensive operations
|
||||
- Cache computed values when appropriate
|
||||
|
||||
<h3>3. Error Handling</h3>
|
||||
- Always wrap async operations in try-catch
|
||||
- Provide user feedback for errors
|
||||
- Log errors for debugging
|
||||
|
||||
<h3>4. User Experience</h3>
|
||||
- Show loading states for async operations
|
||||
- Provide clear error messages
|
||||
- Ensure widgets are responsive
|
||||
|
||||
<h3>5. Code Organization</h3>
|
||||
- Keep widgets focused on a single responsibility
|
||||
- Extract reusable logic into services
|
||||
- Use composition over inheritance when possible
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Widget Not Rendering</h3>
|
||||
- Check <code>doRender()</code> creates <code>this.$widget</code>
|
||||
- Verify widget is properly registered
|
||||
- Check console for errors
|
||||
|
||||
<h3>Events Not Firing</h3>
|
||||
- Ensure event method name matches pattern: <code>${eventName}Event</code>
|
||||
- Check event is being triggered
|
||||
- Verify widget is active/visible
|
||||
|
||||
<h3>State Not Persisting</h3>
|
||||
- Use options or attributes for persistence
|
||||
- Check save operations complete successfully
|
||||
- Verify data serialization
|
||||
|
||||
<h3>Performance Issues</h3>
|
||||
- Profile with browser dev tools
|
||||
- Implement caching and debouncing
|
||||
- Optimize DOM operations
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
|
||||
<p>- Explore existing widgets in <code>/apps/client/src/widgets/</code> for examples
|
||||
- Review the Frontend Script API documentation
|
||||
- Join the Trilium community for support and sharing widgets</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,119 +0,0 @@
|
||||
<h1>Anthropic Configuration Guide</h1>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<p>Anthropic provides access to the Claude 3 family of models, known for their strong analytical capabilities, safety features, and large context windows. This guide will help you configure Anthropic as your AI provider in Trilium Notes.</p>
|
||||
|
||||
<h2>Getting Started</h2>
|
||||
|
||||
<h3>Step 1: Create an Anthropic Account</h3>
|
||||
<ol>
|
||||
<li>Visit <a href="https://console.anthropic.com/signup" target="_blank">Anthropic Console</a></li>
|
||||
<li>Sign up with your email address</li>
|
||||
<li>Verify your email</li>
|
||||
<li>Complete account setup and billing information</li>
|
||||
</ol>
|
||||
|
||||
<h3>Step 2: Generate an API Key</h3>
|
||||
<ol>
|
||||
<li>Log into the <a href="https://console.anthropic.com/" target="_blank">Anthropic Console</a></li>
|
||||
<li>Navigate to <strong>API Keys</strong> section</li>
|
||||
<li>Click <strong>"Create Key"</strong></li>
|
||||
<li>Name your key (e.g., "Trilium Integration")</li>
|
||||
<li><strong>Important:</strong> Copy and save the key immediately</li>
|
||||
<li>Store securely - the key won't be shown again</li>
|
||||
</ol>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Security Notice</p>
|
||||
<p>Your API key provides full access to your Anthropic account. Never share it publicly or commit it to version control.</p>
|
||||
</div>
|
||||
|
||||
<h3>Step 3: Configure in Trilium</h3>
|
||||
<ol>
|
||||
<li>Open Trilium Notes</li>
|
||||
<li>Go to <strong>Options <20> AI/LLM</strong></li>
|
||||
<li>Enable AI features</li>
|
||||
<li>Select <strong>Anthropic</strong> from the provider dropdown</li>
|
||||
<li>Enter your configuration:
|
||||
<ul>
|
||||
<li><strong>API Key:</strong> Your Anthropic API key (sk-ant-...)</li>
|
||||
<li><strong>Base URL:</strong> <code>https://api.anthropic.com</code> (default)</li>
|
||||
<li><strong>Default Model:</strong> Choose from available Claude models</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click <strong>Test Connection</strong> to verify</li>
|
||||
</ol>
|
||||
|
||||
<h2>Available Models</h2>
|
||||
|
||||
<h3>Claude 3 Model Family</h3>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th>Best For</th>
|
||||
<th>Context Window</th>
|
||||
<th>Speed</th>
|
||||
<th>Cost (per 1M tokens)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>claude-3-opus-20240229</code></td>
|
||||
<td>Most capable, complex analysis, research</td>
|
||||
<td>200,000 tokens</td>
|
||||
<td>Slower</td>
|
||||
<td>$15 input / $75 output</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>claude-3-sonnet-20240229</code></td>
|
||||
<td>Balanced performance, general use</td>
|
||||
<td>200,000 tokens</td>
|
||||
<td>Medium</td>
|
||||
<td>$3 input / $15 output</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>claude-3-haiku-20240307</code></td>
|
||||
<td>Fast responses, simple tasks</td>
|
||||
<td>200,000 tokens</td>
|
||||
<td>Fastest</td>
|
||||
<td>$0.25 input / $1.25 output</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Configuration Options</h2>
|
||||
|
||||
<h3>Model Parameters</h3>
|
||||
<ul>
|
||||
<li><strong>Temperature (0.0-1.0):</strong> Controls response randomness
|
||||
<ul>
|
||||
<li>0.0-0.3: Precise, consistent responses</li>
|
||||
<li>0.4-0.7: Balanced creativity and accuracy</li>
|
||||
<li>0.8-1.0: Creative, varied outputs</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Max Tokens:</strong> Maximum response length
|
||||
<ul>
|
||||
<li>Default: 4096 tokens</li>
|
||||
<li>Maximum: Model-dependent (up to 200K)</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>Prompt Engineering for Claude</h3>
|
||||
<ul>
|
||||
<li><strong>Be Direct:</strong> Claude responds well to clear, direct instructions</li>
|
||||
<li><strong>Use XML Tags:</strong> Structure complex prompts with XML-like tags for better organization</li>
|
||||
<li><strong>Provide Examples:</strong> Claude excels at following patterns from examples</li>
|
||||
<li><strong>Think Step-by-Step:</strong> For complex tasks, ask Claude to reason through steps</li>
|
||||
</ul>
|
||||
|
||||
<h2>Additional Resources</h2>
|
||||
<ul>
|
||||
<li><a href="https://docs.anthropic.com/" target="_blank">Anthropic API Documentation</a></li>
|
||||
<li><a href="https://console.anthropic.com/" target="_blank">Anthropic Console</a></li>
|
||||
<li><a href="https://www.anthropic.com/claude" target="_blank">Claude Model Information</a></li>
|
||||
</ul>
|
||||
@@ -1,255 +0,0 @@
|
||||
<h1>OpenAI Configuration Guide</h1>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<p>OpenAI provides access to GPT-4, GPT-3.5-turbo, and other advanced language models through their API. This guide will help you set up and configure OpenAI as your AI provider in Trilium Notes.</p>
|
||||
|
||||
<h2>Getting Started</h2>
|
||||
|
||||
<h3>Step 1: Create an OpenAI Account</h3>
|
||||
<ol>
|
||||
<li>Visit <a href="https://platform.openai.com/signup" target="_blank">OpenAI Platform</a></li>
|
||||
<li>Sign up with your email or Google/Microsoft account</li>
|
||||
<li>Verify your email address</li>
|
||||
<li>Complete your profile information</li>
|
||||
</ol>
|
||||
|
||||
<h3>Step 2: Obtain an API Key</h3>
|
||||
<ol>
|
||||
<li>Navigate to <a href="https://platform.openai.com/api-keys" target="_blank">API Keys</a> in your OpenAI account</li>
|
||||
<li>Click <strong>"Create new secret key"</strong></li>
|
||||
<li>Give your key a descriptive name (e.g., "Trilium Notes")</li>
|
||||
<li><strong>Important:</strong> Copy the key immediately - it won't be shown again!</li>
|
||||
<li>Store the key securely (password manager recommended)</li>
|
||||
</ol>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Security Note</p>
|
||||
<p>Never share your API key or commit it to version control. Treat it like a password.</p>
|
||||
</div>
|
||||
|
||||
<h3>Step 3: Configure in Trilium</h3>
|
||||
<ol>
|
||||
<li>Open Trilium Notes</li>
|
||||
<li>Navigate to <strong>Options <20> AI/LLM</strong></li>
|
||||
<li>Enable AI features if not already enabled</li>
|
||||
<li>Select <strong>OpenAI</strong> from the provider dropdown</li>
|
||||
<li>Enter your configuration:
|
||||
<ul>
|
||||
<li><strong>API Key:</strong> Paste your OpenAI API key</li>
|
||||
<li><strong>Base URL:</strong> <code>https://api.openai.com/v1</code> (default)</li>
|
||||
<li><strong>Default Model:</strong> Select from available models (see below)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click <strong>Test Connection</strong> to verify setup</li>
|
||||
</ol>
|
||||
|
||||
<h2>Available Models</h2>
|
||||
|
||||
<h3>Chat Models</h3>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th>Best For</th>
|
||||
<th>Context Window</th>
|
||||
<th>Cost (per 1K tokens)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>gpt-4-turbo-preview</code></td>
|
||||
<td>Complex reasoning, analysis, latest knowledge</td>
|
||||
<td>128,000 tokens</td>
|
||||
<td>$0.01 input / $0.03 output</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gpt-4</code></td>
|
||||
<td>High-quality responses, complex tasks</td>
|
||||
<td>8,192 tokens</td>
|
||||
<td>$0.03 input / $0.06 output</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gpt-4-32k</code></td>
|
||||
<td>Long documents, extensive context</td>
|
||||
<td>32,768 tokens</td>
|
||||
<td>$0.06 input / $0.12 output</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gpt-3.5-turbo</code></td>
|
||||
<td>Quick responses, general use, cost-effective</td>
|
||||
<td>16,385 tokens</td>
|
||||
<td>$0.0005 input / $0.0015 output</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gpt-3.5-turbo-16k</code></td>
|
||||
<td>Longer conversations, more context</td>
|
||||
<td>16,385 tokens</td>
|
||||
<td>$0.003 input / $0.004 output</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Embedding Models</h3>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th>Dimensions</th>
|
||||
<th>Performance</th>
|
||||
<th>Cost (per 1M tokens)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>text-embedding-3-small</code></td>
|
||||
<td>1,536</td>
|
||||
<td>Good, cost-effective</td>
|
||||
<td>$0.02</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>text-embedding-3-large</code></td>
|
||||
<td>3,072</td>
|
||||
<td>Best quality</td>
|
||||
<td>$0.13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>text-embedding-ada-002</code></td>
|
||||
<td>1,536</td>
|
||||
<td>Legacy, still supported</td>
|
||||
<td>$0.10</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Configuration Options</h2>
|
||||
|
||||
<h3>Model Parameters</h3>
|
||||
<ul>
|
||||
<li><strong>Temperature (0.0-2.0):</strong> Controls randomness. Lower = more focused, Higher = more creative
|
||||
<ul>
|
||||
<li>0.0-0.3: Factual, deterministic responses</li>
|
||||
<li>0.4-0.7: Balanced (recommended)</li>
|
||||
<li>0.8-1.0: Creative, varied responses</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Max Tokens:</strong> Maximum response length (1 token H 0.75 words)
|
||||
<ul>
|
||||
<li>Default: 4000 tokens</li>
|
||||
<li>Adjust based on needs and cost considerations</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Top P (0.0-1.0):</strong> Nucleus sampling threshold
|
||||
<ul>
|
||||
<li>Default: 1.0 (consider all tokens)</li>
|
||||
<li>Lower values = more focused responses</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Advanced Settings</h3>
|
||||
<h4>Custom Endpoints</h4>
|
||||
<p>For Azure OpenAI or OpenAI-compatible services:</p>
|
||||
<pre><code>Base URL: https://your-resource.openai.azure.com/
|
||||
API Version: 2024-02-15-preview
|
||||
Deployment Name: your-deployment-name</code></pre>
|
||||
|
||||
<h4>Rate Limiting</h4>
|
||||
<p>Configure to avoid hitting API limits:</p>
|
||||
<ul>
|
||||
<li>Tier 1: 60 requests/minute, 200,000 tokens/minute</li>
|
||||
<li>Tier 2: 120 requests/minute, 400,000 tokens/minute</li>
|
||||
<li>Higher tiers available upon request</li>
|
||||
</ul>
|
||||
|
||||
<h2>Cost Management</h2>
|
||||
|
||||
<h3>Estimating Costs</h3>
|
||||
<p>Typical usage patterns and estimated monthly costs:</p>
|
||||
<ul>
|
||||
<li><strong>Light Use (Personal):</strong> ~$5-10/month
|
||||
<ul>
|
||||
<li>50 queries/day with GPT-3.5-turbo</li>
|
||||
<li>Basic embeddings for 1000 notes</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Regular Use (Professional):</strong> ~$20-50/month
|
||||
<ul>
|
||||
<li>100 queries/day with mix of GPT-4 and GPT-3.5</li>
|
||||
<li>Comprehensive embeddings for 5000 notes</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Heavy Use (Research/Business):</strong> ~$100+/month
|
||||
<ul>
|
||||
<li>200+ queries/day primarily with GPT-4</li>
|
||||
<li>Large-scale embeddings and regular updates</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Cost Optimization Tips</h3>
|
||||
<ol>
|
||||
<li><strong>Use GPT-3.5-turbo for simple queries</strong> - 60x cheaper than GPT-4</li>
|
||||
<li><strong>Enable response caching</strong> - Avoid repeated API calls</li>
|
||||
<li><strong>Set token limits</strong> - Prevent unexpectedly long responses</li>
|
||||
<li><strong>Use text-embedding-3-small</strong> - Good quality at low cost</li>
|
||||
<li><strong>Monitor usage</strong> - Check OpenAI dashboard regularly</li>
|
||||
</ol>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Issues</h3>
|
||||
|
||||
<div class="admonition info">
|
||||
<p class="admonition-title">Invalid API Key</p>
|
||||
<p><strong>Solution:</strong> Verify the key is copied correctly without spaces. Check it hasn't been revoked in your OpenAI dashboard.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition info">
|
||||
<p class="admonition-title">Rate Limit Exceeded</p>
|
||||
<p><strong>Solution:</strong> Wait a few minutes and retry. Consider upgrading your API tier or implementing request throttling.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition info">
|
||||
<p class="admonition-title">Model Not Found</p>
|
||||
<p><strong>Solution:</strong> Ensure you have access to the model. GPT-4 requires separate approval from OpenAI.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition info">
|
||||
<p class="admonition-title">Connection Timeout</p>
|
||||
<p><strong>Solution:</strong> Check your internet connection and firewall settings. Ensure port 443 is open for HTTPS.</p>
|
||||
</div>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>Security</h3>
|
||||
<ul>
|
||||
<li>Rotate API keys monthly</li>
|
||||
<li>Use separate keys for development and production</li>
|
||||
<li>Monitor usage for unusual activity</li>
|
||||
<li>Set spending limits in OpenAI dashboard</li>
|
||||
</ul>
|
||||
|
||||
<h3>Performance</h3>
|
||||
<ul>
|
||||
<li>Start with smaller models and upgrade as needed</li>
|
||||
<li>Use streaming for better perceived performance</li>
|
||||
<li>Implement retry logic with exponential backoff</li>
|
||||
<li>Cache frequently requested information</li>
|
||||
</ul>
|
||||
|
||||
<h3>Quality</h3>
|
||||
<ul>
|
||||
<li>Provide clear, specific prompts</li>
|
||||
<li>Use system prompts to set behavior</li>
|
||||
<li>Include examples in prompts when needed</li>
|
||||
<li>Test different temperature settings</li>
|
||||
</ul>
|
||||
|
||||
<h2>Additional Resources</h2>
|
||||
<ul>
|
||||
<li><a href="https://platform.openai.com/docs" target="_blank">OpenAI API Documentation</a></li>
|
||||
<li><a href="https://platform.openai.com/usage" target="_blank">Usage Dashboard</a></li>
|
||||
<li><a href="https://openai.com/pricing" target="_blank">Pricing Calculator</a></li>
|
||||
<li><a href="https://status.openai.com/" target="_blank">API Status Page</a></li>
|
||||
<li><a href="https://cookbook.openai.com/" target="_blank">OpenAI Cookbook</a></li>
|
||||
</ul>
|
||||
274
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Features and Usage.html
generated
vendored
274
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Features and Usage.html
generated
vendored
@@ -1,274 +0,0 @@
|
||||
<h1>AI Features and Usage Guide</h1>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<p>This guide explains how to effectively use AI features in Trilium Notes to enhance your knowledge management workflow.</p>
|
||||
|
||||
<h2>Chat with Notes</h2>
|
||||
|
||||
<h3>Getting Started</h3>
|
||||
<p>The Chat with Notes feature provides an interactive AI assistant that has access to your knowledge base.</p>
|
||||
|
||||
<h4>Accessing the Chat Interface</h4>
|
||||
<ol>
|
||||
<li><strong>Via Launcher Button:</strong> Click the "Chat with Notes" button in the left sidebar</li>
|
||||
<li><strong>Via Keyboard Shortcut:</strong> Press <code>Ctrl+Shift+C</code> (customizable)</li>
|
||||
<li><strong>Via Command Palette:</strong> Press <code>Ctrl+P</code> and type "chat"</li>
|
||||
</ol>
|
||||
|
||||
<h3>Basic Chat Operations</h3>
|
||||
|
||||
<h4>Asking Questions</h4>
|
||||
<p>The AI can answer questions based on your notes:</p>
|
||||
<pre><code>User: What are the main points from my meeting notes this week?
|
||||
AI: Based on your meeting notes from this week, here are the main points:
|
||||
- Monday: Project timeline discussed, deadline set for March 30
|
||||
- Wednesday: Budget approved for Q2
|
||||
- Friday: New team member onboarding planned</code></pre>
|
||||
|
||||
<h4>Summarizing Content</h4>
|
||||
<pre><code>User: Summarize my research notes on machine learning
|
||||
AI: Your machine learning research covers:
|
||||
1. Supervised learning algorithms (Random Forests, SVM)
|
||||
2. Neural network architectures
|
||||
3. Data preprocessing techniques
|
||||
4. Model evaluation metrics</code></pre>
|
||||
|
||||
<h2>AI-Powered Tools</h2>
|
||||
|
||||
<h3>Available Tools</h3>
|
||||
<p>The AI can execute various tools to interact with your notes:</p>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tool</th>
|
||||
<th>Function</th>
|
||||
<th>Example Usage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>search_notes</code></td>
|
||||
<td>Semantic search across notes</td>
|
||||
<td>"Find all notes about project management"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>keyword_search</code></td>
|
||||
<td>Exact keyword matching</td>
|
||||
<td>"Search for notes containing 'API documentation'"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>read_note</code></td>
|
||||
<td>Access full note content</td>
|
||||
<td>"Show me the content of my budget proposal"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>create_note</code></td>
|
||||
<td>Generate new notes</td>
|
||||
<td>"Create a note summarizing this discussion"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>update_note</code></td>
|
||||
<td>Modify existing notes</td>
|
||||
<td>"Add these action items to my todo list"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>manage_attributes</code></td>
|
||||
<td>Handle note metadata</td>
|
||||
<td>"Add #important label to my proposal"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>calendar_integration</code></td>
|
||||
<td>Work with date notes</td>
|
||||
<td>"What's on my calendar tomorrow?"</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Tool Execution Examples</h3>
|
||||
|
||||
<div class="admonition example">
|
||||
<p class="admonition-title">Research Assistant Example</p>
|
||||
<pre><code>User: Help me research quantum computing for my presentation
|
||||
|
||||
AI: I'll help you research quantum computing. Let me search your notes.
|
||||
[Executing search_notes: "quantum computing"]
|
||||
Found 3 notes on quantum topics.
|
||||
|
||||
[Executing read_note: "Quantum Basics"]
|
||||
You have notes covering superposition and entanglement.
|
||||
|
||||
[Executing create_note: "Quantum Computing Presentation Outline"]
|
||||
Created outline with:
|
||||
1. Introduction to quantum concepts
|
||||
2. Current applications
|
||||
3. Future possibilities</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Semantic Search</h2>
|
||||
|
||||
<h3>How It Works</h3>
|
||||
<p>Semantic search uses AI embeddings to find conceptually related content, even when exact keywords don't match.</p>
|
||||
|
||||
<h4>Benefits</h4>
|
||||
<ul>
|
||||
<li>Find related concepts without exact keywords</li>
|
||||
<li>Discover unexpected connections</li>
|
||||
<li>More intuitive search results</li>
|
||||
<li>Better context understanding</li>
|
||||
</ul>
|
||||
|
||||
<h4>Example Searches</h4>
|
||||
<pre><code>Query: "improving productivity"
|
||||
Finds: Time management, GTD methodology, focus strategies, tool optimization
|
||||
|
||||
Query: "customer satisfaction"
|
||||
Finds: User feedback, NPS scores, support tickets, product reviews</code></pre>
|
||||
|
||||
<h2>Content Generation</h2>
|
||||
|
||||
<h3>Note Creation</h3>
|
||||
<p>AI can help create various types of notes:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Meeting Summaries:</strong> "Create a summary of today's standup discussion"</li>
|
||||
<li><strong>Research Outlines:</strong> "Generate an outline for my thesis on climate change"</li>
|
||||
<li><strong>Documentation:</strong> "Create API documentation from these code snippets"</li>
|
||||
<li><strong>Reports:</strong> "Generate a weekly progress report from my task notes"</li>
|
||||
</ul>
|
||||
|
||||
<h3>Content Enhancement</h3>
|
||||
<p>Improve existing notes with AI assistance:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Expansion:</strong> "Expand the bullet points in my ideas note"</li>
|
||||
<li><strong>Clarification:</strong> "Rewrite this technical explanation for beginners"</li>
|
||||
<li><strong>Formatting:</strong> "Convert my rough notes into a structured document"</li>
|
||||
<li><strong>Translation:</strong> "Translate this note to Spanish"</li>
|
||||
</ul>
|
||||
|
||||
<h2>Workflow Integration</h2>
|
||||
|
||||
<h3>Daily Review Workflow</h3>
|
||||
<pre><code>User: Give me my daily review
|
||||
|
||||
AI: Daily Review for [Today's Date]:
|
||||
✅ Completed Tasks:
|
||||
- Reviewed design mockups
|
||||
- Sent project update
|
||||
- Prepared agenda
|
||||
|
||||
📝 Notes Created:
|
||||
- "Client Feedback"
|
||||
- "API Research"
|
||||
- "Team Notes"
|
||||
|
||||
📅 Tomorrow:
|
||||
- Project review at 10 AM
|
||||
- Sprint planning at 2 PM</code></pre>
|
||||
|
||||
<h3>Research Workflow</h3>
|
||||
<ol>
|
||||
<li><strong>Gather Information:</strong> "Find all notes about [topic]"</li>
|
||||
<li><strong>Synthesize:</strong> "Create a summary of these research notes"</li>
|
||||
<li><strong>Organize:</strong> "Suggest a structure for organizing this information"</li>
|
||||
<li><strong>Generate:</strong> "Create a draft based on this research"</li>
|
||||
</ol>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
|
||||
<h3>Effective Prompting</h3>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Instead of...</th>
|
||||
<th>Try...</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>"Tell me about my notes"</td>
|
||||
<td>"Summarize my project management notes from Q1"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"What should I do?"</td>
|
||||
<td>"Based on my project timeline, what tasks should I prioritize?"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"Give me information"</td>
|
||||
<td>"Create a bullet-point summary of my research on AI"</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Managing Conversations</h3>
|
||||
<ul>
|
||||
<li><strong>Start fresh:</strong> Begin new chats for different topics</li>
|
||||
<li><strong>Be specific:</strong> Reference note titles when possible</li>
|
||||
<li><strong>Iterate:</strong> Use follow-up questions to refine responses</li>
|
||||
<li><strong>Provide context:</strong> Include relevant background information</li>
|
||||
</ul>
|
||||
|
||||
<h3>Performance Tips</h3>
|
||||
<ul>
|
||||
<li><strong>For speed:</strong> Use simpler models for basic tasks</li>
|
||||
<li><strong>For quality:</strong> Provide examples of desired output</li>
|
||||
<li><strong>For cost:</strong> Enable caching and batch similar queries</li>
|
||||
<li><strong>For relevance:</strong> Keep your notes well-organized and labeled</li>
|
||||
</ul>
|
||||
|
||||
<h2>Advanced Usage Patterns</h2>
|
||||
|
||||
<h3>Creating Knowledge Graphs</h3>
|
||||
<pre><code>User: Create a knowledge graph of my programming notes
|
||||
|
||||
AI: Analyzing your programming notes...
|
||||
[Creates visual representation showing connections between:
|
||||
- Languages (Python, JavaScript, Java)
|
||||
- Frameworks (React, Django, Spring)
|
||||
- Concepts (OOP, Functional, Design Patterns)
|
||||
- Projects (Web App, API, Mobile)]</code></pre>
|
||||
|
||||
<h3>Automated Analysis</h3>
|
||||
<pre><code>User: Analyze sentiment in my journal entries this month
|
||||
|
||||
AI: Sentiment Analysis - Past 30 Days:
|
||||
📈 Positive Trend: Mood improving over time
|
||||
😊 Positive themes: achievements, learning, relationships
|
||||
😟 Challenges: workload, deadlines
|
||||
💡 Suggestion: Schedule breaks after intense work periods</code></pre>
|
||||
|
||||
<h2>Privacy and Control</h2>
|
||||
|
||||
<h3>Excluding Notes from AI</h3>
|
||||
<p>To prevent specific notes from being accessed by AI:</p>
|
||||
<ol>
|
||||
<li>Add the <code>#excludeFromAI</code> label to sensitive notes</li>
|
||||
<li>These notes won't be included in searches or context</li>
|
||||
<li>Perfect for personal or confidential information</li>
|
||||
</ol>
|
||||
|
||||
<h3>Data Usage</h3>
|
||||
<ul>
|
||||
<li>Only explicitly requested notes are sent to AI</li>
|
||||
<li>No automatic uploads or background processing</li>
|
||||
<li>You control what data the AI can access</li>
|
||||
</ul>
|
||||
|
||||
<h2>Troubleshooting Common Issues</h2>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">AI Not Finding Relevant Notes</p>
|
||||
<p><strong>Solution:</strong> Ensure embeddings are generated. Go to Settings → AI/LLM and click "Recreate All Embeddings".</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Tools Not Executing</p>
|
||||
<p><strong>Solution:</strong> Verify tool calling is enabled in your AI settings and your provider supports function calling.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Slow Responses</p>
|
||||
<p><strong>Solution:</strong> Try using a faster model (GPT-3.5-turbo, Claude Haiku) or reduce the context size in settings.</p>
|
||||
</div>
|
||||
27
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Introduction.html
generated
vendored
27
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Introduction.html
generated
vendored
@@ -3,27 +3,12 @@
|
||||
height="1364">
|
||||
<figcaption>An example chat with an LLM</figcaption>
|
||||
</figure>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<p>The AI / LLM features within Trilium Notes are designed to enhance your note-taking and knowledge management experience through intelligent search, content generation, and interactive assistance. These features integrate seamlessly with your personal knowledge base while maintaining complete control over your data.</p>
|
||||
|
||||
<h3>Key Capabilities</h3>
|
||||
<ul>
|
||||
<li><strong>Chat with Notes</strong> - An interactive AI assistant that can answer questions based on your note content, provide summaries, and help discover connections</li>
|
||||
<li><strong>Semantic Search</strong> - Find conceptually related notes even when exact keywords don't match</li>
|
||||
<li><strong>Tool-Enabled Actions</strong> - AI can create, update, search, and manage your notes automatically</li>
|
||||
<li><strong>Content Generation</strong> - Generate summaries, expand ideas, and assist with writing</li>
|
||||
</ul>
|
||||
|
||||
<h3>Supported Providers</h3>
|
||||
<p>We support multiple AI providers to give you flexibility in choosing between cloud and local options:</p>
|
||||
<ul>
|
||||
<li><strong>OpenAI</strong> - GPT-4, GPT-3.5-turbo models with excellent general knowledge</li>
|
||||
<li><strong>Anthropic</strong> - Claude 3 family with strong analytical capabilities</li>
|
||||
<li><strong>Ollama</strong> - Run AI models locally for complete privacy and offline use</li>
|
||||
</ul>
|
||||
|
||||
<p>The quickest way to get started is to navigate to the "AI/LLM" settings:</p>
|
||||
<p>The AI / LLM features within Trilium Notes are designed to allow you to
|
||||
interact with your Notes in a variety of ways, using as many of the major
|
||||
providers as we can support. </p>
|
||||
<p>In addition to being able to send chats to LLM providers such as OpenAI,
|
||||
Anthropic, and Ollama - we also support agentic tool calling, and embeddings.</p>
|
||||
<p>The quickest way to get started is to navigate to the “AI/LLM” settings:</p>
|
||||
<figure
|
||||
class="image image_resized" style="width:74.04%;">
|
||||
<img style="aspect-ratio:1916/1906;" src="5_Introduction_image.png" width="1916"
|
||||
|
||||
328
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Security and Privacy.html
generated
vendored
328
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Security and Privacy.html
generated
vendored
@@ -1,328 +0,0 @@
|
||||
<h1>Security and Privacy Guidelines</h1>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<p>This document outlines important security considerations and privacy best practices for using AI features in Trilium Notes. Your privacy and data security are paramount.</p>
|
||||
|
||||
<h2>Data Privacy by Provider</h2>
|
||||
|
||||
<h3>Cloud Providers (OpenAI, Anthropic)</h3>
|
||||
|
||||
<div class="admonition info">
|
||||
<p class="admonition-title">What Gets Sent</p>
|
||||
<ul>
|
||||
<li>Selected note content based on your queries</li>
|
||||
<li>Your questions and prompts</li>
|
||||
<li>System instructions for AI behavior</li>
|
||||
<li>Tool execution parameters</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="admonition success">
|
||||
<p class="admonition-title">What Stays Private</p>
|
||||
<ul>
|
||||
<li>Notes marked with <code>#excludeFromAI</code> label</li>
|
||||
<li>Encrypted note content (unless explicitly decrypted)</li>
|
||||
<li>System metadata and file paths</li>
|
||||
<li>Other users' data in multi-user setups</li>
|
||||
<li>Your API keys and credentials</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>Provider Data Policies</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Provider</th>
|
||||
<th>Data Usage</th>
|
||||
<th>Retention</th>
|
||||
<th>Compliance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>OpenAI</td>
|
||||
<td>API data not used for training</td>
|
||||
<td>30 days for abuse monitoring</td>
|
||||
<td>SOC 2 Type II</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Anthropic</td>
|
||||
<td>No training on API inputs</td>
|
||||
<td>Limited retention period</td>
|
||||
<td>SOC 2 Type II</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Local Provider (Ollama)</h3>
|
||||
|
||||
<div class="admonition success">
|
||||
<p class="admonition-title">Complete Privacy with Ollama</p>
|
||||
<ul>
|
||||
<li>✅ No data leaves your machine</li>
|
||||
<li>✅ No external API calls</li>
|
||||
<li>✅ No usage tracking or telemetry</li>
|
||||
<li>✅ Works completely offline</li>
|
||||
<li>✅ You control all models and data</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Protecting Sensitive Information</h2>
|
||||
|
||||
<h3>Using the Exclusion System</h3>
|
||||
|
||||
<h4>Excluding Individual Notes</h4>
|
||||
<ol>
|
||||
<li>Open the note you want to protect</li>
|
||||
<li>Add the label: <code>#excludeFromAI</code></li>
|
||||
<li>The note will be completely excluded from AI processing</li>
|
||||
</ol>
|
||||
|
||||
<h4>Bulk Exclusion Script</h4>
|
||||
<pre><code>// Script to exclude all notes in a folder
|
||||
const folder = api.getNoteWithLabel('confidential');
|
||||
const descendants = folder.getDescendants();
|
||||
|
||||
for (const note of descendants) {
|
||||
note.addLabel('excludeFromAI');
|
||||
}
|
||||
|
||||
api.showMessage(`Excluded ${descendants.length} notes from AI`);</code></pre>
|
||||
|
||||
<h4>Verifying Exclusions</h4>
|
||||
<p>To see which notes are excluded from AI:</p>
|
||||
<ol>
|
||||
<li>Go to Search</li>
|
||||
<li>Enter: <code>#excludeFromAI</code></li>
|
||||
<li>Review the list of protected notes</li>
|
||||
</ol>
|
||||
|
||||
<h3>Content Filtering</h3>
|
||||
|
||||
<p>Trilium can automatically filter sensitive patterns:</p>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pattern Type</th>
|
||||
<th>Example</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Social Security Numbers</td>
|
||||
<td>XXX-XX-XXXX</td>
|
||||
<td>Automatically redacted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Credit Card Numbers</td>
|
||||
<td>16-digit numbers</td>
|
||||
<td>Automatically redacted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API Keys</td>
|
||||
<td>Strings matching key patterns</td>
|
||||
<td>Automatically redacted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Passwords</td>
|
||||
<td>password: fields</td>
|
||||
<td>Automatically redacted</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>API Key Security</h2>
|
||||
|
||||
<h3>Best Practices</h3>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Never Do This</p>
|
||||
<ul>
|
||||
<li>❌ Share API keys in notes or messages</li>
|
||||
<li>❌ Commit keys to version control</li>
|
||||
<li>❌ Use the same key across multiple applications</li>
|
||||
<li>❌ Store keys in plain text files</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="admonition success">
|
||||
<p class="admonition-title">Always Do This</p>
|
||||
<ul>
|
||||
<li>✅ Store keys in Trilium's secure settings</li>
|
||||
<li>✅ Rotate keys regularly (monthly recommended)</li>
|
||||
<li>✅ Use separate keys for development/production</li>
|
||||
<li>✅ Set spending limits in provider dashboards</li>
|
||||
<li>✅ Monitor usage for unusual activity</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Key Rotation Schedule</h3>
|
||||
<ol>
|
||||
<li><strong>Monthly:</strong> Rotate API keys</li>
|
||||
<li><strong>Immediately:</strong> If key may be compromised</li>
|
||||
<li><strong>Quarterly:</strong> Review and audit all keys</li>
|
||||
<li><strong>Annually:</strong> Full security review</li>
|
||||
</ol>
|
||||
|
||||
<h2>Network Security</h2>
|
||||
|
||||
<h3>Secure Connections</h3>
|
||||
<ul>
|
||||
<li>All API communications use HTTPS/TLS encryption</li>
|
||||
<li>Certificate verification is enabled by default</li>
|
||||
<li>Minimum TLS version 1.2 required</li>
|
||||
</ul>
|
||||
|
||||
<h3>Firewall Considerations</h3>
|
||||
<p>Required ports for AI providers:</p>
|
||||
<ul>
|
||||
<li><strong>OpenAI/Anthropic:</strong> Port 443 (HTTPS)</li>
|
||||
<li><strong>Ollama:</strong> Port 11434 (local only by default)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Compliance and Regulations</h2>
|
||||
|
||||
<h3>GDPR Compliance</h3>
|
||||
|
||||
<h4>Your Rights</h4>
|
||||
<ul>
|
||||
<li><strong>Right to Access:</strong> Export all AI-related data</li>
|
||||
<li><strong>Right to Deletion:</strong> Remove AI chat history and embeddings</li>
|
||||
<li><strong>Right to Portability:</strong> Export data in standard formats</li>
|
||||
<li><strong>Right to Restriction:</strong> Limit AI processing of your data</li>
|
||||
</ul>
|
||||
|
||||
<h4>Data Minimization</h4>
|
||||
<ul>
|
||||
<li>Send only necessary data to AI providers</li>
|
||||
<li>Regularly clean up old chat sessions</li>
|
||||
<li>Delete unused embeddings</li>
|
||||
<li>Implement retention policies</li>
|
||||
</ul>
|
||||
|
||||
<h3>Healthcare Data (HIPAA)</h3>
|
||||
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">For Healthcare Professionals</p>
|
||||
<p>If handling protected health information (PHI):</p>
|
||||
<ul>
|
||||
<li>Use Ollama (local) exclusively - no cloud providers</li>
|
||||
<li>Or ensure Business Associate Agreement (BAA) with provider</li>
|
||||
<li>Enable maximum audit logging</li>
|
||||
<li>Implement additional encryption</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Security Configurations by Use Case</h2>
|
||||
|
||||
<h3>Personal Use (Maximum Privacy)</h3>
|
||||
<pre><code>Provider: Ollama (local)
|
||||
Model: llama3 or mistral
|
||||
Embeddings: mxbai-embed-large
|
||||
Network: Localhost only
|
||||
Exclusions: Personal notes labeled</code></pre>
|
||||
|
||||
<h3>Professional Use (Balanced)</h3>
|
||||
<pre><code>Provider: OpenAI or Anthropic
|
||||
Model: GPT-4 or Claude Sonnet
|
||||
API Keys: Rotated monthly
|
||||
Exclusions: Confidential projects
|
||||
Audit: Logging enabled</code></pre>
|
||||
|
||||
<h3>Enterprise Use (Maximum Security)</h3>
|
||||
<pre><code>Provider: Azure OpenAI (private instance)
|
||||
Authentication: SSO + MFA
|
||||
Network: VPN required
|
||||
Audit: Full logging to SIEM
|
||||
DLP: Content scanning enabled</code></pre>
|
||||
|
||||
<h2>Incident Response</h2>
|
||||
|
||||
<h3>If API Key is Compromised</h3>
|
||||
|
||||
<div class="admonition danger">
|
||||
<p class="admonition-title">Immediate Actions Required</p>
|
||||
<ol>
|
||||
<li><strong>Revoke the key immediately</strong> in provider dashboard</li>
|
||||
<li><strong>Generate new key</strong> and update in Trilium</li>
|
||||
<li><strong>Review usage logs</strong> for unauthorized activity</li>
|
||||
<li><strong>Check billing</strong> for unexpected charges</li>
|
||||
<li><strong>Document the incident</strong> for future reference</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3>If Sensitive Data Was Sent</h3>
|
||||
<ol>
|
||||
<li>Contact the AI provider's support team</li>
|
||||
<li>Request data deletion if possible</li>
|
||||
<li>Add affected notes to exclusion list</li>
|
||||
<li>Review and update security practices</li>
|
||||
<li>Consider switching to local AI (Ollama)</li>
|
||||
</ol>
|
||||
|
||||
<h2>Security Checklist</h2>
|
||||
|
||||
<h3>Initial Setup</h3>
|
||||
<ul class="checklist">
|
||||
<li>☐ Reviewed provider privacy policies</li>
|
||||
<li>☐ Created strong, unique API keys</li>
|
||||
<li>☐ Configured exclusion labels for sensitive notes</li>
|
||||
<li>☐ Tested security configuration</li>
|
||||
<li>☐ Set up spending limits</li>
|
||||
<li>☐ Enabled audit logging</li>
|
||||
</ul>
|
||||
|
||||
<h3>Ongoing Maintenance</h3>
|
||||
<ul class="checklist">
|
||||
<li>☐ Rotate API keys monthly</li>
|
||||
<li>☐ Review audit logs weekly</li>
|
||||
<li>☐ Update exclusion lists as needed</li>
|
||||
<li>☐ Monitor usage and costs</li>
|
||||
<li>☐ Check for security updates</li>
|
||||
<li>☐ Verify no sensitive data in logs</li>
|
||||
</ul>
|
||||
|
||||
<h2>Privacy-First Recommendations</h2>
|
||||
|
||||
<h3>For Maximum Privacy</h3>
|
||||
<p><strong>Use Ollama exclusively:</strong></p>
|
||||
<ul>
|
||||
<li>Complete data control</li>
|
||||
<li>No external dependencies</li>
|
||||
<li>Works offline</li>
|
||||
<li>No usage tracking</li>
|
||||
</ul>
|
||||
|
||||
<h3>For Convenience with Privacy</h3>
|
||||
<p><strong>Hybrid approach:</strong></p>
|
||||
<ul>
|
||||
<li>Ollama for sensitive content</li>
|
||||
<li>Cloud providers for general queries</li>
|
||||
<li>Strict exclusion labels</li>
|
||||
<li>Regular key rotation</li>
|
||||
</ul>
|
||||
|
||||
<h3>For Teams and Organizations</h3>
|
||||
<p><strong>Enterprise configuration:</strong></p>
|
||||
<ul>
|
||||
<li>Private AI instances (Azure OpenAI)</li>
|
||||
<li>Centralized key management</li>
|
||||
<li>Audit logging to SIEM</li>
|
||||
<li>DLP integration</li>
|
||||
<li>Regular security training</li>
|
||||
</ul>
|
||||
|
||||
<h2>Additional Resources</h2>
|
||||
<ul>
|
||||
<li><a href="https://openai.com/policies/privacy-policy" target="_blank">OpenAI Privacy Policy</a></li>
|
||||
<li><a href="https://www.anthropic.com/privacy" target="_blank">Anthropic Privacy Policy</a></li>
|
||||
<li><a href="https://gdpr.eu/" target="_blank">GDPR Information</a></li>
|
||||
<li><a href="https://www.hhs.gov/hipaa/index.html" target="_blank">HIPAA Guidelines</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Remember</p>
|
||||
<p>Security and privacy require ongoing attention. Start with the most restrictive settings and gradually relax them only as needed. When in doubt, prefer local processing with Ollama for sensitive data.</p>
|
||||
</div>
|
||||
@@ -1,264 +0,0 @@
|
||||
<h1>Bulk Operations</h1>
|
||||
|
||||
<p>Execute actions on multiple notes simultaneously to save time and ensure consistency across your note collection.</p>
|
||||
|
||||
<h2>Quick Start</h2>
|
||||
|
||||
<p>Access bulk operations through:</p>
|
||||
<ul>
|
||||
<li>Search results menu → "Bulk Actions"</li>
|
||||
<li>Select multiple notes → Right-click → "Bulk Operations"</li>
|
||||
<li>Script API: <code>api.executeBulkActions(noteIds, actions)</code></li>
|
||||
</ul>
|
||||
|
||||
<h2>Available Operations</h2>
|
||||
|
||||
<h3>Note Operations</h3>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Move Notes</h4>
|
||||
<p>Relocate multiple notes to a new parent location.</p>
|
||||
<pre><code>{
|
||||
"name": "moveNote",
|
||||
"targetParentNoteId": "target_note_id"
|
||||
}</code></pre>
|
||||
<p class="note">Notes with multiple parents will be cloned rather than moved.</p>
|
||||
</div>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Delete Notes</h4>
|
||||
<p>Permanently remove multiple notes from the database.</p>
|
||||
<pre><code>{
|
||||
"name": "deleteNote"
|
||||
}</code></pre>
|
||||
<div class="warning">
|
||||
<strong>Warning:</strong> This operation cannot be undone. Ensure you have backups.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Rename Notes</h4>
|
||||
<p>Update titles using dynamic patterns with variables.</p>
|
||||
<pre><code>{
|
||||
"name": "renameNote",
|
||||
"newTitle": "Project: ${note.title}"
|
||||
}</code></pre>
|
||||
<p>Available variables: <code>${note.title}</code>, <code>${note.noteId}</code>, <code>${note.dateCreated}</code></p>
|
||||
</div>
|
||||
|
||||
<h3>Attribute Operations</h3>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Add Label</h4>
|
||||
<p>Attach labels to multiple notes at once.</p>
|
||||
<pre><code>{
|
||||
"name": "addLabel",
|
||||
"labelName": "reviewed",
|
||||
"labelValue": "true"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Update Label Value</h4>
|
||||
<p>Modify existing label values across multiple notes.</p>
|
||||
<pre><code>{
|
||||
"name": "updateLabelValue",
|
||||
"labelName": "status",
|
||||
"labelValue": "completed"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Add Relation</h4>
|
||||
<p>Create relationships between notes and a target.</p>
|
||||
<pre><code>{
|
||||
"name": "addRelation",
|
||||
"relationName": "references",
|
||||
"targetNoteId": "bibliography_note"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Custom Script Execution</h3>
|
||||
|
||||
<div class="operation-card">
|
||||
<h4>Execute Script</h4>
|
||||
<p>Run custom JavaScript code on each selected note.</p>
|
||||
<pre><code>{
|
||||
"name": "executeScript",
|
||||
"script": "note.setLabel('processed', new Date().toISOString());"
|
||||
}</code></pre>
|
||||
<p>The <code>note</code> variable is available in the script context.</p>
|
||||
</div>
|
||||
|
||||
<h2>Including Descendants</h2>
|
||||
|
||||
<p>Apply operations to entire subtrees by enabling the "Include descendants" option:</p>
|
||||
|
||||
<pre><code>api.executeBulkActions(noteIds, actions, true);</code></pre>
|
||||
|
||||
<div class="info">
|
||||
<strong>Tip:</strong> This is useful for moving or deleting entire project hierarchies.
|
||||
</div>
|
||||
|
||||
<h2>Performance Guidelines</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Number of Notes</th>
|
||||
<th>Expected Performance</th>
|
||||
<th>Recommendations</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>< 100</td>
|
||||
<td>Instant</td>
|
||||
<td>Execute directly</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>100-1000</td>
|
||||
<td>Few seconds</td>
|
||||
<td>Show progress indicator</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>> 1000</td>
|
||||
<td>May take minutes</td>
|
||||
<td>Run during low activity, consider batching</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Example: Project Archival</h2>
|
||||
|
||||
<p>Archive completed projects with metadata:</p>
|
||||
|
||||
<pre><code class="language-javascript">// Find completed projects
|
||||
const projectNotes = api.searchForNotes('#project #status=completed');
|
||||
|
||||
// Define archive actions
|
||||
const archiveActions = [
|
||||
{
|
||||
name: 'moveNote',
|
||||
targetParentNoteId: 'archive_folder_id'
|
||||
},
|
||||
{
|
||||
name: 'addLabel',
|
||||
labelName: 'archivedDate',
|
||||
labelValue: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
name: 'deleteLabel',
|
||||
labelName: 'active'
|
||||
}
|
||||
];
|
||||
|
||||
// Execute with descendants
|
||||
api.executeBulkActions(projectNotes, archiveActions, true);</code></pre>
|
||||
|
||||
<h2>Safety Considerations</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Always backup</strong> before large bulk operations</li>
|
||||
<li><strong>Test first</strong> on a small subset of notes</li>
|
||||
<li><strong>Review affected count</strong> before confirming</li>
|
||||
<li><strong>Use transactions</strong> for related operations</li>
|
||||
<li><strong>Monitor logs</strong> during execution</li>
|
||||
</ul>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<details>
|
||||
<summary><strong>Operation appears to hang</strong></summary>
|
||||
<p>For large operations (> 1000 notes), the process may take several minutes. Check server logs for progress. Consider breaking into smaller batches.</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Some notes not affected</strong></summary>
|
||||
<p>Verify that:</p>
|
||||
<ul>
|
||||
<li>Notes exist and are accessible</li>
|
||||
<li>Protected notes have unlocked session</li>
|
||||
<li>No validation errors in logs</li>
|
||||
<li>Target attributes don't have constraints</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Memory errors on large operations</strong></summary>
|
||||
<p>Increase Node.js heap size:</p>
|
||||
<pre><code>NODE_OPTIONS="--max-old-space-size=4096" npm start</code></pre>
|
||||
<p>Or process in smaller batches of 500 notes.</p>
|
||||
</details>
|
||||
|
||||
<style>
|
||||
.operation-card {
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
background: var(--accented-background-color);
|
||||
}
|
||||
|
||||
.operation-card h4 {
|
||||
margin-top: 0;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #17a2b8;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-style: italic;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--code-background-color);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
details {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--accented-background-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +1,29 @@
|
||||
<p>Trilium supports configuration via a file named <code>config.ini</code> and environment variables. This document provides a comprehensive reference for all configuration options.</p>
|
||||
|
||||
<p>Trilium supports configuration via a file named <code>config.ini</code> and
|
||||
environment variables. This document provides a comprehensive reference
|
||||
for all configuration options.</p>
|
||||
<h2>Configuration Precedence</h2>
|
||||
<p>Configuration values are loaded in the following order of precedence (highest to lowest):</p>
|
||||
<p>Configuration values are loaded in the following order of precedence (highest
|
||||
to lowest):</p>
|
||||
<ol>
|
||||
<li><strong>Environment variables</strong> (checked first)</li>
|
||||
<li><strong>config.ini file values</strong></li>
|
||||
<li><strong>Default values</strong></li>
|
||||
<li><strong>config.ini file values</strong>
|
||||
</li>
|
||||
<li><strong>Default values</strong>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2>Environment Variable Patterns</h2>
|
||||
<p>Trilium supports multiple environment variable patterns for flexibility. The primary pattern is: <code>TRILIUM_[SECTION]_[KEY]</code></p>
|
||||
<p>Trilium supports multiple environment variable patterns for flexibility.
|
||||
The primary pattern is: <code>TRILIUM_[SECTION]_[KEY]</code>
|
||||
</p>
|
||||
<p>Where:</p>
|
||||
<ul>
|
||||
<li><code>SECTION</code> is the INI section name in UPPERCASE</li>
|
||||
<li><code>KEY</code> is the camelCase configuration key converted to UPPERCASE (e.g., <code>instanceName</code> → <code>INSTANCENAME</code>)</li>
|
||||
<li><code>KEY</code> is the camelCase configuration key converted to UPPERCASE
|
||||
(e.g., <code>instanceName</code> → <code>INSTANCENAME</code>)</li>
|
||||
</ul>
|
||||
<p>Additionally, shorter aliases are available for common configurations (see Alternative Variables section below).</p>
|
||||
|
||||
<p>Additionally, shorter aliases are available for common configurations
|
||||
(see Alternative Variables section below).</p>
|
||||
<h2>Environment Variable Reference</h2>
|
||||
|
||||
<h3>General Section</h3>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -31,31 +36,36 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TRILIUM_GENERAL_INSTANCENAME</code></td>
|
||||
<td><code>TRILIUM_GENERAL_INSTANCENAME</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>Instance name for API identification</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_GENERAL_NOAUTHENTICATION</code></td>
|
||||
<td><code>TRILIUM_GENERAL_NOAUTHENTICATION</code>
|
||||
</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>Disable authentication (server only)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_GENERAL_NOBACKUP</code></td>
|
||||
<td><code>TRILIUM_GENERAL_NOBACKUP</code>
|
||||
</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>Disable automatic backups</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_GENERAL_NODESKTOPICON</code></td>
|
||||
<td><code>TRILIUM_GENERAL_NODESKTOPICON</code>
|
||||
</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>Disable desktop icon creation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_GENERAL_READONLY</code></td>
|
||||
<td><code>TRILIUM_GENERAL_READONLY</code>
|
||||
</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>Enable read-only mode</td>
|
||||
@@ -75,55 +85,64 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_HOST</code></td>
|
||||
<td><code>TRILIUM_NETWORK_HOST</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>"0.0.0.0"</td>
|
||||
<td>Server host binding</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_PORT</code></td>
|
||||
<td><code>TRILIUM_NETWORK_PORT</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>"3000"</td>
|
||||
<td>Server port</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_HTTPS</code></td>
|
||||
<td><code>TRILIUM_NETWORK_HTTPS</code>
|
||||
</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>Enable HTTPS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_CERTPATH</code></td>
|
||||
<td><code>TRILIUM_NETWORK_CERTPATH</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>SSL certificate path</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_KEYPATH</code></td>
|
||||
<td><code>TRILIUM_NETWORK_KEYPATH</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>SSL key path</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_TRUSTEDREVERSEPROXY</code></td>
|
||||
<td><code>TRILIUM_NETWORK_TRUSTEDREVERSEPROXY</code>
|
||||
</td>
|
||||
<td>boolean/string</td>
|
||||
<td>false</td>
|
||||
<td>Reverse proxy trust settings</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_CORSALLOWORIGIN</code></td>
|
||||
<td><code>TRILIUM_NETWORK_CORSALLOWORIGIN</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>CORS allowed origins</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_CORSALLOWMETHODS</code></td>
|
||||
<td><code>TRILIUM_NETWORK_CORSALLOWMETHODS</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>CORS allowed methods</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_NETWORK_CORSALLOWHEADERS</code></td>
|
||||
<td><code>TRILIUM_NETWORK_CORSALLOWHEADERS</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>CORS allowed headers</td>
|
||||
@@ -143,7 +162,8 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TRILIUM_SESSION_COOKIEMAXAGE</code></td>
|
||||
<td><code>TRILIUM_SESSION_COOKIEMAXAGE</code>
|
||||
</td>
|
||||
<td>integer</td>
|
||||
<td>1814400</td>
|
||||
<td>Session cookie max age in seconds (21 days)</td>
|
||||
@@ -163,19 +183,22 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TRILIUM_SYNC_SYNCSERVERHOST</code></td>
|
||||
<td><code>TRILIUM_SYNC_SYNCSERVERHOST</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>Sync server host URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_SYNC_SYNCSERVERTIMEOUT</code></td>
|
||||
<td><code>TRILIUM_SYNC_SYNCSERVERTIMEOUT</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>"120000"</td>
|
||||
<td>Sync server timeout in milliseconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_SYNC_SYNCPROXY</code></td>
|
||||
<td><code>TRILIUM_SYNC_SYNCPROXY</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>Sync proxy URL</td>
|
||||
@@ -195,37 +218,43 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL</code></td>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>OAuth/OpenID base URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID</code></td>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>OAuth client ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET</code></td>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>OAuth client secret</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL</code></td>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>"https://accounts.google.com"</td>
|
||||
<td>"<a href="https://accounts.google.com">https://accounts.google.com</a>"</td>
|
||||
<td>OAuth issuer base URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME</code></td>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>"Google"</td>
|
||||
<td>OAuth issuer display name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON</code></td>
|
||||
<td><code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>""</td>
|
||||
<td>OAuth issuer icon URL</td>
|
||||
@@ -245,7 +274,8 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TRILIUM_LOGGING_RETENTIONDAYS</code></td>
|
||||
<td><code>TRILIUM_LOGGING_RETENTIONDAYS</code>
|
||||
</td>
|
||||
<td>integer</td>
|
||||
<td>90</td>
|
||||
<td>Number of days to retain log files</td>
|
||||
@@ -254,22 +284,20 @@
|
||||
</table>
|
||||
|
||||
<h2>Alternative Environment Variables</h2>
|
||||
<p>The following alternative environment variable names are also supported and work identically to their longer counterparts:</p>
|
||||
|
||||
<p>The following alternative environment variable names are also supported
|
||||
and work identically to their longer counterparts:</p>
|
||||
<h3>Network CORS Variables</h3>
|
||||
<ul>
|
||||
<li><code>TRILIUM_NETWORK_CORS_ALLOW_ORIGIN</code> (alternative to <code>TRILIUM_NETWORK_CORSALLOWORIGIN</code>)</li>
|
||||
<li><code>TRILIUM_NETWORK_CORS_ALLOW_METHODS</code> (alternative to <code>TRILIUM_NETWORK_CORSALLOWMETHODS</code>)</li>
|
||||
<li><code>TRILIUM_NETWORK_CORS_ALLOW_HEADERS</code> (alternative to <code>TRILIUM_NETWORK_CORSALLOWHEADERS</code>)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Sync Variables</h3>
|
||||
<ul>
|
||||
<li><code>TRILIUM_SYNC_SERVER_HOST</code> (alternative to <code>TRILIUM_SYNC_SYNCSERVERHOST</code>)</li>
|
||||
<li><code>TRILIUM_SYNC_SERVER_TIMEOUT</code> (alternative to <code>TRILIUM_SYNC_SYNCSERVERTIMEOUT</code>)</li>
|
||||
<li><code>TRILIUM_SYNC_SERVER_PROXY</code> (alternative to <code>TRILIUM_SYNC_SYNCPROXY</code>)</li>
|
||||
</ul>
|
||||
|
||||
<h3>OAuth/MFA Variables</h3>
|
||||
<ul>
|
||||
<li><code>TRILIUM_OAUTH_BASE_URL</code> (alternative to <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL</code>)</li>
|
||||
@@ -279,32 +307,30 @@
|
||||
<li><code>TRILIUM_OAUTH_ISSUER_NAME</code> (alternative to <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME</code>)</li>
|
||||
<li><code>TRILIUM_OAUTH_ISSUER_ICON</code> (alternative to <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON</code>)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Logging Variables</h3>
|
||||
<ul>
|
||||
<li><code>TRILIUM_LOGGING_RETENTION_DAYS</code> (alternative to <code>TRILIUM_LOGGING_RETENTIONDAYS</code>)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Boolean Values</h2>
|
||||
<p>Boolean environment variables accept the following values:</p>
|
||||
<ul>
|
||||
<li><strong>True</strong>: <code>"true"</code>, <code>"1"</code>, <code>1</code></li>
|
||||
<li><strong>False</strong>: <code>"false"</code>, <code>"0"</code>, <code>0</code></li>
|
||||
<li>Any other value defaults to <code>false</code></li>
|
||||
<li><strong>True</strong>: <code>"true"</code>, <code>"1"</code>, <code>1</code>
|
||||
</li>
|
||||
<li><strong>False</strong>: <code>"false"</code>, <code>"0"</code>, <code>0</code>
|
||||
</li>
|
||||
<li>Any other value defaults to <code>false</code>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Using Environment Variables</h2>
|
||||
<p>Both naming patterns are fully supported and can be used interchangeably:</p>
|
||||
<ul>
|
||||
<li>The longer format follows the section/key pattern for consistency with the INI file structure</li>
|
||||
<li>The longer format follows the section/key pattern for consistency with
|
||||
the INI file structure</li>
|
||||
<li>The shorter alternatives provide convenience for common configurations</li>
|
||||
<li>You can use whichever format you prefer - both are equally valid</li>
|
||||
</ul>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Docker Compose Example</h3>
|
||||
<pre><code class="language-yaml">services:
|
||||
<h3>Docker Compose Example</h3><pre><code class="language-text-x-yaml">services:
|
||||
trilium:
|
||||
image: triliumnext/notes
|
||||
environment:
|
||||
@@ -319,9 +345,7 @@
|
||||
# TRILIUM_NETWORK_CORS_ALLOW_ORIGIN: "https://myapp.com"
|
||||
# TRILIUM_SYNC_SERVER_HOST: "https://sync.example.com"
|
||||
# TRILIUM_OAUTH_BASE_URL: "https://auth.example.com"</code></pre>
|
||||
|
||||
<h3>Shell Export Example</h3>
|
||||
<pre><code class="language-bash"># Using either format
|
||||
<h3>Shell Export Example</h3><pre><code class="language-text-x-sh"># Using either format
|
||||
export TRILIUM_GENERAL_NOAUTHENTICATION=false
|
||||
export TRILIUM_NETWORK_HTTPS=true
|
||||
export TRILIUM_NETWORK_CERTPATH=/path/to/cert.pem
|
||||
@@ -330,6 +354,7 @@ export TRILIUM_LOGGING_RETENTIONDAYS=30
|
||||
|
||||
# Start Trilium
|
||||
npm start</code></pre>
|
||||
|
||||
<h2>config.ini Reference</h2>
|
||||
<p>For the complete list of configuration options and their INI file format, please review the <a href="https://github.com/TriliumNext/Trilium/blob/main/apps/server/src/assets/config-sample.ini">config-sample.ini</a> file in the Trilium repository.</p>
|
||||
<p>For the complete list of configuration options and their INI file format,
|
||||
please review the <a href="https://github.com/TriliumNext/Trilium/blob/main/apps/server/src/assets/config-sample.ini">config-sample.ini</a> file
|
||||
in the Trilium repository</p>
|
||||
378
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Note Revisions.html
generated
vendored
378
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Note Revisions.html
generated
vendored
@@ -1,378 +0,0 @@
|
||||
<h1>Note Revisions</h1>
|
||||
|
||||
<p>Track and restore previous versions of your notes with Trilium's comprehensive revision system.</p>
|
||||
|
||||
<h2>Understanding Revisions</h2>
|
||||
|
||||
<p>Revisions are automatic snapshots of your note content captured at specific intervals. Each revision preserves:</p>
|
||||
<ul>
|
||||
<li>Complete note content</li>
|
||||
<li>Note title at revision time</li>
|
||||
<li>Type and MIME information</li>
|
||||
<li>Creation and modification timestamps</li>
|
||||
<li>Protection status</li>
|
||||
</ul>
|
||||
|
||||
<h2>Accessing Revision History</h2>
|
||||
|
||||
<div class="access-methods">
|
||||
<div class="method">
|
||||
<h4>Via Note Menu</h4>
|
||||
<p>Click note menu (⋮) → "Revisions"</p>
|
||||
</div>
|
||||
<div class="method">
|
||||
<h4>Keyboard Shortcut</h4>
|
||||
<p>Press <kbd>Alt</kbd> + <kbd>R</kbd></p>
|
||||
</div>
|
||||
<div class="method">
|
||||
<h4>Script API</h4>
|
||||
<p><code>note.getRevisions()</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Automatic Creation Rules</h2>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>Default Triggers</h3>
|
||||
<ul>
|
||||
<li>After 5 minutes of continuous editing</li>
|
||||
<li>When switching to a different note</li>
|
||||
<li>Before major operations (delete, move)</li>
|
||||
<li>Content size changes > 20%</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Retention Policies</h2>
|
||||
|
||||
<table class="retention-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Age</th>
|
||||
<th>Retention</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>< 1 day</td>
|
||||
<td>Keep all</td>
|
||||
<td>Every revision saved</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-7 days</td>
|
||||
<td>Daily</td>
|
||||
<td>One per day</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7-30 days</td>
|
||||
<td>Weekly</td>
|
||||
<td>One per week</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>> 30 days</td>
|
||||
<td>Monthly</td>
|
||||
<td>One per month</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Custom Retention Settings</h2>
|
||||
|
||||
<p>Configure retention per note using attributes:</p>
|
||||
|
||||
<div class="code-example">
|
||||
<h4>Keep All Revisions</h4>
|
||||
<pre><code>#revisionRetention=all</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="code-example">
|
||||
<h4>Keep for Specific Duration</h4>
|
||||
<pre><code>#revisionRetention=90d</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="code-example">
|
||||
<h4>Disable Revisions</h4>
|
||||
<pre><code>#disableRevisions</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Comparing Revisions</h2>
|
||||
|
||||
<div class="feature-box">
|
||||
<h3>Visual Comparison</h3>
|
||||
<ol>
|
||||
<li>Open revision history</li>
|
||||
<li>Select two revisions</li>
|
||||
<li>Click "Compare"</li>
|
||||
<li>View side-by-side differences</li>
|
||||
</ol>
|
||||
<p class="highlight">Added content shown in <span style="color: green;">green</span>, removed in <span style="color: red;">red</span>.</p>
|
||||
</div>
|
||||
|
||||
<h2>Restoring Revisions</h2>
|
||||
|
||||
<div class="steps">
|
||||
<h3>Manual Restoration</h3>
|
||||
<ol>
|
||||
<li>Open revision history (<kbd>Alt</kbd> + <kbd>R</kbd>)</li>
|
||||
<li>Browse revisions by date/time</li>
|
||||
<li>Preview revision content</li>
|
||||
<li>Click "Restore this version"</li>
|
||||
<li>Confirm replacement</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="warning">
|
||||
<strong>Important:</strong> Restoring a revision replaces current content. Consider creating a manual snapshot first.
|
||||
</div>
|
||||
|
||||
<h2>Creating Manual Snapshots</h2>
|
||||
|
||||
<p>Force revision creation for important milestones:</p>
|
||||
|
||||
<pre><code class="language-javascript">// Via Script API
|
||||
api.createRevision(note.noteId, {
|
||||
title: note.title,
|
||||
content: note.getContent(),
|
||||
reason: 'Before major refactoring'
|
||||
});</code></pre>
|
||||
|
||||
<h2>Protected Note Revisions</h2>
|
||||
|
||||
<div class="security-box">
|
||||
<h3>Encryption Behavior</h3>
|
||||
<ul>
|
||||
<li>Revisions inherit note's protection status</li>
|
||||
<li>Title and content encrypted separately</li>
|
||||
<li>Requires unlocked session to view</li>
|
||||
<li>Protection changes apply to all revisions</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Storage Management</h2>
|
||||
|
||||
<div class="metrics">
|
||||
<h3>Typical Storage Usage</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Text notes</td>
|
||||
<td>~2KB per revision</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Code notes</td>
|
||||
<td>~5KB per revision</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Image notes</td>
|
||||
<td>Deduplicated via blobs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File notes</td>
|
||||
<td>Full file size</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Cleanup Operations</h2>
|
||||
|
||||
<div class="code-example">
|
||||
<h4>Automatic Cleanup</h4>
|
||||
<pre><code class="language-javascript">// Configure in options
|
||||
api.setOption('revisionCleanupDays', 90);
|
||||
api.setOption('revisionCleanupEnabled', true);</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="code-example">
|
||||
<h4>Manual Cleanup</h4>
|
||||
<pre><code class="language-javascript">// Delete revisions older than 90 days
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - 90);
|
||||
|
||||
for (const note of api.getAllNotes()) {
|
||||
const revisions = note.getRevisions();
|
||||
for (const revision of revisions) {
|
||||
if (revision.dateCreated < cutoffDate) {
|
||||
revision.delete();
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Performance Tips</h2>
|
||||
|
||||
<ul class="tips">
|
||||
<li><strong>Large files:</strong> Consider disabling revisions for binary attachments</li>
|
||||
<li><strong>Frequent edits:</strong> Increase revision interval to reduce storage</li>
|
||||
<li><strong>Cleanup:</strong> Run cleanup during low-activity periods</li>
|
||||
<li><strong>Monitoring:</strong> Check database size regularly</li>
|
||||
</ul>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<details>
|
||||
<summary><strong>Revisions not appearing</strong></summary>
|
||||
<ul>
|
||||
<li>Check if <code>#disableRevisions</code> is set</li>
|
||||
<li>Verify revision creation interval in options</li>
|
||||
<li>Ensure sufficient disk space</li>
|
||||
<li>Check database write permissions</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Cannot view revision content</strong></summary>
|
||||
<ul>
|
||||
<li>For protected notes, unlock protected session</li>
|
||||
<li>Verify blob storage integrity</li>
|
||||
<li>Check database consistency</li>
|
||||
<li>Review error logs for details</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Excessive storage usage</strong></summary>
|
||||
<ul>
|
||||
<li>Implement aggressive cleanup policy</li>
|
||||
<li>Exclude binary notes from tracking</li>
|
||||
<li>Archive old revisions externally</li>
|
||||
<li>Consider compression options</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<style>
|
||||
.access-methods {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.method {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.method h4 {
|
||||
margin-top: 0;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.info-box, .feature-box, .security-box {
|
||||
background: #e8f4f8;
|
||||
border-left: 4px solid #17a2b8;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.code-example h4 {
|
||||
margin-bottom: 5px;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.retention-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.retention-table th,
|
||||
.retention-table td {
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.retention-table th {
|
||||
background: var(--accented-background-color);
|
||||
}
|
||||
|
||||
.metrics table {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.metrics td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.steps {
|
||||
background: var(--accented-background-color);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.steps ol {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.tips {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tips li {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.tips li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: #f4f4f4;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
details {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--code-background-color);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--code-background-color);
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,232 +0,0 @@
|
||||
<h1>Advanced Search Expressions</h1>
|
||||
<p>This guide covers complex search expressions that combine multiple criteria, use advanced operators, and leverage Trilium's relationship system for sophisticated queries.</p>
|
||||
|
||||
<h2>Complex Query Construction</h2>
|
||||
|
||||
<h3>Boolean Logic with Parentheses</h3>
|
||||
<p>Use parentheses to group expressions and control evaluation order:</p>
|
||||
<pre><code>(#book OR #article) AND #author=Tolkien</code></pre>
|
||||
<p>Finds notes that are either books or articles, written by Tolkien.</p>
|
||||
<pre><code>#project AND (#status=active OR #status=pending)</code></pre>
|
||||
<p>Finds active or pending projects.</p>
|
||||
<pre><code>meeting AND (#priority=high OR #urgent) AND note.dateCreated >= TODAY-7</code></pre>
|
||||
<p>Finds recent high-priority or urgent meetings.</p>
|
||||
|
||||
<h3>Negation Patterns</h3>
|
||||
<p>Use <code>NOT</code> or the <code>not()</code> function to exclude certain criteria:</p>
|
||||
<pre><code>#book AND not(#genre=fiction)</code></pre>
|
||||
<p>Finds non-fiction books.</p>
|
||||
<pre><code>project AND not(note.isArchived=true)</code></pre>
|
||||
<p>Finds non-archived notes containing "project".</p>
|
||||
<pre><code>#!completed</code></pre>
|
||||
<p>Short syntax for notes without the "completed" label.</p>
|
||||
|
||||
<h3>Mixed Search Types</h3>
|
||||
<p>Combine full-text, attribute, and property searches:</p>
|
||||
<pre><code>development #category=work note.type=text note.dateModified >= TODAY-30</code></pre>
|
||||
<p>Finds text notes about development, categorized as work, modified in the last 30 days.</p>
|
||||
|
||||
<h2>Advanced Attribute Searches</h2>
|
||||
|
||||
<h3>Fuzzy Attribute Matching</h3>
|
||||
<p>When fuzzy attribute search is enabled, you can use partial matches:</p>
|
||||
<pre><code>#lang</code></pre>
|
||||
<p>Matches labels like "language", "languages", "programming-lang", etc.</p>
|
||||
<pre><code>#category=prog</code></pre>
|
||||
<p>Matches categories like "programming", "progress", "program", etc.</p>
|
||||
|
||||
<h3>Multiple Attribute Conditions</h3>
|
||||
<pre><code>#book #author=Tolkien #publicationYear>=1950 #publicationYear<1960</code></pre>
|
||||
<p>Finds Tolkien's books published in the 1950s.</p>
|
||||
<pre><code>#task #priority=high #status!=completed</code></pre>
|
||||
<p>Finds high-priority incomplete tasks.</p>
|
||||
|
||||
<h3>Complex Label Value Patterns</h3>
|
||||
<p>Use various operators for sophisticated label matching:</p>
|
||||
<pre><code>#isbn %= '978-[0-9-]+'</code></pre>
|
||||
<p>Finds notes with ISBN labels matching the pattern (regex).</p>
|
||||
<pre><code>#email *=* @company.com</code></pre>
|
||||
<p>Finds notes with email labels containing "@company.com".</p>
|
||||
<pre><code>#version >= 2.0</code></pre>
|
||||
<p>Finds notes with version labels of 2.0 or higher (numeric comparison).</p>
|
||||
|
||||
<h2>Relationship Traversal</h2>
|
||||
|
||||
<h3>Basic Relation Queries</h3>
|
||||
<pre><code>~author.title *=* Tolkien</code></pre>
|
||||
<p>Finds notes with an "author" relation to notes containing "Tolkien" in the title.</p>
|
||||
<pre><code>~project.labels.status = active</code></pre>
|
||||
<p>Finds notes related to projects with active status.</p>
|
||||
|
||||
<h3>Multi-Level Relationships</h3>
|
||||
<pre><code>~author.relations.publisher.title = "Penguin Books"</code></pre>
|
||||
<p>Finds notes authored by someone published by Penguin Books.</p>
|
||||
<pre><code>~project.children.title *=* documentation</code></pre>
|
||||
<p>Finds notes related to projects that have child notes about documentation.</p>
|
||||
|
||||
<h3>Relationship Direction</h3>
|
||||
<pre><code>note.children.title = "Chapter 1"</code></pre>
|
||||
<p>Finds parent notes that have a child titled "Chapter 1".</p>
|
||||
<pre><code>note.parents.labels.category = book</code></pre>
|
||||
<p>Finds notes whose parents are categorized as books.</p>
|
||||
<pre><code>note.ancestors.title = "Literature"</code></pre>
|
||||
<p>Finds notes with "Literature" anywhere in their ancestor chain.</p>
|
||||
|
||||
<h2>Property-Based Searches</h2>
|
||||
|
||||
<h3>Note Metadata Queries</h3>
|
||||
<pre><code>note.type=code note.mime=text/javascript note.dateCreated >= MONTH</code></pre>
|
||||
<p>Finds JavaScript code notes created this month.</p>
|
||||
<pre><code>note.isProtected=true note.contentSize > 1000</code></pre>
|
||||
<p>Finds large protected notes.</p>
|
||||
<pre><code>note.childrenCount >= 10 note.type=text</code></pre>
|
||||
<p>Finds text notes with many children.</p>
|
||||
|
||||
<h3>Advanced Property Combinations</h3>
|
||||
<pre><code>note.parentCount > 1 #template</code></pre>
|
||||
<p>Finds template notes that are cloned in multiple places.</p>
|
||||
<pre><code>note.attributeCount > 5 note.type=text note.contentSize < 500</code></pre>
|
||||
<p>Finds small text notes with many attributes (heavily tagged short notes).</p>
|
||||
<pre><code>note.revisionCount > 10 note.dateModified >= TODAY-7</code></pre>
|
||||
<p>Finds frequently edited notes modified recently.</p>
|
||||
|
||||
<h2>Date and Time Expressions</h2>
|
||||
|
||||
<h3>Relative Date Calculations</h3>
|
||||
<pre><code>#dueDate <= TODAY+7 #dueDate >= TODAY</code></pre>
|
||||
<p>Finds tasks due in the next week.</p>
|
||||
<pre><code>note.dateCreated >= MONTH-2 note.dateCreated < MONTH</code></pre>
|
||||
<p>Finds notes created in the past two months.</p>
|
||||
<pre><code>#eventDate = YEAR note.dateCreated >= YEAR-1</code></pre>
|
||||
<p>Finds events scheduled for this year that were planned last year.</p>
|
||||
|
||||
<h3>Complex Date Logic</h3>
|
||||
<pre><code>(#startDate <= TODAY AND #endDate >= TODAY) OR #status=ongoing</code></pre>
|
||||
<p>Finds current events or ongoing items.</p>
|
||||
<pre><code>#reminderDate <= NOW+3600 #reminderDate > NOW</code></pre>
|
||||
<p>Finds reminders due in the next hour (using seconds offset).</p>
|
||||
|
||||
<h2>Fuzzy Search Techniques</h2>
|
||||
|
||||
<h3>Fuzzy Exact Matching</h3>
|
||||
<pre><code>#title ~= managment</code></pre>
|
||||
<p>Finds notes with titles like "management" even with typos.</p>
|
||||
<pre><code>~category.title ~= progaming</code></pre>
|
||||
<p>Finds notes related to categories like "programming" with misspellings.</p>
|
||||
|
||||
<h3>Fuzzy Contains Matching</h3>
|
||||
<pre><code>note.content ~* algoritm</code></pre>
|
||||
<p>Finds notes containing words like "algorithm" with spelling variations.</p>
|
||||
<pre><code>#description ~* recieve</code></pre>
|
||||
<p>Finds notes with descriptions containing "receive" despite the common misspelling.</p>
|
||||
|
||||
<h3>Progressive Fuzzy Strategy</h3>
|
||||
<p>By default, Trilium uses exact matching first, then fuzzy as fallback:</p>
|
||||
<pre><code>development project</code></pre>
|
||||
<p>First finds exact matches for "development" and "project", then adds fuzzy matches if needed.</p>
|
||||
|
||||
<p>To force fuzzy behavior:</p>
|
||||
<pre><code>#title ~= development #category ~= projet</code></pre>
|
||||
|
||||
<h2>Ordering and Limiting</h2>
|
||||
|
||||
<h3>Multiple Sort Criteria</h3>
|
||||
<pre><code>#book orderBy #publicationYear desc, note.title asc limit 20</code></pre>
|
||||
<p>Orders books by publication year (newest first), then by title alphabetically, limited to 20 results.</p>
|
||||
<pre><code>#task orderBy #priority desc, #dueDate asc</code></pre>
|
||||
<p>Orders tasks by priority (high first), then by due date (earliest first).</p>
|
||||
|
||||
<h3>Dynamic Ordering</h3>
|
||||
<pre><code>#meeting note.dateCreated >= TODAY-30 orderBy note.dateModified desc</code></pre>
|
||||
<p>Finds recent meetings ordered by last modification.</p>
|
||||
<pre><code>#project #status=active orderBy note.childrenCount desc limit 10</code></pre>
|
||||
<p>Finds the 10 most complex active projects (by number of sub-notes).</p>
|
||||
|
||||
<h2>Performance Optimization Patterns</h2>
|
||||
|
||||
<h3>Efficient Query Structure</h3>
|
||||
<p>Start with the most selective criteria:</p>
|
||||
<pre><code>#book #author=Tolkien note.dateCreated >= 1950-01-01</code></pre>
|
||||
<p>Better than:</p>
|
||||
<pre><code>note.dateCreated >= 1950-01-01 #book #author=Tolkien</code></pre>
|
||||
|
||||
<h3>Fast Search for Large Datasets</h3>
|
||||
<pre><code>#category=project #status=active</code></pre>
|
||||
<p>With fast search enabled, this searches only attributes, not content.</p>
|
||||
|
||||
<h3>Limiting Expensive Operations</h3>
|
||||
<pre><code>note.content *=* "complex search term" limit 50</code></pre>
|
||||
<p>Limits content search to prevent performance issues.</p>
|
||||
|
||||
<h2>Error Handling and Debugging</h2>
|
||||
|
||||
<h3>Syntax Validation</h3>
|
||||
<p>Invalid syntax produces helpful error messages:</p>
|
||||
<pre><code>#book AND OR #author=Tolkien</code></pre>
|
||||
<p>Error: "Mixed usage of AND/OR - always use parentheses to group AND/OR expressions."</p>
|
||||
|
||||
<h3>Debug Mode</h3>
|
||||
<p>Enable debug mode to see how queries are parsed:</p>
|
||||
<pre><code>#book #author=Tolkien</code></pre>
|
||||
<p>With debug enabled, shows the internal expression tree structure.</p>
|
||||
|
||||
<h3>Common Pitfalls</h3>
|
||||
<ul>
|
||||
<li>Unescaped special characters: Use quotes or backslashes</li>
|
||||
<li>Missing parentheses in complex boolean expressions</li>
|
||||
<li>Incorrect property names: Use <code>note.title</code> not <code>title</code></li>
|
||||
<li>Case sensitivity assumptions: All searches are case-insensitive</li>
|
||||
</ul>
|
||||
|
||||
<h2>Expression Shortcuts</h2>
|
||||
|
||||
<h3>Label Shortcuts</h3>
|
||||
<p>Full syntax:</p>
|
||||
<pre><code>note.labels.category = book</code></pre>
|
||||
<p>Shortcut:</p>
|
||||
<pre><code>#category = book</code></pre>
|
||||
|
||||
<h3>Relation Shortcuts</h3>
|
||||
<p>Full syntax:</p>
|
||||
<pre><code>note.relations.author.title *=* Tolkien</code></pre>
|
||||
<p>Shortcut:</p>
|
||||
<pre><code>~author.title *=* Tolkien</code></pre>
|
||||
|
||||
<h3>Property Shortcuts</h3>
|
||||
<p>Some properties have convenient shortcuts:</p>
|
||||
<pre><code>note.text *=* content</code></pre>
|
||||
<p>Searches both title and content for "content".</p>
|
||||
|
||||
<h2>Real-World Complex Examples</h2>
|
||||
|
||||
<h3>Project Management</h3>
|
||||
<pre><code>(#project OR #task) AND #status!=completed AND
|
||||
(#priority=high OR #dueDate <= TODAY+7) AND
|
||||
not(note.isArchived=true)
|
||||
orderBy #priority desc, #dueDate asc</code></pre>
|
||||
|
||||
<h3>Research Organization</h3>
|
||||
<pre><code>(#paper OR #article OR #book) AND
|
||||
~author.title *=* smith AND
|
||||
#topic *=* "machine learning" AND
|
||||
note.dateCreated >= YEAR-2
|
||||
orderBy #citationCount desc limit 25</code></pre>
|
||||
|
||||
<h3>Content Management</h3>
|
||||
<pre><code>note.type=text AND note.contentSize > 5000 AND
|
||||
#category=documentation AND note.childrenCount >= 3 AND
|
||||
note.dateModified >= MONTH-1
|
||||
orderBy note.dateModified desc</code></pre>
|
||||
|
||||
<h3>Knowledge Base Maintenance</h3>
|
||||
<pre><code>note.attributeCount = 0 AND note.childrenCount = 0 AND
|
||||
note.parentCount = 1 AND note.contentSize < 100 AND
|
||||
note.dateModified < TODAY-90</code></pre>
|
||||
<p>Finds potential cleanup candidates: small, untagged, isolated notes not modified in 90 days.</p>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_search_examples">Search Examples and Use Cases</a> - Practical applications</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_saved">Saved Searches</a> - Creating reusable search configurations</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_technical">Technical Search Details</a> - Implementation details and performance tuning</li>
|
||||
</ul>
|
||||
@@ -1,360 +0,0 @@
|
||||
<h1>Saved Searches</h1>
|
||||
<p>Saved searches in Trilium allow you to create dynamic collections of notes that automatically update based on search criteria. They appear as special notes in your tree and provide a powerful way to organize and access related content.</p>
|
||||
|
||||
<h2>Understanding Saved Searches</h2>
|
||||
<p>A saved search is a special note type that:</p>
|
||||
<ul>
|
||||
<li>Stores search criteria and configuration</li>
|
||||
<li>Dynamically displays matching notes as children</li>
|
||||
<li>Updates automatically when notes change</li>
|
||||
<li>Can be bookmarked and accessed like any other note</li>
|
||||
<li>Supports all search features including ordering and limits</li>
|
||||
</ul>
|
||||
|
||||
<h2>Creating Saved Searches</h2>
|
||||
|
||||
<h3>From Search Dialog</h3>
|
||||
<ol>
|
||||
<li>Open the search dialog (Ctrl+S or search icon)</li>
|
||||
<li>Configure your search criteria and options</li>
|
||||
<li>Click "Save to note" button</li>
|
||||
<li>Choose a name and location for the saved search</li>
|
||||
</ol>
|
||||
|
||||
<h3>Manual Creation</h3>
|
||||
<ol>
|
||||
<li>Create a new note and set its type to "Saved Search"</li>
|
||||
<li>Configure the search using labels:
|
||||
<ul>
|
||||
<li><code>#searchString</code> - The search query</li>
|
||||
<li><code>#fastSearch</code> - Enable fast search mode</li>
|
||||
<li><code>#includeArchivedNotes</code> - Include archived notes</li>
|
||||
<li><code>#orderBy</code> - Sort field</li>
|
||||
<li><code>#orderDirection</code> - "asc" or "desc"</li>
|
||||
<li><code>#limit</code> - Maximum number of results</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>Using Search Scripts</h3>
|
||||
<p>For complex logic, create a JavaScript note and link it:</p>
|
||||
<ul>
|
||||
<li><code>~searchScript</code> - Relation pointing to a backend script note</li>
|
||||
</ul>
|
||||
|
||||
<h2>Basic Saved Search Examples</h2>
|
||||
|
||||
<h3>Simple Text Search</h3>
|
||||
<pre><code>#searchString=project management</code></pre>
|
||||
<p>Finds all notes containing "project management".</p>
|
||||
|
||||
<h3>Tag-Based Collection</h3>
|
||||
<pre><code>#searchString=#book #author=Tolkien
|
||||
#orderBy=publicationYear
|
||||
#orderDirection=desc</code></pre>
|
||||
<p>Creates a collection of Tolkien's books ordered by publication year.</p>
|
||||
|
||||
<h3>Task Dashboard</h3>
|
||||
<pre><code>#searchString=#task #status!=completed #assignee=me
|
||||
#orderBy=priority
|
||||
#orderDirection=desc
|
||||
#limit=20</code></pre>
|
||||
<p>Shows your top 20 incomplete tasks by priority.</p>
|
||||
|
||||
<h3>Recent Activity</h3>
|
||||
<pre><code>#searchString=note.dateModified >= TODAY-7
|
||||
#orderBy=dateModified
|
||||
#orderDirection=desc
|
||||
#limit=50</code></pre>
|
||||
<p>Shows the 50 most recently modified notes from the last week.</p>
|
||||
|
||||
<h2>Advanced Saved Search Patterns</h2>
|
||||
|
||||
<h3>Dynamic Date-Based Collections</h3>
|
||||
|
||||
<h4>This Week's Content</h4>
|
||||
<pre><code>#searchString=note.dateCreated >= TODAY-7 note.dateCreated < TODAY
|
||||
#orderBy=dateCreated
|
||||
#orderDirection=desc</code></pre>
|
||||
|
||||
<h4>Monthly Review Collection</h4>
|
||||
<pre><code>#searchString=#reviewed=false note.dateCreated >= MONTH note.dateCreated < MONTH+1
|
||||
#orderBy=dateCreated</code></pre>
|
||||
|
||||
<h4>Upcoming Deadlines</h4>
|
||||
<pre><code>#searchString=#dueDate >= TODAY #dueDate <= TODAY+14 #status!=completed
|
||||
#orderBy=dueDate
|
||||
#orderDirection=asc</code></pre>
|
||||
|
||||
<h3>Project-Specific Collections</h3>
|
||||
|
||||
<h4>Project Dashboard</h4>
|
||||
<pre><code>#searchString=#project=alpha (#task OR #milestone OR #document)
|
||||
#orderBy=priority
|
||||
#orderDirection=desc</code></pre>
|
||||
|
||||
<h4>Project Health Monitor</h4>
|
||||
<pre><code>#searchString=#project=alpha #status=blocked OR (#dueDate < TODAY #status!=completed)
|
||||
#orderBy=dueDate
|
||||
#orderDirection=asc</code></pre>
|
||||
|
||||
<h3>Content Type Collections</h3>
|
||||
|
||||
<h4>Documentation Hub</h4>
|
||||
<pre><code>#searchString=(#documentation OR #guide OR #manual) #product=api
|
||||
#orderBy=dateModified
|
||||
#orderDirection=desc</code></pre>
|
||||
|
||||
<h4>Learning Path</h4>
|
||||
<pre><code>#searchString=#course #level=beginner #topic=programming
|
||||
#orderBy=difficulty
|
||||
#orderDirection=asc</code></pre>
|
||||
|
||||
<h2>Search Script Examples</h2>
|
||||
<p>For complex logic that can't be expressed in search strings, use JavaScript:</p>
|
||||
|
||||
<h3>Custom Business Logic</h3>
|
||||
<pre><code>// Find notes that need attention based on complex criteria
|
||||
const api = require('api');
|
||||
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - 30);
|
||||
|
||||
const results = [];
|
||||
|
||||
// Find high-priority tasks overdue by more than a week
|
||||
const overdueTasks = api.searchForNotes(`
|
||||
#task #priority=high #dueDate < TODAY-7 #status!=completed
|
||||
`);
|
||||
|
||||
// Find projects with no recent activity
|
||||
const staleProjects = api.searchForNotes(`
|
||||
#project #status=active note.dateModified < TODAY-30
|
||||
`);
|
||||
|
||||
// Find notes with many attributes but no content
|
||||
const overlabeledNotes = api.searchForNotes(`
|
||||
note.attributeCount > 5 note.contentSize < 100
|
||||
`);
|
||||
|
||||
return [...overdueTasks, ...staleProjects, ...overlabeledNotes]
|
||||
.map(note => note.noteId);</code></pre>
|
||||
|
||||
<h3>Dynamic Tag-Based Grouping</h3>
|
||||
<pre><code>// Group notes by quarter based on creation date
|
||||
const api = require('api');
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const results = [];
|
||||
|
||||
for (let quarter = 1; quarter <= 4; quarter++) {
|
||||
const startMonth = (quarter - 1) * 3 + 1;
|
||||
const endMonth = quarter * 3;
|
||||
|
||||
const quarterNotes = api.searchForNotes(`
|
||||
note.dateCreated >= "${currentYear}-${String(startMonth).padStart(2, '0')}-01"
|
||||
note.dateCreated < "${currentYear}-${String(endMonth + 1).padStart(2, '0')}-01"
|
||||
#project
|
||||
`);
|
||||
|
||||
results.push(...quarterNotes.map(note => note.noteId));
|
||||
}
|
||||
|
||||
return results;</code></pre>
|
||||
|
||||
<h3>Conditional Search Logic</h3>
|
||||
<pre><code>// Smart dashboard that changes based on day of week
|
||||
const api = require('api');
|
||||
|
||||
const today = new Date();
|
||||
const dayOfWeek = today.getDay(); // 0 = Sunday, 1 = Monday, etc.
|
||||
|
||||
let searchQuery;
|
||||
|
||||
if (dayOfWeek === 1) { // Monday - weekly planning
|
||||
searchQuery = '#task #status=planned #week=' + getWeekNumber(today);
|
||||
} else if (dayOfWeek === 5) { // Friday - weekly review
|
||||
searchQuery = '#task #completed=true #week=' + getWeekNumber(today);
|
||||
} else { // Regular days - focus on today's work
|
||||
searchQuery = '#task #dueDate=TODAY #status!=completed';
|
||||
}
|
||||
|
||||
const notes = api.searchForNotes(searchQuery);
|
||||
return notes.map(note => note.noteId);
|
||||
|
||||
function getWeekNumber(date) {
|
||||
const firstDay = new Date(date.getFullYear(), 0, 1);
|
||||
const pastDays = Math.floor((date - firstDay) / 86400000);
|
||||
return Math.ceil((pastDays + firstDay.getDay() + 1) / 7);
|
||||
}</code></pre>
|
||||
|
||||
<h2>Performance Optimization</h2>
|
||||
|
||||
<h3>Fast Search for Large Collections</h3>
|
||||
<p>For collections that don't need content search:</p>
|
||||
<pre><code>#searchString=#category=reference #type=article
|
||||
#fastSearch=true
|
||||
#limit=100</code></pre>
|
||||
|
||||
<h3>Efficient Ordering</h3>
|
||||
<p>Use indexed properties for better performance:</p>
|
||||
<pre><code>#orderBy=dateCreated
|
||||
#orderBy=title
|
||||
#orderBy=noteId</code></pre>
|
||||
<p>Avoid complex calculated orderings in large collections.</p>
|
||||
|
||||
<h3>Result Limiting</h3>
|
||||
<p>Always set reasonable limits for large collections:</p>
|
||||
<pre><code>#limit=50</code></pre>
|
||||
<p>For very large result sets, consider breaking into multiple saved searches.</p>
|
||||
|
||||
<h2>Saved Search Organization</h2>
|
||||
|
||||
<h3>Hierarchical Organization</h3>
|
||||
<p>Create a folder structure for saved searches:</p>
|
||||
<pre><code>📁 Searches
|
||||
├── 📁 Projects
|
||||
│ ├── 🔍 Active Projects
|
||||
│ ├── 🔍 Overdue Tasks
|
||||
│ └── 🔍 Project Archive
|
||||
├── 📁 Content
|
||||
│ ├── 🔍 Recent Drafts
|
||||
│ ├── 🔍 Published Articles
|
||||
│ └── 🔍 Review Queue
|
||||
└── 📁 Maintenance
|
||||
├── 🔍 Untagged Notes
|
||||
├── 🔍 Cleanup Candidates
|
||||
└── 🔍 Orphaned Notes</code></pre>
|
||||
|
||||
<h3>Search Naming Conventions</h3>
|
||||
<p>Use clear, descriptive names:</p>
|
||||
<ul>
|
||||
<li>"Active High-Priority Tasks"</li>
|
||||
<li>"This Month's Meeting Notes"</li>
|
||||
<li>"Unprocessed Inbox Items"</li>
|
||||
<li>"Literature Review Papers"</li>
|
||||
</ul>
|
||||
|
||||
<h3>Search Labels</h3>
|
||||
<p>Tag saved searches for organization:</p>
|
||||
<pre><code>#searchType=dashboard
|
||||
#searchType=maintenance
|
||||
#searchType=archive
|
||||
#frequency=daily
|
||||
#frequency=weekly</code></pre>
|
||||
|
||||
<h2>Dashboard Creation</h2>
|
||||
|
||||
<h3>Personal Dashboard</h3>
|
||||
<p>Combine multiple saved searches in a parent note:</p>
|
||||
<pre><code>📋 My Dashboard
|
||||
├── 🔍 Today's Tasks
|
||||
├── 🔍 Urgent Items
|
||||
├── 🔍 Recent Notes
|
||||
├── 🔍 Upcoming Deadlines
|
||||
└── 🔍 Weekly Review Items</code></pre>
|
||||
|
||||
<h3>Project Dashboard</h3>
|
||||
<pre><code>📋 Project Alpha Dashboard
|
||||
├── 🔍 Active Tasks
|
||||
├── 🔍 Blocked Items
|
||||
├── 🔍 Recent Updates
|
||||
├── 🔍 Milestones
|
||||
└── 🔍 Team Notes</code></pre>
|
||||
|
||||
<h3>Content Dashboard</h3>
|
||||
<pre><code>📋 Content Management
|
||||
├── 🔍 Draft Articles
|
||||
├── 🔍 Review Queue
|
||||
├── 🔍 Published This Month
|
||||
├── 🔍 High-Engagement Posts
|
||||
└── 🔍 Content Ideas</code></pre>
|
||||
|
||||
<h2>Maintenance and Updates</h2>
|
||||
|
||||
<h3>Regular Review</h3>
|
||||
<p>Periodically review saved searches for:</p>
|
||||
<ul>
|
||||
<li>Outdated search criteria</li>
|
||||
<li>Performance issues</li>
|
||||
<li>Unused collections</li>
|
||||
<li>Scope creep</li>
|
||||
</ul>
|
||||
|
||||
<h3>Search Evolution</h3>
|
||||
<p>As your note-taking evolves, update searches:</p>
|
||||
<ul>
|
||||
<li>Add new tags to existing searches</li>
|
||||
<li>Refine criteria based on usage patterns</li>
|
||||
<li>Split large collections into smaller ones</li>
|
||||
<li>Merge rarely-used collections</li>
|
||||
</ul>
|
||||
|
||||
<h3>Performance Monitoring</h3>
|
||||
<p>Watch for performance issues:</p>
|
||||
<ul>
|
||||
<li>Slow-loading saved searches</li>
|
||||
<li>Memory usage with large result sets</li>
|
||||
<li>Search timeout errors</li>
|
||||
</ul>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Issues</h3>
|
||||
|
||||
<h4>Empty Results</h4>
|
||||
<ul>
|
||||
<li>Check search syntax</li>
|
||||
<li>Verify tag spellings</li>
|
||||
<li>Ensure notes have required attributes</li>
|
||||
<li>Test search components individually</li>
|
||||
</ul>
|
||||
|
||||
<h4>Performance Problems</h4>
|
||||
<ul>
|
||||
<li>Add <code>#fastSearch=true</code> for attribute-only searches</li>
|
||||
<li>Reduce result limits</li>
|
||||
<li>Simplify complex criteria</li>
|
||||
<li>Use indexed properties for ordering</li>
|
||||
</ul>
|
||||
|
||||
<h4>Unexpected Results</h4>
|
||||
<ul>
|
||||
<li>Enable debug mode to see query parsing</li>
|
||||
<li>Test search in search dialog first</li>
|
||||
<li>Check for case sensitivity issues</li>
|
||||
<li>Verify date formats and ranges</li>
|
||||
</ul>
|
||||
|
||||
<h3>Best Practices</h3>
|
||||
|
||||
<h4>Search Design</h4>
|
||||
<ul>
|
||||
<li>Start simple and add complexity gradually</li>
|
||||
<li>Test searches thoroughly before saving</li>
|
||||
<li>Document complex search logic</li>
|
||||
<li>Use meaningful names and descriptions</li>
|
||||
</ul>
|
||||
|
||||
<h4>Performance</h4>
|
||||
<ul>
|
||||
<li>Set appropriate limits</li>
|
||||
<li>Use fast search when possible</li>
|
||||
<li>Avoid overly complex expressions</li>
|
||||
<li>Monitor search execution time</li>
|
||||
</ul>
|
||||
|
||||
<h4>Organization</h4>
|
||||
<ul>
|
||||
<li>Group related searches</li>
|
||||
<li>Use consistent naming conventions</li>
|
||||
<li>Archive unused searches</li>
|
||||
<li>Regular cleanup and maintenance</li>
|
||||
</ul>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_search_technical">Technical Search Details</a> - Understanding search performance and implementation</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_examples">Search Examples and Use Cases</a> - More practical examples</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_advanced">Advanced Search Expressions</a> - Complex query construction</li>
|
||||
</ul>
|
||||
@@ -1,160 +0,0 @@
|
||||
<h1>Trilium Search Documentation</h1>
|
||||
<p>Welcome to the comprehensive guide for Trilium's powerful search capabilities. This documentation covers everything from basic text searches to advanced query expressions and performance optimization.</p>
|
||||
|
||||
<h2>Quick Start</h2>
|
||||
<p>New to Trilium search? Start here:</p>
|
||||
<ul>
|
||||
<li><strong><a class="reference-link" href="#root/_help_search_fundamentals">Search Fundamentals</a></strong> - Basic concepts, syntax, and operators</li>
|
||||
</ul>
|
||||
|
||||
<h2>Documentation Sections</h2>
|
||||
|
||||
<h3>Core Search Features</h3>
|
||||
<ul>
|
||||
<li><strong><a class="reference-link" href="#root/_help_search_fundamentals">Search Fundamentals</a></strong> - Basic search syntax, operators, and concepts</li>
|
||||
<li><strong><a class="reference-link" href="#root/_help_search_advanced">Advanced Search Expressions</a></strong> - Complex queries, boolean logic, and relationship traversal</li>
|
||||
</ul>
|
||||
|
||||
<h3>Practical Applications</h3>
|
||||
<ul>
|
||||
<li><strong><a class="reference-link" href="#root/_help_search_examples">Search Examples and Use Cases</a></strong> - Real-world examples for common workflows</li>
|
||||
<li><strong><a class="reference-link" href="#root/_help_search_saved">Saved Searches</a></strong> - Creating dynamic collections and dashboards</li>
|
||||
</ul>
|
||||
|
||||
<h3>Technical Reference</h3>
|
||||
<ul>
|
||||
<li><strong><a class="reference-link" href="#root/_help_search_technical">Technical Search Details</a></strong> - Performance, implementation, and optimization</li>
|
||||
</ul>
|
||||
|
||||
<h2>Key Search Capabilities</h2>
|
||||
|
||||
<h3>Full-Text Search</h3>
|
||||
<ul>
|
||||
<li>Search note titles and content</li>
|
||||
<li>Exact phrase matching with quotes</li>
|
||||
<li>Case-insensitive with diacritic normalization</li>
|
||||
<li>Support for multiple note types (text, code, mermaid, canvas)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Attribute-Based Search</h3>
|
||||
<ul>
|
||||
<li>Label searches: <code>#tag</code>, <code>#category=book</code></li>
|
||||
<li>Relation searches: <code>~author</code>, <code>~author.title=Tolkien</code></li>
|
||||
<li>Complex attribute combinations</li>
|
||||
<li>Fuzzy attribute matching</li>
|
||||
</ul>
|
||||
|
||||
<h3>Property Search</h3>
|
||||
<ul>
|
||||
<li>Note metadata: <code>note.type=text</code>, <code>note.dateCreated >= TODAY-7</code></li>
|
||||
<li>Hierarchical queries: <code>note.parents.title=Books</code></li>
|
||||
<li>Relationship traversal: <code>note.children.labels.status=active</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Advanced Features</h3>
|
||||
<ul>
|
||||
<li><strong>Progressive Search</strong>: Exact matching first, fuzzy fallback when needed</li>
|
||||
<li><strong>Fuzzy Search</strong>: Typo tolerance and spelling variations</li>
|
||||
<li><strong>Boolean Logic</strong>: Complex AND/OR/NOT combinations</li>
|
||||
<li><strong>Date Arithmetic</strong>: Dynamic date calculations (TODAY-30, YEAR+1)</li>
|
||||
<li><strong>Regular Expressions</strong>: Pattern matching with <code>%=</code> operator</li>
|
||||
<li><strong>Ordering and Limiting</strong>: Custom sort orders and result limits</li>
|
||||
</ul>
|
||||
|
||||
<h2>Search Operators Quick Reference</h2>
|
||||
|
||||
<h3>Text Operators</h3>
|
||||
<ul>
|
||||
<li><code>=</code> - Exact match</li>
|
||||
<li><code>!=</code> - Not equal</li>
|
||||
<li><code>*=*</code> - Contains</li>
|
||||
<li><code>=*</code> - Starts with</li>
|
||||
<li><code>*=</code> - Ends with</li>
|
||||
<li><code>%=</code> - Regular expression</li>
|
||||
<li><code>~=</code> - Fuzzy exact match</li>
|
||||
<li><code>~*</code> - Fuzzy contains match</li>
|
||||
</ul>
|
||||
|
||||
<h3>Numeric Operators</h3>
|
||||
<ul>
|
||||
<li><code>=</code>, <code>!=</code>, <code>></code>, <code>>=</code>, <code><</code>, <code><=</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Boolean Operators</h3>
|
||||
<ul>
|
||||
<li><code>AND</code>, <code>OR</code>, <code>NOT</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Special Syntax</h3>
|
||||
<ul>
|
||||
<li><code>#labelName</code> - Label exists</li>
|
||||
<li><code>#labelName=value</code> - Label equals value</li>
|
||||
<li><code>~relationName</code> - Relation exists</li>
|
||||
<li><code>~relationName.property</code> - Relation target property</li>
|
||||
<li><code>note.property</code> - Note property access</li>
|
||||
<li><code>"exact phrase"</code> - Quoted phrase search</li>
|
||||
</ul>
|
||||
|
||||
<h2>Common Search Patterns</h2>
|
||||
|
||||
<h3>Simple Searches</h3>
|
||||
<pre><code>hello world # Find notes containing both words
|
||||
"project management" # Find exact phrase
|
||||
#task # Find notes with "task" label
|
||||
~author # Find notes with "author" relation</code></pre>
|
||||
|
||||
<h3>Attribute Searches</h3>
|
||||
<pre><code>#book #author=Tolkien # Books by Tolkien
|
||||
#task #priority=high #status!=completed # High-priority incomplete tasks
|
||||
~project.title *=* alpha # Notes related to projects with "alpha" in title</code></pre>
|
||||
|
||||
<h3>Date-Based Searches</h3>
|
||||
<pre><code>note.dateCreated >= TODAY-7 # Notes created in last week
|
||||
#dueDate <= TODAY+30 # Items due in next 30 days
|
||||
#eventDate = YEAR # Events scheduled for this year</code></pre>
|
||||
|
||||
<h3>Complex Queries</h3>
|
||||
<pre><code>(#book OR #article) AND #topic=programming AND note.dateModified >= MONTH
|
||||
#project AND (#status=active OR #status=pending) AND not(note.isArchived=true)</code></pre>
|
||||
|
||||
<h2>Getting Started Checklist</h2>
|
||||
<ol>
|
||||
<li><strong>Learn Basic Syntax</strong> - Start with simple text and tag searches</li>
|
||||
<li><strong>Understand Operators</strong> - Master the core operators (<code>=</code>, <code>*=*</code>, etc.)</li>
|
||||
<li><strong>Practice Attributes</strong> - Use <code>#</code> for labels and <code>~</code> for relations</li>
|
||||
<li><strong>Try Boolean Logic</strong> - Combine searches with AND/OR/NOT</li>
|
||||
<li><strong>Explore Properties</strong> - Use <code>note.</code> prefix for metadata searches</li>
|
||||
<li><strong>Create Saved Searches</strong> - Turn useful queries into dynamic collections</li>
|
||||
<li><strong>Optimize Performance</strong> - Learn about fast search and limits</li>
|
||||
</ol>
|
||||
|
||||
<h2>Performance Tips</h2>
|
||||
<ul>
|
||||
<li><strong>Use Fast Search</strong> for attribute-only queries</li>
|
||||
<li><strong>Set Reasonable Limits</strong> to prevent large result sets</li>
|
||||
<li><strong>Start Specific</strong> with the most selective criteria first</li>
|
||||
<li><strong>Leverage Attributes</strong> instead of content search when possible</li>
|
||||
<li><strong>Cache Common Queries</strong> as saved searches</li>
|
||||
</ul>
|
||||
|
||||
<h2>Need Help?</h2>
|
||||
<ul>
|
||||
<li><strong>Examples</strong>: Check <a class="reference-link" href="#root/_help_search_examples">Search Examples and Use Cases</a> for practical patterns</li>
|
||||
<li><strong>Complex Queries</strong>: See <a class="reference-link" href="#root/_help_search_advanced">Advanced Search Expressions</a> for sophisticated techniques</li>
|
||||
<li><strong>Performance Issues</strong>: Review <a class="reference-link" href="#root/_help_search_technical">Technical Search Details</a> for optimization</li>
|
||||
<li><strong>Dynamic Collections</strong>: Learn about <a class="reference-link" href="#root/_help_search_saved">Saved Searches</a> for automated organization</li>
|
||||
</ul>
|
||||
|
||||
<h2>Search Workflow Integration</h2>
|
||||
<p>Trilium's search integrates seamlessly with your note-taking workflow:</p>
|
||||
<ul>
|
||||
<li><strong>Quick Search</strong> (Ctrl+S) for instant access</li>
|
||||
<li><strong>Saved Searches</strong> for dynamic organization</li>
|
||||
<li><strong>Search from Subtree</strong> for focused queries</li>
|
||||
<li><strong>Auto-complete</strong> suggestions in search dialogs</li>
|
||||
<li><strong>URL-triggered searches</strong> for bookmarkable queries</li>
|
||||
</ul>
|
||||
|
||||
<p>Start with the fundamentals and gradually explore advanced features as your needs grow. Trilium's search system is designed to scale from simple text queries to sophisticated knowledge management systems.</p>
|
||||
|
||||
<p>Happy searching! 🔍</p>
|
||||
@@ -1,245 +0,0 @@
|
||||
<h1>Search Examples and Use Cases</h1>
|
||||
<p>This guide provides practical examples of how to use Trilium's search capabilities for common organizational patterns and workflows.</p>
|
||||
|
||||
<h2>Personal Knowledge Management</h2>
|
||||
|
||||
<h3>Research and Learning</h3>
|
||||
<p>Track your learning progress and find related materials:</p>
|
||||
<pre><code>#topic=javascript #status=learning</code></pre>
|
||||
<p>Find all JavaScript materials you're currently learning.</p>
|
||||
<pre><code>#course #completed=false note.dateCreated >= MONTH-1</code></pre>
|
||||
<p>Find courses started in the last month that aren't completed.</p>
|
||||
<pre><code>#book #topic *=* programming #rating >= 4</code></pre>
|
||||
<p>Find highly-rated programming books.</p>
|
||||
<pre><code>#paper ~author.title *=* "Andrew Ng" #field=machine-learning</code></pre>
|
||||
<p>Find machine learning papers by Andrew Ng.</p>
|
||||
|
||||
<h3>Meeting and Event Management</h3>
|
||||
<p>Organize meetings, notes, and follow-ups:</p>
|
||||
<pre><code>#meeting note.dateCreated >= TODAY-7 #attendee *=* smith</code></pre>
|
||||
<p>Find this week's meetings with Smith.</p>
|
||||
<pre><code>#meeting #actionItems #status!=completed</code></pre>
|
||||
<p>Find meetings with outstanding action items.</p>
|
||||
<pre><code>#event #date >= TODAY #date <= TODAY+30</code></pre>
|
||||
<p>Find upcoming events in the next 30 days.</p>
|
||||
<pre><code>#meeting #project=alpha note.dateCreated >= MONTH</code></pre>
|
||||
<p>Find this month's meetings about project alpha.</p>
|
||||
|
||||
<h3>Note Organization and Cleanup</h3>
|
||||
<p>Maintain and organize your note structure:</p>
|
||||
<pre><code>note.childrenCount = 0 note.parentCount = 1 note.contentSize < 50 note.dateModified < TODAY-180</code></pre>
|
||||
<p>Find small, isolated notes not modified in 6 months (cleanup candidates).</p>
|
||||
<pre><code>note.attributeCount = 0 note.type=text note.contentSize > 1000</code></pre>
|
||||
<p>Find large text notes without any labels (might need categorization).</p>
|
||||
<pre><code>#draft note.dateCreated < TODAY-30</code></pre>
|
||||
<p>Find old draft notes that might need attention.</p>
|
||||
<pre><code>note.parentCount > 3 note.type=text</code></pre>
|
||||
<p>Find notes that are heavily cloned (might indicate important content).</p>
|
||||
|
||||
<h2>Project Management</h2>
|
||||
|
||||
<h3>Task Tracking</h3>
|
||||
<p>Manage tasks and project progress:</p>
|
||||
<pre><code>#task #priority=high #status!=completed #assignee=me</code></pre>
|
||||
<p>Find your high-priority incomplete tasks.</p>
|
||||
<pre><code>#task #dueDate <= TODAY+3 #dueDate >= TODAY #status!=completed</code></pre>
|
||||
<p>Find tasks due in the next 3 days.</p>
|
||||
<pre><code>#project=website #task #status=blocked</code></pre>
|
||||
<p>Find blocked tasks in the website project.</p>
|
||||
<pre><code>#task #estimatedHours > 0 #actualHours > 0 orderBy note.dateModified desc</code></pre>
|
||||
<p>Find tasks with time tracking data, sorted by recent updates.</p>
|
||||
|
||||
<h3>Project Oversight</h3>
|
||||
<p>Monitor project health and progress:</p>
|
||||
<pre><code>#project #status=active note.children.labels.status = blocked</code></pre>
|
||||
<p>Find active projects with blocked tasks.</p>
|
||||
<pre><code>#project #startDate <= TODAY-90 #status!=completed</code></pre>
|
||||
<p>Find projects that started over 90 days ago but aren't completed.</p>
|
||||
<pre><code>#milestone #targetDate <= TODAY #status!=achieved</code></pre>
|
||||
<p>Find overdue milestones.</p>
|
||||
<pre><code>#project orderBy note.childrenCount desc limit 10</code></pre>
|
||||
<p>Find the 10 largest projects by number of sub-notes.</p>
|
||||
|
||||
<h3>Resource Planning</h3>
|
||||
<p>Track resources and dependencies:</p>
|
||||
<pre><code>#resource #type=person #availability < 50</code></pre>
|
||||
<p>Find people with low availability.</p>
|
||||
<pre><code>#dependency #status=pending #project=mobile-app</code></pre>
|
||||
<p>Find pending dependencies for the mobile app project.</p>
|
||||
<pre><code>#budget #project #spent > #allocated</code></pre>
|
||||
<p>Find projects over budget.</p>
|
||||
|
||||
<h2>Content Creation and Writing</h2>
|
||||
|
||||
<h3>Writing Projects</h3>
|
||||
<p>Manage articles, books, and documentation:</p>
|
||||
<pre><code>#article #status=draft #wordCount >= 1000</code></pre>
|
||||
<p>Find substantial draft articles.</p>
|
||||
<pre><code>#chapter #book=novel #status=outline</code></pre>
|
||||
<p>Find novel chapters still in outline stage.</p>
|
||||
<pre><code>#blog-post #published=false #topic=technology</code></pre>
|
||||
<p>Find unpublished technology blog posts.</p>
|
||||
<pre><code>#documentation #lastReviewed < TODAY-90 #product=api</code></pre>
|
||||
<p>Find API documentation not reviewed in 90 days.</p>
|
||||
|
||||
<h3>Editorial Workflow</h3>
|
||||
<p>Track editing and publication status:</p>
|
||||
<pre><code>#article #editor=jane #status=review</code></pre>
|
||||
<p>Find articles assigned to Jane for review.</p>
|
||||
<pre><code>#manuscript #submissionDate >= TODAY-30 #status=pending</code></pre>
|
||||
<p>Find manuscripts submitted in the last 30 days still pending.</p>
|
||||
<pre><code>#publication #acceptanceDate >= YEAR #status=accepted</code></pre>
|
||||
<p>Find accepted publications this year.</p>
|
||||
|
||||
<h3>Content Research</h3>
|
||||
<p>Organize research materials and sources:</p>
|
||||
<pre><code>#source #reliability >= 8 #topic *=* climate</code></pre>
|
||||
<p>Find reliable sources about climate topics.</p>
|
||||
<pre><code>#quote #author *=* Einstein #verified=true</code></pre>
|
||||
<p>Find verified Einstein quotes.</p>
|
||||
<pre><code>#citation #used=false #relevance=high</code></pre>
|
||||
<p>Find high-relevance citations not yet used.</p>
|
||||
|
||||
<h2>Business and Professional Use</h2>
|
||||
|
||||
<h3>Client Management</h3>
|
||||
<p>Track client relationships and projects:</p>
|
||||
<pre><code>#client=acme #project #status=active</code></pre>
|
||||
<p>Find active projects for ACME client.</p>
|
||||
<pre><code>#meeting #client #date >= MONTH #followUp=required</code></pre>
|
||||
<p>Find client meetings this month requiring follow-up.</p>
|
||||
<pre><code>#contract #renewalDate <= TODAY+60 #renewalDate >= TODAY</code></pre>
|
||||
<p>Find contracts expiring in the next 60 days.</p>
|
||||
<pre><code>#invoice #status=unpaid #dueDate < TODAY</code></pre>
|
||||
<p>Find overdue unpaid invoices.</p>
|
||||
|
||||
<h3>Process Documentation</h3>
|
||||
<p>Maintain procedures and workflows:</p>
|
||||
<pre><code>#procedure #department=engineering #lastUpdated < TODAY-365</code></pre>
|
||||
<p>Find engineering procedures not updated in a year.</p>
|
||||
<pre><code>#workflow #status=active #automation=possible</code></pre>
|
||||
<p>Find active workflows that could be automated.</p>
|
||||
<pre><code>#checklist #process=onboarding #role=developer</code></pre>
|
||||
<p>Find onboarding checklists for developers.</p>
|
||||
|
||||
<h3>Compliance and Auditing</h3>
|
||||
<p>Track compliance requirements and audits:</p>
|
||||
<pre><code>#compliance #standard=sox #nextReview <= TODAY+30</code></pre>
|
||||
<p>Find SOX compliance items due for review soon.</p>
|
||||
<pre><code>#audit #finding #severity=high #status!=resolved</code></pre>
|
||||
<p>Find unresolved high-severity audit findings.</p>
|
||||
<pre><code>#policy #department=hr #effectiveDate >= YEAR</code></pre>
|
||||
<p>Find HR policies that became effective this year.</p>
|
||||
|
||||
<h2>Academic and Educational Use</h2>
|
||||
|
||||
<h3>Course Management</h3>
|
||||
<p>Organize courses and educational content:</p>
|
||||
<pre><code>#course #semester=fall-2024 #assignment #dueDate >= TODAY</code></pre>
|
||||
<p>Find upcoming assignments for fall 2024 courses.</p>
|
||||
<pre><code>#lecture #course=physics #topic *=* quantum</code></pre>
|
||||
<p>Find physics lectures about quantum topics.</p>
|
||||
<pre><code>#student #grade < 70 #course=mathematics</code></pre>
|
||||
<p>Find students struggling in mathematics.</p>
|
||||
<pre><code>#syllabus #course #lastUpdated < TODAY-180</code></pre>
|
||||
<p>Find syllabi not updated in 6 months.</p>
|
||||
|
||||
<h3>Research Management</h3>
|
||||
<p>Track research projects and publications:</p>
|
||||
<pre><code>#experiment #status=running #endDate <= TODAY+7</code></pre>
|
||||
<p>Find experiments ending in the next week.</p>
|
||||
<pre><code>#dataset #size > 1000000 #cleaned=true #public=false</code></pre>
|
||||
<p>Find large, cleaned, private datasets.</p>
|
||||
<pre><code>#hypothesis #tested=false #priority=high</code></pre>
|
||||
<p>Find high-priority untested hypotheses.</p>
|
||||
<pre><code>#collaboration #institution *=* stanford #status=active</code></pre>
|
||||
<p>Find active collaborations with Stanford.</p>
|
||||
|
||||
<h3>Grant and Funding</h3>
|
||||
<p>Manage funding applications and requirements:</p>
|
||||
<pre><code>#grant #deadline <= TODAY+30 #deadline >= TODAY #status=in-progress</code></pre>
|
||||
<p>Find grant applications due in the next 30 days.</p>
|
||||
<pre><code>#funding #amount >= 100000 #status=awarded #startDate >= YEAR</code></pre>
|
||||
<p>Find large grants awarded this year.</p>
|
||||
<pre><code>#report #funding #dueDate <= TODAY+14 #status!=submitted</code></pre>
|
||||
<p>Find funding reports due in 2 weeks.</p>
|
||||
|
||||
<h2>Technical Documentation</h2>
|
||||
|
||||
<h3>Code and Development</h3>
|
||||
<p>Track code-related notes and documentation:</p>
|
||||
<pre><code>#bug #severity=critical #status!=fixed #product=webapp</code></pre>
|
||||
<p>Find critical unfixed bugs in the web app.</p>
|
||||
<pre><code>#feature #version=2.0 #status=implemented #tested=false</code></pre>
|
||||
<p>Find version 2.0 features that are implemented but not tested.</p>
|
||||
<pre><code>#api #endpoint #deprecated=true #removalDate <= TODAY+90</code></pre>
|
||||
<p>Find deprecated API endpoints scheduled for removal soon.</p>
|
||||
<pre><code>#architecture #component=database #lastReviewed < TODAY-180</code></pre>
|
||||
<p>Find database architecture documentation not reviewed in 6 months.</p>
|
||||
|
||||
<h3>System Administration</h3>
|
||||
<p>Manage infrastructure and operations:</p>
|
||||
<pre><code>#server #status=maintenance #scheduledDate >= TODAY #scheduledDate <= TODAY+7</code></pre>
|
||||
<p>Find servers scheduled for maintenance this week.</p>
|
||||
<pre><code>#backup #status=failed #date >= TODAY-7</code></pre>
|
||||
<p>Find backup failures in the last week.</p>
|
||||
<pre><code>#security #vulnerability #severity=high #patched=false</code></pre>
|
||||
<p>Find unpatched high-severity vulnerabilities.</p>
|
||||
<pre><code>#monitoring #alert #frequency > 10 #period=week</code></pre>
|
||||
<p>Find alerts triggering more than 10 times per week.</p>
|
||||
|
||||
<h2>Data Analysis and Reporting</h2>
|
||||
|
||||
<h3>Performance Tracking</h3>
|
||||
<p>Monitor metrics and KPIs:</p>
|
||||
<pre><code>#metric #kpi=true #trend=declining #period=month</code></pre>
|
||||
<p>Find declining monthly KPIs.</p>
|
||||
<pre><code>#report #frequency=weekly #lastGenerated < TODAY-10</code></pre>
|
||||
<p>Find weekly reports that haven't been generated in 10 days.</p>
|
||||
<pre><code>#dashboard #stakeholder=executive #lastUpdated < TODAY-7</code></pre>
|
||||
<p>Find executive dashboards not updated this week.</p>
|
||||
|
||||
<h3>Trend Analysis</h3>
|
||||
<p>Track patterns and changes over time:</p>
|
||||
<pre><code>#data #source=sales #period=quarter #analyzed=false</code></pre>
|
||||
<p>Find unanalyzed quarterly sales data.</p>
|
||||
<pre><code>#trend #direction=up #significance=high #period=month</code></pre>
|
||||
<p>Find significant positive monthly trends.</p>
|
||||
<pre><code>#forecast #accuracy < 80 #model=linear #period=quarter</code></pre>
|
||||
<p>Find inaccurate quarterly linear forecasts.</p>
|
||||
|
||||
<h2>Search Strategy Tips</h2>
|
||||
|
||||
<h3>Building Effective Queries</h3>
|
||||
<ol>
|
||||
<li><strong>Start Specific</strong>: Begin with the most selective criteria</li>
|
||||
<li><strong>Add Gradually</strong>: Build complexity incrementally</li>
|
||||
<li><strong>Test Components</strong>: Verify each part of complex queries</li>
|
||||
<li><strong>Use Shortcuts</strong>: Leverage <code>#</code> and <code>~</code> shortcuts for efficiency</li>
|
||||
</ol>
|
||||
|
||||
<h3>Performance Optimization</h3>
|
||||
<ol>
|
||||
<li><strong>Use Fast Search</strong>: For large databases, enable fast search when content isn't needed</li>
|
||||
<li><strong>Limit Results</strong>: Add limits to prevent overwhelming result sets</li>
|
||||
<li><strong>Order Strategically</strong>: Put the most useful results first</li>
|
||||
<li><strong>Cache Common Queries</strong>: Save frequently used searches</li>
|
||||
</ol>
|
||||
|
||||
<h3>Maintenance Patterns</h3>
|
||||
<p>Regular queries for note maintenance:</p>
|
||||
<pre><code># Weekly cleanup check
|
||||
note.attributeCount = 0 note.type=text note.contentSize < 100 note.dateModified < TODAY-30
|
||||
|
||||
# Monthly project review
|
||||
#project #status=active note.dateModified < TODAY-30
|
||||
|
||||
# Quarterly archive review
|
||||
note.isArchived=false note.dateModified < TODAY-90 note.childrenCount = 0</code></pre>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_search_saved">Saved Searches</a> - Convert these examples into reusable saved searches</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_technical">Technical Search Details</a> - Understanding performance and implementation</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_fundamentals">Search Fundamentals</a> - Review basic concepts and syntax</li>
|
||||
</ul>
|
||||
@@ -1,181 +0,0 @@
|
||||
<h1>Search Fundamentals</h1>
|
||||
<p>Trilium's search system is a powerful tool for finding and organizing notes. It supports multiple search modes, from simple text queries to complex expressions using attributes, relationships, and note properties.</p>
|
||||
|
||||
<h2>Search Types Overview</h2>
|
||||
<p>Trilium provides three main search approaches:</p>
|
||||
<ol>
|
||||
<li><strong>Full-text Search</strong> - Searches within note titles and content</li>
|
||||
<li><strong>Attribute Search</strong> - Searches based on labels and relations attached to notes</li>
|
||||
<li><strong>Property Search</strong> - Searches based on note metadata (type, creation date, etc.)</li>
|
||||
</ol>
|
||||
<p>These can be combined in powerful ways to create precise queries.</p>
|
||||
|
||||
<h2>Basic Search Syntax</h2>
|
||||
|
||||
<h3>Simple Text Search</h3>
|
||||
<pre><code>hello world</code></pre>
|
||||
<p>Finds notes containing both "hello" and "world" anywhere in the title or content.</p>
|
||||
|
||||
<h3>Quoted Text Search</h3>
|
||||
<pre><code>"hello world"</code></pre>
|
||||
<p>Finds notes containing the exact phrase "hello world".</p>
|
||||
|
||||
<h3>Attribute Search</h3>
|
||||
<pre><code>#tag</code></pre>
|
||||
<p>Finds notes with the label "tag".</p>
|
||||
<pre><code>#category=book</code></pre>
|
||||
<p>Finds notes with label "category" set to "book".</p>
|
||||
|
||||
<h3>Relation Search</h3>
|
||||
<pre><code>~author</code></pre>
|
||||
<p>Finds notes with a relation named "author".</p>
|
||||
<pre><code>~author.title=Tolkien</code></pre>
|
||||
<p>Finds notes with an "author" relation pointing to a note titled "Tolkien".</p>
|
||||
|
||||
<h2>Search Operators</h2>
|
||||
|
||||
<h3>Text Operators</h3>
|
||||
<ul>
|
||||
<li><code>=</code> - Exact match</li>
|
||||
<li><code>!=</code> - Not equal</li>
|
||||
<li><code>*=*</code> - Contains (substring)</li>
|
||||
<li><code>=*</code> - Starts with</li>
|
||||
<li><code>*=</code> - Ends with</li>
|
||||
<li><code>%=</code> - Regular expression match</li>
|
||||
<li><code>~=</code> - Fuzzy exact match</li>
|
||||
<li><code>~*</code> - Fuzzy contains match</li>
|
||||
</ul>
|
||||
|
||||
<h3>Numeric Operators</h3>
|
||||
<ul>
|
||||
<li><code>=</code> - Equal</li>
|
||||
<li><code>!=</code> - Not equal</li>
|
||||
<li><code>></code> - Greater than</li>
|
||||
<li><code>>=</code> - Greater than or equal</li>
|
||||
<li><code><</code> - Less than</li>
|
||||
<li><code><=</code> - Less than or equal</li>
|
||||
</ul>
|
||||
|
||||
<h3>Boolean Operators</h3>
|
||||
<ul>
|
||||
<li><code>AND</code> - Both conditions must be true</li>
|
||||
<li><code>OR</code> - Either condition must be true</li>
|
||||
<li><code>NOT</code> or <code>not()</code> - Condition must be false</li>
|
||||
</ul>
|
||||
|
||||
<h2>Search Context and Scope</h2>
|
||||
|
||||
<h3>Search Scope</h3>
|
||||
<p>By default, search covers:</p>
|
||||
<ul>
|
||||
<li>Note titles</li>
|
||||
<li>Note content (for text-based note types)</li>
|
||||
<li>Label names and values</li>
|
||||
<li>Relation names</li>
|
||||
<li>Note properties</li>
|
||||
</ul>
|
||||
|
||||
<h3>Fast Search Mode</h3>
|
||||
<p>When enabled, fast search:</p>
|
||||
<ul>
|
||||
<li>Searches only titles and attributes</li>
|
||||
<li>Skips note content</li>
|
||||
<li>Provides faster results for large databases</li>
|
||||
</ul>
|
||||
|
||||
<h3>Archived Notes</h3>
|
||||
<ul>
|
||||
<li>Excluded by default</li>
|
||||
<li>Can be included with "Include archived" option</li>
|
||||
</ul>
|
||||
|
||||
<h2>Case Sensitivity and Normalization</h2>
|
||||
<ul>
|
||||
<li>All searches are case-insensitive</li>
|
||||
<li>Diacritics are normalized ("café" matches "cafe")</li>
|
||||
<li>Unicode characters are properly handled</li>
|
||||
</ul>
|
||||
|
||||
<h2>Performance Considerations</h2>
|
||||
|
||||
<h3>Content Size Limits</h3>
|
||||
<ul>
|
||||
<li>Note content is limited to 10MB for search processing</li>
|
||||
<li>Larger notes are still searchable by title and attributes</li>
|
||||
</ul>
|
||||
|
||||
<h3>Progressive Search Strategy</h3>
|
||||
<ol>
|
||||
<li><strong>Exact Search Phase</strong>: Fast exact matching (handles 90%+ of searches)</li>
|
||||
<li><strong>Fuzzy Search Phase</strong>: Activated when exact search returns fewer than 5 high-quality results</li>
|
||||
<li><strong>Result Ordering</strong>: Exact matches always appear before fuzzy matches</li>
|
||||
</ol>
|
||||
|
||||
<h3>Search Optimization Tips</h3>
|
||||
<ul>
|
||||
<li>Use specific terms rather than very common words</li>
|
||||
<li>Combine full-text with attribute searches for precision</li>
|
||||
<li>Use fast search for large databases when content search isn't needed</li>
|
||||
<li>Limit results when dealing with very large result sets</li>
|
||||
</ul>
|
||||
|
||||
<h2>Special Characters and Escaping</h2>
|
||||
|
||||
<h3>Reserved Characters</h3>
|
||||
<p>These characters have special meaning in search queries:</p>
|
||||
<ul>
|
||||
<li><code>#</code> - Label indicator</li>
|
||||
<li><code>~</code> - Relation indicator</li>
|
||||
<li><code>()</code> - Grouping</li>
|
||||
<li><code>"</code> <code>'</code> <code>`</code> - Quotes for exact phrases</li>
|
||||
</ul>
|
||||
|
||||
<h3>Escaping Special Characters</h3>
|
||||
<p>Use backslash to search for literal special characters:</p>
|
||||
<pre><code>\#hashtag</code></pre>
|
||||
<p>Searches for the literal text "#hashtag" instead of a label.</p>
|
||||
|
||||
<p>Use quotes to include special characters in phrases:</p>
|
||||
<pre><code>"note.txt file"</code></pre>
|
||||
<p>Searches for the exact phrase including the dot.</p>
|
||||
|
||||
<h2>Date and Time Values</h2>
|
||||
|
||||
<h3>Special Date Keywords</h3>
|
||||
<ul>
|
||||
<li><code>TODAY</code> - Current date</li>
|
||||
<li><code>NOW</code> - Current date and time</li>
|
||||
<li><code>MONTH</code> - Current month</li>
|
||||
<li><code>YEAR</code> - Current year</li>
|
||||
</ul>
|
||||
|
||||
<h3>Date Arithmetic</h3>
|
||||
<pre><code>#dateCreated >= TODAY-30</code></pre>
|
||||
<p>Finds notes created in the last 30 days.</p>
|
||||
<pre><code>#eventDate = YEAR+1</code></pre>
|
||||
<p>Finds notes with eventDate set to next year.</p>
|
||||
|
||||
<h2>Search Results and Scoring</h2>
|
||||
|
||||
<h3>Result Ranking</h3>
|
||||
<p>Results are ordered by:</p>
|
||||
<ol>
|
||||
<li>Relevance score (based on term frequency and position)</li>
|
||||
<li>Note depth (closer to root ranks higher)</li>
|
||||
<li>Alphabetical order for ties</li>
|
||||
</ol>
|
||||
|
||||
<h3>Progressive Search Behavior</h3>
|
||||
<ul>
|
||||
<li>Exact matches always rank before fuzzy matches</li>
|
||||
<li>High-quality exact matches prevent fuzzy search activation</li>
|
||||
<li>Fuzzy matches help find content with typos or variations</li>
|
||||
</ul>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_search_advanced">Advanced Search Expressions</a> - Complex queries and combinations</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_examples">Search Examples and Use Cases</a> - Practical applications</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_saved">Saved Searches</a> - Creating dynamic collections</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_technical">Technical Search Details</a> - Under-the-hood implementation</li>
|
||||
</ul>
|
||||
@@ -1,499 +0,0 @@
|
||||
<h1>Technical Search Details</h1>
|
||||
<p>This guide provides technical information about Trilium's search implementation, performance characteristics, and optimization strategies for power users and administrators.</p>
|
||||
|
||||
<h2>Search Architecture Overview</h2>
|
||||
|
||||
<h3>Three-Layer Search System</h3>
|
||||
<p>Trilium's search operates across three cache layers:</p>
|
||||
<ol>
|
||||
<li><strong>Becca (Backend Cache)</strong>: Server-side entity cache containing notes, attributes, and relationships</li>
|
||||
<li><strong>Froca (Frontend Cache)</strong>: Client-side mirror providing fast UI updates</li>
|
||||
<li><strong>Database Layer</strong>: SQLite database with FTS (Full-Text Search) support</li>
|
||||
</ol>
|
||||
|
||||
<h3>Search Processing Pipeline</h3>
|
||||
<ol>
|
||||
<li><strong>Lexical Analysis</strong>: Query parsing and tokenization</li>
|
||||
<li><strong>Expression Building</strong>: Converting tokens to executable expressions</li>
|
||||
<li><strong>Progressive Execution</strong>: Exact search followed by optional fuzzy search</li>
|
||||
<li><strong>Result Scoring</strong>: Relevance calculation and ranking</li>
|
||||
<li><strong>Result Presentation</strong>: Formatting and highlighting</li>
|
||||
</ol>
|
||||
|
||||
<h2>Query Processing Details</h2>
|
||||
|
||||
<h3>Lexical Analysis (Lex)</h3>
|
||||
<p>The lexer breaks down search queries into components:</p>
|
||||
<pre><code>// Input: 'project #status=active note.dateCreated >= TODAY-7'
|
||||
// Output:
|
||||
{
|
||||
fulltextTokens: ['project'],
|
||||
expressionTokens: ['#status', '=', 'active', 'note', '.', 'dateCreated', '>=', 'TODAY-7']
|
||||
}</code></pre>
|
||||
|
||||
<h4>Token Types</h4>
|
||||
<ul>
|
||||
<li><strong>Fulltext Tokens</strong>: Regular search terms</li>
|
||||
<li><strong>Expression Tokens</strong>: Attributes, operators, and property references</li>
|
||||
<li><strong>Quoted Strings</strong>: Exact phrase matches</li>
|
||||
<li><strong>Escaped Characters</strong>: Literal special characters</li>
|
||||
</ul>
|
||||
|
||||
<h3>Expression Building (Parse)</h3>
|
||||
<p>Tokens are converted into executable expression trees:</p>
|
||||
<pre><code>// Expression tree for: #book AND #author=Tolkien
|
||||
AndExp([
|
||||
AttributeExistsExp('label', 'book'),
|
||||
LabelComparisonExp('label', 'author', equals('tolkien'))
|
||||
])</code></pre>
|
||||
|
||||
<h4>Expression Types</h4>
|
||||
<ul>
|
||||
<li><code>AndExp</code>, <code>OrExp</code>, <code>NotExp</code>: Boolean logic</li>
|
||||
<li><code>AttributeExistsExp</code>: Label/relation existence</li>
|
||||
<li><code>LabelComparisonExp</code>: Label value comparison</li>
|
||||
<li><code>RelationWhereExp</code>: Relation target queries</li>
|
||||
<li><code>PropertyComparisonExp</code>: Note property filtering</li>
|
||||
<li><code>NoteContentFulltextExp</code>: Content search</li>
|
||||
<li><code>OrderByAndLimitExp</code>: Result ordering and limiting</li>
|
||||
</ul>
|
||||
|
||||
<h3>Progressive Search Strategy</h3>
|
||||
|
||||
<h4>Phase 1: Exact Search</h4>
|
||||
<pre><code>// Fast exact matching
|
||||
const exactResults = performSearch(expression, searchContext, false);</code></pre>
|
||||
<p>Characteristics:</p>
|
||||
<ul>
|
||||
<li>Substring matching for text</li>
|
||||
<li>Exact attribute matching</li>
|
||||
<li>Property-based filtering</li>
|
||||
<li>Handles 90%+ of searches</li>
|
||||
<li>Sub-second response time</li>
|
||||
</ul>
|
||||
|
||||
<h4>Phase 2: Fuzzy Fallback</h4>
|
||||
<pre><code>// Activated when exact results < 5 high-quality matches
|
||||
if (highQualityResults.length < 5) {
|
||||
const fuzzyResults = performSearch(expression, searchContext, true);
|
||||
return mergeExactAndFuzzyResults(exactResults, fuzzyResults);
|
||||
}</code></pre>
|
||||
<p>Characteristics:</p>
|
||||
<ul>
|
||||
<li>Edit distance calculations</li>
|
||||
<li>Phrase proximity matching</li>
|
||||
<li>Typo tolerance</li>
|
||||
<li>Performance safeguards</li>
|
||||
<li>Exact matches always rank first</li>
|
||||
</ul>
|
||||
|
||||
<h2>Performance Characteristics</h2>
|
||||
|
||||
<h3>Search Limits and Thresholds</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Value</th>
|
||||
<th>Purpose</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>MAX_SEARCH_CONTENT_SIZE</code></td>
|
||||
<td>2MB</td>
|
||||
<td>Database-level content filtering</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MIN_FUZZY_TOKEN_LENGTH</code></td>
|
||||
<td>3 chars</td>
|
||||
<td>Minimum length for fuzzy matching</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MAX_EDIT_DISTANCE</code></td>
|
||||
<td>2 chars</td>
|
||||
<td>Maximum character changes for fuzzy</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MAX_PHRASE_PROXIMITY</code></td>
|
||||
<td>10 words</td>
|
||||
<td>Maximum distance for phrase matching</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>RESULT_SUFFICIENCY_THRESHOLD</code></td>
|
||||
<td>5 results</td>
|
||||
<td>Threshold for fuzzy activation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ABSOLUTE_MAX_CONTENT_SIZE</code></td>
|
||||
<td>100MB</td>
|
||||
<td>Hard limit to prevent system crash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ABSOLUTE_MAX_WORD_COUNT</code></td>
|
||||
<td>2M words</td>
|
||||
<td>Hard limit for word processing</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Performance Optimization</h3>
|
||||
|
||||
<h4>Database-Level Optimizations</h4>
|
||||
<pre><code>-- Content size filtering at database level
|
||||
SELECT noteId, type, mime, content, isProtected
|
||||
FROM notes JOIN blobs USING (blobId)
|
||||
WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap')
|
||||
AND isDeleted = 0
|
||||
AND LENGTH(content) < 2097152 -- 2MB limit</code></pre>
|
||||
|
||||
<h4>Memory Management</h4>
|
||||
<ul>
|
||||
<li>Single-array edit distance calculation</li>
|
||||
<li>Early termination for distant matches</li>
|
||||
<li>Progressive content processing</li>
|
||||
<li>Cached regular expressions</li>
|
||||
</ul>
|
||||
|
||||
<h4>Search Context Optimization</h4>
|
||||
<pre><code>// Efficient search context configuration
|
||||
const searchContext = new SearchContext({
|
||||
fastSearch: true, // Skip content search
|
||||
limit: 50, // Reasonable result limit
|
||||
orderBy: 'dateCreated', // Use indexed property
|
||||
includeArchivedNotes: false // Reduce search space
|
||||
});</code></pre>
|
||||
|
||||
<h2>Fuzzy Search Implementation</h2>
|
||||
|
||||
<h3>Edit Distance Algorithm</h3>
|
||||
<p>Trilium uses an optimized Levenshtein distance calculation:</p>
|
||||
<pre><code>// Optimized single-array implementation
|
||||
function calculateOptimizedEditDistance(str1, str2, maxDistance) {
|
||||
// Early termination checks
|
||||
if (Math.abs(str1.length - str2.length) > maxDistance) {
|
||||
return maxDistance + 1;
|
||||
}
|
||||
|
||||
// Single array optimization
|
||||
let previousRow = Array.from({ length: str2.length + 1 }, (_, i) => i);
|
||||
let currentRow = new Array(str2.length + 1);
|
||||
|
||||
for (let i = 1; i <= str1.length; i++) {
|
||||
currentRow[0] = i;
|
||||
let minInRow = i;
|
||||
|
||||
for (let j = 1; j <= str2.length; j++) {
|
||||
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
||||
currentRow[j] = Math.min(
|
||||
previousRow[j] + 1, // deletion
|
||||
currentRow[j - 1] + 1, // insertion
|
||||
previousRow[j - 1] + cost // substitution
|
||||
);
|
||||
minInRow = Math.min(minInRow, currentRow[j]);
|
||||
}
|
||||
|
||||
// Early termination if row minimum exceeds threshold
|
||||
if (minInRow > maxDistance) return maxDistance + 1;
|
||||
|
||||
[previousRow, currentRow] = [currentRow, previousRow];
|
||||
}
|
||||
|
||||
return previousRow[str2.length];
|
||||
}</code></pre>
|
||||
|
||||
<h3>Phrase Proximity Matching</h3>
|
||||
<p>For multi-token fuzzy searches:</p>
|
||||
<pre><code>// Check if tokens appear within reasonable proximity
|
||||
function hasProximityMatch(tokenPositions, maxDistance = 10) {
|
||||
// For 2 tokens, simple distance check
|
||||
if (tokenPositions.length === 2) {
|
||||
const [pos1, pos2] = tokenPositions;
|
||||
return pos1.some(p1 => pos2.some(p2 => Math.abs(p1 - p2) <= maxDistance));
|
||||
}
|
||||
|
||||
// For multiple tokens, find sequence within range
|
||||
const findSequence = (remaining, currentPos) => {
|
||||
if (remaining.length === 0) return true;
|
||||
const [nextPositions, ...rest] = remaining;
|
||||
return nextPositions.some(pos =>
|
||||
Math.abs(pos - currentPos) <= maxDistance &&
|
||||
findSequence(rest, pos)
|
||||
);
|
||||
};
|
||||
|
||||
const [firstPositions, ...rest] = tokenPositions;
|
||||
return firstPositions.some(startPos => findSequence(rest, startPos));
|
||||
}</code></pre>
|
||||
|
||||
<h2>Indexing and Storage</h2>
|
||||
|
||||
<h3>Database Schema Optimization</h3>
|
||||
<pre><code>-- Relevant indexes for search performance
|
||||
CREATE INDEX idx_notes_type ON notes(type);
|
||||
CREATE INDEX idx_notes_isDeleted ON notes(isDeleted);
|
||||
CREATE INDEX idx_notes_dateCreated ON notes(dateCreated);
|
||||
CREATE INDEX idx_notes_dateModified ON notes(dateModified);
|
||||
CREATE INDEX idx_attributes_name ON attributes(name);
|
||||
CREATE INDEX idx_attributes_type ON attributes(type);
|
||||
CREATE INDEX idx_attributes_value ON attributes(value);</code></pre>
|
||||
|
||||
<h3>Content Processing</h3>
|
||||
<p>Notes are processed differently based on type:</p>
|
||||
<pre><code>// Content preprocessing by note type
|
||||
function preprocessContent(content, type, mime) {
|
||||
content = normalize(content.toString());
|
||||
|
||||
if (type === "text" && mime === "text/html") {
|
||||
content = stripTags(content);
|
||||
content = content.replace(/ /g, " ");
|
||||
} else if (type === "mindMap" && mime === "application/json") {
|
||||
content = processMindmapContent(content);
|
||||
} else if (type === "canvas" && mime === "application/json") {
|
||||
const canvasData = JSON.parse(content);
|
||||
const textElements = canvasData.elements
|
||||
.filter(el => el.type === "text" && el.text)
|
||||
.map(el => el.text);
|
||||
content = normalize(textElements.join(" "));
|
||||
}
|
||||
|
||||
return content.trim();
|
||||
}</code></pre>
|
||||
|
||||
<h2>Search Result Processing</h2>
|
||||
|
||||
<h3>Scoring Algorithm</h3>
|
||||
<p>Results are scored based on multiple factors:</p>
|
||||
<pre><code>function computeScore(fulltextQuery, highlightedTokens, enableFuzzyMatching) {
|
||||
let score = 0;
|
||||
|
||||
// Title matches get higher score
|
||||
if (this.noteTitle.toLowerCase().includes(fulltextQuery.toLowerCase())) {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
// Path matches (hierarchical context)
|
||||
const pathMatch = this.notePathArray.some(pathNote =>
|
||||
pathNote.title.toLowerCase().includes(fulltextQuery.toLowerCase())
|
||||
);
|
||||
if (pathMatch) score += 5;
|
||||
|
||||
// Attribute matches
|
||||
score += this.attributeMatches * 3;
|
||||
|
||||
// Content snippet quality
|
||||
if (this.contentSnippet && this.contentSnippet.length > 0) {
|
||||
score += 2;
|
||||
}
|
||||
|
||||
// Fuzzy match penalty
|
||||
if (enableFuzzyMatching && this.isFuzzyMatch) {
|
||||
score *= 0.8; // 20% penalty for fuzzy matches
|
||||
}
|
||||
|
||||
return score;
|
||||
}</code></pre>
|
||||
|
||||
<h3>Result Merging</h3>
|
||||
<p>Exact and fuzzy results are carefully merged:</p>
|
||||
<pre><code>function mergeExactAndFuzzyResults(exactResults, fuzzyResults) {
|
||||
// Deduplicate - exact results take precedence
|
||||
const exactNoteIds = new Set(exactResults.map(r => r.noteId));
|
||||
const additionalFuzzyResults = fuzzyResults.filter(r =>
|
||||
!exactNoteIds.has(r.noteId)
|
||||
);
|
||||
|
||||
// Sort within each category
|
||||
exactResults.sort(byScoreAndDepth);
|
||||
additionalFuzzyResults.sort(byScoreAndDepth);
|
||||
|
||||
// CRITICAL: Exact matches always come first
|
||||
return [...exactResults, ...additionalFuzzyResults];
|
||||
}</code></pre>
|
||||
|
||||
<h2>Performance Monitoring</h2>
|
||||
|
||||
<h3>Search Metrics</h3>
|
||||
<p>Monitor these performance indicators:</p>
|
||||
<pre><code>// Performance tracking
|
||||
const searchMetrics = {
|
||||
totalQueries: 0,
|
||||
exactSearchTime: 0,
|
||||
fuzzySearchTime: 0,
|
||||
resultCount: 0,
|
||||
cacheHitRate: 0,
|
||||
slowQueries: [] // queries taking > 1 second
|
||||
};</code></pre>
|
||||
|
||||
<h3>Memory Usage</h3>
|
||||
<p>Track memory consumption:</p>
|
||||
<pre><code>// Memory monitoring
|
||||
const memoryMetrics = {
|
||||
searchCacheSize: 0,
|
||||
activeSearchContexts: 0,
|
||||
largeContentNotes: 0, // notes > 1MB
|
||||
indexSize: 0
|
||||
};</code></pre>
|
||||
|
||||
<h3>Query Complexity Analysis</h3>
|
||||
<p>Identify expensive queries:</p>
|
||||
<pre><code>// Query complexity factors
|
||||
const complexityFactors = {
|
||||
tokenCount: query.split(' ').length,
|
||||
hasRegex: query.includes('%='),
|
||||
hasFuzzy: query.includes('~=') || query.includes('~*'),
|
||||
hasRelationTraversal: query.includes('.relations.'),
|
||||
hasNestedProperties: (query.match(/\./g) || []).length > 2,
|
||||
hasOrderBy: query.includes('orderBy'),
|
||||
estimatedResultSize: 'unknown'
|
||||
};</code></pre>
|
||||
|
||||
<h2>Troubleshooting Performance Issues</h2>
|
||||
|
||||
<h3>Common Performance Problems</h3>
|
||||
|
||||
<h4>Slow Full-Text Search</h4>
|
||||
<p><strong>Diagnosis:</strong></p>
|
||||
<ul>
|
||||
<li>Check note content sizes</li>
|
||||
<li>Verify content type filtering</li>
|
||||
<li>Monitor regex usage</li>
|
||||
<li>Review fuzzy search activation</li>
|
||||
</ul>
|
||||
<p><strong>Solutions:</strong></p>
|
||||
<ul>
|
||||
<li>Enable fast search for attribute-only queries</li>
|
||||
<li>Add content size limits</li>
|
||||
<li>Optimize regex patterns</li>
|
||||
<li>Tune fuzzy search thresholds</li>
|
||||
</ul>
|
||||
|
||||
<h4>Memory Issues</h4>
|
||||
<p><strong>Diagnosis:</strong></p>
|
||||
<ul>
|
||||
<li>Monitor result set sizes</li>
|
||||
<li>Check for large content processing</li>
|
||||
<li>Review search context caching</li>
|
||||
<li>Identify memory leaks</li>
|
||||
</ul>
|
||||
<p><strong>Solutions:</strong></p>
|
||||
<ul>
|
||||
<li>Add result limits</li>
|
||||
<li>Implement progressive loading</li>
|
||||
<li>Clear unused search contexts</li>
|
||||
<li>Optimize content preprocessing</li>
|
||||
</ul>
|
||||
|
||||
<h4>High CPU Usage</h4>
|
||||
<p><strong>Diagnosis:</strong></p>
|
||||
<ul>
|
||||
<li>Profile fuzzy search operations</li>
|
||||
<li>Check edit distance calculations</li>
|
||||
<li>Monitor regex compilation</li>
|
||||
<li>Review phrase proximity matching</li>
|
||||
</ul>
|
||||
<p><strong>Solutions:</strong></p>
|
||||
<ul>
|
||||
<li>Increase minimum fuzzy token length</li>
|
||||
<li>Reduce maximum edit distance</li>
|
||||
<li>Cache compiled regexes</li>
|
||||
<li>Limit phrase proximity distance</li>
|
||||
</ul>
|
||||
|
||||
<h3>Debugging Tools</h3>
|
||||
|
||||
<h4>Debug Mode</h4>
|
||||
<p>Enable search debugging:</p>
|
||||
<pre><code>// Search context with debugging
|
||||
const searchContext = new SearchContext({
|
||||
debug: true // Logs expression parsing and execution
|
||||
});</code></pre>
|
||||
<p>Output includes:</p>
|
||||
<ul>
|
||||
<li>Token parsing results</li>
|
||||
<li>Expression tree structure</li>
|
||||
<li>Execution timing</li>
|
||||
<li>Result scoring details</li>
|
||||
</ul>
|
||||
|
||||
<h4>Performance Profiling</h4>
|
||||
<pre><code>// Manual performance measurement
|
||||
const startTime = Date.now();
|
||||
const results = searchService.findResultsWithQuery(query, searchContext);
|
||||
const endTime = Date.now();
|
||||
console.log(`Search took ${endTime - startTime}ms for ${results.length} results`);</code></pre>
|
||||
|
||||
<h4>Query Analysis</h4>
|
||||
<pre><code>// Analyze query complexity
|
||||
function analyzeQuery(query) {
|
||||
return {
|
||||
tokenCount: query.split(/\s+/).length,
|
||||
hasAttributes: /#|\~/.test(query),
|
||||
hasProperties: /note\./.test(query),
|
||||
hasRegex: /%=/.test(query),
|
||||
hasFuzzy: /~[=*]/.test(query),
|
||||
complexity: calculateComplexityScore(query)
|
||||
};
|
||||
}</code></pre>
|
||||
|
||||
<h2>Configuration and Tuning</h2>
|
||||
|
||||
<h3>Server Configuration</h3>
|
||||
<p>Relevant settings in <code>config.ini</code>:</p>
|
||||
<pre><code>[Search]
|
||||
maxContentSize=2097152 # 2MB content limit
|
||||
minFuzzyTokenLength=3 # Minimum chars for fuzzy
|
||||
maxEditDistance=2 # Edit distance limit
|
||||
resultSufficiencyThreshold=5 # Fuzzy activation threshold
|
||||
enableProgressiveSearch=true # Enable progressive strategy
|
||||
cacheSearchResults=true # Cache frequent searches
|
||||
|
||||
[Performance]
|
||||
searchTimeoutMs=30000 # 30 second search timeout
|
||||
maxSearchResults=1000 # Hard limit on results
|
||||
enableSearchProfiling=false # Performance logging</code></pre>
|
||||
|
||||
<h3>Runtime Tuning</h3>
|
||||
<p>Adjust search behavior programmatically:</p>
|
||||
<pre><code>// Dynamic configuration
|
||||
const searchConfig = {
|
||||
maxContentSize: 1024 * 1024, // 1MB for faster processing
|
||||
enableFuzzySearch: false, // Exact only for speed
|
||||
resultLimit: 50, // Smaller result sets
|
||||
useIndexedPropertiesOnly: true // Skip expensive calculations
|
||||
};</code></pre>
|
||||
|
||||
<h2>Best Practices for Performance</h2>
|
||||
|
||||
<h3>Query Design</h3>
|
||||
<ol>
|
||||
<li><strong>Start Specific</strong>: Use selective criteria first</li>
|
||||
<li><strong>Limit Results</strong>: Always set reasonable limits</li>
|
||||
<li><strong>Use Indexes</strong>: Prefer indexed properties for ordering</li>
|
||||
<li><strong>Avoid Regex</strong>: Use simple operators when possible</li>
|
||||
<li><strong>Cache Common Queries</strong>: Save frequently used searches</li>
|
||||
</ol>
|
||||
|
||||
<h3>System Administration</h3>
|
||||
<ol>
|
||||
<li><strong>Monitor Performance</strong>: Track slow queries and memory usage</li>
|
||||
<li><strong>Regular Maintenance</strong>: Clean up unused notes and attributes</li>
|
||||
<li><strong>Index Optimization</strong>: Ensure database indexes are current</li>
|
||||
<li><strong>Content Management</strong>: Archive or compress large content</li>
|
||||
</ol>
|
||||
|
||||
<h3>Development Guidelines</h3>
|
||||
<ol>
|
||||
<li><strong>Test Performance</strong>: Benchmark complex queries</li>
|
||||
<li><strong>Profile Regularly</strong>: Identify performance regressions</li>
|
||||
<li><strong>Optimize Incrementally</strong>: Make small, measured improvements</li>
|
||||
<li><strong>Document Complexity</strong>: Note expensive operations</li>
|
||||
</ol>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_search_fundamentals">Search Fundamentals</a> - Basic concepts and syntax</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_advanced">Advanced Search Expressions</a> - Complex query construction</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_examples">Search Examples and Use Cases</a> - Practical applications</li>
|
||||
<li><a class="reference-link" href="#root/_help_search_saved">Saved Searches</a> - Creating dynamic collections</li>
|
||||
</ul>
|
||||
@@ -5,18 +5,19 @@
|
||||
<p>The <em>Quick search</em> function does a full-text search (that is, it
|
||||
searches through the content of notes and not just the title of a note)
|
||||
and displays the result in an easy-to-access manner.</p>
|
||||
<p>The alternative to the quick search is the <a class="reference-link" href="#root/_help_eIg8jdvaoNNd">Search</a> function,
|
||||
which opens in a dedicated tab and has support for advanced queries.</p>
|
||||
<p>For even faster navigation, it's possible to use <a class="reference-link"
|
||||
href="#root/_help_F1r9QtzQLZqm">Jump to Note</a> which will only search
|
||||
<p>The alternative to the quick search is the <a class="reference-link"
|
||||
href="#root/_help_eIg8jdvaoNNd">Search</a> function, which opens in
|
||||
a dedicated tab and has support for advanced queries.</p>
|
||||
<p>For even faster navigation, it's possible to use <a class="reference-link"
|
||||
href="#root/_help_F1r9QtzQLZqm">Jump to...</a> which will only search
|
||||
through the note titles instead of the content.</p>
|
||||
<h2>Layout</h2>
|
||||
<p>Based on the <a class="reference-link" href="#root/_help_x0JgW8UqGXvq">Vertical and horizontal layout</a>,
|
||||
<p>Based on the <a class="reference-link" href="#root/_help_x0JgW8UqGXvq">Vertical and horizontal layout</a>,
|
||||
the quick search is placed:</p>
|
||||
<ul>
|
||||
<li>On the vertical layout, it is displayed right above the <a class="reference-link"
|
||||
<li data-list-item-id="eb498e0518c4efc433c9569270c9c7a5c">On the vertical layout, it is displayed right above the <a class="reference-link"
|
||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||
<li>On the horizontal layout, it is displayed in the <a class="reference-link"
|
||||
<li data-list-item-id="e6a9159606a513e839ca71ff4735857bb">On the horizontal layout, it is displayed in the <a class="reference-link"
|
||||
href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>, where it can be positioned
|
||||
just like any other icon.</li>
|
||||
</ul>
|
||||
@@ -30,37 +31,120 @@
|
||||
<h3>Infinite Scrolling</h3>
|
||||
<p>Results are loaded progressively as you scroll:</p>
|
||||
<ul>
|
||||
<li>Initial display shows 15 results</li>
|
||||
<li>Scrolling near the bottom automatically loads 10 more results</li>
|
||||
<li>Continue scrolling to load all matching notes</li>
|
||||
<li data-list-item-id="e6d151aab6b52d08e9a93e6f9c29c081a">Initial display shows 15 results</li>
|
||||
<li data-list-item-id="e006eeac7574a398324f214edcb9a383b">Scrolling near the bottom automatically loads 10 more results</li>
|
||||
<li
|
||||
data-list-item-id="e5f6fcb1ec0d496fcf599fa90c3911c89">Continue scrolling to load all matching notes</li>
|
||||
</ul>
|
||||
<h3>Visual Features</h3>
|
||||
<ul>
|
||||
<li><strong>Highlighting</strong>: Search terms appear in bold with accent
|
||||
<li data-list-item-id="e44f3402a55ac37c63abae20490d66d70"><strong>Highlighting</strong>: Search terms appear in bold with accent
|
||||
colors</li>
|
||||
<li><strong>Separation</strong>: Results are separated with dividers</li>
|
||||
<li><strong>Theme Support</strong>: Highlighting colors adapt to light/dark
|
||||
<li data-list-item-id="e1c8743ac639f15750171788790df2bb0"><strong>Separation</strong>: Results are separated with dividers</li>
|
||||
<li
|
||||
data-list-item-id="ec5c5dbaa44ba426d220718804b9b27db"><strong>Theme Support</strong>: Highlighting colors adapt to light/dark
|
||||
themes</li>
|
||||
</ul>
|
||||
<h3>Search Behavior</h3>
|
||||
<p>Quick search uses progressive search:</p>
|
||||
<ol>
|
||||
<li>Shows exact matches first</li>
|
||||
<li>Includes fuzzy matches when exact results are fewer than 5</li>
|
||||
<li>Exact matches appear before fuzzy matches</li>
|
||||
<li data-list-item-id="e9a34edaccc0174140e1183c5e43a2327">Shows exact matches first</li>
|
||||
<li data-list-item-id="e5b751c044ae5189095fd08655a55372f">Includes fuzzy matches when exact results are fewer than 5</li>
|
||||
<li data-list-item-id="ee63c39a04b7511cd4e031cdd963f58d2">Exact matches appear before fuzzy matches</li>
|
||||
</ol>
|
||||
<h3>Keyboard Navigation</h3>
|
||||
<ul>
|
||||
<li>Press <code>Enter</code> to open the first result</li>
|
||||
<li>Use arrow keys to navigate through results</li>
|
||||
<li>Press <code>Escape</code> to close the quick search</li>
|
||||
<li data-list-item-id="e1161754a60afdea3656561abcb46f9ea">Press <code>Enter</code> to open the first result</li>
|
||||
<li data-list-item-id="ebdffa32bcd3d8e24c3938b472521034d">Use arrow keys to navigate through results</li>
|
||||
<li data-list-item-id="eed08e1e6867dcef7eaa6ce7a21fd5e02">Press <code>Escape</code> to close the quick search</li>
|
||||
</ul>
|
||||
<h2>Using Quick Search</h2>
|
||||
<ol>
|
||||
<li><strong>Typo tolerance</strong>: Search finds results despite minor typos</li>
|
||||
<li><strong>Content previews</strong>: 200-character snippets show match context</li>
|
||||
<li><strong>Infinite scrolling</strong>: Additional results load on scroll</li>
|
||||
<li><strong>Specific terms</strong>: Specific search terms return more focused
|
||||
results</li>
|
||||
<li><strong>Match locations</strong>: Bold text indicates where matches occur</li>
|
||||
<li data-list-item-id="e88738101cdad95c7ffe2fc45d19250b7"><strong>Typo tolerance</strong>: Search finds results despite minor typos</li>
|
||||
<li
|
||||
data-list-item-id="ead4c50c8ae5e86987073741285271140"><strong>Content previews</strong>: 200-character snippets show match context</li>
|
||||
<li
|
||||
data-list-item-id="ee135ac66eafef5962b5221fa149dc31c"><strong>Infinite scrolling</strong>: Additional results load on scroll</li>
|
||||
<li
|
||||
data-list-item-id="ebecf1647bb3e6631383fa1cad5e0d222"><strong>Specific terms</strong>: Specific search terms return more focused
|
||||
results</li>
|
||||
<li data-list-item-id="e7d6ee3a67dbf55e7c72788cde795f2b2"><strong>Match locations</strong>: Bold text indicates where matches occur</li>
|
||||
</ol>
|
||||
<h2>Quick Search - Exact Match Operator</h2>
|
||||
<p>Quick Search now supports the exact match operator (<code>=</code>) at
|
||||
the beginning of your search query. This allows you to search for notes
|
||||
where the title or content exactly matches your search term, rather than
|
||||
just containing it.</p>
|
||||
<h3>Usage</h3>
|
||||
<p>To use exact match in Quick Search:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="e98c91a13502a0ddd321432cd2cdab193">Start your search query with the <code>=</code> operator</li>
|
||||
<li data-list-item-id="e0db9fc3f530c8e0eb96f4df5ef74d955">Follow it immediately with your search term (no space after <code>=</code>)</li>
|
||||
</ol>
|
||||
<h4>Examples</h4>
|
||||
<ul>
|
||||
<li data-list-item-id="e188d5c0a39291e2f665072b02b4b6cc0"><code>=example</code> - Finds notes with title exactly "example" or content
|
||||
exactly "example"</li>
|
||||
<li data-list-item-id="e275568bc5123c979fddff51fac370983"><code>=Project Plan</code> - Finds notes with title exactly "Project Plan"
|
||||
or content exactly "Project Plan"</li>
|
||||
<li data-list-item-id="e04c10070d9800148f641efcbee16ab3d"><code>='hello world'</code> - Use quotes for multi-word exact matches</li>
|
||||
</ul>
|
||||
<h4>Comparison with Regular Search</h4>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Query</th>
|
||||
<th>Behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>example</code>
|
||||
</td>
|
||||
<td>Finds all notes containing "example" anywhere in title or content</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>=example</code>
|
||||
</td>
|
||||
<td>Finds only notes where the title equals "example" or content equals "example"
|
||||
exactly</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h3>Technical Details</h3>
|
||||
<p>When you use the <code>=</code> operator:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ebd357e2f6afa77ccb3aed347103d47c3">The search performs an exact match on note titles</li>
|
||||
<li data-list-item-id="e64c84d77017e4cd43fe95c0e4f537044">For note content, it looks for exact matches of the entire content</li>
|
||||
<li
|
||||
data-list-item-id="ef4f790816f24b9484fea127837025935">Partial word matches are excluded</li>
|
||||
<li data-list-item-id="e94a53c59dc4f1a8bef101df66538d06a">The search is case-insensitive</li>
|
||||
</ul>
|
||||
<h3>Limitations</h3>
|
||||
<ul>
|
||||
<li data-list-item-id="e4ed2c12de6681eb26d2ec2daa1985956">The <code>=</code> operator must be at the very beginning of the search
|
||||
query</li>
|
||||
<li data-list-item-id="e30845adb77a12106475b88b68b614009">Spaces after <code>=</code> will treat it as a regular search</li>
|
||||
<li data-list-item-id="e89322d60b3f5cb6b2b318cc7247721cf">Multiple <code>=</code> operators (like <code>==example</code>) are treated
|
||||
as regular text search</li>
|
||||
</ul>
|
||||
<h3>Use Cases</h3>
|
||||
<p>This feature is particularly useful when:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="eb23079c90785534a68963977e993d253">You know the exact title of a note</li>
|
||||
<li data-list-item-id="e92f02cb8b28fc02f264ebeb09376af91">You want to find notes with specific, complete content</li>
|
||||
<li data-list-item-id="e37aa1707a8440213fe404d1ed7a2e941">You need to distinguish between notes with similar but not identical titles</li>
|
||||
<li
|
||||
data-list-item-id="e8b04a0a97aa970e6984370ff17160208">You want to avoid false positives from partial matches</li>
|
||||
</ul>
|
||||
<h3>Related Features</h3>
|
||||
<ul>
|
||||
<li data-list-item-id="e3d0656590d49c6e09ae5f39a0a773dff">For more complex exact matching queries, use the full <a href="Search.md">Search</a> functionality</li>
|
||||
<li
|
||||
data-list-item-id="e7d77021ebedb1b1d25e8bfe2726af21e">For fuzzy matching (finding results despite typos), use the <code>~=</code> operator
|
||||
in the full search</li>
|
||||
<li data-list-item-id="eabcf1ff7a9dfa822192ee9afe3268469">For partial matches with wildcards, use operators like <code>*=*</code>, <code>=*</code>,
|
||||
or <code>*=</code> in the full search</li>
|
||||
</ul>
|
||||
@@ -1,600 +0,0 @@
|
||||
<div class="note-content">
|
||||
|
||||
<h1>Advanced Protection Setup Guide</h1>
|
||||
|
||||
<p>This guide provides step-by-step instructions for implementing advanced security features in Trilium, including enterprise-level protection measures and compliance configurations.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Target Audience:</strong> System administrators, security professionals, and advanced users implementing Trilium in production environments.
|
||||
</div>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<ol>
|
||||
<li><a href="#mfa-setup">Multi-Factor Authentication Setup</a></li>
|
||||
<li><a href="#enterprise-auth">Enterprise Authentication</a></li>
|
||||
<li><a href="#advanced-encryption">Advanced Encryption Configuration</a></li>
|
||||
<li><a href="#security-monitoring">Security Monitoring Setup</a></li>
|
||||
<li><a href="#compliance-config">Compliance Configuration</a></li>
|
||||
<li><a href="#backup-security">Secure Backup Implementation</a></li>
|
||||
</ol>
|
||||
|
||||
<h2 id="mfa-setup">Multi-Factor Authentication Setup</h2>
|
||||
|
||||
<h3>Prerequisites</h3>
|
||||
<ul>
|
||||
<li>Trilium instance with password authentication configured</li>
|
||||
<li>Authenticator app (Google Authenticator, Authy, etc.)</li>
|
||||
<li>Secure storage for recovery codes</li>
|
||||
</ul>
|
||||
|
||||
<h3>Step-by-Step MFA Configuration</h3>
|
||||
|
||||
<h4>Step 1: Enable MFA</h4>
|
||||
<ol>
|
||||
<li>Navigate to <strong>Options → Security → Multi-Factor Authentication</strong></li>
|
||||
<li>Click <strong>"Enable Multi-Factor Authentication"</strong></li>
|
||||
<li>Confirm your current password when prompted</li>
|
||||
</ol>
|
||||
|
||||
<h4>Step 2: Generate TOTP Secret</h4>
|
||||
<ol>
|
||||
<li>Click <strong>"Generate New Secret"</strong></li>
|
||||
<li>A QR code will be displayed along with the secret key</li>
|
||||
<li>Copy the secret key to a secure location (backup)</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Security Note:</strong> The secret key is your backup method for setting up the authenticator on new devices. Store it securely.
|
||||
</div>
|
||||
|
||||
<h4>Step 3: Configure Authenticator App</h4>
|
||||
|
||||
<p><strong>Option A: QR Code (Recommended)</strong></p>
|
||||
<ol>
|
||||
<li>Open your authenticator app</li>
|
||||
<li>Tap "Add Account" or "+"</li>
|
||||
<li>Select "Scan QR Code"</li>
|
||||
<li>Point camera at the QR code displayed in Trilium</li>
|
||||
<li>Verify the account is added with name "Trilium Notes"</li>
|
||||
</ol>
|
||||
|
||||
<p><strong>Option B: Manual Entry</strong></p>
|
||||
<ol>
|
||||
<li>Open your authenticator app</li>
|
||||
<li>Tap "Add Account" or "+"</li>
|
||||
<li>Select "Enter Key Manually"</li>
|
||||
<li>Enter the following details:
|
||||
<ul>
|
||||
<li><strong>Account:</strong> Your email or username</li>
|
||||
<li><strong>Key:</strong> The secret key from Trilium</li>
|
||||
<li><strong>Type:</strong> Time-based</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h4>Step 4: Verify TOTP Setup</h4>
|
||||
<ol>
|
||||
<li>Wait for the authenticator to generate a 6-digit code</li>
|
||||
<li>Enter the code in the "Verification Code" field</li>
|
||||
<li>Click <strong>"Verify and Enable MFA"</strong></li>
|
||||
<li>If successful, you'll see a confirmation message</li>
|
||||
</ol>
|
||||
|
||||
<h4>Step 5: Save Recovery Codes</h4>
|
||||
<ol>
|
||||
<li>Click <strong>"Generate Recovery Codes"</strong></li>
|
||||
<li>Save the 10 recovery codes in a secure location:
|
||||
<ul>
|
||||
<li>Password manager</li>
|
||||
<li>Encrypted file</li>
|
||||
<li>Physical secure storage</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click <strong>"I have saved my recovery codes"</strong></li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<strong>Critical:</strong> Recovery codes are your only way to access Trilium if you lose access to your authenticator. Store them securely and treat them like passwords.
|
||||
</div>
|
||||
|
||||
<h3>MFA Login Process</h3>
|
||||
|
||||
<p>After enabling MFA, the login process becomes:</p>
|
||||
<ol>
|
||||
<li>Enter your username and password</li>
|
||||
<li>Click "Login"</li>
|
||||
<li>Enter the 6-digit TOTP code from your authenticator</li>
|
||||
<li>Alternatively, use a recovery code if TOTP is unavailable</li>
|
||||
<li>Click "Verify" to complete login</li>
|
||||
</ol>
|
||||
|
||||
<h3>Troubleshooting MFA</h3>
|
||||
|
||||
<h4>Common Issues</h4>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Issue:</strong> TOTP codes are rejected<br>
|
||||
<strong>Solutions:</strong>
|
||||
<ul>
|
||||
<li>Check that your device time is synchronized (most common cause)</li>
|
||||
<li>Ensure you're entering the current 6-digit code</li>
|
||||
<li>Try the next generated code if the current one expires</li>
|
||||
<li>Verify the secret was entered correctly in your authenticator</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Issue:</strong> Authenticator app lost or unavailable<br>
|
||||
<strong>Solutions:</strong>
|
||||
<ul>
|
||||
<li>Use one of your saved recovery codes</li>
|
||||
<li>Each recovery code can only be used once</li>
|
||||
<li>Generate new recovery codes after using several</li>
|
||||
<li>Re-setup MFA if all recovery codes are used</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="enterprise-auth">Enterprise Authentication</h2>
|
||||
|
||||
<h3>Single Sign-On (SSO) Configuration</h3>
|
||||
|
||||
<h4>OpenID Connect Setup</h4>
|
||||
|
||||
<p>Configure Trilium to integrate with enterprise identity providers:</p>
|
||||
|
||||
<h5>Configuration File Setup</h5>
|
||||
<pre><code># config.ini
|
||||
[OpenID]
|
||||
enabled=true
|
||||
issuer=https://your-provider.com
|
||||
client_id=your-client-id
|
||||
client_secret=your-client-secret
|
||||
redirect_uri=https://your-trilium.com/auth/callback
|
||||
scope=openid email profile</code></pre>
|
||||
|
||||
<h5>Common Provider Configurations</h5>
|
||||
|
||||
<p><strong>Google Workspace:</strong></p>
|
||||
<pre><code>[OpenID]
|
||||
enabled=true
|
||||
issuer=https://accounts.google.com
|
||||
client_id=your-google-client-id.apps.googleusercontent.com
|
||||
client_secret=your-google-client-secret
|
||||
redirect_uri=https://your-trilium.com/auth/callback
|
||||
scope=openid email profile</code></pre>
|
||||
|
||||
<p><strong>Microsoft Azure AD:</strong></p>
|
||||
<pre><code>[OpenID]
|
||||
enabled=true
|
||||
issuer=https://login.microsoftonline.com/your-tenant-id/v2.0
|
||||
client_id=your-azure-client-id
|
||||
client_secret=your-azure-client-secret
|
||||
redirect_uri=https://your-trilium.com/auth/callback
|
||||
scope=openid email profile</code></pre>
|
||||
|
||||
<h4>Environment Variable Configuration</h4>
|
||||
|
||||
<p>For containerized deployments:</p>
|
||||
<pre><code># Docker environment variables
|
||||
TRILIUM_OPENID_ENABLED=true
|
||||
TRILIUM_OPENID_ISSUER=https://your-provider.com
|
||||
TRILIUM_OPENID_CLIENT_ID=your-client-id
|
||||
TRILIUM_OPENID_CLIENT_SECRET=your-client-secret
|
||||
TRILIUM_OPENID_REDIRECT_URI=https://your-trilium.com/auth/callback</code></pre>
|
||||
|
||||
<h2 id="advanced-encryption">Advanced Encryption Configuration</h2>
|
||||
|
||||
<h3>Custom Encryption Parameters</h3>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<strong>Warning:</strong> Modifying encryption parameters requires expert knowledge and may break compatibility with future versions. Only proceed if you understand the implications.
|
||||
</div>
|
||||
|
||||
<h4>Scrypt Parameter Tuning</h4>
|
||||
|
||||
<p>For high-security environments, you can increase scrypt parameters:</p>
|
||||
|
||||
<pre><code>// Location: apps/server/src/services/encryption/my_scrypt.ts
|
||||
const customScryptParams = {
|
||||
N: 32768, // Higher CPU/memory cost (default: 16384)
|
||||
r: 8, // Block size (default: 8)
|
||||
p: 2 // Parallelization (default: 1)
|
||||
};
|
||||
</code></pre>
|
||||
|
||||
<p><strong>Impact Assessment:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Security:</strong> Higher parameters increase resistance to brute force</li>
|
||||
<li><strong>Performance:</strong> Significantly slower password verification</li>
|
||||
<li><strong>Compatibility:</strong> May break with future updates</li>
|
||||
<li><strong>Hardware:</strong> Requires more RAM and CPU</li>
|
||||
</ul>
|
||||
|
||||
<h3>Database-Level Encryption</h3>
|
||||
|
||||
<h4>SQLite Encryption Extension</h4>
|
||||
|
||||
<p>For additional database encryption (enterprise feature):</p>
|
||||
|
||||
<ol>
|
||||
<li>Install SQLCipher extension</li>
|
||||
<li>Configure database encryption key</li>
|
||||
<li>Modify connection string to use encryption</li>
|
||||
</ol>
|
||||
|
||||
<pre><code># Database connection with encryption
|
||||
PRAGMA key = 'your-database-encryption-key';
|
||||
PRAGMA cipher_compatibility = 4;</code></pre>
|
||||
|
||||
<h2 id="security-monitoring">Security Monitoring Setup</h2>
|
||||
|
||||
<h3>Log Configuration</h3>
|
||||
|
||||
<h4>Comprehensive Logging Setup</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Create Log Directory</strong>
|
||||
<pre><code>sudo mkdir -p /var/log/trilium
|
||||
sudo chown trilium:adm /var/log/trilium
|
||||
sudo chmod 750 /var/log/trilium</code></pre>
|
||||
</li>
|
||||
|
||||
<li><strong>Configure Log Rotation</strong>
|
||||
<pre><code># /etc/logrotate.d/trilium
|
||||
/var/log/trilium/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 90
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 640 trilium adm
|
||||
sharedscripts
|
||||
postrotate
|
||||
systemctl reload trilium
|
||||
endscript
|
||||
}</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>Security Event Monitoring</h3>
|
||||
|
||||
<h4>Real-time Monitoring Script</h4>
|
||||
|
||||
<p>Create automated monitoring for security events:</p>
|
||||
|
||||
<pre><code>#!/bin/bash
|
||||
# /usr/local/bin/trilium-security-monitor.sh
|
||||
|
||||
LOG_FILE="/var/log/trilium/security.log"
|
||||
ALERT_EMAIL="admin@yourdomain.com"
|
||||
|
||||
# Monitor failed login attempts
|
||||
check_failed_logins() {
|
||||
local count=$(grep -c "Failed login" "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
if [ "$count" -gt 5 ]; then
|
||||
echo "ALERT: $count failed login attempts" | \
|
||||
mail -s "Trilium Security Alert" "$ALERT_EMAIL"
|
||||
fi
|
||||
}
|
||||
|
||||
# Monitor CSRF violations
|
||||
check_csrf_violations() {
|
||||
local count=$(grep -c "CSRF violation" "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
if [ "$count" -gt 0 ]; then
|
||||
echo "ALERT: $count CSRF violations detected" | \
|
||||
mail -s "Trilium Security Alert" "$ALERT_EMAIL"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run checks
|
||||
check_failed_logins
|
||||
check_csrf_violations</code></pre>
|
||||
|
||||
<h4>Automated Monitoring with Cron</h4>
|
||||
|
||||
<pre><code># Add to crontab
|
||||
*/15 * * * * /usr/local/bin/trilium-security-monitor.sh</code></pre>
|
||||
|
||||
<h2 id="compliance-config">Compliance Configuration</h2>
|
||||
|
||||
<h3>GDPR Compliance</h3>
|
||||
|
||||
<h4>Data Protection Settings</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Enable Enhanced Logging</strong>
|
||||
<pre><code># config.ini
|
||||
[Security]
|
||||
auditLogging=true
|
||||
dataAccessLogging=true
|
||||
retentionPeriod=2557</code></pre>
|
||||
</li>
|
||||
|
||||
<li><strong>Configure Data Export</strong>
|
||||
<ul>
|
||||
<li>Regular automated exports for data portability</li>
|
||||
<li>User-accessible export functionality</li>
|
||||
<li>Structured data format (JSON/XML)</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Right to Erasure Implementation</strong>
|
||||
<ul>
|
||||
<li>Secure deletion procedures</li>
|
||||
<li>Encryption key destruction</li>
|
||||
<li>Backup purging processes</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3>HIPAA Compliance</h3>
|
||||
|
||||
<h4>Healthcare Data Protection</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Access Controls</strong>
|
||||
<ul>
|
||||
<li>Strong authentication (password + MFA)</li>
|
||||
<li>Session timeout configuration (max 10 minutes)</li>
|
||||
<li>Automatic logout on inactivity</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Audit Requirements</strong>
|
||||
<pre><code># Enhanced audit logging
|
||||
[HIPAA]
|
||||
auditAllAccess=true
|
||||
logUserActions=true
|
||||
retainLogs=2555 # 7 years in days
|
||||
encryptAuditLogs=true</code></pre>
|
||||
</li>
|
||||
|
||||
<li><strong>Encryption Standards</strong>
|
||||
<ul>
|
||||
<li>AES-128 minimum (meets HIPAA requirements)</li>
|
||||
<li>Key management procedures</li>
|
||||
<li>Encrypted backups and transmission</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="backup-security">Secure Backup Implementation</h2>
|
||||
|
||||
<h3>Automated Encrypted Backups</h3>
|
||||
|
||||
<h4>Backup Script Configuration</h4>
|
||||
|
||||
<pre><code>#!/bin/bash
|
||||
# /usr/local/bin/trilium-secure-backup.sh
|
||||
|
||||
BACKUP_DIR="/opt/trilium/backups"
|
||||
GPG_RECIPIENT="trilium-backup@yourdomain.com"
|
||||
RETENTION_DAYS=90
|
||||
|
||||
# Create encrypted backup
|
||||
create_backup() {
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_name="trilium_backup_$timestamp"
|
||||
|
||||
# Stop Trilium for consistent backup
|
||||
systemctl stop trilium
|
||||
|
||||
# Create backup
|
||||
tar czf - -C /opt/trilium/data . | \
|
||||
gpg --trust-model always --encrypt -r "$GPG_RECIPIENT" \
|
||||
> "$BACKUP_DIR/${backup_name}.tar.gz.gpg"
|
||||
|
||||
# Start Trilium
|
||||
systemctl start trilium
|
||||
|
||||
# Verify backup
|
||||
if gpg --decrypt "$BACKUP_DIR/${backup_name}.tar.gz.gpg" | tar tz > /dev/null; then
|
||||
echo "Backup verification successful: $backup_name"
|
||||
else
|
||||
echo "Backup verification failed!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set permissions
|
||||
chown trilium:trilium "$BACKUP_DIR/${backup_name}.tar.gz.gpg"
|
||||
chmod 600 "$BACKUP_DIR/${backup_name}.tar.gz.gpg"
|
||||
}
|
||||
|
||||
# Cleanup old backups
|
||||
cleanup_backups() {
|
||||
find "$BACKUP_DIR" -name "*.tar.gz.gpg" -mtime +$RETENTION_DAYS -delete
|
||||
}
|
||||
|
||||
# Execute backup
|
||||
create_backup
|
||||
cleanup_backups</code></pre>
|
||||
|
||||
<h4>Automated Backup Schedule</h4>
|
||||
|
||||
<pre><code># Add to crontab for daily backups at 2 AM
|
||||
0 2 * * * /usr/local/bin/trilium-secure-backup.sh</code></pre>
|
||||
|
||||
<h3>Backup Verification</h3>
|
||||
|
||||
<h4>Regular Backup Testing</h4>
|
||||
|
||||
<pre><code>#!/bin/bash
|
||||
# /usr/local/bin/trilium-backup-test.sh
|
||||
|
||||
BACKUP_DIR="/opt/trilium/backups"
|
||||
TEST_DIR="/tmp/trilium-backup-test"
|
||||
|
||||
# Test latest backup
|
||||
test_latest_backup() {
|
||||
local latest_backup=$(ls -t "$BACKUP_DIR"/*.tar.gz.gpg | head -1)
|
||||
|
||||
if [ -z "$latest_backup" ]; then
|
||||
echo "No backups found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing backup: $latest_backup"
|
||||
|
||||
# Create test directory
|
||||
mkdir -p "$TEST_DIR"
|
||||
|
||||
# Decrypt and extract
|
||||
if gpg --decrypt "$latest_backup" | tar xzf - -C "$TEST_DIR"; then
|
||||
echo "Backup extraction successful"
|
||||
|
||||
# Test database integrity
|
||||
if sqlite3 "$TEST_DIR/document.db" "PRAGMA integrity_check;" | grep -q "ok"; then
|
||||
echo "Database integrity check passed"
|
||||
else
|
||||
echo "Database integrity check failed!"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Backup extraction failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$TEST_DIR"
|
||||
|
||||
echo "Backup test completed successfully"
|
||||
}
|
||||
|
||||
test_latest_backup</code></pre>
|
||||
|
||||
<h3>Off-site Backup Synchronization</h3>
|
||||
|
||||
<h4>Secure Remote Sync</h4>
|
||||
|
||||
<pre><code>#!/bin/bash
|
||||
# /usr/local/bin/trilium-remote-sync.sh
|
||||
|
||||
REMOTE_HOST="backup.yourdomain.com"
|
||||
REMOTE_USER="trilium-backup"
|
||||
REMOTE_PATH="/backups/trilium"
|
||||
LOCAL_BACKUP_DIR="/opt/trilium/backups"
|
||||
|
||||
# Sync to remote location
|
||||
sync_to_remote() {
|
||||
rsync -avz --progress --delete \
|
||||
-e "ssh -i /home/trilium/.ssh/backup_key" \
|
||||
"$LOCAL_BACKUP_DIR/" \
|
||||
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Remote sync completed successfully"
|
||||
else
|
||||
echo "Remote sync failed!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sync_to_remote</code></pre>
|
||||
|
||||
<h2>Security Assessment Tools</h2>
|
||||
|
||||
<h3>Security Scanner</h3>
|
||||
|
||||
<h4>Automated Security Assessment</h4>
|
||||
|
||||
<pre><code>#!/bin/bash
|
||||
# /usr/local/bin/trilium-security-scan.sh
|
||||
|
||||
SCORE=0
|
||||
MAX_SCORE=0
|
||||
|
||||
print_check() {
|
||||
local status="$1"
|
||||
local message="$2"
|
||||
local points="$3"
|
||||
|
||||
case "$status" in
|
||||
"PASS")
|
||||
echo "✓ $message (+$points points)"
|
||||
SCORE=$((SCORE + points))
|
||||
;;
|
||||
"FAIL")
|
||||
echo "✗ $message"
|
||||
;;
|
||||
"WARN")
|
||||
echo "! $message"
|
||||
;;
|
||||
esac
|
||||
|
||||
MAX_SCORE=$((MAX_SCORE + points))
|
||||
}
|
||||
|
||||
# Check HTTPS configuration
|
||||
check_https() {
|
||||
if curl -s -I https://localhost:8080 2>/dev/null | grep -q "HTTP/"; then
|
||||
print_check "PASS" "HTTPS is configured" 20
|
||||
else
|
||||
print_check "FAIL" "HTTPS is not configured" 20
|
||||
fi
|
||||
}
|
||||
|
||||
# Check MFA status
|
||||
check_mfa() {
|
||||
local mfa_enabled=$(sqlite3 /opt/trilium/data/document.db \
|
||||
"SELECT value FROM options WHERE name = 'mfaEnabled';" 2>/dev/null)
|
||||
if [ "$mfa_enabled" = "true" ]; then
|
||||
print_check "PASS" "MFA is enabled" 20
|
||||
else
|
||||
print_check "WARN" "MFA is not enabled" 20
|
||||
fi
|
||||
}
|
||||
|
||||
# Check file permissions
|
||||
check_permissions() {
|
||||
local db_perms=$(stat -c "%a" /opt/trilium/data/document.db 2>/dev/null)
|
||||
if [ "$db_perms" = "600" ]; then
|
||||
print_check "PASS" "Database permissions are secure" 10
|
||||
else
|
||||
print_check "FAIL" "Database permissions are too permissive" 10
|
||||
fi
|
||||
}
|
||||
|
||||
# Run all checks
|
||||
echo "=== Trilium Security Assessment ==="
|
||||
check_https
|
||||
check_mfa
|
||||
check_permissions
|
||||
|
||||
echo "=== Summary ==="
|
||||
echo "Score: $SCORE / $MAX_SCORE ($(($SCORE * 100 / $MAX_SCORE))%)"
|
||||
|
||||
if [ $SCORE -eq $MAX_SCORE ]; then
|
||||
echo "✓ Excellent security configuration!"
|
||||
elif [ $SCORE -ge $((MAX_SCORE * 80 / 100)) ]; then
|
||||
echo "✓ Good security with minor improvements needed"
|
||||
else
|
||||
echo "⚠ Security improvements required"
|
||||
fi</code></pre>
|
||||
|
||||
<h2>Support and Resources</h2>
|
||||
|
||||
<h3>Getting Help</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>Documentation:</strong> Comprehensive guides in help system</li>
|
||||
<li><strong>Community:</strong> GitHub discussions and issues</li>
|
||||
<li><strong>Security Issues:</strong> Report to security team</li>
|
||||
<li><strong>Professional Support:</strong> Enterprise support options</li>
|
||||
</ul>
|
||||
|
||||
<h3>Additional Resources</h3>
|
||||
|
||||
<ul>
|
||||
<li><a href="#comprehensive-security-guide">Comprehensive Security Guide</a></li>
|
||||
<li><a href="#protected-notes-encryption">Protected Notes and Encryption</a></li>
|
||||
<li><a href="#authentication-session-management">Authentication and Session Management</a></li>
|
||||
<li><a href="#security-best-practices">Security Best Practices</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-success">
|
||||
<strong>Security is a Journey:</strong> Implementing these advanced protection measures is just the beginning. Regular security assessments, updates, and monitoring are essential for maintaining a secure Trilium environment.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,412 +0,0 @@
|
||||
<div class="note-content">
|
||||
|
||||
<h1>Authentication and Session Management</h1>
|
||||
|
||||
<p>Trilium provides multiple authentication methods and robust session management to secure access to your notes while maintaining usability.</p>
|
||||
|
||||
<h2>Authentication Methods</h2>
|
||||
|
||||
<h3>Password Authentication</h3>
|
||||
|
||||
<p>The primary authentication method uses a master password to secure your Trilium instance.</p>
|
||||
|
||||
<h4>Password Setup</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Initial Setup</strong>: Set during first launch or server installation</li>
|
||||
<li><strong>Password Requirements</strong>: Configurable strength requirements</li>
|
||||
<li><strong>Verification</strong>: Scrypt-based password hashing for security</li>
|
||||
<li><strong>Storage</strong>: Hashed using scrypt with random salt</li>
|
||||
</ol>
|
||||
|
||||
<h4>Password Security</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Hashing Algorithm</strong>: Scrypt with parameters N=16384, r=8, p=1</li>
|
||||
<li><strong>Salt</strong>: Unique random salt generated per installation</li>
|
||||
<li><strong>Verification Hash</strong>: Stored separately from encryption keys</li>
|
||||
<li><strong>Timing Attack Protection</strong>: Constant-time comparison</li>
|
||||
</ul>
|
||||
|
||||
<h3>Multi-Factor Authentication (TOTP)</h3>
|
||||
|
||||
<p>Trilium supports Time-based One-Time Password (TOTP) authentication for enhanced security.</p>
|
||||
|
||||
<h4>Setup Process</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Enable MFA</strong>: Navigate to Options → Multi-Factor Authentication</li>
|
||||
<li><strong>Generate Secret</strong>: Click "Generate New Secret"</li>
|
||||
<li><strong>Add to Authenticator</strong>: Scan QR code or enter secret manually</li>
|
||||
<li><strong>Verify Setup</strong>: Enter TOTP code to confirm configuration</li>
|
||||
<li><strong>Save Recovery Codes</strong>: Store backup codes securely</li>
|
||||
</ol>
|
||||
|
||||
<h4>Supported Authenticators</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Google Authenticator</strong>: Mobile app for Android/iOS</li>
|
||||
<li><strong>Authy</strong>: Cross-platform authenticator with cloud sync</li>
|
||||
<li><strong>Microsoft Authenticator</strong>: Integrated with Microsoft accounts</li>
|
||||
<li><strong>1Password</strong>: Built-in TOTP support</li>
|
||||
<li><strong>Any RFC 6238 Compatible App</strong>: Standard TOTP implementation</li>
|
||||
</ul>
|
||||
|
||||
<h4>TOTP Configuration</h4>
|
||||
|
||||
<pre><code class="language-typescript">// TOTP settings in options
|
||||
{
|
||||
mfaEnabled: "true", // Enable/disable MFA
|
||||
mfaMethod: "totp", // Authentication method
|
||||
totpEncryptedSecret: "...", // Encrypted TOTP secret
|
||||
totpVerificationHash: "..." // Secret verification hash
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Recovery Codes</h3>
|
||||
|
||||
<p>Recovery codes provide backup access when TOTP is unavailable.</p>
|
||||
|
||||
<h4>Code Generation</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Format</strong>: Base64-encoded 24-character strings ending in "=="</li>
|
||||
<li><strong>Quantity</strong>: Multiple codes generated during setup</li>
|
||||
<li><strong>Encryption</strong>: AES-256-CBC encrypted storage</li>
|
||||
<li><strong>One-time Use</strong>: Each code invalidated after use</li>
|
||||
</ul>
|
||||
|
||||
<h4>Usage Guidelines</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Secure Storage</strong>: Keep codes in password manager or secure location</li>
|
||||
<li><strong>Limited Use</strong>: Only use when primary authentication unavailable</li>
|
||||
<li><strong>Regeneration</strong>: Generate new codes if compromised</li>
|
||||
<li><strong>Expiration</strong>: Codes replaced with timestamp when used</li>
|
||||
</ol>
|
||||
|
||||
<h3>Single Sign-On (SSO)</h3>
|
||||
|
||||
<p>Trilium supports OpenID Connect for enterprise authentication.</p>
|
||||
|
||||
<h4>Supported Providers</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Google</strong>: Google Workspace accounts</li>
|
||||
<li><strong>Microsoft</strong>: Azure AD integration</li>
|
||||
<li><strong>GitHub</strong>: Developer account authentication</li>
|
||||
<li><strong>Custom OIDC</strong>: Any OpenID Connect provider</li>
|
||||
</ul>
|
||||
|
||||
<h4>Configuration</h4>
|
||||
|
||||
<p>Set environment variables or config.ini:</p>
|
||||
|
||||
<pre><code class="language-ini">[OpenID]
|
||||
enabled=true
|
||||
issuer=https://accounts.google.com
|
||||
client_id=your-client-id
|
||||
client_secret=your-client-secret
|
||||
redirect_uri=https://your-trilium.example.com/auth/callback
|
||||
</code></pre>
|
||||
|
||||
<h2>Session Management</h2>
|
||||
|
||||
<h3>Session Security</h3>
|
||||
|
||||
<p>Trilium implements secure session management with multiple protection layers.</p>
|
||||
|
||||
<h4>Session Storage</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Database Storage</strong>: Sessions stored in SQLite database</li>
|
||||
<li><strong>Secure Secrets</strong>: Cryptographically secure session secrets</li>
|
||||
<li><strong>Expiration Tracking</strong>: Automatic cleanup of expired sessions</li>
|
||||
<li><strong>Multiple Sessions</strong>: Support for concurrent user sessions</li>
|
||||
</ul>
|
||||
|
||||
<h4>Session Configuration</h4>
|
||||
|
||||
<pre><code class="language-typescript">// Session settings
|
||||
{
|
||||
secret: sessionSecret, // Cryptographic secret
|
||||
resave: false, // Don't save unchanged sessions
|
||||
saveUninitialized: false, // Don't save empty sessions
|
||||
rolling: true, // Reset expiration on activity
|
||||
cookie: {
|
||||
httpOnly: true, // Prevent XSS attacks
|
||||
secure: false, // HTTPS-only in production
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24-hour expiration
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Session Lifecycle</h3>
|
||||
|
||||
<h4>Session Creation</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Authentication</strong>: User provides valid credentials</li>
|
||||
<li><strong>Session ID</strong>: Generate cryptographically secure session ID</li>
|
||||
<li><strong>Database Storage</strong>: Store session data with expiration</li>
|
||||
<li><strong>Cookie Setting</strong>: Send session cookie to client</li>
|
||||
<li><strong>State Tracking</strong>: Monitor authentication state changes</li>
|
||||
</ol>
|
||||
|
||||
<h4>Session Maintenance</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Activity Tracking</strong>: Update session expiration on each request</li>
|
||||
<li><strong>State Validation</strong>: Verify session integrity on each access</li>
|
||||
<li><strong>Timeout Management</strong>: Automatic logout after inactivity</li>
|
||||
<li><strong>Cross-tab Sync</strong>: Session state synchronized across browser tabs</li>
|
||||
</ul>
|
||||
|
||||
<h4>Session Termination</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Manual Logout</strong>: User-initiated session termination</li>
|
||||
<li><strong>Timeout Expiration</strong>: Automatic logout after inactivity</li>
|
||||
<li><strong>Security Events</strong>: Forced logout on security state changes</li>
|
||||
<li><strong>Cleanup</strong>: Remove session data from database</li>
|
||||
</ol>
|
||||
|
||||
<h3>CSRF Protection</h3>
|
||||
|
||||
<p>Trilium implements double-submit cookie CSRF protection.</p>
|
||||
|
||||
<h4>Protection Mechanism</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>Token Generation</strong>: Cryptographically secure CSRF tokens</li>
|
||||
<li><strong>Cookie Storage</strong>: Token stored in httpOnly cookie</li>
|
||||
<li><strong>Header Validation</strong>: Token required in request headers</li>
|
||||
<li><strong>Double Submit</strong>: Cookie and header values must match</li>
|
||||
</ul>
|
||||
|
||||
<h4>Configuration</h4>
|
||||
|
||||
<pre><code class="language-typescript">// CSRF protection settings
|
||||
{
|
||||
cookieOptions: {
|
||||
path: "/",
|
||||
secure: false, // HTTPS-only in production
|
||||
sameSite: "strict", // Strict same-site policy
|
||||
httpOnly: true // Prevent JavaScript access
|
||||
},
|
||||
cookieName: "_csrf" // Cookie name for CSRF token
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Session Security Headers</h3>
|
||||
|
||||
<p>Trilium sets security headers to protect against common attacks.</p>
|
||||
|
||||
<h4>Standard Headers</h4>
|
||||
|
||||
<ul>
|
||||
<li><strong>X-Frame-Options</strong>: Prevent clickjacking attacks</li>
|
||||
<li><strong>X-Content-Type-Options</strong>: Prevent MIME sniffing</li>
|
||||
<li><strong>X-XSS-Protection</strong>: Enable browser XSS protection</li>
|
||||
<li><strong>Strict-Transport-Security</strong>: Enforce HTTPS connections</li>
|
||||
<li><strong>Content-Security-Policy</strong>: Control resource loading</li>
|
||||
</ul>
|
||||
|
||||
<h2>Authentication Flow</h2>
|
||||
|
||||
<h3>Standard Login Process</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Initial Request</strong>: User accesses protected resource</li>
|
||||
<li><strong>Redirect</strong>: System redirects to login page</li>
|
||||
<li><strong>Credential Entry</strong>: User enters username/password</li>
|
||||
<li><strong>Verification</strong>: System validates credentials</li>
|
||||
<li><strong>MFA Challenge</strong>: TOTP prompt if MFA enabled</li>
|
||||
<li><strong>Session Creation</strong>: Generate and store session</li>
|
||||
<li><strong>Redirect</strong>: Send user to requested resource</li>
|
||||
</ol>
|
||||
|
||||
<h3>MFA Login Process</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Primary Authentication</strong>: Password verification succeeds</li>
|
||||
<li><strong>MFA Challenge</strong>: Display TOTP input form</li>
|
||||
<li><strong>Code Verification</strong>: Validate TOTP code</li>
|
||||
<li><strong>Recovery Option</strong>: Allow recovery code if TOTP fails</li>
|
||||
<li><strong>Session Creation</strong>: Create authenticated session</li>
|
||||
<li><strong>State Tracking</strong>: Update last authentication state</li>
|
||||
</ol>
|
||||
|
||||
<h3>SSO Login Process</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Provider Redirect</strong>: Redirect to OpenID provider</li>
|
||||
<li><strong>Provider Authentication</strong>: User authenticates with provider</li>
|
||||
<li><strong>Authorization Code</strong>: Provider returns authorization code</li>
|
||||
<li><strong>Token Exchange</strong>: Exchange code for access token</li>
|
||||
<li><strong>User Info</strong>: Retrieve user information from provider</li>
|
||||
<li><strong>Local Session</strong>: Create local session for user</li>
|
||||
<li><strong>Access Grant</strong>: Allow access to protected resources</li>
|
||||
</ol>
|
||||
|
||||
<h2>Security Best Practices</h2>
|
||||
|
||||
<h3>Password Security</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Strong Passwords</strong>: Require complex passwords</li>
|
||||
<li><strong>Regular Updates</strong>: Encourage periodic password changes</li>
|
||||
<li><strong>Unique Passwords</strong>: Don't reuse passwords from other services</li>
|
||||
<li><strong>Secure Storage</strong>: Use password managers</li>
|
||||
</ol>
|
||||
|
||||
<h3>Session Security</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>HTTPS Only</strong>: Always use HTTPS in production</li>
|
||||
<li><strong>Secure Cookies</strong>: Enable secure flag for session cookies</li>
|
||||
<li><strong>Short Timeouts</strong>: Configure appropriate session timeouts</li>
|
||||
<li><strong>Regular Cleanup</strong>: Automatically clean expired sessions</li>
|
||||
</ol>
|
||||
|
||||
<h3>Multi-Factor Authentication</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Enable MFA</strong>: Always enable MFA for sensitive installations</li>
|
||||
<li><strong>Secure Recovery</strong>: Store recovery codes securely</li>
|
||||
<li><strong>Regular Review</strong>: Periodically review MFA configuration</li>
|
||||
<li><strong>Backup Methods</strong>: Maintain multiple authentication methods</li>
|
||||
</ol>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Authentication Issues</h3>
|
||||
|
||||
<h4>Login Failures</h4>
|
||||
|
||||
<p><strong>Symptoms</strong>: Cannot login with correct credentials</p>
|
||||
|
||||
<p><strong>Possible Causes</strong>:</p>
|
||||
<ul>
|
||||
<li>Incorrect password</li>
|
||||
<li>Database connectivity issues</li>
|
||||
<li>Session storage problems</li>
|
||||
<li>Browser cookie issues</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Solutions</strong>:</p>
|
||||
<ol>
|
||||
<li>Verify password accuracy (check caps lock)</li>
|
||||
<li>Clear browser cookies and cache</li>
|
||||
<li>Check database connectivity</li>
|
||||
<li>Review server logs for errors</li>
|
||||
<li>Restart application if needed</li>
|
||||
</ol>
|
||||
|
||||
<h4>MFA Issues</h4>
|
||||
|
||||
<p><strong>Symptoms</strong>: TOTP codes rejected or recovery codes fail</p>
|
||||
|
||||
<p><strong>Possible Causes</strong>:</p>
|
||||
<ul>
|
||||
<li>Clock synchronization issues</li>
|
||||
<li>Corrupted TOTP secret</li>
|
||||
<li>Used recovery codes</li>
|
||||
<li>Configuration problems</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Solutions</strong>:</p>
|
||||
<ol>
|
||||
<li>Synchronize device time</li>
|
||||
<li>Regenerate TOTP secret</li>
|
||||
<li>Use fresh recovery codes</li>
|
||||
<li>Check MFA configuration</li>
|
||||
<li>Contact administrator if needed</li>
|
||||
</ol>
|
||||
|
||||
<h4>Session Problems</h4>
|
||||
|
||||
<p><strong>Symptoms</strong>: Frequent logouts or session errors</p>
|
||||
|
||||
<p><strong>Possible Causes</strong>:</p>
|
||||
<ul>
|
||||
<li>Short session timeout</li>
|
||||
<li>Database session storage issues</li>
|
||||
<li>Browser cookie problems</li>
|
||||
<li>Network connectivity issues</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Solutions</strong>:</p>
|
||||
<ol>
|
||||
<li>Increase session timeout</li>
|
||||
<li>Check database permissions</li>
|
||||
<li>Enable browser cookies</li>
|
||||
<li>Verify network stability</li>
|
||||
<li>Review session configuration</li>
|
||||
</ol>
|
||||
|
||||
<h3>Security Monitoring</h3>
|
||||
|
||||
<h4>Log Analysis</h4>
|
||||
|
||||
<p>Monitor authentication logs for:</p>
|
||||
<ul>
|
||||
<li>Failed login attempts</li>
|
||||
<li>MFA failures</li>
|
||||
<li>Session anomalies</li>
|
||||
<li>Unusual access patterns</li>
|
||||
</ul>
|
||||
|
||||
<h4>Alert Configuration</h4>
|
||||
|
||||
<p>Set up alerts for:</p>
|
||||
<ul>
|
||||
<li>Multiple failed logins</li>
|
||||
<li>MFA bypass attempts</li>
|
||||
<li>Session manipulation</li>
|
||||
<li>Account lockouts</li>
|
||||
</ul>
|
||||
|
||||
<h4>Regular Audits</h4>
|
||||
|
||||
<p>Perform regular security audits:</p>
|
||||
<ul>
|
||||
<li>Review authentication logs</li>
|
||||
<li>Check session configurations</li>
|
||||
<li>Validate MFA setup</li>
|
||||
<li>Test recovery procedures</li>
|
||||
</ul>
|
||||
|
||||
<h2>Configuration Reference</h2>
|
||||
|
||||
<h3>Environment Variables</h3>
|
||||
|
||||
<pre><code class="language-bash"># Authentication settings
|
||||
TRILIUM_NO_AUTHENTICATION=false
|
||||
TRILIUM_PASSWORD_MIN_LENGTH=8
|
||||
TRILIUM_SESSION_TIMEOUT=86400
|
||||
|
||||
# MFA settings
|
||||
TRILIUM_MFA_ENABLED=true
|
||||
TRILIUM_MFA_METHOD=totp
|
||||
|
||||
# OpenID settings
|
||||
TRILIUM_OPENID_ENABLED=false
|
||||
TRILIUM_OPENID_ISSUER=https://provider.example.com
|
||||
TRILIUM_OPENID_CLIENT_ID=your-client-id
|
||||
TRILIUM_OPENID_CLIENT_SECRET=your-client-secret
|
||||
</code></pre>
|
||||
|
||||
<h3>Database Options</h3>
|
||||
|
||||
<pre><code class="language-sql">-- Authentication options
|
||||
INSERT INTO options (name, value) VALUES
|
||||
('passwordMinLength', '8'),
|
||||
('sessionTimeout', '86400'),
|
||||
('mfaEnabled', 'true'),
|
||||
('mfaMethod', 'totp');
|
||||
</code></pre>
|
||||
|
||||
<p><strong>Remember</strong>: Strong authentication and session management are critical for protecting your notes. Always use HTTPS in production and enable MFA for enhanced security.</p>
|
||||
|
||||
</div>
|
||||
@@ -1,476 +0,0 @@
|
||||
<div class="note-content">
|
||||
|
||||
<h1>Trilium Comprehensive Security Guide</h1>
|
||||
|
||||
<p>This comprehensive guide covers all aspects of Trilium security, from protected notes to enterprise deployment security practices.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Note:</strong> This guide contains advanced security configurations. Always test changes in a non-production environment first.
|
||||
</div>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<ol>
|
||||
<li><a href="#protected-notes">Protected Notes and Encryption</a></li>
|
||||
<li><a href="#authentication">Authentication and Access Control</a></li>
|
||||
<li><a href="#deployment">Secure Deployment</a></li>
|
||||
<li><a href="#best-practices">Security Best Practices</a></li>
|
||||
<li><a href="#monitoring">Security Monitoring</a></li>
|
||||
<li><a href="#incident-response">Incident Response</a></li>
|
||||
</ol>
|
||||
|
||||
<h2 id="protected-notes">Protected Notes and Encryption</h2>
|
||||
|
||||
<h3>Overview</h3>
|
||||
|
||||
<p>Trilium's Protected Notes system provides robust encryption for sensitive content using industry-standard AES-128-CBC encryption with scrypt-based key derivation.</p>
|
||||
|
||||
<h4>Key Features</h4>
|
||||
<ul>
|
||||
<li><strong>Selective Encryption:</strong> Only notes marked as protected are encrypted</li>
|
||||
<li><strong>Strong Encryption:</strong> AES-128-CBC with scrypt key derivation</li>
|
||||
<li><strong>Session-based Access:</strong> Encrypted content accessible during protected sessions</li>
|
||||
<li><strong>Zero-knowledge:</strong> Server never stores unencrypted protected content</li>
|
||||
</ul>
|
||||
|
||||
<h3>Setting Up Protected Notes</h3>
|
||||
|
||||
<h4>Initial Configuration</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Set Master Password</strong>
|
||||
<ul>
|
||||
<li>Go to <em>Options → Security → Password</em></li>
|
||||
<li>Choose a strong password (minimum 8 characters, recommended 12+)</li>
|
||||
<li>Use a unique password not used elsewhere</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Configure Protected Session</strong>
|
||||
<ul>
|
||||
<li>Set session timeout (default: 10 minutes)</li>
|
||||
<li>Configure auto-logout preferences</li>
|
||||
<li>Enable session notifications</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><strong>Create Your First Protected Note</strong>
|
||||
<ul>
|
||||
<li>Right-click any note → "Toggle Protected Status"</li>
|
||||
<li>Or use keyboard shortcut: <kbd>Ctrl+Shift+U</kbd></li>
|
||||
<li>Or click Actions menu → "Protect this note"</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h4>Managing Protected Sessions</h4>
|
||||
|
||||
<p><strong>Entering Protected Session:</strong></p>
|
||||
<ul>
|
||||
<li>Click the shield icon in the toolbar</li>
|
||||
<li>Use keyboard shortcut: <kbd>Ctrl+Shift+P</kbd></li>
|
||||
<li>Automatic prompt when accessing protected content</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Session Security:</strong></p>
|
||||
<ul>
|
||||
<li>Sessions timeout automatically after inactivity</li>
|
||||
<li>Green shield indicates active protected session</li>
|
||||
<li>Manual logout available via shield menu</li>
|
||||
<li>Independent sessions per browser/client</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Security Note:</strong> Protected sessions store encryption keys in memory. Always log out when finished working with protected content.
|
||||
</div>
|
||||
|
||||
<h3>Encryption Technical Details</h3>
|
||||
|
||||
<h4>Encryption Process</h4>
|
||||
<pre><code>1. Generate random 16-byte IV
|
||||
2. Compute SHA-1 digest of plaintext (integrity check)
|
||||
3. Prepend digest (4 bytes) to plaintext
|
||||
4. Encrypt with AES-128-CBC using data key and IV
|
||||
5. Prepend IV to encrypted data
|
||||
6. Encode result as Base64</code></pre>
|
||||
|
||||
<h4>Key Management</h4>
|
||||
<ul>
|
||||
<li><strong>Master Password:</strong> User-provided secret</li>
|
||||
<li><strong>Password-Derived Key:</strong> Generated using scrypt (N=16384, r=8, p=1)</li>
|
||||
<li><strong>Data Key:</strong> 32-byte random key, encrypted with password-derived key</li>
|
||||
<li><strong>Session Key:</strong> Data key loaded into memory during protected session</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="authentication">Authentication and Access Control</h2>
|
||||
|
||||
<h3>Password Authentication</h3>
|
||||
|
||||
<h4>Security Features</h4>
|
||||
<ul>
|
||||
<li><strong>Scrypt Hashing:</strong> CPU-intensive hashing prevents brute force attacks</li>
|
||||
<li><strong>Salt:</strong> Unique random salt per installation</li>
|
||||
<li><strong>Timing Attack Protection:</strong> Constant-time comparison</li>
|
||||
<li><strong>Separation:</strong> Authentication separate from encryption keys</li>
|
||||
</ul>
|
||||
|
||||
<h4>Password Management</h4>
|
||||
<ul>
|
||||
<li><strong>Strong Passwords:</strong> Use complex, unique passwords</li>
|
||||
<li><strong>Regular Updates:</strong> Change passwords periodically</li>
|
||||
<li><strong>Secure Storage:</strong> Consider using a password manager</li>
|
||||
<li><strong>Recovery:</strong> No built-in password recovery - keep backup access</li>
|
||||
</ul>
|
||||
|
||||
<h3>Multi-Factor Authentication (MFA)</h3>
|
||||
|
||||
<h4>TOTP Setup</h4>
|
||||
|
||||
<ol>
|
||||
<li>Go to <em>Options → Security → Multi-Factor Authentication</em></li>
|
||||
<li>Click "Generate New Secret"</li>
|
||||
<li>Scan QR code with authenticator app</li>
|
||||
<li>Enter TOTP code to verify setup</li>
|
||||
<li>Save recovery codes securely</li>
|
||||
</ol>
|
||||
|
||||
<h4>Supported Authenticators</h4>
|
||||
<ul>
|
||||
<li>Google Authenticator</li>
|
||||
<li>Authy</li>
|
||||
<li>Microsoft Authenticator</li>
|
||||
<li>1Password</li>
|
||||
<li>Any RFC 6238 compatible app</li>
|
||||
</ul>
|
||||
|
||||
<h4>Recovery Codes</h4>
|
||||
<ul>
|
||||
<li><strong>Format:</strong> Base64-encoded 24-character strings</li>
|
||||
<li><strong>Storage:</strong> AES-256-CBC encrypted</li>
|
||||
<li><strong>Usage:</strong> One-time use only</li>
|
||||
<li><strong>Security:</strong> Store in secure location (password manager)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Session Management</h3>
|
||||
|
||||
<h4>Session Security</h4>
|
||||
<ul>
|
||||
<li><strong>Secure Storage:</strong> Sessions stored in encrypted database</li>
|
||||
<li><strong>Automatic Cleanup:</strong> Expired sessions removed periodically</li>
|
||||
<li><strong>CSRF Protection:</strong> Double-submit cookie pattern</li>
|
||||
<li><strong>Secure Cookies:</strong> HTTPOnly, Secure, SameSite attributes</li>
|
||||
</ul>
|
||||
|
||||
<h4>Configuration Options</h4>
|
||||
<ul>
|
||||
<li><strong>Session Timeout:</strong> Configurable timeout period</li>
|
||||
<li><strong>Remember Me:</strong> Extended session duration option</li>
|
||||
<li><strong>Auto Logout:</strong> Automatic logout on browser close</li>
|
||||
<li><strong>Multi-client:</strong> Independent sessions per device</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="deployment">Secure Deployment</h2>
|
||||
|
||||
<h3>HTTPS Configuration</h3>
|
||||
|
||||
<h4>SSL/TLS Requirements</h4>
|
||||
<ul>
|
||||
<li><strong>Mandatory for Production:</strong> Always use HTTPS in production</li>
|
||||
<li><strong>TLS Version:</strong> Use TLS 1.2 or higher</li>
|
||||
<li><strong>Certificates:</strong> Valid SSL certificates (Let's Encrypt recommended)</li>
|
||||
<li><strong>HSTS:</strong> Enable HTTP Strict Transport Security</li>
|
||||
</ul>
|
||||
|
||||
<h4>Security Headers</h4>
|
||||
<pre><code># Essential security headers
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
Content-Security-Policy: default-src 'self'; ...</code></pre>
|
||||
|
||||
<h3>Network Security</h3>
|
||||
|
||||
<h4>Firewall Configuration</h4>
|
||||
<ul>
|
||||
<li><strong>Restrict Ports:</strong> Only allow necessary ports (22, 443)</li>
|
||||
<li><strong>Block Direct Access:</strong> Block direct access to Trilium port (8080)</li>
|
||||
<li><strong>IP Restrictions:</strong> Limit access to trusted IP ranges</li>
|
||||
<li><strong>Rate Limiting:</strong> Implement connection rate limiting</li>
|
||||
</ul>
|
||||
|
||||
<h4>Reverse Proxy</h4>
|
||||
<ul>
|
||||
<li><strong>Nginx/Apache:</strong> Use reverse proxy for SSL termination</li>
|
||||
<li><strong>Load Balancing:</strong> Distribute traffic across instances</li>
|
||||
<li><strong>Caching:</strong> Cache static content</li>
|
||||
<li><strong>Compression:</strong> Enable content compression</li>
|
||||
</ul>
|
||||
|
||||
<h3>Database Security</h3>
|
||||
|
||||
<h4>File Permissions</h4>
|
||||
<ul>
|
||||
<li><strong>Database:</strong> 600 (owner read/write only)</li>
|
||||
<li><strong>Data Directory:</strong> 700 (owner access only)</li>
|
||||
<li><strong>Configuration:</strong> 600 (owner read/write only)</li>
|
||||
<li><strong>Ownership:</strong> Dedicated trilium user</li>
|
||||
</ul>
|
||||
|
||||
<h4>Backup Security</h4>
|
||||
<ul>
|
||||
<li><strong>Encryption:</strong> Always encrypt backups</li>
|
||||
<li><strong>Storage:</strong> Secure off-site storage</li>
|
||||
<li><strong>Access Control:</strong> Limit backup access</li>
|
||||
<li><strong>Testing:</strong> Regular restoration testing</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="best-practices">Security Best Practices</h2>
|
||||
|
||||
<h3>Password Security</h3>
|
||||
|
||||
<h4>Password Requirements</h4>
|
||||
<ul>
|
||||
<li><strong>Length:</strong> Minimum 12 characters</li>
|
||||
<li><strong>Complexity:</strong> Mix of letters, numbers, symbols</li>
|
||||
<li><strong>Uniqueness:</strong> Don't reuse passwords</li>
|
||||
<li><strong>Passphrases:</strong> Consider using memorable passphrases</li>
|
||||
</ul>
|
||||
|
||||
<h4>Password Management</h4>
|
||||
<ul>
|
||||
<li><strong>Password Manager:</strong> Use reputable password manager</li>
|
||||
<li><strong>Regular Updates:</strong> Change passwords periodically</li>
|
||||
<li><strong>Secure Recovery:</strong> Store recovery information safely</li>
|
||||
<li><strong>Team Coordination:</strong> Coordinate password changes in shared environments</li>
|
||||
</ul>
|
||||
|
||||
<h3>Access Control</h3>
|
||||
|
||||
<h4>User Management</h4>
|
||||
<ul>
|
||||
<li><strong>Single User Model:</strong> Trilium designed for single-user access</li>
|
||||
<li><strong>Shared Access:</strong> Use with caution in shared environments</li>
|
||||
<li><strong>Guest Access:</strong> Disable unless specifically needed</li>
|
||||
<li><strong>Admin Privileges:</strong> Run with minimal necessary privileges</li>
|
||||
</ul>
|
||||
|
||||
<h4>Session Management</h4>
|
||||
<ul>
|
||||
<li><strong>Timeout Configuration:</strong> Set appropriate timeouts for usage pattern</li>
|
||||
<li><strong>Device Security:</strong> Lock workstation when away</li>
|
||||
<li><strong>Shared Computers:</strong> Always log out completely</li>
|
||||
<li><strong>Browser Security:</strong> Use up-to-date browsers</li>
|
||||
</ul>
|
||||
|
||||
<h3>Data Protection</h3>
|
||||
|
||||
<h4>Backup Strategy</h4>
|
||||
<ul>
|
||||
<li><strong>Regular Backups:</strong> Automated daily backups</li>
|
||||
<li><strong>Encryption:</strong> All backups encrypted with strong keys</li>
|
||||
<li><strong>Multiple Locations:</strong> Store backups in multiple secure locations</li>
|
||||
<li><strong>Version Control:</strong> Maintain multiple backup versions</li>
|
||||
<li><strong>Testing:</strong> Regular restoration testing</li>
|
||||
</ul>
|
||||
|
||||
<h4>Data Classification</h4>
|
||||
<ul>
|
||||
<li><strong>Sensitive Data:</strong> Always use protected notes for sensitive content</li>
|
||||
<li><strong>Public Data:</strong> Separate public and private information</li>
|
||||
<li><strong>Compliance:</strong> Follow industry-specific requirements</li>
|
||||
<li><strong>Retention:</strong> Implement data retention policies</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="monitoring">Security Monitoring</h2>
|
||||
|
||||
<h3>Log Monitoring</h3>
|
||||
|
||||
<h4>Security Events</h4>
|
||||
<ul>
|
||||
<li><strong>Authentication:</strong> Monitor login attempts and failures</li>
|
||||
<li><strong>Authorization:</strong> Track access to protected resources</li>
|
||||
<li><strong>Sessions:</strong> Monitor session creation and termination</li>
|
||||
<li><strong>Data Access:</strong> Log protected note access</li>
|
||||
</ul>
|
||||
|
||||
<h4>Alerting</h4>
|
||||
<ul>
|
||||
<li><strong>Failed Logins:</strong> Alert on multiple failed attempts</li>
|
||||
<li><strong>MFA Failures:</strong> Monitor MFA bypass attempts</li>
|
||||
<li><strong>Session Anomalies:</strong> Detect unusual session patterns</li>
|
||||
<li><strong>Data Changes:</strong> Monitor unexpected data modifications</li>
|
||||
</ul>
|
||||
|
||||
<h3>Intrusion Detection</h3>
|
||||
|
||||
<h4>Behavioral Analysis</h4>
|
||||
<ul>
|
||||
<li><strong>Login Patterns:</strong> Detect unusual login times/locations</li>
|
||||
<li><strong>Access Patterns:</strong> Monitor unusual data access</li>
|
||||
<li><strong>Session Behavior:</strong> Identify suspicious session activity</li>
|
||||
<li><strong>Network Activity:</strong> Monitor network connection patterns</li>
|
||||
</ul>
|
||||
|
||||
<h4>Automated Response</h4>
|
||||
<ul>
|
||||
<li><strong>Account Lockout:</strong> Temporary suspension of suspicious accounts</li>
|
||||
<li><strong>IP Blocking:</strong> Block suspicious IP addresses</li>
|
||||
<li><strong>Rate Limiting:</strong> Dynamic rate limit adjustment</li>
|
||||
<li><strong>Alert Generation:</strong> Immediate notification of threats</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="incident-response">Incident Response</h2>
|
||||
|
||||
<h3>Preparation</h3>
|
||||
|
||||
<h4>Response Plan</h4>
|
||||
<ul>
|
||||
<li><strong>Procedures:</strong> Document step-by-step response procedures</li>
|
||||
<li><strong>Contacts:</strong> Maintain emergency contact information</li>
|
||||
<li><strong>Tools:</strong> Prepare incident response tools and scripts</li>
|
||||
<li><strong>Communication:</strong> Plan user notification procedures</li>
|
||||
</ul>
|
||||
|
||||
<h4>Training</h4>
|
||||
<ul>
|
||||
<li><strong>Team Training:</strong> Regular incident response training</li>
|
||||
<li><strong>Simulations:</strong> Practice incident response scenarios</li>
|
||||
<li><strong>Documentation:</strong> Keep response procedures updated</li>
|
||||
<li><strong>Lessons Learned:</strong> Update procedures based on incidents</li>
|
||||
</ul>
|
||||
|
||||
<h3>Detection and Response</h3>
|
||||
|
||||
<h4>Immediate Actions</h4>
|
||||
<ol>
|
||||
<li><strong>Identify:</strong> Quickly identify the type and scope of incident</li>
|
||||
<li><strong>Contain:</strong> Isolate affected systems to prevent spread</li>
|
||||
<li><strong>Preserve:</strong> Preserve evidence for forensic analysis</li>
|
||||
<li><strong>Notify:</strong> Inform relevant stakeholders</li>
|
||||
<li><strong>Assess:</strong> Evaluate impact and required response</li>
|
||||
</ol>
|
||||
|
||||
<h4>Recovery Procedures</h4>
|
||||
<ul>
|
||||
<li><strong>System Isolation:</strong> Temporarily isolate compromised systems</li>
|
||||
<li><strong>Forensic Backup:</strong> Create forensic copies for analysis</li>
|
||||
<li><strong>Restore from Backup:</strong> Restore from known good backups</li>
|
||||
<li><strong>Verify Integrity:</strong> Confirm system and data integrity</li>
|
||||
<li><strong>Resume Operations:</strong> Safely resume normal operations</li>
|
||||
</ul>
|
||||
|
||||
<h3>Post-Incident</h3>
|
||||
|
||||
<h4>Documentation</h4>
|
||||
<ul>
|
||||
<li><strong>Incident Report:</strong> Complete incident documentation</li>
|
||||
<li><strong>Timeline:</strong> Detailed timeline of events and responses</li>
|
||||
<li><strong>Impact Assessment:</strong> Evaluation of damage and losses</li>
|
||||
<li><strong>Lessons Learned:</strong> Identify improvements for future</li>
|
||||
</ul>
|
||||
|
||||
<h4>Improvement</h4>
|
||||
<ul>
|
||||
<li><strong>Process Updates:</strong> Update response procedures</li>
|
||||
<li><strong>Security Enhancements:</strong> Implement additional security controls</li>
|
||||
<li><strong>Training Updates:</strong> Update training based on lessons learned</li>
|
||||
<li><strong>Regular Reviews:</strong> Periodic review of incident response capability</li>
|
||||
</ul>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Issues</h3>
|
||||
|
||||
<h4>Authentication Problems</h4>
|
||||
<div class="alert alert-info">
|
||||
<strong>Problem:</strong> Cannot login with correct credentials<br>
|
||||
<strong>Solutions:</strong>
|
||||
<ul>
|
||||
<li>Check password spelling and case sensitivity</li>
|
||||
<li>Clear browser cookies and cache</li>
|
||||
<li>Verify database connectivity</li>
|
||||
<li>Check server logs for errors</li>
|
||||
<li>Restart application if needed</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>Protected Notes Issues</h4>
|
||||
<div class="alert alert-warning">
|
||||
<strong>Problem:</strong> "Could not decrypt string" error<br>
|
||||
<strong>Solutions:</strong>
|
||||
<ul>
|
||||
<li>Verify correct password entry</li>
|
||||
<li>Check for active protected session</li>
|
||||
<li>Restart application and retry</li>
|
||||
<li>Restore from backup if corruption suspected</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>MFA Problems</h4>
|
||||
<div class="alert alert-info">
|
||||
<strong>Problem:</strong> TOTP codes rejected<br>
|
||||
<strong>Solutions:</strong>
|
||||
<ul>
|
||||
<li>Synchronize device time</li>
|
||||
<li>Try recovery codes</li>
|
||||
<li>Regenerate TOTP secret</li>
|
||||
<li>Check authenticator app configuration</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Emergency Procedures</h3>
|
||||
|
||||
<h4>Password Recovery</h4>
|
||||
<div class="alert alert-danger">
|
||||
<strong>Important:</strong> Trilium cannot recover forgotten passwords. Options include:
|
||||
<ul>
|
||||
<li>Restore from backup with known password</li>
|
||||
<li>Export unprotected content before password reset</li>
|
||||
<li>Complete reset (loses all protected content)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>Data Recovery</h4>
|
||||
<ul>
|
||||
<li><strong>Backup Restoration:</strong> Use recent encrypted backups</li>
|
||||
<li><strong>Database Repair:</strong> Use SQLite repair tools if needed</li>
|
||||
<li><strong>Partial Recovery:</strong> Export accessible content</li>
|
||||
<li><strong>Professional Help:</strong> Contact data recovery services for critical data</li>
|
||||
</ul>
|
||||
|
||||
<h2>Compliance and Standards</h2>
|
||||
|
||||
<h3>Regulatory Compliance</h3>
|
||||
|
||||
<h4>GDPR Compliance</h4>
|
||||
<ul>
|
||||
<li><strong>Data Protection:</strong> AES encryption provides technical safeguards</li>
|
||||
<li><strong>Right to Erasure:</strong> Secure deletion of encryption keys</li>
|
||||
<li><strong>Data Portability:</strong> Export capabilities for protected content</li>
|
||||
<li><strong>Privacy by Design:</strong> Encryption built into architecture</li>
|
||||
</ul>
|
||||
|
||||
<h4>Industry Standards</h4>
|
||||
<ul>
|
||||
<li><strong>ISO 27001:</strong> Information security management compliance</li>
|
||||
<li><strong>SOC 2:</strong> Security and availability controls</li>
|
||||
<li><strong>HIPAA:</strong> Healthcare data protection requirements</li>
|
||||
<li><strong>PCI DSS:</strong> Payment card industry standards</li>
|
||||
</ul>
|
||||
|
||||
<h3>Encryption Standards</h3>
|
||||
|
||||
<h4>Algorithm Compliance</h4>
|
||||
<ul>
|
||||
<li><strong>AES-128:</strong> NIST approved, FIPS 140-2 Level 1</li>
|
||||
<li><strong>Scrypt:</strong> RFC 7914 standard</li>
|
||||
<li><strong>SHA-1:</strong> NIST standard (integrity verification only)</li>
|
||||
<li><strong>Random Generation:</strong> Cryptographically secure sources</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-success">
|
||||
<strong>Remember:</strong> Security is an ongoing process, not a one-time configuration. Regularly review and update your security posture to address evolving threats and requirements.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,326 +0,0 @@
|
||||
<div class="note-content">
|
||||
|
||||
<h1>Protected Notes and Encryption</h1>
|
||||
|
||||
<p>Trilium provides robust encryption capabilities through its Protected Notes system, ensuring your sensitive information remains secure even if your database is compromised.</p>
|
||||
|
||||
<h2>Overview</h2>
|
||||
|
||||
<p>Protected notes in Trilium use <strong>AES-128-CBC encryption</strong> with scrypt-based key derivation to protect sensitive content. The encryption is designed to be:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Secure</strong>: Uses industry-standard AES encryption with strong key derivation</li>
|
||||
<li><strong>Selective</strong>: Only notes marked as protected are encrypted</li>
|
||||
<li><strong>Session-based</strong>: Decrypted content remains accessible during a protected session</li>
|
||||
<li><strong>Zero-knowledge</strong>: The server never stores unencrypted protected content</li>
|
||||
</ul>
|
||||
|
||||
<h2>How Encryption Works</h2>
|
||||
|
||||
<h3>Encryption Algorithm</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>Cipher</strong>: AES-128-CBC (Advanced Encryption Standard in Cipher Block Chaining mode)</li>
|
||||
<li><strong>Key Derivation</strong>: Scrypt with configurable parameters (N=16384, r=8, p=1)</li>
|
||||
<li><strong>Initialization Vector</strong>: 16-byte random IV generated for each encryption operation</li>
|
||||
<li><strong>Integrity Protection</strong>: SHA-1 digest (first 4 bytes) prepended to plaintext for tamper detection</li>
|
||||
</ul>
|
||||
|
||||
<h3>Key Management</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Master Password</strong>: User-provided password used for key derivation</li>
|
||||
<li><strong>Data Key</strong>: 32-byte random key generated during setup, encrypted with password-derived key</li>
|
||||
<li><strong>Password-Derived Key</strong>: Generated using scrypt from master password and salt</li>
|
||||
<li><strong>Session Key</strong>: Data key loaded into memory during protected session</li>
|
||||
</ol>
|
||||
|
||||
<h3>Encryption Process</h3>
|
||||
|
||||
<pre><code>1. Generate random 16-byte IV
|
||||
2. Compute SHA-1 digest of plaintext (use first 4 bytes)
|
||||
3. Prepend digest to plaintext
|
||||
4. Encrypt (digest + plaintext) using AES-128-CBC
|
||||
5. Prepend IV to encrypted data
|
||||
6. Encode result as Base64
|
||||
</code></pre>
|
||||
|
||||
<h3>Decryption Process</h3>
|
||||
|
||||
<pre><code>1. Decode Base64 ciphertext
|
||||
2. Extract IV (first 16 bytes) and encrypted data
|
||||
3. Decrypt using AES-128-CBC with data key and IV
|
||||
4. Extract digest (first 4 bytes) and plaintext
|
||||
5. Verify integrity by comparing computed vs. stored digest
|
||||
6. Return plaintext if verification succeeds
|
||||
</code></pre>
|
||||
|
||||
<h2>Setting Up Protected Notes</h2>
|
||||
|
||||
<h3>Initial Setup</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Set Master Password</strong>: Configure a strong password during initial setup</li>
|
||||
<li><strong>Create Protected Note</strong>: Right-click a note and select "Toggle Protected Status"</li>
|
||||
<li><strong>Enter Protected Session</strong>: Click the shield icon or use Ctrl+Shift+P</li>
|
||||
</ol>
|
||||
|
||||
<h3>Password Requirements</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>Minimum Length</strong>: 8 characters (recommended: 12+ characters)</li>
|
||||
<li><strong>Complexity</strong>: Use a mix of uppercase, lowercase, numbers, and symbols</li>
|
||||
<li><strong>Uniqueness</strong>: Don't reuse passwords from other services</li>
|
||||
<li><strong>Storage</strong>: Consider using a password manager for complex passwords</li>
|
||||
</ul>
|
||||
|
||||
<h3>Best Practices</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Strong Passwords</strong>: Use passphrases or generated passwords</li>
|
||||
<li><strong>Regular Changes</strong>: Update passwords periodically</li>
|
||||
<li><strong>Secure Storage</strong>: Store password recovery information securely</li>
|
||||
<li><strong>Backup Strategy</strong>: Ensure encrypted backups are properly secured</li>
|
||||
</ol>
|
||||
|
||||
<h2>Protected Sessions</h2>
|
||||
|
||||
<h3>Session Management</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>Automatic Timeout</strong>: Sessions expire after configurable timeout (default: 10 minutes)</li>
|
||||
<li><strong>Manual Control</strong>: Explicitly enter/exit protected sessions</li>
|
||||
<li><strong>Activity Tracking</strong>: Session timeout resets with each protected note access</li>
|
||||
<li><strong>Multi-client</strong>: Each client maintains its own protected session</li>
|
||||
</ul>
|
||||
|
||||
<h3>Session Lifecycle</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Enter Session</strong>: User enters master password</li>
|
||||
<li><strong>Key Derivation</strong>: System derives data key from password</li>
|
||||
<li><strong>Session Active</strong>: Protected content accessible in plaintext</li>
|
||||
<li><strong>Timeout/Logout</strong>: Data key removed from memory</li>
|
||||
<li><strong>Protection Restored</strong>: Content returns to encrypted state</li>
|
||||
</ol>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
|
||||
<p>Access via Options → Protected Session:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Session Timeout</strong>: Duration before automatic logout (seconds)</li>
|
||||
<li><strong>Password Verification</strong>: Enable/disable password strength requirements</li>
|
||||
<li><strong>Recovery Options</strong>: Configure password recovery mechanisms</li>
|
||||
</ul>
|
||||
|
||||
<h2>Performance Considerations</h2>
|
||||
|
||||
<h3>Encryption Overhead</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>CPU Impact</strong>: Scrypt key derivation is intentionally CPU-intensive</li>
|
||||
<li><strong>Memory Usage</strong>: Minimal additional memory for encrypted content</li>
|
||||
<li><strong>Storage Size</strong>: Encrypted content is slightly larger due to Base64 encoding</li>
|
||||
<li><strong>Network Transfer</strong>: Encrypted notes transfer as Base64 strings</li>
|
||||
</ul>
|
||||
|
||||
<h3>Optimization Tips</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Selective Protection</strong>: Only encrypt truly sensitive notes</li>
|
||||
<li><strong>Session Management</strong>: Keep sessions active during intensive work</li>
|
||||
<li><strong>Hardware Acceleration</strong>: Modern CPUs provide AES acceleration</li>
|
||||
<li><strong>Batch Operations</strong>: Group protected note operations when possible</li>
|
||||
</ol>
|
||||
|
||||
<h2>Security Considerations</h2>
|
||||
|
||||
<h3>Threat Model</h3>
|
||||
|
||||
<p><strong>Protected Against</strong>:</p>
|
||||
<ul>
|
||||
<li>Database theft or unauthorized access</li>
|
||||
<li>Network interception (data at rest)</li>
|
||||
<li>Server-side data breaches</li>
|
||||
<li>Backup file compromise</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Not Protected Against</strong>:</p>
|
||||
<ul>
|
||||
<li>Keyloggers or screen capture malware</li>
|
||||
<li>Physical access to unlocked device</li>
|
||||
<li>Memory dumps during active session</li>
|
||||
<li>Social engineering attacks</li>
|
||||
</ul>
|
||||
|
||||
<h3>Limitations</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Note Titles</strong>: Currently encrypted, may leak structural information</li>
|
||||
<li><strong>Metadata</strong>: Creation dates, modification times remain unencrypted</li>
|
||||
<li><strong>Search Indexing</strong>: Protected notes excluded from full-text search</li>
|
||||
<li><strong>Sync Conflicts</strong>: May be harder to resolve for protected content</li>
|
||||
</ol>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>Common Issues</h3>
|
||||
|
||||
<h4>"Could not decrypt string" Error</h4>
|
||||
|
||||
<p><strong>Causes</strong>:</p>
|
||||
<ul>
|
||||
<li>Incorrect password entered</li>
|
||||
<li>Corrupted encrypted data</li>
|
||||
<li>Database migration issues</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Solutions</strong>:</p>
|
||||
<ol>
|
||||
<li>Verify password spelling and case sensitivity</li>
|
||||
<li>Check for active protected session</li>
|
||||
<li>Restart application and retry</li>
|
||||
<li>Restore from backup if corruption suspected</li>
|
||||
</ol>
|
||||
|
||||
<h4>Protected Session Won't Start</h4>
|
||||
|
||||
<p><strong>Causes</strong>:</p>
|
||||
<ul>
|
||||
<li>Password verification hash mismatch</li>
|
||||
<li>Missing encryption salt</li>
|
||||
<li>Database schema issues</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Solutions</strong>:</p>
|
||||
<ol>
|
||||
<li>Check error logs for specific error messages</li>
|
||||
<li>Verify database integrity</li>
|
||||
<li>Restore from known good backup</li>
|
||||
<li>Contact support with error details</li>
|
||||
</ol>
|
||||
|
||||
<h4>Performance Issues</h4>
|
||||
|
||||
<p><strong>Symptoms</strong>:</p>
|
||||
<ul>
|
||||
<li>Slow password verification</li>
|
||||
<li>Long delays entering protected session</li>
|
||||
<li>High CPU usage during encryption</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Solutions</strong>:</p>
|
||||
<ol>
|
||||
<li>Reduce scrypt parameters (advanced users only)</li>
|
||||
<li>Limit number of protected notes</li>
|
||||
<li>Upgrade hardware (more RAM/faster CPU)</li>
|
||||
<li>Close other resource-intensive applications</li>
|
||||
</ol>
|
||||
|
||||
<h3>Recovery Procedures</h3>
|
||||
|
||||
<h4>Password Recovery</h4>
|
||||
|
||||
<p>If you forget your master password:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>No Built-in Recovery</strong>: Trilium cannot recover forgotten passwords</li>
|
||||
<li><strong>Backup Restoration</strong>: Restore from backup with known password</li>
|
||||
<li><strong>Data Export</strong>: Export unprotected content before password change</li>
|
||||
<li><strong>Complete Reset</strong>: Last resort - lose all protected content</li>
|
||||
</ol>
|
||||
|
||||
<h4>Data Recovery</h4>
|
||||
|
||||
<p>For corrupted protected notes:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Verify Backup</strong>: Check if backups contain uncorrupted data</li>
|
||||
<li><strong>Export/Import</strong>: Try exporting and re-importing the note</li>
|
||||
<li><strong>Database Repair</strong>: Use database repair tools if available</li>
|
||||
<li><strong>Professional Help</strong>: Contact data recovery services for critical data</li>
|
||||
</ol>
|
||||
|
||||
<h2>Advanced Configuration</h2>
|
||||
|
||||
<h3>Custom Encryption Parameters</h3>
|
||||
|
||||
<p><strong>Warning</strong>: Modifying encryption parameters requires advanced knowledge and may break compatibility.</p>
|
||||
|
||||
<p>For expert users, encryption parameters can be modified in the source code:</p>
|
||||
|
||||
<pre><code class="language-typescript">// In my_scrypt.ts
|
||||
const scryptParams = {
|
||||
N: 16384, // CPU/memory cost parameter
|
||||
r: 8, // Block size parameter
|
||||
p: 1 // Parallelization parameter
|
||||
};
|
||||
</code></pre>
|
||||
|
||||
<h3>Integration with External Tools</h3>
|
||||
|
||||
<p>Protected notes can be accessed programmatically:</p>
|
||||
|
||||
<pre><code class="language-javascript">// Backend script example
|
||||
const protectedNote = api.getNote('noteId');
|
||||
if (protectedNote.isProtected) {
|
||||
// Content will be encrypted unless in protected session
|
||||
const content = protectedNote.getContent();
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Compliance and Auditing</h2>
|
||||
|
||||
<h3>Encryption Standards</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>Algorithm</strong>: AES-128-CBC (FIPS 140-2 approved)</li>
|
||||
<li><strong>Key Derivation</strong>: Scrypt (RFC 7914)</li>
|
||||
<li><strong>Random Generation</strong>: Node.js crypto.randomBytes() (OS entropy)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Audit Trail</h3>
|
||||
|
||||
<ul>
|
||||
<li>Protected session entry/exit events logged</li>
|
||||
<li>Encryption/decryption operations tracked</li>
|
||||
<li>Password verification attempts recorded</li>
|
||||
<li>Key derivation operations monitored</li>
|
||||
</ul>
|
||||
|
||||
<h3>Compliance Considerations</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>GDPR</strong>: Encryption provides data protection safeguards</li>
|
||||
<li><strong>HIPAA</strong>: AES encryption meets security requirements</li>
|
||||
<li><strong>SOX</strong>: Audit trails support compliance requirements</li>
|
||||
<li><strong>PCI DSS</strong>: Strong encryption protects sensitive data</li>
|
||||
</ul>
|
||||
|
||||
<h2>Migration and Backup</h2>
|
||||
|
||||
<h3>Backup Strategies</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>Encrypted Backups</strong>: Regular backups preserve encrypted state</li>
|
||||
<li><strong>Unencrypted Exports</strong>: Export protected content during session</li>
|
||||
<li><strong>Key Management</strong>: Securely store password recovery information</li>
|
||||
<li><strong>Testing</strong>: Regularly test backup restoration procedures</li>
|
||||
</ol>
|
||||
|
||||
<h3>Migration Procedures</h3>
|
||||
|
||||
<p>When moving to new installation:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Export Data</strong>: Export all notes including protected content</li>
|
||||
<li><strong>Backup Database</strong>: Create complete database backup</li>
|
||||
<li><strong>Transfer Files</strong>: Move exported files to new installation</li>
|
||||
<li><strong>Import Data</strong>: Import using same master password</li>
|
||||
<li><strong>Verify</strong>: Confirm all protected content accessible</li>
|
||||
</ol>
|
||||
|
||||
<p><strong>Remember</strong>: The security of protected notes ultimately depends on choosing a strong master password and following security best practices for your overall system.</p>
|
||||
|
||||
</div>
|
||||
@@ -1,457 +0,0 @@
|
||||
<div class="note-content">
|
||||
|
||||
<h1>Security Best Practices</h1>
|
||||
|
||||
<p>This guide provides comprehensive security recommendations for deploying and maintaining a secure Trilium installation.</p>
|
||||
|
||||
<h2>Deployment Security</h2>
|
||||
|
||||
<h3>Server Configuration</h3>
|
||||
|
||||
<h4>HTTPS Deployment</h4>
|
||||
|
||||
<p><strong>Always use HTTPS in production environments:</strong></p>
|
||||
|
||||
<ol>
|
||||
<li><strong>TLS Configuration</strong>: Use TLS 1.2 or higher</li>
|
||||
<li><strong>Certificate Management</strong>: Use valid SSL certificates (Let's Encrypt recommended)</li>
|
||||
<li><strong>HSTS Headers</strong>: Enable HTTP Strict Transport Security</li>
|
||||
<li><strong>Secure Redirects</strong>: Redirect all HTTP traffic to HTTPS</li>
|
||||
</ol>
|
||||
|
||||
<p>Example Nginx configuration:</p>
|
||||
<pre><code class="language-nginx">server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-trilium.example.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/private.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h4>Network Security</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Firewall Configuration</strong>: Restrict access to necessary ports only</li>
|
||||
<li><strong>Port Security</strong>: Use non-standard ports if required</li>
|
||||
<li><strong>IP Restrictions</strong>: Limit access to trusted IP ranges</li>
|
||||
<li><strong>VPN Access</strong>: Consider VPN for remote access</li>
|
||||
</ol>
|
||||
|
||||
<p>Example firewall rules:</p>
|
||||
<pre><code class="language-bash"># Allow only HTTPS and SSH
|
||||
ufw allow 22/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw deny 8080/tcp # Block direct access to Trilium
|
||||
ufw enable
|
||||
</code></pre>
|
||||
|
||||
<h3>Access Control</h3>
|
||||
|
||||
<h4>User Management</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Single User Model</strong>: Trilium is designed for single-user access</li>
|
||||
<li><strong>Shared Access</strong>: Use shared hosting or family sharing with caution</li>
|
||||
<li><strong>Guest Access</strong>: Disable guest access unless specifically needed</li>
|
||||
<li><strong>Admin Privileges</strong>: Run Trilium with minimal necessary privileges</li>
|
||||
</ol>
|
||||
|
||||
<h4>Authentication Hardening</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Strong Passwords</strong>: Enforce complex password requirements</li>
|
||||
<li><strong>Multi-Factor Authentication</strong>: Always enable MFA for production</li>
|
||||
<li><strong>Password Rotation</strong>: Regular password updates</li>
|
||||
<li><strong>Account Lockout</strong>: Monitor for brute force attempts</li>
|
||||
</ol>
|
||||
|
||||
<h3>Data Protection</h3>
|
||||
|
||||
<h4>Backup Security</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Encrypted Backups</strong>: Ensure backups are encrypted at rest</li>
|
||||
<li><strong>Secure Storage</strong>: Store backups in secure locations</li>
|
||||
<li><strong>Access Control</strong>: Limit backup access to authorized personnel</li>
|
||||
<li><strong>Regular Testing</strong>: Verify backup integrity regularly</li>
|
||||
</ol>
|
||||
|
||||
<p>Backup encryption example:</p>
|
||||
<pre><code class="language-bash"># Create encrypted backup
|
||||
tar czf - trilium-data/ | gpg --cipher-algo AES256 --compress-algo 1 --symmetric --output trilium-backup-$(date +%Y%m%d).tar.gz.gpg
|
||||
|
||||
# Restore encrypted backup
|
||||
gpg --decrypt trilium-backup-20240101.tar.gz.gpg | tar xzf -
|
||||
</code></pre>
|
||||
|
||||
<h4>Database Security</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>File Permissions</strong>: Restrict database file access (600 or 640)</li>
|
||||
<li><strong>Directory Security</strong>: Secure data directory permissions</li>
|
||||
<li><strong>Regular Monitoring</strong>: Monitor for unauthorized access attempts</li>
|
||||
<li><strong>Integrity Checks</strong>: Verify database integrity regularly</li>
|
||||
</ol>
|
||||
|
||||
<pre><code class="language-bash"># Secure file permissions
|
||||
chmod 600 /path/to/trilium/data/document.db
|
||||
chmod 700 /path/to/trilium/data/
|
||||
chown trilium:trilium /path/to/trilium/data/ -R
|
||||
</code></pre>
|
||||
|
||||
<h2>Application Security</h2>
|
||||
|
||||
<h3>Configuration Hardening</h3>
|
||||
|
||||
<h4>Security Headers</h4>
|
||||
|
||||
<p>Configure security headers for web protection:</p>
|
||||
|
||||
<pre><code class="language-typescript">// Security headers configuration
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
res.setHeader('Content-Security-Policy',
|
||||
"default-src 'self'; " +
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
|
||||
"style-src 'self' 'unsafe-inline';"
|
||||
);
|
||||
next();
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h4>Session Security</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Session Timeout</strong>: Configure appropriate timeout values</li>
|
||||
<li><strong>Secure Cookies</strong>: Enable secure flag for all cookies</li>
|
||||
<li><strong>Session Regeneration</strong>: Regenerate session IDs after login</li>
|
||||
<li><strong>CSRF Protection</strong>: Enable and properly configure CSRF protection</li>
|
||||
</ol>
|
||||
|
||||
<p>Example session configuration:</p>
|
||||
<pre><code class="language-javascript">// Secure session configuration
|
||||
{
|
||||
cookie: {
|
||||
secure: true, // HTTPS only
|
||||
httpOnly: true, // Prevent XSS
|
||||
maxAge: 30 * 60 * 1000, // 30 minutes
|
||||
sameSite: 'strict' // CSRF protection
|
||||
},
|
||||
rolling: true, // Reset timeout on activity
|
||||
resave: false, // Don't save unchanged sessions
|
||||
saveUninitialized: false // Don't save empty sessions
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h3>Input Validation</h3>
|
||||
|
||||
<h4>Content Security</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>HTML Sanitization</strong>: Properly sanitize user-generated content</li>
|
||||
<li><strong>File Upload Security</strong>: Validate file types and sizes</li>
|
||||
<li><strong>Script Execution</strong>: Control custom script execution</li>
|
||||
<li><strong>SQL Injection Prevention</strong>: Use parameterized queries</li>
|
||||
</ol>
|
||||
|
||||
<h4>API Security</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Rate Limiting</strong>: Implement API rate limiting</li>
|
||||
<li><strong>Input Validation</strong>: Validate all API inputs</li>
|
||||
<li><strong>Authentication</strong>: Require authentication for sensitive operations</li>
|
||||
<li><strong>Authorization</strong>: Implement proper access controls</li>
|
||||
</ol>
|
||||
|
||||
<p>Example rate limiting:</p>
|
||||
<pre><code class="language-typescript">import rateLimit from 'express-rate-limit';
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // limit each IP to 100 requests per windowMs
|
||||
message: 'Too many requests, please try again later.'
|
||||
});
|
||||
|
||||
app.use('/api/', limiter);
|
||||
</code></pre>
|
||||
|
||||
<h2>Operational Security</h2>
|
||||
|
||||
<h3>Monitoring and Logging</h3>
|
||||
|
||||
<h4>Security Logging</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Authentication Events</strong>: Log all login attempts and failures</li>
|
||||
<li><strong>Authorization Events</strong>: Track access to protected resources</li>
|
||||
<li><strong>Data Access</strong>: Monitor sensitive data access patterns</li>
|
||||
<li><strong>System Events</strong>: Log system-level security events</li>
|
||||
</ol>
|
||||
|
||||
<p>Example log monitoring:</p>
|
||||
<pre><code class="language-bash"># Monitor failed login attempts
|
||||
tail -f /var/log/trilium/security.log | grep "Failed login"
|
||||
|
||||
# Alert on multiple failures
|
||||
tail -f /var/log/trilium/security.log | awk '/Failed login/ {count++} count>=5 {print "Alert: Multiple failed logins"; count=0}'
|
||||
</code></pre>
|
||||
|
||||
<h4>Security Metrics</h4>
|
||||
|
||||
<p>Monitor key security metrics:</p>
|
||||
<ul>
|
||||
<li>Failed authentication attempts</li>
|
||||
<li>Session anomalies</li>
|
||||
<li>Unusual access patterns</li>
|
||||
<li>Data export activities</li>
|
||||
<li>Configuration changes</li>
|
||||
</ul>
|
||||
|
||||
<h3>Incident Response</h3>
|
||||
|
||||
<h4>Preparation</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Incident Response Plan</strong>: Develop and document procedures</li>
|
||||
<li><strong>Contact Lists</strong>: Maintain emergency contact information</li>
|
||||
<li><strong>Backup Procedures</strong>: Ensure rapid recovery capabilities</li>
|
||||
<li><strong>Communication Plans</strong>: Prepare user notification procedures</li>
|
||||
</ol>
|
||||
|
||||
<h4>Detection and Response</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Automated Monitoring</strong>: Implement automated threat detection</li>
|
||||
<li><strong>Alert Systems</strong>: Configure appropriate alerting thresholds</li>
|
||||
<li><strong>Response Procedures</strong>: Define step-by-step response actions</li>
|
||||
<li><strong>Forensic Preparation</strong>: Preserve evidence for analysis</li>
|
||||
</ol>
|
||||
|
||||
<p>Example incident response checklist:</p>
|
||||
<pre><code>□ Identify and isolate affected systems
|
||||
□ Preserve logs and evidence
|
||||
□ Assess scope and impact
|
||||
□ Notify relevant stakeholders
|
||||
□ Implement containment measures
|
||||
□ Begin recovery procedures
|
||||
□ Document lessons learned
|
||||
□ Update security controls
|
||||
</code></pre>
|
||||
|
||||
<h3>Regular Maintenance</h3>
|
||||
|
||||
<h4>Security Updates</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Application Updates</strong>: Keep Trilium updated to latest version</li>
|
||||
<li><strong>Dependency Updates</strong>: Regularly update dependencies</li>
|
||||
<li><strong>System Updates</strong>: Maintain OS and security patches</li>
|
||||
<li><strong>Certificate Renewal</strong>: Monitor and renew SSL certificates</li>
|
||||
</ol>
|
||||
|
||||
<h4>Security Audits</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Regular Reviews</strong>: Conduct periodic security assessments</li>
|
||||
<li><strong>Penetration Testing</strong>: Perform authorized security testing</li>
|
||||
<li><strong>Configuration Audits</strong>: Review security configurations</li>
|
||||
<li><strong>Access Reviews</strong>: Audit user access and permissions</li>
|
||||
</ol>
|
||||
|
||||
<p>Automated update checking:</p>
|
||||
<pre><code class="language-bash">#!/bin/bash
|
||||
# Check for Trilium updates
|
||||
CURRENT_VERSION=$(curl -s https://api.github.com/repos/TriliumNext/Trilium/releases/latest | grep tag_name | cut -d'"' -f4)
|
||||
INSTALLED_VERSION=$(grep version /opt/trilium/package.json | cut -d'"' -f4)
|
||||
|
||||
if [ "$CURRENT_VERSION" != "v$INSTALLED_VERSION" ]; then
|
||||
echo "Update available: $CURRENT_VERSION (current: $INSTALLED_VERSION)"
|
||||
# Add notification logic here
|
||||
fi
|
||||
</code></pre>
|
||||
|
||||
<h2>Threat Mitigation</h2>
|
||||
|
||||
<h3>Common Attack Vectors</h3>
|
||||
|
||||
<h4>Web Application Attacks</h4>
|
||||
|
||||
<p><strong>Cross-Site Scripting (XSS)</strong>:</p>
|
||||
<ul>
|
||||
<li>Content Security Policy headers</li>
|
||||
<li>Input sanitization</li>
|
||||
<li>Output encoding</li>
|
||||
<li>Secure cookie flags</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Cross-Site Request Forgery (CSRF)</strong>:</p>
|
||||
<ul>
|
||||
<li>CSRF token validation</li>
|
||||
<li>SameSite cookie attributes</li>
|
||||
<li>Referrer validation</li>
|
||||
<li>Double-submit cookies</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Session Hijacking</strong>:</p>
|
||||
<ul>
|
||||
<li>Secure session management</li>
|
||||
<li>HTTPS enforcement</li>
|
||||
<li>Session timeout controls</li>
|
||||
<li>Session regeneration</li>
|
||||
</ul>
|
||||
|
||||
<h4>Infrastructure Attacks</h4>
|
||||
|
||||
<p><strong>Denial of Service (DoS)</strong>:</p>
|
||||
<ul>
|
||||
<li>Rate limiting</li>
|
||||
<li>Request size limits</li>
|
||||
<li>Connection throttling</li>
|
||||
<li>Resource monitoring</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Data Breaches</strong>:</p>
|
||||
<ul>
|
||||
<li>Encryption at rest</li>
|
||||
<li>Access controls</li>
|
||||
<li>Audit logging</li>
|
||||
<li>Regular backups</li>
|
||||
</ul>
|
||||
|
||||
<h3>Security Controls Implementation</h3>
|
||||
|
||||
<h4>Preventive Controls</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Authentication</strong>: Strong password policies and MFA</li>
|
||||
<li><strong>Authorization</strong>: Proper access controls and permissions</li>
|
||||
<li><strong>Encryption</strong>: Data encryption at rest and in transit</li>
|
||||
<li><strong>Input Validation</strong>: Comprehensive input sanitization</li>
|
||||
</ol>
|
||||
|
||||
<h4>Detective Controls</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Logging</strong>: Comprehensive security logging</li>
|
||||
<li><strong>Monitoring</strong>: Real-time security monitoring</li>
|
||||
<li><strong>Alerting</strong>: Automated threat detection</li>
|
||||
<li><strong>Auditing</strong>: Regular security audits</li>
|
||||
</ol>
|
||||
|
||||
<h4>Responsive Controls</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Incident Response</strong>: Documented response procedures</li>
|
||||
<li><strong>Backup and Recovery</strong>: Reliable backup systems</li>
|
||||
<li><strong>Isolation</strong>: Network segmentation capabilities</li>
|
||||
<li><strong>Communication</strong>: Stakeholder notification systems</li>
|
||||
</ol>
|
||||
|
||||
<h2>Compliance Considerations</h2>
|
||||
|
||||
<h3>Data Protection Regulations</h3>
|
||||
|
||||
<h4>GDPR Compliance</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Data Minimization</strong>: Only collect necessary data</li>
|
||||
<li><strong>Consent Management</strong>: Obtain proper user consent</li>
|
||||
<li><strong>Right to Erasure</strong>: Implement data deletion capabilities</li>
|
||||
<li><strong>Data Portability</strong>: Enable data export functionality</li>
|
||||
<li><strong>Privacy by Design</strong>: Build privacy into system design</li>
|
||||
</ol>
|
||||
|
||||
<h4>HIPAA Compliance (Healthcare)</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Access Controls</strong>: Implement user authentication and authorization</li>
|
||||
<li><strong>Audit Logs</strong>: Maintain comprehensive audit trails</li>
|
||||
<li><strong>Encryption</strong>: Encrypt data at rest and in transit</li>
|
||||
<li><strong>Risk Assessment</strong>: Conduct regular risk assessments</li>
|
||||
<li><strong>Business Associate Agreements</strong>: Ensure proper agreements</li>
|
||||
</ol>
|
||||
|
||||
<h3>Industry Standards</h3>
|
||||
|
||||
<h4>ISO 27001</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Information Security Management</strong>: Implement ISMS</li>
|
||||
<li><strong>Risk Management</strong>: Conduct regular risk assessments</li>
|
||||
<li><strong>Security Controls</strong>: Implement appropriate controls</li>
|
||||
<li><strong>Continuous Improvement</strong>: Regular reviews and updates</li>
|
||||
</ol>
|
||||
|
||||
<h4>SOC 2</h4>
|
||||
|
||||
<ol>
|
||||
<li><strong>Security</strong>: Implement comprehensive security controls</li>
|
||||
<li><strong>Availability</strong>: Ensure system availability and reliability</li>
|
||||
<li><strong>Processing Integrity</strong>: Maintain data processing integrity</li>
|
||||
<li><strong>Confidentiality</strong>: Protect sensitive information</li>
|
||||
<li><strong>Privacy</strong>: Implement privacy protection measures</li>
|
||||
</ol>
|
||||
|
||||
<h2>Security Assessment Checklist</h2>
|
||||
|
||||
<h3>Infrastructure Security</h3>
|
||||
<ul>
|
||||
<li>☐ HTTPS configured with valid certificates</li>
|
||||
<li>☐ Firewall rules properly configured</li>
|
||||
<li>☐ Network access controls implemented</li>
|
||||
<li>☐ System updates current</li>
|
||||
<li>☐ Backup procedures tested</li>
|
||||
<li>☐ Monitoring systems active</li>
|
||||
</ul>
|
||||
|
||||
<h3>Application Security</h3>
|
||||
<ul>
|
||||
<li>☐ Strong authentication configured</li>
|
||||
<li>☐ Multi-factor authentication enabled</li>
|
||||
<li>☐ Session security properly configured</li>
|
||||
<li>☐ CSRF protection enabled</li>
|
||||
<li>☐ Security headers configured</li>
|
||||
<li>☐ Input validation implemented</li>
|
||||
</ul>
|
||||
|
||||
<h3>Data Security</h3>
|
||||
<ul>
|
||||
<li>☐ Database properly secured</li>
|
||||
<li>☐ File permissions configured</li>
|
||||
<li>☐ Encryption properly implemented</li>
|
||||
<li>☐ Backup encryption verified</li>
|
||||
<li>☐ Access controls tested</li>
|
||||
<li>☐ Data retention policies defined</li>
|
||||
</ul>
|
||||
|
||||
<h3>Operational Security</h3>
|
||||
<ul>
|
||||
<li>☐ Security logging enabled</li>
|
||||
<li>☐ Monitoring systems configured</li>
|
||||
<li>☐ Incident response plan documented</li>
|
||||
<li>☐ Security training completed</li>
|
||||
<li>☐ Regular audits scheduled</li>
|
||||
<li>☐ Update procedures documented</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Remember</strong>: Security is an ongoing process, not a one-time configuration. Regularly review and update your security posture to address evolving threats and requirements.</p>
|
||||
|
||||
</div>
|
||||
@@ -134,7 +134,8 @@ docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-
|
||||
<li><code>TRILIUM_DATA_DIR</code>: Path to the data directory inside the container
|
||||
(default: <code>/home/node/trilium-data</code>)</li>
|
||||
</ul>
|
||||
<p>For a complete list of configuration environment variables (network settings, authentication, sync, etc.), see <a class="reference-link" href="#root/_help_Gzjqa934BdH4">Configuration (config.ini or environment variables)</a>.</p>
|
||||
<p>For a complete list of configuration environment variables (network settings,
|
||||
authentication, sync, etc.), see <a class="reference-link" href="#root/_help_dmi3wz9muS2O">Configuration (config.ini or environment variables)</a>.</p>
|
||||
<h3>Volume Permissions</h3>
|
||||
<p>If you encounter permission issues with the data volume, ensure that:</p>
|
||||
<ol>
|
||||
|
||||
@@ -51,8 +51,10 @@ class="admonition warning">
|
||||
<ol>
|
||||
<li>You can also setup through environment variables:
|
||||
<ul>
|
||||
<li>Standard: <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET</code></li>
|
||||
<li>Legacy (still supported): <code>TRILIUM_OAUTH_BASE_URL</code>, <code>TRILIUM_OAUTH_CLIENT_ID</code>, <code>TRILIUM_OAUTH_CLIENT_SECRET</code></li>
|
||||
<li>Standard: <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET</code>
|
||||
</li>
|
||||
<li>Legacy (still supported): <code>TRILIUM_OAUTH_BASE_URL</code>, <code>TRILIUM_OAUTH_CLIENT_ID</code>, <code>TRILIUM_OAUTH_CLIENT_SECRET</code>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>oauthBaseUrl</code> should be the link of your Trilium instance server,
|
||||
@@ -69,13 +71,15 @@ class="admonition warning">
|
||||
<p>The default OAuth issuer is Google. To use other services such as Authentik
|
||||
or Auth0, you can configure the settings via <code>oauthIssuerBaseUrl</code>, <code>oauthIssuerName</code>,
|
||||
and <code>oauthIssuerIcon</code> in the <code>config.ini</code> file. Alternatively,
|
||||
these values can be set using environment variables:
|
||||
<ul>
|
||||
<li>Standard: <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON</code></li>
|
||||
<li>Legacy (still supported): <code>TRILIUM_OAUTH_ISSUER_BASE_URL</code>, <code>TRILIUM_OAUTH_ISSUER_NAME</code>, <code>TRILIUM_OAUTH_ISSUER_ICON</code></li>
|
||||
</ul>
|
||||
<code>oauthIssuerName</code> and <code>oauthIssuerIcon</code> are
|
||||
required for displaying correct issuer information at the Login page.</p>
|
||||
these values can be set using environment variables:</p>
|
||||
<ul>
|
||||
<li>Standard: <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME</code>, <code>TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON</code>
|
||||
</li>
|
||||
<li>Legacy (still supported): <code>TRILIUM_OAUTH_ISSUER_BASE_URL</code>, <code>TRILIUM_OAUTH_ISSUER_NAME</code>, <code>TRILIUM_OAUTH_ISSUER_ICON</code>
|
||||
</li>
|
||||
</ul>
|
||||
<p><code>oauthIssuerName</code> and <code>oauthIssuerIcon</code> are required
|
||||
for displaying correct issuer information at the Login page.</p>
|
||||
</aside>
|
||||
<h4>Authentik</h4>
|
||||
<p>If you don’t already have a running Authentik instance, please follow
|
||||
|
||||
@@ -26,8 +26,8 @@ https=true
|
||||
certPath=/[username]/.acme.sh/[hostname]/fullchain.cer
|
||||
keyPath=/[username]/.acme.sh/[hostname]/example.com.key</code></pre>
|
||||
<p>You can also review the <a href="#root/_help_Gzjqa934BdH4">configuration</a> file
|
||||
to provide all <code>config.ini</code> values as environment variables instead. For example, you can configure TLS using environment variables:</p>
|
||||
<pre><code class="language-bash">export TRILIUM_NETWORK_HTTPS=true
|
||||
to provide all <code>config.ini</code> values as environment variables instead.
|
||||
For example, you can configure TLS using environment variables:</p><pre><code class="language-text-x-sh">export TRILIUM_NETWORK_HTTPS=true
|
||||
export TRILIUM_NETWORK_CERTPATH=/path/to/cert.pem
|
||||
export TRILIUM_NETWORK_KEYPATH=/path/to/key.pem</code></pre>
|
||||
<p>The above example shows how this is set up in an environment where the
|
||||
|
||||
@@ -6,7 +6,7 @@ info:
|
||||
contact:
|
||||
name: zadam
|
||||
email: zadam.apps@gmail.com
|
||||
url: https://github.com/TriliumNext/Trilium
|
||||
url: https://github.com/zadam/trilium
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
@@ -1,305 +1,428 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Öffne das Dialogfeld \"Zu Notiz springen\"",
|
||||
"search-in-subtree": "Nach Notizen im Unterbaum der aktuellen Notiz suchen",
|
||||
"expand-subtree": "Unterbaum der aktuellen Notiz ausklappen",
|
||||
"collapse-tree": "Gesamten Notizbaum einklappen",
|
||||
"collapse-subtree": "Unterbaum der aktuellen Notiz einklappen",
|
||||
"sort-child-notes": "Untergeordnete Notizen sortieren",
|
||||
"creating-and-moving-notes": "Notizen erstellen und verschieben",
|
||||
"create-note-into-inbox": "Erstelle eine Notiz im Posteingang (falls definiert) oder in der Tagesnotiz",
|
||||
"delete-note": "Notiz löschen",
|
||||
"move-note-up": "Notiz nach oben verschieben",
|
||||
"move-note-down": "Notiz nach unten verschieben",
|
||||
"move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben",
|
||||
"move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben",
|
||||
"edit-note-title": "Vom Notiz-Baum zur Notiz-Detailansicht springen und den Titel bearbeiten",
|
||||
"edit-branch-prefix": "Dialog zum Bearbeiten des Zweigpräfixes anzeigen",
|
||||
"note-clipboard": "Notiz-Zwischenablage",
|
||||
"copy-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus der Zwischenablage in die aktive Notiz einfügen",
|
||||
"cut-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage ausschneiden",
|
||||
"select-all-notes-in-parent": "Alle Notizen der aktuellen Notizenebene auswählen",
|
||||
"add-note-above-to-the-selection": "Notiz oberhalb der Auswahl hinzufügen",
|
||||
"add-note-below-to-selection": "Notiz unterhalb der Auswahl hinzufügen",
|
||||
"duplicate-subtree": "Unterbaum duplizieren",
|
||||
"tabs-and-windows": "Tabs & Fenster",
|
||||
"open-new-tab": "Neuen Tab öffnen",
|
||||
"close-active-tab": "Aktiven Tab schließen",
|
||||
"reopen-last-tab": "Zuletzt geschlossenen Tab wieder öffnen",
|
||||
"activate-next-tab": "Rechten Tab aktivieren",
|
||||
"activate-previous-tab": "Linken Tab aktivieren",
|
||||
"open-new-window": "Neues leeres Fenster öffnen",
|
||||
"toggle-tray": "Anwendung im Systemtray anzeigen/verstecken",
|
||||
"first-tab": "Ersten Tab in der Liste aktivieren",
|
||||
"second-tab": "Zweiten Tab in der Liste aktivieren",
|
||||
"third-tab": "Dritten Tab in der Liste aktivieren",
|
||||
"fourth-tab": "Vierten Tab in der Liste aktivieren",
|
||||
"fifth-tab": "Fünften Tab in der Liste aktivieren",
|
||||
"sixth-tab": "Sechsten Tab in der Liste aktivieren",
|
||||
"seventh-tab": "Siebten Tab in der Liste aktivieren",
|
||||
"eight-tab": "Achten Tab in der Liste aktivieren",
|
||||
"ninth-tab": "Neunten Tab in der Liste aktivieren",
|
||||
"last-tab": "Letzten Tab in der Liste aktivieren",
|
||||
"dialogs": "Dialoge",
|
||||
"show-note-source": "Notizquellen-Dialog anzeigen",
|
||||
"show-options": "Optionen-Dialog anzeigen",
|
||||
"show-revisions": "Notizrevisionen-Dialog anzeigen",
|
||||
"show-recent-changes": "Letzte Änderungen-Dialog anzeigen",
|
||||
"show-sql-console": "SQL-Konsole-Dialog anzeigen",
|
||||
"show-backend-log": "Backend-Logs-Dialog anzeigen",
|
||||
"text-note-operations": "Textnotiz-Operationen",
|
||||
"add-link-to-text": "Dialogfeld zum Hinzufügen eines Links zum Text öffnen",
|
||||
"follow-link-under-cursor": "Folge dem Link, unter dem Mauszeiger",
|
||||
"insert-date-and-time-to-text": "Aktuelles Datum & Uhrzeit in den Text einfügen",
|
||||
"paste-markdown-into-text": "Markdown aus der Zwischenablage in die Textnotiz einfügen",
|
||||
"cut-into-note": "Auswahl aus der aktuellen Notiz ausschneiden und eine Unternotiz mit dem ausgewählten Text erstellen",
|
||||
"add-include-note-to-text": "Notiz-Einfügen-Dialog öffnen",
|
||||
"edit-readonly-note": "Schreibgeschützte Notiz bearbeiten",
|
||||
"attributes-labels-and-relations": "Attribute (Labels & Verknüpfungen)",
|
||||
"add-new-label": "Neues Label erstellen",
|
||||
"create-new-relation": "Neue Verknüpfungen",
|
||||
"ribbon-tabs": "Ribbon-Tabs",
|
||||
"toggle-basic-properties": "Grundattribute umschalten",
|
||||
"toggle-file-properties": "Dateiattribute umschalten",
|
||||
"toggle-image-properties": "Bildattribute umschalten",
|
||||
"toggle-owned-attributes": "Eigene Attribute umschalten",
|
||||
"toggle-inherited-attributes": "Vererbte Attribute umschalten",
|
||||
"toggle-promoted-attributes": "Beworbene Attribute umschalten",
|
||||
"toggle-link-map": "Link-Karte umschalten",
|
||||
"toggle-note-info": "Notizinformationen umschalten",
|
||||
"toggle-note-paths": "Notizpfade umschalten",
|
||||
"toggle-similar-notes": "Ähnliche Notizen umschalten",
|
||||
"other": "Sonstige",
|
||||
"toggle-right-pane": "Anzeige der rechten Leiste umschalten, das Inhaltsverzeichnis und Markierungen enthält",
|
||||
"print-active-note": "Aktive Notiz drucken",
|
||||
"open-note-externally": "Notiz als Datei mit Standardanwendung öffnen",
|
||||
"render-active-note": "Aktive Notiz rendern (erneut rendern)",
|
||||
"run-active-note": "Aktive JavaScript(Frontend/Backend)-Codenotiz ausführen",
|
||||
"toggle-note-hoisting": "Notiz-Fokus der aktiven Notiz umschalten",
|
||||
"unhoist": "Notiz-Fokus aufheben",
|
||||
"reload-frontend-app": "Frontend-App neuladen",
|
||||
"open-dev-tools": "Entwicklertools öffnen",
|
||||
"toggle-left-note-tree-panel": "Linke Notizbaum-Leiste umschalten",
|
||||
"toggle-full-screen": "Vollbildmodus umschalten",
|
||||
"zoom-out": "Herauszoomen",
|
||||
"zoom-in": "Hineinzoomen",
|
||||
"note-navigation": "Notiznavigation",
|
||||
"reset-zoom-level": "Zoomlevel zurücksetzen",
|
||||
"copy-without-formatting": "Ausgewählten Text ohne Formatierung kopieren",
|
||||
"force-save-revision": "Erstellen / Speichern einer neuen Notizrevision der aktiven Notiz erzwingen",
|
||||
"show-help": "Eingebaute Hilfe / Cheat-Sheet anzeigen",
|
||||
"toggle-book-properties": "Buch-Eigenschaften umschalten",
|
||||
"clone-notes-to": "Ausgewählte Notizen duplizieren",
|
||||
"open-command-palette": "Kommandopalette öffnen",
|
||||
"export-as-pdf": "Aktuelle Notiz als PDF exportieren",
|
||||
"back-in-note-history": "Navigiere zur vorherigen Notiz im Verlauf",
|
||||
"forward-in-note-history": "Navigiere zur nächsten Notiz im Verlauf",
|
||||
"scroll-to-active-note": "Zum aktiven Notizbaumeintrag springen",
|
||||
"quick-search": "Schnellsuche öffnen",
|
||||
"create-note-after": "Erstelle eine neue Notiz nach der aktuellen Notiz",
|
||||
"create-note-into": "Unternotiz zur aktiven Notiz anlegen",
|
||||
"move-notes-to": "Ausgewählte Notizen verschieben",
|
||||
"show-cheatsheet": "Übersicht der Tastenkombinationen anzeigen",
|
||||
"find-in-text": "Suchleiste umschalten",
|
||||
"toggle-classic-editor-toolbar": "Schalte um zum Formatierungs-Tab für den Editor mit fester Werkzeugleiste",
|
||||
"toggle-zen-mode": "Zen-Modus ein-/ausschalten (reduzierte Benutzeroberfläche für ablenkungsfreies Arbeiten)"
|
||||
},
|
||||
"login": {
|
||||
"title": "Anmeldung",
|
||||
"heading": "Trilium Anmeldung",
|
||||
"incorrect-password": "Das Passwort ist falsch. Bitte versuche es erneut.",
|
||||
"password": "Passwort",
|
||||
"remember-me": "Angemeldet bleiben",
|
||||
"button": "Anmelden"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Passwort festlegen",
|
||||
"heading": "Passwort festlegen",
|
||||
"description": "Bevor du Trilium im Web verwenden kannst, musst du zuerst ein Passwort festlegen. Du wirst dieses Passwort dann zur Anmeldung verwenden.",
|
||||
"password": "Passwort",
|
||||
"password-confirmation": "Passwortbestätigung",
|
||||
"button": "Passwort festlegen"
|
||||
},
|
||||
"javascript-required": "Trilium erfordert, dass JavaScript aktiviert ist.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes Setup",
|
||||
"new-document": "Ich bin ein neuer Benutzer und möchte ein neues Trilium-Dokument für meine Notizen erstellen",
|
||||
"sync-from-desktop": "Ich habe bereits eine Desktop-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"sync-from-server": "Ich habe bereits eine Server-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"next": "Weiter",
|
||||
"init-in-progress": "Dokumenteninitialisierung läuft",
|
||||
"redirecting": "Du wirst in Kürze zur Anwendung weitergeleitet.",
|
||||
"title": "Setup"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchronisation vom Desktop",
|
||||
"description": "Dieses Setup muss von der Desktop-Instanz aus initiiert werden:",
|
||||
"step1": "Öffne deine Trilium Notes Desktop-Instanz.",
|
||||
"step2": "Klicke im Trilium-Menü auf Optionen.",
|
||||
"step3": "Klicke auf die Kategorie Synchronisation.",
|
||||
"step4": "Ändere die Server-Instanzadresse auf: {{- host}} und klicke auf Speichern.",
|
||||
"step5": "Klicke auf den Button \"Test-Synchronisation\", um zu überprüfen, ob die Verbindung erfolgreich ist.",
|
||||
"step6": "Sobald du diese Schritte abgeschlossen hast, klicke auf {{- link}}.",
|
||||
"step6-here": "hier"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchronisation vom Server",
|
||||
"instructions": "Bitte gib unten die Trilium-Server-Adresse und die Zugangsdaten ein. Dies wird das gesamte Trilium-Dokument vom Server herunterladen und die Synchronisation einrichten. Je nach Dokumentgröße und Verbindungsgeschwindigkeit kann dies eine Weile dauern.",
|
||||
"server-host": "Trilium Server-Adresse",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Proxy-Server (optional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Hinweis:",
|
||||
"proxy-instruction": "Wenn du die Proxy-Einstellung leer lässt, wird der System-Proxy verwendet (gilt nur für die Desktop-Anwendung)",
|
||||
"password": "Passwort",
|
||||
"password-placeholder": "Passwort",
|
||||
"back": "Zurück",
|
||||
"finish-setup": "Setup abschließen"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation läuft",
|
||||
"successful": "Die Synchronisation wurde erfolgreich eingerichtet. Es wird eine Weile dauern, bis die erste Synchronisation abgeschlossen ist. Sobald dies erledigt ist, wirst du zur Anmeldeseite weitergeleitet.",
|
||||
"outstanding-items": "Ausstehende Synchronisationselemente:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Nicht gefunden",
|
||||
"heading": "Nicht gefunden"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "Übergeordnete Notiz:",
|
||||
"clipped-from": "Diese Notiz wurde ursprünglich von {{- url}} ausgeschnitten",
|
||||
"child-notes": "Untergeordnete Notizen:",
|
||||
"no-content": "Diese Notiz hat keinen Inhalt."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Montag",
|
||||
"tuesday": "Dienstag",
|
||||
"wednesday": "Mittwoch",
|
||||
"thursday": "Donnerstag",
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag"
|
||||
},
|
||||
"months": {
|
||||
"january": "Januar",
|
||||
"february": "Februar",
|
||||
"march": "März",
|
||||
"april": "April",
|
||||
"may": "Mai",
|
||||
"june": "Juni",
|
||||
"july": "Juli",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Suche:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
|
||||
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Versteckte Notizen",
|
||||
"search-history-title": "Suchverlauf",
|
||||
"note-map-title": "Notiz Karte",
|
||||
"sql-console-history-title": "SQL Konsolen Verlauf",
|
||||
"shared-notes-title": "Geteilte Notizen",
|
||||
"bulk-action-title": "Massenverarbeitung",
|
||||
"backend-log-title": "Backend Log",
|
||||
"user-hidden-title": "Versteckt vom Nutzer",
|
||||
"launch-bar-templates-title": "Startleiste Vorlagen",
|
||||
"base-abstract-launcher-title": "Basis Abstrakte Startleiste",
|
||||
"command-launcher-title": "Befehlslauncher",
|
||||
"note-launcher-title": "Notiz Launcher",
|
||||
"script-launcher-title": "Script Launcher",
|
||||
"built-in-widget-title": "Eingebautes Widget",
|
||||
"spacer-title": "Freifeld",
|
||||
"custom-widget-title": "Custom Widget",
|
||||
"launch-bar-title": "Launchbar",
|
||||
"available-launchers-title": "Verfügbare Launchers",
|
||||
"go-to-previous-note-title": "Zur vorherigen Notiz gehen",
|
||||
"go-to-next-note-title": "Zur nächsten Notiz gehen",
|
||||
"new-note-title": "Neue Notiz",
|
||||
"search-notes-title": "Notizen durchsuchen",
|
||||
"calendar-title": "Kalender",
|
||||
"recent-changes-title": "neue Änderungen",
|
||||
"bookmarks-title": "Lesezeichen",
|
||||
"open-today-journal-note-title": "Heutigen Journaleintrag öffnen",
|
||||
"quick-search-title": "Schnellsuche",
|
||||
"protected-session-title": "Geschützte Sitzung",
|
||||
"sync-status-title": "Sync Status",
|
||||
"settings-title": "Einstellungen",
|
||||
"options-title": "Optionen",
|
||||
"appearance-title": "Erscheinungsbild",
|
||||
"shortcuts-title": "Tastaturkürzel",
|
||||
"text-notes": "Text Notizen",
|
||||
"code-notes-title": "Code Notizen",
|
||||
"images-title": "Bilder",
|
||||
"spellcheck-title": "Rechtschreibprüfung",
|
||||
"password-title": "Passwort",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sicherung",
|
||||
"sync-title": "Sync",
|
||||
"other": "Weitere",
|
||||
"advanced-title": "Erweitert",
|
||||
"visible-launchers-title": "Sichtbare Launcher",
|
||||
"user-guide": "Nutzerhandbuch"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Neue Notiz",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Die Backend-Log-Datei '{{ fileName }}' existiert (noch) nicht.",
|
||||
"reading-log-failed": "Das Lesen der Backend-Log-Datei '{{ fileName }}' ist fehlgeschlagen."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Dieser Notiztyp kann nicht angezeigt werden."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF Dokument (*.pdf)",
|
||||
"unable-to-export-message": "Die aktuelle Notiz konnte nicht als PDF exportiert werden.",
|
||||
"unable-to-export-title": "Export als PDF fehlgeschlagen",
|
||||
"unable-to-save-message": "Die ausgewählte Datei konnte nicht beschrieben werden. Erneut versuchen oder ein anderes Ziel auswählen."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Trilium schließen",
|
||||
"recents": "Kürzliche Notizen",
|
||||
"bookmarks": "Lesezeichen",
|
||||
"today": "Heutigen Journal Eintrag öffnen",
|
||||
"new-note": "Neue Notiz",
|
||||
"show-windows": "Fenster anzeigen"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"table": "Tabelle",
|
||||
"board_status_done": "Erledigt"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"copy-notes-to-clipboard": "Notizen in Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus Zwischenablage einfügen",
|
||||
"back-in-note-history": "Zurück im Notizverlauf",
|
||||
"forward-in-note-history": "Vorwärts im Notizverlauf",
|
||||
"jump-to-note": "Wechseln zu...",
|
||||
"command-palette": "Befehlsübersicht",
|
||||
"scroll-to-active-note": "Zur aktiven Notiz scrollen",
|
||||
"quick-search": "Schnellsuche",
|
||||
"search-in-subtree": "In Unterzweig suchen",
|
||||
"expand-subtree": "Unterzweig aufklappen",
|
||||
"collapse-tree": "Baumstruktur einklappen",
|
||||
"collapse-subtree": "Unterzweig einklappen",
|
||||
"sort-child-notes": "Unternotizen sortieren",
|
||||
"create-note-after": "Erstelle eine neue Notiz dahinter",
|
||||
"create-note-into": "Erstelle eine neue Notiz davor",
|
||||
"create-note-into-inbox": "Neue Notiz in Inbox erstellen",
|
||||
"delete-notes": "Notizen löschen",
|
||||
"move-note-up": "Notiz nach oben verschieben",
|
||||
"move-note-down": "Notiz nach unten verschieben"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Öffne das Dialogfeld \"Zu Notiz springen\"",
|
||||
"search-in-subtree": "Nach Notizen im Unterbaum der aktuellen Notiz suchen",
|
||||
"expand-subtree": "Unterbaum der aktuellen Notiz ausklappen",
|
||||
"collapse-tree": "Gesamten Notizbaum einklappen",
|
||||
"collapse-subtree": "Unterbaum der aktuellen Notiz einklappen",
|
||||
"sort-child-notes": "Untergeordnete Notizen sortieren",
|
||||
"creating-and-moving-notes": "Notizen erstellen und verschieben",
|
||||
"create-note-into-inbox": "Erstelle eine Notiz im Posteingang (falls definiert) oder in der Tagesnotiz",
|
||||
"delete-note": "Notiz löschen",
|
||||
"move-note-up": "Notiz nach oben verschieben",
|
||||
"move-note-down": "Notiz nach unten verschieben",
|
||||
"move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben",
|
||||
"move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben",
|
||||
"edit-note-title": "Vom Notiz-Baum zur Notiz-Detailansicht springen und den Titel bearbeiten",
|
||||
"edit-branch-prefix": "Dialog zum Bearbeiten des Zweigpräfixes anzeigen",
|
||||
"note-clipboard": "Notiz-Zwischenablage",
|
||||
"copy-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus der Zwischenablage in die aktive Notiz einfügen",
|
||||
"cut-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage ausschneiden",
|
||||
"select-all-notes-in-parent": "Alle Notizen der aktuellen Notizenebene auswählen",
|
||||
"add-note-above-to-the-selection": "Notiz oberhalb der Auswahl hinzufügen",
|
||||
"add-note-below-to-selection": "Notiz unterhalb der Auswahl hinzufügen",
|
||||
"duplicate-subtree": "Unterbaum duplizieren",
|
||||
"tabs-and-windows": "Tabs & Fenster",
|
||||
"open-new-tab": "Neuen Tab öffnen",
|
||||
"close-active-tab": "Aktiven Tab schließen",
|
||||
"reopen-last-tab": "Zuletzt geschlossenen Tab wieder öffnen",
|
||||
"activate-next-tab": "Rechten Tab aktivieren",
|
||||
"activate-previous-tab": "Linken Tab aktivieren",
|
||||
"open-new-window": "Neues leeres Fenster öffnen",
|
||||
"toggle-tray": "Anwendung im Systemtray anzeigen/verstecken",
|
||||
"first-tab": "Ersten Tab in der Liste aktivieren",
|
||||
"second-tab": "Zweiten Tab in der Liste aktivieren",
|
||||
"third-tab": "Dritten Tab in der Liste aktivieren",
|
||||
"fourth-tab": "Vierten Tab in der Liste aktivieren",
|
||||
"fifth-tab": "Fünften Tab in der Liste aktivieren",
|
||||
"sixth-tab": "Sechsten Tab in der Liste aktivieren",
|
||||
"seventh-tab": "Siebten Tab in der Liste aktivieren",
|
||||
"eight-tab": "Achten Tab in der Liste aktivieren",
|
||||
"ninth-tab": "Neunten Tab in der Liste aktivieren",
|
||||
"last-tab": "Letzten Tab in der Liste aktivieren",
|
||||
"dialogs": "Dialoge",
|
||||
"show-note-source": "Notizquellen-Dialog anzeigen",
|
||||
"show-options": "Optionen-Dialog anzeigen",
|
||||
"show-revisions": "Notizrevisionen-Dialog anzeigen",
|
||||
"show-recent-changes": "Letzte Änderungen-Dialog anzeigen",
|
||||
"show-sql-console": "SQL-Konsole-Dialog anzeigen",
|
||||
"show-backend-log": "Backend-Logs-Dialog anzeigen",
|
||||
"text-note-operations": "Textnotiz-Operationen",
|
||||
"add-link-to-text": "Dialogfeld zum Hinzufügen eines Links zum Text öffnen",
|
||||
"follow-link-under-cursor": "Folge dem Link, unter dem Mauszeiger",
|
||||
"insert-date-and-time-to-text": "Aktuelles Datum & Uhrzeit in den Text einfügen",
|
||||
"paste-markdown-into-text": "Markdown aus der Zwischenablage in die Textnotiz einfügen",
|
||||
"cut-into-note": "Auswahl aus der aktuellen Notiz ausschneiden und eine Unternotiz mit dem ausgewählten Text erstellen",
|
||||
"add-include-note-to-text": "Notiz-Einfügen-Dialog öffnen",
|
||||
"edit-readonly-note": "Schreibgeschützte Notiz bearbeiten",
|
||||
"attributes-labels-and-relations": "Attribute (Labels & Verknüpfungen)",
|
||||
"add-new-label": "Neues Label erstellen",
|
||||
"create-new-relation": "Neue Verknüpfungen",
|
||||
"ribbon-tabs": "Ribbon-Tabs",
|
||||
"toggle-basic-properties": "Grundattribute umschalten",
|
||||
"toggle-file-properties": "Dateiattribute umschalten",
|
||||
"toggle-image-properties": "Bildattribute umschalten",
|
||||
"toggle-owned-attributes": "Eigene Attribute umschalten",
|
||||
"toggle-inherited-attributes": "Vererbte Attribute umschalten",
|
||||
"toggle-promoted-attributes": "Beworbene Attribute umschalten",
|
||||
"toggle-link-map": "Link-Karte umschalten",
|
||||
"toggle-note-info": "Notizinformationen umschalten",
|
||||
"toggle-note-paths": "Notizpfade umschalten",
|
||||
"toggle-similar-notes": "Ähnliche Notizen umschalten",
|
||||
"other": "Sonstige",
|
||||
"toggle-right-pane": "Anzeige der rechten Leiste umschalten, das Inhaltsverzeichnis und Markierungen enthält",
|
||||
"print-active-note": "Aktive Notiz drucken",
|
||||
"open-note-externally": "Notiz als Datei mit Standardanwendung öffnen",
|
||||
"render-active-note": "Aktive Notiz rendern (erneut rendern)",
|
||||
"run-active-note": "Aktive JavaScript(Frontend/Backend)-Codenotiz ausführen",
|
||||
"toggle-note-hoisting": "Notiz-Fokus der aktiven Notiz umschalten",
|
||||
"unhoist": "Notiz-Fokus aufheben",
|
||||
"reload-frontend-app": "Frontend-App neuladen",
|
||||
"open-dev-tools": "Entwicklertools öffnen",
|
||||
"toggle-left-note-tree-panel": "Linke Notizbaum-Leiste umschalten",
|
||||
"toggle-full-screen": "Vollbildmodus umschalten",
|
||||
"zoom-out": "Herauszoomen",
|
||||
"zoom-in": "Hineinzoomen",
|
||||
"note-navigation": "Notiznavigation",
|
||||
"reset-zoom-level": "Zoomlevel zurücksetzen",
|
||||
"copy-without-formatting": "Ausgewählten Text ohne Formatierung kopieren",
|
||||
"force-save-revision": "Erstellen / Speichern einer neuen Notizrevision der aktiven Notiz erzwingen",
|
||||
"show-help": "Eingebaute Hilfe / Cheat-Sheet anzeigen",
|
||||
"toggle-book-properties": "Buch-Eigenschaften umschalten",
|
||||
"clone-notes-to": "Ausgewählte Notizen duplizieren",
|
||||
"open-command-palette": "Kommandopalette öffnen",
|
||||
"export-as-pdf": "Aktuelle Notiz als PDF exportieren",
|
||||
"back-in-note-history": "Navigiere zur vorherigen Notiz im Verlauf",
|
||||
"forward-in-note-history": "Navigiere zur nächsten Notiz im Verlauf",
|
||||
"scroll-to-active-note": "Zum aktiven Notizbaumeintrag springen",
|
||||
"quick-search": "Schnellsuche öffnen",
|
||||
"create-note-after": "Erstelle eine neue Notiz nach der aktuellen Notiz",
|
||||
"create-note-into": "Unternotiz zur aktiven Notiz anlegen",
|
||||
"move-notes-to": "Ausgewählte Notizen verschieben",
|
||||
"show-cheatsheet": "Übersicht der Tastenkombinationen anzeigen",
|
||||
"find-in-text": "Suchleiste umschalten",
|
||||
"toggle-classic-editor-toolbar": "Schalte um zum Formatierungs-Tab für den Editor mit fester Werkzeugleiste",
|
||||
"toggle-zen-mode": "Zen-Modus ein-/ausschalten (reduzierte Benutzeroberfläche für ablenkungsfreies Arbeiten)"
|
||||
},
|
||||
"login": {
|
||||
"title": "Anmeldung",
|
||||
"heading": "Trilium Anmeldung",
|
||||
"incorrect-password": "Das Passwort ist falsch. Bitte versuche es erneut.",
|
||||
"password": "Passwort",
|
||||
"remember-me": "Angemeldet bleiben",
|
||||
"button": "Anmelden",
|
||||
"incorrect-totp": "Einmaltoken ist falsch. Bitte erneut versuchen.",
|
||||
"sign_in_with_sso": "Mit {{ ssoIssuerName }} anmelden"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Passwort festlegen",
|
||||
"heading": "Passwort festlegen",
|
||||
"description": "Bevor du Trilium im Web verwenden kannst, musst du zuerst ein Passwort festlegen. Du wirst dieses Passwort dann zur Anmeldung verwenden.",
|
||||
"password": "Passwort",
|
||||
"password-confirmation": "Passwortbestätigung",
|
||||
"button": "Passwort festlegen"
|
||||
},
|
||||
"javascript-required": "Trilium erfordert, dass JavaScript aktiviert ist.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes Setup",
|
||||
"new-document": "Ich bin ein neuer Benutzer und möchte ein neues Trilium-Dokument für meine Notizen erstellen",
|
||||
"sync-from-desktop": "Ich habe bereits eine Desktop-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"sync-from-server": "Ich habe bereits eine Server-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"next": "Weiter",
|
||||
"init-in-progress": "Dokumenteninitialisierung läuft",
|
||||
"redirecting": "Du wirst in Kürze zur Anwendung weitergeleitet.",
|
||||
"title": "Setup"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchronisation vom Desktop",
|
||||
"description": "Dieses Setup muss von der Desktop-Instanz aus initiiert werden:",
|
||||
"step1": "Öffne deine Trilium Notes Desktop-Instanz.",
|
||||
"step2": "Klicke im Trilium-Menü auf Optionen.",
|
||||
"step3": "Klicke auf die Kategorie Synchronisation.",
|
||||
"step4": "Ändere die Server-Instanzadresse auf: {{- host}} und klicke auf Speichern.",
|
||||
"step5": "Klicke auf den Button \"Test-Synchronisation\", um zu überprüfen, ob die Verbindung erfolgreich ist.",
|
||||
"step6": "Sobald du diese Schritte abgeschlossen hast, klicke auf {{- link}}.",
|
||||
"step6-here": "hier"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchronisation vom Server",
|
||||
"instructions": "Bitte gib unten die Trilium-Server-Adresse und die Zugangsdaten ein. Dies wird das gesamte Trilium-Dokument vom Server herunterladen und die Synchronisation einrichten. Je nach Dokumentgröße und Verbindungsgeschwindigkeit kann dies eine Weile dauern.",
|
||||
"server-host": "Trilium Server-Adresse",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Proxy-Server (optional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Hinweis:",
|
||||
"proxy-instruction": "Wenn du die Proxy-Einstellung leer lässt, wird der System-Proxy verwendet (gilt nur für die Desktop-Anwendung)",
|
||||
"password": "Passwort",
|
||||
"password-placeholder": "Passwort",
|
||||
"back": "Zurück",
|
||||
"finish-setup": "Setup abschließen"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation läuft",
|
||||
"successful": "Die Synchronisation wurde erfolgreich eingerichtet. Es wird eine Weile dauern, bis die erste Synchronisation abgeschlossen ist. Sobald dies erledigt ist, wirst du zur Anmeldeseite weitergeleitet.",
|
||||
"outstanding-items": "Ausstehende Synchronisationselemente:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Nicht gefunden",
|
||||
"heading": "Nicht gefunden"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "Übergeordnete Notiz:",
|
||||
"clipped-from": "Diese Notiz wurde ursprünglich von {{- url}} ausgeschnitten",
|
||||
"child-notes": "Untergeordnete Notizen:",
|
||||
"no-content": "Diese Notiz hat keinen Inhalt."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Montag",
|
||||
"tuesday": "Dienstag",
|
||||
"wednesday": "Mittwoch",
|
||||
"thursday": "Donnerstag",
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag"
|
||||
},
|
||||
"months": {
|
||||
"january": "Januar",
|
||||
"february": "Februar",
|
||||
"march": "März",
|
||||
"april": "April",
|
||||
"may": "Mai",
|
||||
"june": "Juni",
|
||||
"july": "Juli",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Suche:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
|
||||
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Versteckte Notizen",
|
||||
"search-history-title": "Suchverlauf",
|
||||
"note-map-title": "Notiz Karte",
|
||||
"sql-console-history-title": "SQL Konsolen Verlauf",
|
||||
"shared-notes-title": "Geteilte Notizen",
|
||||
"bulk-action-title": "Massenverarbeitung",
|
||||
"backend-log-title": "Backend Log",
|
||||
"user-hidden-title": "Versteckt vom Nutzer",
|
||||
"launch-bar-templates-title": "Startleiste Vorlagen",
|
||||
"base-abstract-launcher-title": "Basis Abstrakte Startleiste",
|
||||
"command-launcher-title": "Befehlslauncher",
|
||||
"note-launcher-title": "Notiz Launcher",
|
||||
"script-launcher-title": "Skript-Starter",
|
||||
"built-in-widget-title": "Eingebautes Widget",
|
||||
"spacer-title": "Freifeld",
|
||||
"custom-widget-title": "Benutzerdefiniertes Widget",
|
||||
"launch-bar-title": "Launchbar",
|
||||
"available-launchers-title": "Verfügbare Launchers",
|
||||
"go-to-previous-note-title": "Zur vorherigen Notiz gehen",
|
||||
"go-to-next-note-title": "Zur nächsten Notiz gehen",
|
||||
"new-note-title": "Neue Notiz",
|
||||
"search-notes-title": "Notizen durchsuchen",
|
||||
"calendar-title": "Kalender",
|
||||
"recent-changes-title": "neue Änderungen",
|
||||
"bookmarks-title": "Lesezeichen",
|
||||
"open-today-journal-note-title": "Heutigen Journaleintrag öffnen",
|
||||
"quick-search-title": "Schnellsuche",
|
||||
"protected-session-title": "Geschützte Sitzung",
|
||||
"sync-status-title": "Status Synchronisation",
|
||||
"settings-title": "Einstellungen",
|
||||
"options-title": "Optionen",
|
||||
"appearance-title": "Erscheinungsbild",
|
||||
"shortcuts-title": "Tastenkürzel",
|
||||
"text-notes": "Text Notizen",
|
||||
"code-notes-title": "Code Notizen",
|
||||
"images-title": "Bilder",
|
||||
"spellcheck-title": "Rechtschreibprüfung",
|
||||
"password-title": "Passwort",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sicherung",
|
||||
"sync-title": "Synchronisation",
|
||||
"other": "Weitere",
|
||||
"advanced-title": "Erweitert",
|
||||
"visible-launchers-title": "Sichtbare Launcher",
|
||||
"user-guide": "Nutzerhandbuch",
|
||||
"jump-to-note-title": "Springe zu...",
|
||||
"llm-chat-title": "Chat mit Notizen",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"localization": "Sprache & Region",
|
||||
"inbox-title": "Posteingang"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Neue Notiz",
|
||||
"duplicate-note-suffix": "(dopp)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Die Backend-Log-Datei '{{ fileName }}' existiert (noch) nicht.",
|
||||
"reading-log-failed": "Das Lesen der Backend-Log-Datei '{{ fileName }}' ist fehlgeschlagen."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Dieser Notiztyp kann nicht angezeigt werden."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF Dokument (*.pdf)",
|
||||
"unable-to-export-message": "Die aktuelle Notiz konnte nicht als PDF exportiert werden.",
|
||||
"unable-to-export-title": "Export als PDF fehlgeschlagen",
|
||||
"unable-to-save-message": "Die ausgewählte Datei konnte nicht beschrieben werden. Erneut versuchen oder ein anderes Ziel auswählen."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Trilium schließen",
|
||||
"recents": "Kürzliche Notizen",
|
||||
"bookmarks": "Lesezeichen",
|
||||
"today": "Heutigen Journal Eintrag öffnen",
|
||||
"new-note": "Neue Notiz",
|
||||
"show-windows": "Fenster anzeigen",
|
||||
"open_new_window": "Öffne neues Fenster"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"table": "Tabelle",
|
||||
"board_status_done": "Erledigt",
|
||||
"text-snippet": "Text Ausschnitt",
|
||||
"description": "Beschreibung",
|
||||
"list-view": "Listenansicht",
|
||||
"grid-view": "Gitteransicht",
|
||||
"calendar": "Kalender",
|
||||
"geo-map": "Geokarte",
|
||||
"start-date": "Startdatum",
|
||||
"end-date": "Enddatum",
|
||||
"start-time": "Startzeit",
|
||||
"end-time": "Endzeit",
|
||||
"geolocation": "Geolokation",
|
||||
"built-in-templates": "Integrierte Vorlagen",
|
||||
"board": "Tafel",
|
||||
"status": "Status",
|
||||
"board_note_first": "Erste Notiz",
|
||||
"board_note_second": "Zweite Notiz",
|
||||
"board_note_third": "Dritte Notiz",
|
||||
"board_status_todo": "To-Do",
|
||||
"board_status_progress": "In Bearbeitung"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"copy-notes-to-clipboard": "Notizen in Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus Zwischenablage einfügen",
|
||||
"back-in-note-history": "Zurück im Notizverlauf",
|
||||
"forward-in-note-history": "Vorwärts im Notizverlauf",
|
||||
"jump-to-note": "Wechseln zu...",
|
||||
"command-palette": "Befehlsübersicht",
|
||||
"scroll-to-active-note": "Zur aktiven Notiz scrollen",
|
||||
"quick-search": "Schnellsuche",
|
||||
"search-in-subtree": "In Unterzweig suchen",
|
||||
"expand-subtree": "Unterzweig aufklappen",
|
||||
"collapse-tree": "Baumstruktur einklappen",
|
||||
"collapse-subtree": "Unterzweig einklappen",
|
||||
"sort-child-notes": "Unternotizen sortieren",
|
||||
"create-note-after": "Erstelle eine neue Notiz dahinter",
|
||||
"create-note-into": "Erstelle eine neue Notiz davor",
|
||||
"create-note-into-inbox": "Neue Notiz in Inbox erstellen",
|
||||
"delete-notes": "Notizen löschen",
|
||||
"move-note-up": "Notiz nach oben verschieben",
|
||||
"move-note-down": "Notiz nach unten verschieben",
|
||||
"edit-note-title": "Bearbeite Notiz Titel",
|
||||
"clone-notes-to": "Vervielfältige Notiz nach",
|
||||
"move-notes-to": "Verschiebe Notiz nach",
|
||||
"cut-notes-to-clipboard": "Notizen in Zwischenablage ausschneiden",
|
||||
"add-note-above-to-selection": "Notiz oberhalb der Selektion hinzufügen",
|
||||
"add-note-below-to-selection": "Notiz unterhalb der Selektion hinzufügen",
|
||||
"open-new-tab": "Öffne im neuen Tab",
|
||||
"close-active-tab": "Schließe aktiven Tab",
|
||||
"reopen-last-tab": "Öffne zuletzt geschlossenen Tab",
|
||||
"activate-next-tab": "Aktiviere nächsten Tab",
|
||||
"activate-previous-tab": "Aktiviere vorherigen Tab",
|
||||
"open-new-window": "Öffne im neuen Fenster",
|
||||
"toggle-system-tray-icon": "Systemablage-Symbol umschalten",
|
||||
"toggle-zen-mode": "Zen-Modus umschalten",
|
||||
"switch-to-first-tab": "Wechsle zum ersten Tab",
|
||||
"switch-to-second-tab": "Wechsle zum zweiten Tab",
|
||||
"switch-to-third-tab": "Wechsle zum dritten Tab",
|
||||
"switch-to-fourth-tab": "Wechsle zum vierten Tab",
|
||||
"switch-to-fifth-tab": "Wechsle zum fünften Tab",
|
||||
"switch-to-sixth-tab": "Wechsle zum sechsten Tab",
|
||||
"switch-to-seventh-tab": "Wechsle zum siebten Tab",
|
||||
"switch-to-eighth-tab": "Wechsle zum achten Tab",
|
||||
"switch-to-ninth-tab": "Wechsle zum neunten Tab",
|
||||
"switch-to-last-tab": "Wechsle zum letzten Tab",
|
||||
"show-note-source": "Zeige Notiz Quelle",
|
||||
"show-options": "Zeige Optionen",
|
||||
"show-revisions": "Zeige Revisionen",
|
||||
"show-recent-changes": "Zeige letzte Änderungen",
|
||||
"show-sql-console": "Zeige SQL Konsole",
|
||||
"show-backend-log": "Zeige Backend-Protokoll",
|
||||
"show-help": "Zeige Hilfe",
|
||||
"show-cheatsheet": "Zeige Cheatsheet",
|
||||
"add-link-to-text": "Link zum Text hinzufügen",
|
||||
"cut-into-note": "In neue Notiz verschieben",
|
||||
"add-new-label": "Neues Label hinzufügen",
|
||||
"add-new-relation": "Neue Beziehung hinzufügen",
|
||||
"print-active-note": "Drucke aktive Notiz",
|
||||
"export-active-note-as-pdf": "Exportiere aktive Notiz als PDF",
|
||||
"move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben",
|
||||
"move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben",
|
||||
"edit-branch-prefix": "Zweigpräfix bearbeiten",
|
||||
"select-all-notes-in-parent": "Alle Notizen in übergeordnetem Element auswählen",
|
||||
"duplicate-subtree": "Unterbaum duplizieren",
|
||||
"follow-link-under-cursor": "Folge Link unterhalb des Mauszeigers",
|
||||
"insert-date-and-time-to-text": "Datum und Uhrzeit in Text einfügen",
|
||||
"paste-markdown-into-text": "Markdown in Text einfügen",
|
||||
"add-include-note-to-text": "Notiz in Text einfügen",
|
||||
"edit-read-only-note": "Schreibgeschützte Notiz bearbeiten",
|
||||
"toggle-ribbon-tab-classic-editor": "Registerkarte Klassischer Editor umschalten",
|
||||
"toggle-ribbon-tab-basic-properties": "Registerkarte Grundlegende Eigenschaften umschalten",
|
||||
"toggle-ribbon-tab-book-properties": "Registerkarte Buch-Eigenschaften umschalten",
|
||||
"toggle-ribbon-tab-file-properties": "Registerkarte Datei-Eigenschaften umschalten",
|
||||
"toggle-ribbon-tab-image-properties": "Registerkarte Bilder-Eigenschaften umschalten",
|
||||
"toggle-ribbon-tab-owned-attributes": "Registerkarte Besitzerattribute umschalten",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Registerkarte geerbte Attribute umschalten",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Registerkarte verliehene Attribute umschalten",
|
||||
"toggle-ribbon-tab-note-map": "Registerkarte Notizkarte umschalten",
|
||||
"toggle-ribbon-tab-note-info": "Registerkarte Notiz-Info umschalten",
|
||||
"toggle-ribbon-tab-note-paths": "Registerkarte Notiz-Pfad umschalten",
|
||||
"toggle-ribbon-tab-similar-notes": "Registerkarte ähnliche Notizen umschalten",
|
||||
"toggle-right-pane": "Rechten Bereich ein-/ausblenden",
|
||||
"open-note-externally": "Notiz extern öffnen",
|
||||
"render-active-note": "Aktive Notiz rendern",
|
||||
"run-active-note": "Aktive Notiz ausführen",
|
||||
"toggle-note-hoisting": "Notiz hochziehen umschalten",
|
||||
"unhoist-note": "Notiz hochziehen rückgängig machen",
|
||||
"reload-frontend-app": "Oberfläche neu laden",
|
||||
"open-developer-tools": "Öffne Entwickler-Tools",
|
||||
"find-in-text": "Im Text suchen",
|
||||
"toggle-left-pane": "Linken Bereich ein-/ausblenden",
|
||||
"toggle-full-screen": "Vollbild-Modus de-/aktivieren",
|
||||
"zoom-out": "Vergrößern",
|
||||
"zoom-in": "Verkleinern",
|
||||
"reset-zoom-level": "Zoom zurücksetzen",
|
||||
"copy-without-formatting": "Kopieren ohne Formatierung",
|
||||
"force-save-revision": "Speichern der Notizrevision erzwingen"
|
||||
},
|
||||
"weekdayNumber": "Woche {weekNumber}",
|
||||
"quarterNumber": "Quartal {quarterNumber}",
|
||||
"migration": {
|
||||
"old_version": "Eine direkte Migration von Ihrer aktuellen Version wird nicht unterstützt. Bitte führen Sie zunächst ein Upgrade auf die neueste Version v0.60.4 durch und erst dann auf diese Version.",
|
||||
"error_message": "Fehler bei der Migration zu Version {{version}}: {{stack}}",
|
||||
"wrong_db_version": "Die Version der Datenbank ({{version}}) ist neuer als die von der Anwendung erwartete Version ({{targetVersion}}), was bedeutet, dass diese mit einer neueren und inkompatiblen Version von Trilium erstellt wurde. Führen Sie ein Upgrade auf die neueste Version von Trilium durch, um dieses Problem zu beheben."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Fehler"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Webseite Stil",
|
||||
"search_placeholder": "Suche...",
|
||||
"image_alt": "Artikel Bild",
|
||||
"last-updated": "Zuletzt aktualisiert am {{- date}}",
|
||||
"subpages": "Unterseiten:",
|
||||
"on-this-page": "Auf dieser Seite",
|
||||
"expand": "Erweitern"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,9 @@
|
||||
"incorrect-password": "Le mot de passe est incorrect. Veuillez réessayer.",
|
||||
"password": "Mot de passe",
|
||||
"remember-me": "Se souvenir de moi",
|
||||
"button": "Connexion"
|
||||
"button": "Connexion",
|
||||
"sign_in_with_sso": "Se connecter avec {{ ssoIssuerName }}",
|
||||
"incorrect-totp": "TOTP incorrect. Veuillez réessayer."
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Définir un mot de passe",
|
||||
@@ -350,6 +352,29 @@
|
||||
"add-include-note-to-text": "Ajouter une note inclusion au texte",
|
||||
"edit-read-only-note": "Modifier une note en lecture seule",
|
||||
"add-new-label": "Ajouter une nouvelle étiquette",
|
||||
"add-new-relation": "Ajouter une nouvelle relation"
|
||||
"add-new-relation": "Ajouter une nouvelle relation",
|
||||
"toggle-ribbon-tab-classic-editor": "Basculer l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"toggle-ribbon-tab-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-ribbon-tab-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-ribbon-tab-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-ribbon-tab-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-ribbon-tab-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-right-pane": "Afficher le panneau de droite",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"export-active-note-as-pdf": "Exporter la note active en PDF",
|
||||
"open-note-externally": "Ouvrir la note à l'extérieur",
|
||||
"render-active-note": "Faire un rendu de la note active",
|
||||
"run-active-note": "Lancer la note active",
|
||||
"reload-frontend-app": "Recharger l'application Frontend",
|
||||
"open-developer-tools": "Ouvrir les outils développeur",
|
||||
"find-in-text": "Chercher un texte",
|
||||
"toggle-left-pane": "Afficher le panneau de gauche",
|
||||
"toggle-full-screen": "Passer en mode plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"reset-zoom-level": "Réinitilaliser le zoom",
|
||||
"copy-without-formatting": "Copier sans mise en forme",
|
||||
"force-save-revision": "Forcer la sauvegarde de la révision"
|
||||
}
|
||||
}
|
||||
|
||||
43
apps/server/src/assets/translations/ko/server.json
Normal file
43
apps/server/src/assets/translations/ko/server.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "기록 내 이전 노트로 이동하기",
|
||||
"open-command-palette": "명령어 팔레트 열기",
|
||||
"delete-note": "노트 삭제",
|
||||
"quick-search": "빠른 검색바 활성화",
|
||||
"move-note-up": "노트를 위로 이동",
|
||||
"move-note-down": "노트를 아래로 이동",
|
||||
"move-note-up-in-hierarchy": "노트를 상위 계층으로 이동",
|
||||
"move-note-down-in-hierarchy": "노트를 하위 계층으로 이동",
|
||||
"sort-child-notes": "자식 노트 정렬",
|
||||
"forward-in-note-history": "기록 내 다음 노트로 이동하기",
|
||||
"open-jump-to-note-dialog": "\"노트로 이동하기\" 대화창 열기",
|
||||
"scroll-to-active-note": "노트 트리를 활성화된 노트로 스크롤하기",
|
||||
"search-in-subtree": "활성화된 노트 하위에서 노트 찾기",
|
||||
"expand-subtree": "현재 노트의 서브트리 펼치기",
|
||||
"collapse-tree": "전체 노트 트리 접기",
|
||||
"collapse-subtree": "현재 노트의 서브트리 접기",
|
||||
"creating-and-moving-notes": "노트 만들기 및 이동하기",
|
||||
"create-note-after": "노트 활성화 후 노트 만들기",
|
||||
"create-note-into": "활성화된 노트 하위에 노트 만들기",
|
||||
"create-note-into-inbox": "받은 편지함(정의된 경우) 또는 당일 노트에 노트 만들기",
|
||||
"edit-note-title": "트리에서 노트 세부 사항으로 이동하고 제목 편집하기",
|
||||
"edit-branch-prefix": "\"브랜치 접두사 편집\" 대화창 표시하기",
|
||||
"clone-notes-to": "선택된 노트들을 복사하기",
|
||||
"move-notes-to": "선택된 노트들을 이동하기",
|
||||
"note-clipboard": "노트 클립보드",
|
||||
"copy-notes-to-clipboard": "선택된 노트들을 클립보드에 복사하기",
|
||||
"paste-notes-from-clipboard": "클립보드 내 노트를 활성화된 노트에 붙여넣기",
|
||||
"cut-notes-to-clipboard": "선택된 노트들을 클립보드에 잘라내기",
|
||||
"select-all-notes-in-parent": "현재 노트 레벨에서 모든 노트 선택하기",
|
||||
"add-note-above-to-the-selection": "선택한 영역 위에 노트 추가하기",
|
||||
"add-note-below-to-selection": "선택한 영역 아래에 노트 추가하기",
|
||||
"duplicate-subtree": "서브트리 복사하기",
|
||||
"open-new-tab": "새 탭 열기",
|
||||
"close-active-tab": "활성화된 탭 닫기",
|
||||
"reopen-last-tab": "마지막으로 닫은 탭 열기",
|
||||
"activate-next-tab": "우측 탭 활성화",
|
||||
"activate-previous-tab": "좌측 탭 활성화",
|
||||
"open-new-window": "새 비어있는 창 열기",
|
||||
"toggle-tray": "시스템 트레이에서 애플리케이션 보여주기/숨기기"
|
||||
}
|
||||
}
|
||||
12
apps/server/src/assets/translations/nl/server.json
Normal file
12
apps/server/src/assets/translations/nl/server.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Navigeer naar vorige notitie in geschiedenis",
|
||||
"forward-in-note-history": "Navigeer naar de volgende notitie in de geschiedenis",
|
||||
"open-jump-to-note-dialog": "Open het dialoogvenster 'Ga naar notitie'",
|
||||
"open-command-palette": "Open het opdrachtvenster",
|
||||
"scroll-to-active-note": "Scroll naar actieve notitie in de notitieboom",
|
||||
"quick-search": "Snelle zoekbalk activeren",
|
||||
"search-in-subtree": "Zoek naar notities in de subboom van de actieve notitie",
|
||||
"expand-subtree": "Subboom van huidige notitie uitbreiden"
|
||||
}
|
||||
}
|
||||
@@ -71,10 +71,10 @@
|
||||
"add-new-label": "Создать новую заметку",
|
||||
"create-new-relation": "Создать новое отношение",
|
||||
"ribbon-tabs": "Вкладки ленты",
|
||||
"toggle-basic-properties": "Перейти к основным свойствам",
|
||||
"toggle-basic-properties": "Перейти к общим параметрам",
|
||||
"toggle-file-properties": "Перейти к свойствам файла",
|
||||
"toggle-image-properties": "Перейти к свойствам изображения",
|
||||
"toggle-owned-attributes": "Перейти к собственным атрибутами",
|
||||
"toggle-owned-attributes": "Перейти к атрибутам",
|
||||
"toggle-inherited-attributes": "Перейти к унаследованным атрибутам",
|
||||
"toggle-promoted-attributes": "Перейти к продвигаемым атрибутам",
|
||||
"toggle-link-map": "Перейти к картуессылок",
|
||||
@@ -100,7 +100,10 @@
|
||||
"toggle-book-properties": "Перейти к свойствам коллекции",
|
||||
"toggle-classic-editor-toolbar": "Перейти на вкладку «Форматирование» для редактора с фиксированной панелью инструментов",
|
||||
"export-as-pdf": "Экспортировать текущую заметку в формате PDF",
|
||||
"toggle-zen-mode": "Включает/отключает режим дзен (минимальный пользовательский интерфейс для фокусирования)"
|
||||
"toggle-zen-mode": "Включает/отключает режим дзен (минимальный пользовательский интерфейс для фокусирования)",
|
||||
"toggle-note-hoisting": "Переключить закрепление активной заметки",
|
||||
"unhoist": "Убрать закрепление везде",
|
||||
"force-save-revision": "Принудительное создать/сохранить снимок версии активной заметки"
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"localization": "Язык и регион",
|
||||
@@ -147,12 +150,13 @@
|
||||
"visible-launchers-title": "Видимые лаунчеры",
|
||||
"user-guide": "Руководство пользователя",
|
||||
"sql-console-history-title": "История консоли SQL",
|
||||
"user-hidden-title": "Пользователь скрыт",
|
||||
"user-hidden-title": "Скрытый пользователь",
|
||||
"launch-bar-templates-title": "Шаблоны панели запуска",
|
||||
"base-abstract-launcher-title": "Базовый абстрактный лаунчер",
|
||||
"go-to-previous-note-title": "К предыдущей заметке",
|
||||
"go-to-next-note-title": "К следующей заметке",
|
||||
"open-today-journal-note-title": "Открыть сегодняшнюю заметку в журнале"
|
||||
"open-today-journal-note-title": "Открыть сегодняшнюю заметку в журнале",
|
||||
"llm-chat-title": "ИИ чат с заметками"
|
||||
},
|
||||
"tray": {
|
||||
"bookmarks": "Закладки",
|
||||
@@ -160,7 +164,9 @@
|
||||
"close": "Выйти из Trilium",
|
||||
"recents": "Последние заметки",
|
||||
"new-note": "Новая заметка",
|
||||
"show-windows": "Показать окна"
|
||||
"show-windows": "Показать окна",
|
||||
"open_new_window": "Открыть новое окно",
|
||||
"today": "Открыть заметку дня"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"scroll-to-active-note": "Прокрутить к активной заметке",
|
||||
@@ -238,7 +244,26 @@
|
||||
"toggle-full-screen": "Переключить на полный экран",
|
||||
"reset-zoom-level": "Сбросить уровень масштабирования",
|
||||
"copy-without-formatting": "Копировать без форматирования",
|
||||
"force-save-revision": "Принудительное сохранение версии"
|
||||
"force-save-revision": "Принудительное сохранение версии",
|
||||
"unhoist-note": "Открепить заметку",
|
||||
"toggle-right-pane": "Переключить правую панель",
|
||||
"print-active-note": "Печать активной заметки",
|
||||
"render-active-note": "Рендеринг активной заметки",
|
||||
"run-active-note": "Запуск активной заметки",
|
||||
"add-include-note-to-text": "Добавить включение другой заметки в текст",
|
||||
"toggle-ribbon-tab-basic-properties": "Переключить на вкладку \"Общее\"",
|
||||
"toggle-ribbon-tab-book-properties": "Переключить на вкладку \"Свойства книги\"",
|
||||
"toggle-ribbon-tab-file-properties": "Переключить на вкладку \"Свойства файла\"",
|
||||
"toggle-ribbon-tab-image-properties": "Переключить на вкладку \"Свойства изображения\"",
|
||||
"toggle-ribbon-tab-owned-attributes": "Переключить на вкладку \"Атрибуты\"",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Переключить на вкладку \"Унаследованные атрибуты\"",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Переключить на вкладку \"Продвигаемые атрибуты\"",
|
||||
"toggle-ribbon-tab-note-map": "Переключить на вкладку \"Карта заметок\"",
|
||||
"toggle-ribbon-tab-note-info": "Переключить на вкладку \"Информация о заметке\"",
|
||||
"toggle-ribbon-tab-note-paths": "Переключить на вкладку \"Пути к заметке\"",
|
||||
"toggle-ribbon-tab-similar-notes": "Переключить на вкладку \"Похожие заметки\"",
|
||||
"export-active-note-as-pdf": "Экспортировать активную заметку в формате PDF",
|
||||
"toggle-note-hoisting": "Переключить закрепление заметки"
|
||||
},
|
||||
"months": {
|
||||
"august": "Август",
|
||||
@@ -318,7 +343,8 @@
|
||||
},
|
||||
"notes": {
|
||||
"duplicate-note-suffix": "(дубликат)",
|
||||
"new-note": "Новая заметка"
|
||||
"new-note": "Новая заметка",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Ошибка"
|
||||
@@ -328,7 +354,9 @@
|
||||
"subpages": "Подстраницы:",
|
||||
"expand": "Развернуть",
|
||||
"site-theme": "Тема оформления сайта",
|
||||
"image_alt": "Изображение статьи"
|
||||
"image_alt": "Изображение статьи",
|
||||
"on-this-page": "На текущей странице",
|
||||
"last-updated": "Последнее обновление: {{- date}}"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"description": "Описание",
|
||||
@@ -359,12 +387,14 @@
|
||||
},
|
||||
"share_page": {
|
||||
"child-notes": "Дочерние заметки:",
|
||||
"no-content": "Эта заметка пуста."
|
||||
"no-content": "Эта заметка пуста.",
|
||||
"parent": "родитель:",
|
||||
"clipped-from": "Эта заметка изначально была вырезана из {{- url}}"
|
||||
},
|
||||
"javascript-required": "Для работы Trilium требуется JavaScript.",
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Синхронизация с приложения ПК",
|
||||
"description": "Эту настройку необходимо инициировать из приложения для ПК.",
|
||||
"description": "Эту настройку необходимо инициировать из приложения для ПК:",
|
||||
"step1": "Откройте приложение Trilium Notes на ПК.",
|
||||
"step2": "В меню Trilium выберите «Параметры».",
|
||||
"step3": "Нажмите на категорию «Синхронизация».",
|
||||
@@ -376,5 +406,23 @@
|
||||
"test_sync": {
|
||||
"not-configured": "Адрес сервера синхронизации не установлен. Сначала настройте синхронизацию.",
|
||||
"successful": "Установление связи с сервером синхронизации прошло успешно, синхронизация начата."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Документ PDF (*.pdf)",
|
||||
"unable-to-save-message": "Не удалось записать выбранный файл. Попробуйте ещё раз или выберите другой путь.",
|
||||
"unable-to-export-message": "Текущую заметку невозможно экспортировать в формате PDF.",
|
||||
"unable-to-export-title": "Невозможно экспортировать в PDF"
|
||||
},
|
||||
"migration": {
|
||||
"wrong_db_version": "Версия базы данных ({{version}}) новее, чем та, которую ожидает приложение ({{targetVersion}}). Это означает, что она была создана более новой и несовместимой версией Trilium. Для решения этой проблемы обновите Trilium до последней версии.",
|
||||
"old_version": "Прямая миграция с текущей версии не поддерживается. Сначала обновитесь до последней версии 0.60.4, а затем — до этой.",
|
||||
"error_message": "Ошибка при миграции на версию {{version}}: {{stack}}"
|
||||
},
|
||||
"backend_log": {
|
||||
"reading-log-failed": "Не удалось прочитать файл лога бэкенда '{{ fileName }}'.",
|
||||
"log-does-not-exist": "Файл лога бэкенда '{{ fileName }}' (пока) не существует."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Этот тип заметки не может быть отображен."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "備份",
|
||||
"sync-title": "同步",
|
||||
"ai-llm-title": "AI / LLM",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"other": "其他",
|
||||
"advanced-title": "進階",
|
||||
"visible-launchers-title": "可見啟動器",
|
||||
|
||||
@@ -2,40 +2,40 @@
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Перейти до попередньої нотатки в історії",
|
||||
"forward-in-note-history": "Перейти до наступної нотатки в історії",
|
||||
"open-command-palette": "Відкрити панель команд",
|
||||
"scroll-to-active-note": "Прокрутити дерево нотаток до активної нотатки",
|
||||
"quick-search": "Показати панель швидкого пошуку",
|
||||
"open-command-palette": "Відкрити палітру команд",
|
||||
"scroll-to-active-note": "Прокрутити дерево до активної нотатки",
|
||||
"quick-search": "Активувати панель швидкого пошуку",
|
||||
"search-in-subtree": "Пошук нотаток в піддереві активної нотатки",
|
||||
"expand-subtree": "Розкрити піддерево поточної нотатки",
|
||||
"expand-subtree": "Розгорнути піддерево поточної нотатки",
|
||||
"collapse-tree": "Згорнути все дерево нотаток",
|
||||
"open-jump-to-note-dialog": "Відкрити вікно \"Перейти до нотатки\"",
|
||||
"open-jump-to-note-dialog": "Відкрити діалог \"Перейти до нотатки\"",
|
||||
"collapse-subtree": "Згорнути піддерево поточної нотатки",
|
||||
"sort-child-notes": "Сортувати дочірні нотатки",
|
||||
"creating-and-moving-notes": "Створення та переміщення нотаток",
|
||||
"create-note-after": "Створити нотатку після активної нотатки",
|
||||
"create-note-into": "Створити нотатку як дочірній елемент активної нотатки",
|
||||
"create-note-into-inbox": "Створити нотатку у «Вхідні» (якщо визначено) або в щоденнику",
|
||||
"create-note-into": "Створити нотатку як дочірню до активної нотатки",
|
||||
"create-note-into-inbox": "Створити нотатку у вхідні (якщо визначено) або денну нотатку",
|
||||
"delete-note": "Видалити нотатку",
|
||||
"move-note-up": "Перемістити нотатку вгору",
|
||||
"move-note-down": "Перемістити нотатку вниз",
|
||||
"move-note-up-in-hierarchy": "Перемістити нотатку вище в ієрархії",
|
||||
"move-note-down-in-hierarchy": "Перемістити нотатку вниз в ієрархії",
|
||||
"edit-note-title": "Перейти від дерева до деталей нотатки та редагувати заголовок",
|
||||
"edit-branch-prefix": "Показати вікно \"Редагувати префікс гілки\"",
|
||||
"edit-branch-prefix": "Показати діалог \"Редагувати префікс гілки\"",
|
||||
"clone-notes-to": "Клонувати вибрані нотатки",
|
||||
"move-notes-to": "Перемістити вибрані нотатки",
|
||||
"note-clipboard": "Буфер обміну нотаток",
|
||||
"note-clipboard": "Буфер обміну нотатки",
|
||||
"copy-notes-to-clipboard": "Копіювати вибрані нотатки в буфер обміну",
|
||||
"paste-notes-from-clipboard": "Вставити нотатки з буфера обміну в активну нотатку",
|
||||
"paste-notes-from-clipboard": "Вставити нотатки з буферу обміну в активну нотатку",
|
||||
"cut-notes-to-clipboard": "Вирізати вибрані нотатки в буфер обміну",
|
||||
"select-all-notes-in-parent": "Вибрати всі нотатки з поточного рівня нотаток",
|
||||
"add-note-above-to-the-selection": "Додати нотатку вище до виділення",
|
||||
"add-note-above-to-the-selection": "Додати нотатку вище до вибраного",
|
||||
"add-note-below-to-selection": "Додати нотатку нижче до вибраного",
|
||||
"duplicate-subtree": "Дублікат гілок дерева",
|
||||
"duplicate-subtree": "Дублювання піддерева",
|
||||
"tabs-and-windows": "Вкладки & Вікна",
|
||||
"open-new-tab": "Відкрити нову вкладку",
|
||||
"close-active-tab": "Закрити активну вкладку",
|
||||
"reopen-last-tab": "Знову відкрити останню закриту вкладку",
|
||||
"reopen-last-tab": "Відкрити останню закриту вкладку",
|
||||
"activate-next-tab": "Активувати вкладку праворуч",
|
||||
"activate-previous-tab": "Активувати вкладку ліворуч",
|
||||
"open-new-window": "Відкрити нове порожнє вікно",
|
||||
@@ -51,48 +51,48 @@
|
||||
"ninth-tab": "Активувати дев'яту вкладку у списку",
|
||||
"last-tab": "Активувати останню вкладку у списку",
|
||||
"dialogs": "Діалоги",
|
||||
"show-note-source": "Показати вікно «Джерело нотатки»",
|
||||
"show-options": "Відкрити \"Параметри\"",
|
||||
"show-revisions": "Показати вікно \"Зміни нотаток\"",
|
||||
"show-recent-changes": "Показати вікно \"Останні зміни\"",
|
||||
"show-sql-console": "Відкрити \"Консоль SQL\"",
|
||||
"show-backend-log": "Відкрити \"Backend Log\"",
|
||||
"show-note-source": "Показати діалог \"Джерело нотатки\"",
|
||||
"show-options": "Відкрити сторінку \"Параметри\"",
|
||||
"show-revisions": "Показати діалог \"Версії нотаток\"",
|
||||
"show-recent-changes": "Показати діалог \"Останні зміни\"",
|
||||
"show-sql-console": "Відкрити сторінку \"Консоль SQL\"",
|
||||
"show-backend-log": "Відкрити сторінку \"Backend Log\"",
|
||||
"show-help": "Відкрити вбудований Посібник користувача",
|
||||
"show-cheatsheet": "Показати вікно зі стандартними діями клавіатури",
|
||||
"text-note-operations": "Дії з текстовими нотатками",
|
||||
"add-link-to-text": "Відкрити діалогове вікно для додавання посилання до тексту",
|
||||
"follow-link-under-cursor": "Перейдіть за посиланням, на якому знаходиться курсор",
|
||||
"insert-date-and-time-to-text": "Вставити поточну дату та час у текст",
|
||||
"add-link-to-text": "Відкрити діалог для додавання посилання до тексту",
|
||||
"follow-link-under-cursor": "Перехід за посиланням, на якому знаходиться курсор",
|
||||
"insert-date-and-time-to-text": "Вставити поточну дату & час у текст",
|
||||
"paste-markdown-into-text": "Вставити Markdown з буфера обміну в текстову нотатку",
|
||||
"cut-into-note": "Вирізати виділений фрагмент із поточної нотатки та створити піднотатку з виділеним текстом",
|
||||
"add-include-note-to-text": "Відкрити вікно для додавання примітки",
|
||||
"add-include-note-to-text": "Відкрити діалог для додавання нотатки",
|
||||
"edit-readonly-note": "Редагувати нотатку, доступну тільки для читання",
|
||||
"attributes-labels-and-relations": "Атрибути (мітки та зв'язки)",
|
||||
"attributes-labels-and-relations": "Атрибути (мітки & зв'язки)",
|
||||
"add-new-label": "Створити нову мітку",
|
||||
"create-new-relation": "Створити новий зв'язок",
|
||||
"ribbon-tabs": "Вкладки стрічки",
|
||||
"toggle-basic-properties": "Увімкнути Основні властивості",
|
||||
"toggle-file-properties": "Увімкнути Властивості файлу",
|
||||
"toggle-image-properties": "Увімкнути Властивості зображення",
|
||||
"toggle-owned-attributes": "Увімкнути Власні атрибути",
|
||||
"toggle-inherited-attributes": "Увімкнути Успадковані атрибути",
|
||||
"toggle-promoted-attributes": "Увімкнути Рекламні атрибути",
|
||||
"toggle-link-map": "Увімкнути Карта посилань",
|
||||
"toggle-note-info": "Увімкнути Інформація про нотатку",
|
||||
"toggle-note-paths": "Увімкнути Шляхі нотатки",
|
||||
"toggle-similar-notes": "Увімкнути Схожі нотатки",
|
||||
"toggle-basic-properties": "Увімкнути Основні Властивості",
|
||||
"toggle-file-properties": "Увімкнути Властивості Файлу",
|
||||
"toggle-image-properties": "Увімкнути Властивості Зображення",
|
||||
"toggle-owned-attributes": "Увімкнути Власні Атрибути",
|
||||
"toggle-inherited-attributes": "Увімкнути Успадковані Атрибути",
|
||||
"toggle-promoted-attributes": "Увімкнути Просунуті Атрибути",
|
||||
"toggle-link-map": "Увімкнути Карта Посилань",
|
||||
"toggle-note-info": "Увімкнути Інформація про Нотатку",
|
||||
"toggle-note-paths": "Увімкнути Шляхи Нотатки",
|
||||
"toggle-similar-notes": "Увімкнути Схожі Нотатки",
|
||||
"other": "Інше",
|
||||
"toggle-right-pane": "Увімкнути відображення правої панелі, яка містить Зміст та Основні моменти",
|
||||
"print-active-note": "Друк активної нотатки",
|
||||
"open-note-externally": "Відкрити нотатку як файл у програмі за замовчуванням",
|
||||
"render-active-note": "Відтворити (повторно відтворити) активну нотатку",
|
||||
"run-active-note": "Виконати активний код JavaScript (фронтенд/бекенд) нотатки",
|
||||
"toggle-note-hoisting": "Увімкнути Хостинг активної нотатки",
|
||||
"unhoist": "Зніміть з будь-якого місця",
|
||||
"render-active-note": "Рендеринг (перерендерінг) активної нотатки",
|
||||
"run-active-note": "Виконати активний код JavaScript (frontend/backend) нотатки з кодом",
|
||||
"toggle-note-hoisting": "Увімкнути хостинг активної нотатки",
|
||||
"unhoist": "Відкріпити з будь-якого місця",
|
||||
"reload-frontend-app": "Перезавантажити інтерфейс",
|
||||
"open-dev-tools": "Відкрити інструменти розробника",
|
||||
"find-in-text": "Увімкнути панель пошуку",
|
||||
"toggle-left-note-tree-panel": "Увімкнути ліву панель (дерево нотаток)",
|
||||
"toggle-left-note-tree-panel": "Увімкнути ліву панель (дерево нотатки)",
|
||||
"toggle-full-screen": "Увімкнути повноекранний режим",
|
||||
"zoom-out": "Зменшити масштаб",
|
||||
"zoom-in": "Збільшити масштаб",
|
||||
@@ -100,66 +100,107 @@
|
||||
"reset-zoom-level": "Скинути рівень масштабування",
|
||||
"copy-without-formatting": "Копіювати виділений текст без форматування",
|
||||
"force-save-revision": "Примусове створення/збереження нової версії активної нотатки",
|
||||
"toggle-book-properties": "Увімкнути Властивості колекції",
|
||||
"toggle-book-properties": "Увімкнути Властивості Колекції",
|
||||
"toggle-classic-editor-toolbar": "Увімкнути вкладку Форматування для редактора з фіксованою панеллю інструментів",
|
||||
"export-as-pdf": "Експортувати поточну нотатку у PDF",
|
||||
"toggle-zen-mode": "Вмикає/вимикає дзен-режим (мінімальний інтерфейс для більш цілеспрямованого редагування)"
|
||||
"export-as-pdf": "Експортувати поточну нотатку в PDF",
|
||||
"toggle-zen-mode": "Вмикає/вимикає Дзен-режим (мінімальний інтерфейс сфокусованого редагування)"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"back-in-note-history": "Назад до Історії нотаток",
|
||||
"back-in-note-history": "Назад в Історії нотаток",
|
||||
"forward-in-note-history": "Вперед в Історії нотаток",
|
||||
"jump-to-note": "Перейти до...",
|
||||
"command-palette": "Палітра команд",
|
||||
"command-palette": "Палітра Команд",
|
||||
"scroll-to-active-note": "Прокрутити до Активної нотатки",
|
||||
"quick-search": "Швидкий пошук",
|
||||
"search-in-subtree": "Пошук у піддереві",
|
||||
"expand-subtree": "Розгорнути піддерево",
|
||||
"collapse-tree": "Згорнути дерево",
|
||||
"collapse-subtree": "Згорнути піддерево",
|
||||
"sort-child-notes": "Сортувати дочірні нотатки",
|
||||
"create-note-after": "Створити нотатку після",
|
||||
"create-note-into": "Створити нотатку в",
|
||||
"create-note-into-inbox": "Створити нотатку у \"Вхідні\"",
|
||||
"delete-notes": "Видалити нотатки",
|
||||
"move-note-up": "Перемістити нотатку вгору",
|
||||
"move-note-down": "Перемістити нотатку вниз",
|
||||
"move-note-up-in-hierarchy": "Перемістити нотатку вгору в ієрархії",
|
||||
"move-note-down-in-hierarchy": "Перемістити нотатку вниз в ієрархії",
|
||||
"edit-note-title": "Редагувати назву нотатки",
|
||||
"edit-branch-prefix": "Редагувати префікс гілки",
|
||||
"quick-search": "Швидкий Пошук",
|
||||
"search-in-subtree": "Пошук у Піддереві",
|
||||
"expand-subtree": "Розгорнути Піддерево",
|
||||
"collapse-tree": "Згорнути Дерево",
|
||||
"collapse-subtree": "Згорнути Піддерево",
|
||||
"sort-child-notes": "Сортувати Дочірні нотатки",
|
||||
"create-note-after": "Створити Нотатку після",
|
||||
"create-note-into": "Створити Нотатку в",
|
||||
"create-note-into-inbox": "Створити Нотатку у Вхідні",
|
||||
"delete-notes": "Видалити Нотатки",
|
||||
"move-note-up": "Перемістити Нотатку вгору",
|
||||
"move-note-down": "Перемістити Нотатку вниз",
|
||||
"move-note-up-in-hierarchy": "Перемістити Нотатку вгору в Ієрархії",
|
||||
"move-note-down-in-hierarchy": "Перемістити Нотатку вниз в Ієрархії",
|
||||
"edit-note-title": "Редагувати Заголовок нотатки",
|
||||
"edit-branch-prefix": "Редагувати префікс Гілки",
|
||||
"clone-notes-to": "Клонувати нотатки до",
|
||||
"move-notes-to": "Перемістити нотатки до",
|
||||
"copy-notes-to-clipboard": "Копіювати нотатки в буфер обміну",
|
||||
"paste-notes-from-clipboard": "Вставити нотатки з буфера обміну",
|
||||
"cut-notes-to-clipboard": "Вирізати нотатки в буфер обміну",
|
||||
"copy-notes-to-clipboard": "Копіювати нотатки в Буфер обміну",
|
||||
"paste-notes-from-clipboard": "Вставити нотатки з Буфера обміну",
|
||||
"cut-notes-to-clipboard": "Вирізати нотатки в Буфер обміну",
|
||||
"select-all-notes-in-parent": "Вибрати всі нотатки в Батьківські",
|
||||
"add-note-above-to-selection": "Додати примітку вище до виділення",
|
||||
"add-note-below-to-selection": "Додати нотатку нижче до виділення",
|
||||
"duplicate-subtree": "Дублікат піддерева",
|
||||
"open-new-tab": "Відкрити нову вкладку",
|
||||
"add-note-above-to-selection": "Додати Нотатку вище до вибраного",
|
||||
"add-note-below-to-selection": "Додати Нотатку нижче до вибраного",
|
||||
"duplicate-subtree": "Дублікат Піддерева",
|
||||
"open-new-tab": "Відкрити Нову вкладку",
|
||||
"close-active-tab": "Закрити активну вкладку",
|
||||
"reopen-last-tab": "Відкрити останню вкладку",
|
||||
"activate-next-tab": "Активувати наступну вкладку",
|
||||
"activate-previous-tab": "Активувати попередню вкладку",
|
||||
"open-new-window": "Відкрити нове вікно",
|
||||
"open-new-window": "Відкрити Нове вікно",
|
||||
"toggle-system-tray-icon": "Увімкнути Значок системного трея",
|
||||
"toggle-zen-mode": "Увімкнути режим дзен",
|
||||
"toggle-zen-mode": "Увімкнути Дзен-режим",
|
||||
"switch-to-first-tab": "Перейти до першої вкладки",
|
||||
"switch-to-second-tab": "Перейти до другої вкладки",
|
||||
"switch-to-third-tab": "Перейти до третьої вкладки",
|
||||
"switch-to-fourth-tab": "Перейти до четвертої вкладки",
|
||||
"switch-to-fifth-tab": "Перейти на п'яту вкладку",
|
||||
"switch-to-fifth-tab": "Перейти до п'ятої вкладки",
|
||||
"switch-to-sixth-tab": "Перейти до шостої вкладки",
|
||||
"switch-to-seventh-tab": "Перейти до сьомої вкладки",
|
||||
"switch-to-eighth-tab": "Перейти до восьмої вкладки",
|
||||
"find-in-text": "Знайти в тексті",
|
||||
"toggle-left-pane": "Перемкнути ліву панель",
|
||||
"toggle-full-screen": "Перемкнути повноекранний режим",
|
||||
"toggle-left-pane": "Увімкнути Ліву панель",
|
||||
"toggle-full-screen": "Увімкнути Повноекранний режим",
|
||||
"zoom-out": "Зменшити масштаб",
|
||||
"zoom-in": "Збільшити масштаб",
|
||||
"reset-zoom-level": "Скинути рівень масштабування",
|
||||
"reset-zoom-level": "Скинути Масштабування",
|
||||
"copy-without-formatting": "Копіювати без форматування",
|
||||
"force-save-revision": "Примусове збереження версії"
|
||||
"force-save-revision": "Примусове збереження версії",
|
||||
"switch-to-ninth-tab": "Перейти до дев'ятої вкладки",
|
||||
"switch-to-last-tab": "Перейти до останньої вкладки",
|
||||
"show-note-source": "Показати Джерело нотатки",
|
||||
"show-options": "Показати Параметри",
|
||||
"show-revisions": "Показати Версії",
|
||||
"show-recent-changes": "Показати Останні зміни",
|
||||
"show-sql-console": "Показати Консоль SQL",
|
||||
"show-backend-log": "Показати Backend Log",
|
||||
"show-help": "Показати Допомогу",
|
||||
"show-cheatsheet": "Показати Шпаргалку",
|
||||
"add-link-to-text": "Додати Посилання до тексту",
|
||||
"follow-link-under-cursor": "Перейти за Посиланням під курсором",
|
||||
"insert-date-and-time-to-text": "Вставити Дату та Час у текст",
|
||||
"paste-markdown-into-text": "Вставити Markdown у текст",
|
||||
"cut-into-note": "Вирізати у нотатку",
|
||||
"add-include-note-to-text": "Додати включену нотатку до тексту",
|
||||
"edit-read-only-note": "Редагувати нотатку лише для читання",
|
||||
"add-new-label": "Додати Нову мітку",
|
||||
"add-new-relation": "Додати Новий зв'язок",
|
||||
"toggle-ribbon-tab-classic-editor": "Включити Вкладку стрічки Класичний Редактор",
|
||||
"toggle-ribbon-tab-basic-properties": "Включити Вкладку стрічки Основні властивості",
|
||||
"toggle-ribbon-tab-book-properties": "Включити вкладку стрічки Властивості книги",
|
||||
"toggle-ribbon-tab-file-properties": "Включити вкладку стрічки Властивості Файлу",
|
||||
"toggle-ribbon-tab-image-properties": "Включити вкладку стрічки Властивості Зображення",
|
||||
"toggle-ribbon-tab-owned-attributes": "Включити вкладку стрічки Власні Атрибути",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Включити вкладку стрічки Успадковані Атрибути",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Включити вкладку стрічки Просунуті Атрибути",
|
||||
"toggle-ribbon-tab-note-map": "Включити вкладку стрічки Карта Нотатки",
|
||||
"toggle-ribbon-tab-note-info": "Включити вкладку стрічки Інформація про Нотатку",
|
||||
"toggle-ribbon-tab-note-paths": "Включити вкладку стрічки Шляхи Нотатки",
|
||||
"toggle-ribbon-tab-similar-notes": "Включити вкладку стрічки Схожі Нотатки",
|
||||
"toggle-right-pane": "Включити Праву панель",
|
||||
"print-active-note": "Друк Активної Нотатки",
|
||||
"export-active-note-as-pdf": "Експорт Активної нотатки у PDF",
|
||||
"open-note-externally": "Відкрити Нотатку зовнішньою програмою",
|
||||
"render-active-note": "Рендеринг Активної Нотатки",
|
||||
"run-active-note": "Запустити Активну Нотатку",
|
||||
"toggle-note-hoisting": "Включити хостинг нотатки",
|
||||
"reload-frontend-app": "Перезавантажити Інтерфейс програми",
|
||||
"open-developer-tools": "Відкрити Інструменти розробника",
|
||||
"unhoist-note": "Відкріпити Нотатку"
|
||||
},
|
||||
"login": {
|
||||
"title": "Увійти",
|
||||
@@ -172,6 +213,216 @@
|
||||
"sign_in_with_sso": "Увійти за допомогою {{ ssoIssuerName }}"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Встановити пароль"
|
||||
"title": "Встановити Пароль",
|
||||
"heading": "Встановити пароль",
|
||||
"description": "Перш ніж почати користуватися Trilium з веб-сайту, вам потрібно спочатку встановити пароль. Потім ви будете використовувати цей пароль для входу в систему.",
|
||||
"password": "Пароль",
|
||||
"password-confirmation": "Підтвердження пароля",
|
||||
"button": "Встановити пароль"
|
||||
},
|
||||
"javascript-required": "Для роботи Trilium потрібен JavaScript.",
|
||||
"setup": {
|
||||
"heading": "Налаштування Trilium Notes",
|
||||
"new-document": "Я новий користувач і хочу створити новий документ Trilium для своїх нотаток",
|
||||
"sync-from-desktop": "У мене вже є екземпляр для ПК і я хочу налаштувати синхронізацію з ним",
|
||||
"sync-from-server": "У мене вже є екземпляр сервера, і я хочу налаштувати синхронізацію з ним",
|
||||
"next": "Наступна",
|
||||
"init-in-progress": "Триває ініціалізація документа",
|
||||
"redirecting": "Невдовзі вас буде перенаправлено до програми.",
|
||||
"title": "Налаштування"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Синхронізація з ПК",
|
||||
"description": "Це налаштування потрібно ініціювати з екземпляра ПК:",
|
||||
"step1": "Відкрийте екземпляр Trilium Notes на ПК.",
|
||||
"step2": "У меню Trilium натисніть Параметри.",
|
||||
"step3": "Натисніть на Категорія Синхронізації.",
|
||||
"step4": "Змініть адресу екземпляра сервера на: {{- host}} та натисніть кнопку Зберегти.",
|
||||
"step5": "Натисніть \"Тест синхронізації\", щоб перевірити успішність підключення.",
|
||||
"step6": "Після виконання цих кроків натисніть {{- link}}.",
|
||||
"step6-here": "тут"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Синхронізація з сервера",
|
||||
"instructions": "Будь ласка, введіть адресу сервера Trilium та облікові дані нижче. Це завантажить весь документ Trilium із сервера та налаштує синхронізацію з ним. Залежно від розміру документа та швидкості вашого з’єднання, це може зайняти деякий час.",
|
||||
"server-host": "Адреса сервера Trilium",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Проксі-сервер (необов'язково)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Нотатка:",
|
||||
"proxy-instruction": "Якщо залишити налаштування проксі-сервера порожнім, використовуватиметься системний проксі-сервер (стосується лише ПК програми)",
|
||||
"password": "Пароль",
|
||||
"password-placeholder": "Пароль",
|
||||
"back": "Назад",
|
||||
"finish-setup": "Завершити налаштування"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Триває синхронізація",
|
||||
"successful": "Синхронізацію налаштовано правильно. Початкова синхронізація завершиться через деякий час. Після її завершення вас буде перенаправлено на сторінку входу.",
|
||||
"outstanding-items": "Незавершені елементи синхронізації:",
|
||||
"outstanding-items-default": "Недоступно"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Не знайдено",
|
||||
"heading": "Не знайдено"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "батьківська:",
|
||||
"clipped-from": "Цю нотатку було спочатку вирізано з {{- url}}",
|
||||
"child-notes": "Дочірні нотатки:",
|
||||
"no-content": "Ця нотатка не має вмісту."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Понеділок",
|
||||
"tuesday": "Вівторок",
|
||||
"wednesday": "Середа",
|
||||
"thursday": "Четвер",
|
||||
"friday": "П'ятниця",
|
||||
"saturday": "Субота",
|
||||
"sunday": "Неділя"
|
||||
},
|
||||
"weekdayNumber": "Тиждень {weekNumber}",
|
||||
"months": {
|
||||
"january": "Січень",
|
||||
"february": "Лютий",
|
||||
"march": "Березень",
|
||||
"april": "Квітень",
|
||||
"may": "Травень",
|
||||
"june": "Червень",
|
||||
"july": "Липень",
|
||||
"august": "Серпень",
|
||||
"september": "Вересень",
|
||||
"october": "Жовтень",
|
||||
"november": "Листопад",
|
||||
"december": "Грудень"
|
||||
},
|
||||
"quarterNumber": "Квартал {quarterNumber}",
|
||||
"special_notes": {
|
||||
"search_prefix": "Пошук:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Хост сервера синхронізації не налаштовано. Спочатку налаштуйте синхронізацію.",
|
||||
"successful": "Установлення зв'язку з сервером синхронізації пройшло успішно, синхронізація розпочалася."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Приховані Нотатки",
|
||||
"search-history-title": "Історія пошуку",
|
||||
"note-map-title": "Карта Нотатки",
|
||||
"sql-console-history-title": "Історія консолі SQL",
|
||||
"shared-notes-title": "Спільні Нотатки",
|
||||
"bulk-action-title": "Масова дія",
|
||||
"backend-log-title": "Backend Log",
|
||||
"user-hidden-title": "Прихований користувач",
|
||||
"launch-bar-templates-title": "Запуск Шаблони панелей",
|
||||
"base-abstract-launcher-title": "Базовий Лаунчер Abstract",
|
||||
"command-launcher-title": "Лаунчер Command",
|
||||
"note-launcher-title": "Лаунчер Note",
|
||||
"script-launcher-title": "Лаунчер Script",
|
||||
"built-in-widget-title": "Вбудований віджет",
|
||||
"custom-widget-title": "Користувацький віджет",
|
||||
"launch-bar-title": "Панель запуску",
|
||||
"available-launchers-title": "Доступні Лаунчери",
|
||||
"go-to-previous-note-title": "Перейти до попередньої нотатки",
|
||||
"go-to-next-note-title": "Перейти до наступної нотатки",
|
||||
"new-note-title": "Нова Нотатка",
|
||||
"search-notes-title": "Пошук нотаток",
|
||||
"jump-to-note-title": "Перейти до...",
|
||||
"calendar-title": "Календар",
|
||||
"recent-changes-title": "Останні Зміни",
|
||||
"bookmarks-title": "Закладки",
|
||||
"open-today-journal-note-title": "Відкрити Щоденник за Сьогодні",
|
||||
"quick-search-title": "Швидкий Пошук",
|
||||
"protected-session-title": "Захищений Сеанс",
|
||||
"sync-status-title": "Статус синхронізації",
|
||||
"settings-title": "Налаштування",
|
||||
"llm-chat-title": "Чат з Нотатками",
|
||||
"options-title": "Параметри",
|
||||
"appearance-title": "Зовнішній вигляд",
|
||||
"shortcuts-title": "Комбінації клавіші",
|
||||
"text-notes": "Текстові Нотатки",
|
||||
"code-notes-title": "Нотатка з кодом",
|
||||
"images-title": "Зображення",
|
||||
"spellcheck-title": "Перевірка Орфографії",
|
||||
"password-title": "Пароль",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Резервне копіювання",
|
||||
"sync-title": "Синхронізація",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"other": "Інше",
|
||||
"advanced-title": "Розширені",
|
||||
"visible-launchers-title": "Видимі Лаунчери",
|
||||
"user-guide": "Посібник користувача",
|
||||
"localization": "Мова & Регіон",
|
||||
"inbox-title": "Вхідні",
|
||||
"spacer-title": "Роздільник"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Нова нотатка",
|
||||
"duplicate-note-suffix": "(дублікат)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Файл backend log '{{ fileName }}' не існує.",
|
||||
"reading-log-failed": "Не вдалося прочитати backend log file {{fileName}}."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Цей тип нотатки не може бути відображений."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF Document (*.pdf)",
|
||||
"unable-to-export-message": "Поточну нотатку не вдалося експортувати у PDF.",
|
||||
"unable-to-export-title": "Не вдається експортувати у PDF",
|
||||
"unable-to-save-message": "Не вдалося записати вибраний файл. Спробуйте ще раз або виберіть інше місце призначення."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Вихід з Trilium",
|
||||
"recents": "Останні нотатки",
|
||||
"bookmarks": "Закладки",
|
||||
"today": "Відкрити щоденник за сьогодні",
|
||||
"new-note": "Нова нотатка",
|
||||
"show-windows": "Показати вікна",
|
||||
"open_new_window": "Відкрити нове вікно"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "Пряма міграція з вашої поточної версії не підтримується. Будь ласка, спочатку оновіть систему до останньої версії v0.60.4, а потім лише потім до цієї.",
|
||||
"error_message": "Помилка під час міграції до версії {{version}}: {{stack}}",
|
||||
"wrong_db_version": "Версія бази даних ({{version}}) новіша за ту, яку очікує програма ({{targetVersion}}), це означає, що її було створено новішою та несумісною версією Trilium. Оновіть Trilium до останньої версії, щоб вирішити цю проблему."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Помилка"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Тема сайту",
|
||||
"search_placeholder": "Пошук...",
|
||||
"image_alt": "Зображення до статті",
|
||||
"last-updated": "Останнє оновлення: {{- date}}",
|
||||
"subpages": "Підсторінки:",
|
||||
"on-this-page": "На цій сторінці",
|
||||
"expand": "Розгорнути"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "Фрагмент тексту",
|
||||
"description": "Опис",
|
||||
"list-view": "Список",
|
||||
"grid-view": "Сітка",
|
||||
"calendar": "Календар",
|
||||
"table": "Таблиця",
|
||||
"geo-map": "Географічна карта",
|
||||
"start-date": "Дата початку",
|
||||
"end-date": "Дата завершення",
|
||||
"start-time": "Час початку",
|
||||
"end-time": "Час завершення",
|
||||
"geolocation": "Геолокація",
|
||||
"built-in-templates": "Вбудовані шаблони",
|
||||
"board": "Дошка",
|
||||
"status": "Статус",
|
||||
"board_note_first": "Перша нотатка",
|
||||
"board_note_second": "Друга нотатка",
|
||||
"board_note_third": "Третя нотатка",
|
||||
"board_status_todo": "Зробити",
|
||||
"board_status_progress": "У процесі",
|
||||
"board_status_done": "Готово"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user