Compare commits

..

58 Commits

Author SHA1 Message Date
Adorian Doran
e8bf12c4ab style/zen mode: tweak text vertical centering offset 2025-11-03 00:40:00 +02:00
Adorian Doran
344f2d819e style/zen mode: hide the shared note info bar 2025-11-03 00:19:40 +02:00
Adorian Doran
d2e9101675 style/zen mode/fixed formatting toolbar: allow items to overflow if narrow window 2025-11-03 00:14:44 +02:00
Adorian Doran
e6d2009605 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-02 23:00:20 +02:00
Adorian Doran
5d706a88d8 style/zen mode/fixed formatting toolbar: fix the position in multi-split mode 2025-11-02 23:00:00 +02:00
Adorian Doran
c7c7e05106 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-02 21:12:36 +02:00
Adorian Doran
e250107202 style/zen mode: fix collections not showing up 2025-11-02 21:12:23 +02:00
Adorian Doran
c141cbcf07 style/zen mode/fixed formatting toolbar: improve pointer events handling 2025-11-02 19:08:59 +02:00
Adorian Doran
8e83562e6a style/zen mode/fixed formatting toolbar: fix backdrop blur 2025-11-02 18:37:02 +02:00
Adorian Doran
08197f56d0 style/zen mode/fixed formatting toolbar: add entrance animation 2025-11-02 18:22:07 +02:00
Adorian Doran
ff0d8a70ad style/zen mode/fixed formatting toolbar: add backdrop blur 2025-11-02 18:08:05 +02:00
Adorian Doran
605b317f29 style/zen mode: restyle the fixed formatting toolbar, relocate at the bottom of the screen 2025-11-02 17:21:27 +02:00
Adorian Doran
6a89e096e5 style/zen mode: hide promoted attributes 2025-11-02 16:37:19 +02:00
Adorian Doran
e4e0f7619b Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-02 16:34:18 +02:00
Adorian Doran
214ba5265a Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-01 18:20:08 +02:00
Adorian Doran
ecb6dc7923 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-31 11:33:07 +02:00
Adorian Doran
131fb43ab7 style/zen mode: tweak 2025-10-30 02:48:38 +02:00
Adorian Doran
8a76fdb8d1 style/zen mode: center content, increase font size 2025-10-30 02:30:51 +02:00
Adorian Doran
8e3dbb2f65 client: remove redundant code 2025-10-30 02:27:57 +02:00
Adorian Doran
8b4fee1680 client: make code notes full-width 2025-10-30 01:03:22 +02:00
Adorian Doran
a370b52614 client: remove no longer used translation strings 2025-10-30 00:57:27 +02:00
Adorian Doran
4063229982 client: allow changing the max content width option without a frontend reload 2025-10-30 00:49:02 +02:00
Adorian Doran
1ef03b7a77 client: rework max content width handling 2025-10-30 00:23:41 +02:00
Adorian Doran
e510653edb Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-29 23:38:05 +02:00
Adorian Doran
a2c523def1 style: fix a bug preventing background effects to work properly 2025-10-29 20:28:53 +02:00
Adorian Doran
e3604edad7 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-29 19:59:30 +02:00
Adorian Doran
426d8296be Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-29 09:30:50 +02:00
Adorian Doran
947e43a615 style: allow custom themes to turn off background effects 2025-10-29 03:20:25 +02:00
Adorian Doran
0424fe4fba client/css var utility: add support for parsing boolean values 2025-10-29 03:13:16 +02:00
Adorian Doran
f789b69506 style: disable tab switching animation while background effects are active, improve performance 2025-10-29 02:39:25 +02:00
Adorian Doran
5df512a69c style: use background effects for empty note, refactor 2025-10-29 01:32:47 +02:00
Adorian Doran
2a5f329ada style: darken the main background color 2025-10-29 00:45:40 +02:00
Adorian Doran
4fe3944585 style/right panel: use own background color when background effects are active 2025-10-29 00:34:20 +02:00
Adorian Doran
98b8e97fd9 style/right panel: add translucent background when background effects are active 2025-10-29 00:20:50 +02:00
Adorian Doran
38a1cd0d35 style: tweak gutters 2025-10-28 23:55:51 +02:00
Adorian Doran
ae544a80c2 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-28 23:43:55 +02:00
Adorian Doran
ea45024559 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-28 09:51:32 +02:00
Adorian Doran
64d3589b40 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-25 22:42:20 +03:00
Adorian Doran
638cb4281e style/center pane: optimize the identification of options pages 2025-10-25 11:28:26 +03:00
Adorian Doran
1568908982 style/center pane: allow distinct background colors for note splits 2025-10-25 11:12:00 +03:00
Adorian Doran
4459561308 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-25 10:20:28 +03:00
Adorian Doran
3341e59a80 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-24 02:43:52 +03:00
Adorian Doran
74a805056b style/settings: use Mica for settings pages 2025-10-24 01:03:28 +03:00
Adorian Doran
f42e870de1 style/dropdowns: increase the radius of the backdrop blur 2025-10-23 20:28:36 +03:00
Adorian Doran
ca3964f8b7 style/floating buttons: tweak position 2025-10-23 20:18:52 +03:00
Adorian Doran
ddafda5f4e style/quick edit dialog: tweak the colors for the dark color scheme 2025-10-23 20:01:29 +03:00
Adorian Doran
40b08e1828 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-23 19:43:17 +03:00
Adorian Doran
5141f0a0d5 style/quick edit dialog: tweak the colors for the dark color scheme 2025-10-23 19:43:06 +03:00
Adorian Doran
8c165c0401 style/quick edit dialog: add support for the dark color scheme 2025-10-23 10:57:01 +03:00
Adorian Doran
b4dd40e128 style/quick edit dialog: refactor 2025-10-23 10:48:04 +03:00
Adorian Doran
535b960b76 style/quick edit dialog: ignore monochromatic custom colors 2025-10-23 10:44:23 +03:00
Adorian Doran
b58f37cd4a style/quick edit dialog: tint the dialog background, border and promoted attributes card according to the note's custom color 2025-10-23 10:18:52 +03:00
Adorian Doran
a01fb39599 client/quick edit dialog: make available the CSS variables with the custom color of the edited note 2025-10-23 10:16:18 +03:00
Adorian Doran
be15934b22 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-23 10:03:03 +03:00
Adorian Doran
96b3464f00 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-22 21:53:07 +03:00
Adorian Doran
2470b0b334 style: tweak the appearance of the promoted attributes cards 2025-10-22 20:06:06 +03:00
Adorian Doran
4344687303 style: tweak the appearance of the promoted attributes cards 2025-10-22 19:58:42 +03:00
Adorian Doran
b4fe46eba3 style: tweak the appearance of option cards 2025-10-22 19:41:49 +03:00
43 changed files with 415 additions and 4755 deletions

View File

@@ -1809,12 +1809,15 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
}
.note-split {
/* Limits the maximum width of the note */
--max-content-width: var(--preferred-max-content-width);
margin-inline-start: auto;
margin-inline-end: auto;
}
.note-split.full-content-width {
max-width: 999999px;
--max-content-width: unset;
}
button.close:hover {
@@ -2034,13 +2037,16 @@ body.zen #right-pane,
body.zen #mobile-sidebar-wrapper,
body.zen .tab-row-container,
body.zen .tab-row-widget,
body.zen .shared-info-widget,
body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget,
body.zen .title-row .icon-action,
body.zen .promoted-attributes-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button {
body.zen .action-button,
body.zen .note-list-widget:not(.full-height) {
display: none !important;
}
@@ -2084,12 +2090,85 @@ body.zen .note-title-widget,
body.zen .note-title-widget input {
font-size: 1rem !important;
background: transparent !important;
pointer-events: none;
}
body.zen #detail-container {
width: 100%;
}
body.zen .note-split:not(.full-content-width) .scrolling-container {
display: flex;
flex-direction: column;
scroll-behavior: unset !important;
}
body.zen .note-split:not(.full-content-width) .note-detail {
margin: auto;
padding-bottom: 25vh;
width: var(--max-content-width);
}
body.zen .note-split:not(.full-content-width) .scroll-padding-widget {
display: none;
}
body.zen .note-split.type-text {
font-size: 1.15em;
}
/* Fixed formatting toolbar */
body.zen .note-split .ribbon-container {
position: fixed;
left: 0;
bottom: 20px;
width: 100%;
z-index: 1000;
opacity: 0; /* Hidden unless the current note split is focused */
pointer-events: none;
transition: opacity 100ms linear;
}
body.zen .note-split:focus-within .ribbon-container {
opacity: 1; /* Show when the note split is focused */
}
body.zen .note-split .ribbon-container .ribbon-body {
border: 0;
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
margin: auto;
width: fit-content;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
border-radius: 8px;
border: 1px solid var(--main-border-color);
padding: 4px;
background: var(--menu-background-color);
}
body.zen .note-split:focus-within .ribbon-container .classic-toolbar-widget {
pointer-events: all;
}
@media (max-width: 1300px) {
body.zen .note-split .ribbon-container .classic-toolbar-widget {
/* Set the toolbar to full with */
width: 100%;
}
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
/* Force toolbar items overflow dropdowns open upwards */
top: auto;
bottom: 100%;
}
}
/* Content renderer */
footer.file-footer,

View File

@@ -15,7 +15,7 @@
--native-titlebar-background: #00000000;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: #272727;
--main-background-color: #242424;
--main-text-color: #ccc;
--main-border-color: #454545;
--subtle-border-color: #313131;
@@ -166,6 +166,9 @@
--protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871;
--center-pane-vert-layout-background-color-bgfx: #0c0c0c69;
--center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7;
--right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color);
@@ -192,9 +195,9 @@
--badge-background-color: #ffffff1a;
--badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #000000b3;
--promoted-attribute-card-background-color: #ffffff21;
--promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000080;
--floating-button-background-color: #494949d2;
--floating-button-color: var(--button-text-color);
@@ -208,6 +211,8 @@
--floating-button-hide-button-background: #00000029;
--floating-button-hide-button-color: #ffffff63;
--right-pane-background-color: var(--main-background-color);
--right-pane-background-color-bgfx: #0c0c0c24; /* Only for the vertical layout */
--right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white;
@@ -227,8 +232,8 @@
--card-background-color: #ffffff12;
--card-background-hover-color: #3c3c3c;
--card-background-press-color: #464646;
--card-border-color: #222222;
--card-box-shadow: 0 0 12px rgba(0, 0, 0, 0.15);
--card-border-color: transparent;
--card-box-shadow: none;
--calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color);
@@ -294,4 +299,10 @@ body ::-webkit-calendar-picker-indicator {
body .todo-list input[type="checkbox"]:not(:checked):before {
border-color: var(--muted-text-color) !important;
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%);
--modal-border-color: hsl(var(--custom-color-hue), 9.4%, 25.1%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%);
}

View File

@@ -159,6 +159,9 @@
--protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528;
--center-pane-vert-layout-background-color-bgfx: #ffffff75;
--center-pane-horiz-layout-background-color-bgfx: #ffffffd6;
--right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color);
@@ -185,8 +188,8 @@
--badge-background-color: #00000011;
--badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #00000033;
--promoted-attribute-card-background-color: #00000014;
--promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000042;
--floating-button-background-color: #eaeaeacc;
@@ -207,6 +210,8 @@
--new-tab-button-hover-background: white;
--new-tab-button-hover-color: black;
--right-pane-background-color: var(--main-background-color);
--right-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx); /* Only for the vertical layout */
--right-pane-item-hover-background: #ececec;
--right-pane-item-hover-color: inherit;
@@ -223,12 +228,12 @@
--code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2);
--card-background-color: var(--accented-background-color);
--card-background-color: #0000000d;
--card-background-hover-color: #f9f9f9;
--card-background-press-color: #efefef;
--card-border-color: #eaeaea;
--card-border-color: transparent;
--card-shadow-color: rgba(0, 0, 0, 0.1);
--card-box-shadow: 0 0 12px var(--card-shadow-color);
--card-box-shadow: none;
--calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color);
@@ -270,4 +275,10 @@
* The --custom-color-hue variable contains the hue of the user-selected note color.
* This value is unset for gray tones. */
--custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1);
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 56%, 96%);
--modal-border-color: hsl(var(--custom-color-hue), 33%, 41%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);
}

View File

@@ -82,6 +82,7 @@
/* Theme capabilities */
--tab-note-icons: true;
--allow-background-effects: true;
/* To ensure that a tree item's custom color remains sufficiently contrasted and readable,
* the color is adjusted based on the current color scheme (light or dark). The lightness
@@ -131,7 +132,8 @@ body.mobile .dropdown-menu .dropdown-menu {
body.desktop .dropdown-menu::before,
:root .ck.ck-dropdown__panel::before,
:root .excalidraw .popover::before {
:root .excalidraw .popover::before,
body.zen .note-split .ribbon-container .classic-toolbar-widget::before {
content: "";
backdrop-filter: var(--dropdown-backdrop-filter);
border-radius: var(--dropdown-border-radius);

View File

@@ -148,7 +148,7 @@ div.note-detail-empty {
--options-card-min-width: 500px;
--options-card-max-width: 900px;
--options-card-padding: 17px;
--options-title-font-size: 1rem;
--options-title-font-size: .75rem;
--options-title-offset: 13px;
}
/* Create a gap at the top of the option pages */
@@ -173,8 +173,7 @@ div.note-detail-empty {
}
.options-section:not(.tn-no-card) {
margin: auto;
border-radius: 12px;
border-radius: 8px;
border: 1px solid var(--card-border-color) !important;
box-shadow: var(--card-box-shadow);
background: var(--card-background-color);
@@ -182,7 +181,7 @@ div.note-detail-empty {
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
}
body.desktop .option-section:not(.tn-no-card) {
body.desktop .options-section:not(.tn-no-card) {
min-width: var(--options-card-min-width);
max-width: var(--options-card-max-width);
}
@@ -193,9 +192,16 @@ body.desktop .option-section:not(.tn-no-card) {
padding-bottom: var(--default-padding);
}
.options-section:not(.tn-no-card) h4,
.options-section:not(.tn-no-card) h5 {
text-transform: uppercase;
letter-spacing: .4pt;
}
.options-section:not(.tn-no-card) h4 {
font-size: var(--options-title-font-size);
font-weight: bold;
font-weight: 600;
color: var(--launcher-pane-text-color);
margin-top: calc(-1 * var(--options-card-padding) - var(--options-title-font-size) - var(--options-title-offset)) !important;
margin-bottom: calc(var(--options-title-offset) + var(--options-card-padding)) !important;

View File

@@ -34,6 +34,7 @@
div.promoted-attributes-container {
margin-top: 8px;
margin-bottom: 8px;
margin-inline-start: 12px;
}
/*

View File

@@ -8,7 +8,7 @@
}
:root {
--dropdown-backdrop-filter: blur(10px) saturate(6);
--dropdown-backdrop-filter: blur(20px) saturate(6);
--dropdown-border-radius: 10px;
}
@@ -35,30 +35,52 @@ body.mobile {
}
/* #region Mica */
body.background-effects.platform-win32 {
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
--background-material: tabbed;
}
body.background-effects.theme-supports-background-effects.platform-win32 {
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
--tab-background-color: var(--window-background-color-bgfx);
--new-tab-button-background: var(--window-background-color-bgfx);
--active-tab-background-color: var(--launcher-pane-horiz-background-color);
--root-background: transparent;
}
body.background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--background-material: mica;
}
body.background-effects.platform-win32,
body.background-effects.platform-win32 #root-widget {
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--center-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx);
--right-pane-background-color: var(--right-pane-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal {
--center-pane-background-color-bgfx: var(--center-pane-horiz-layout-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.platform-win32,
body.background-effects.theme-supports-background-effects.platform-win32 #root-widget {
background: var(--window-background-color-bgfx) !important;
}
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.platform-win32.layout-vertical #vertical-main-container {
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical #vertical-main-container {
background-color: var(--root-background);
}
/* Note split with background effects */
body.background-effects.theme-supports-background-effects.platform-win32 #center-pane .note-split.bgfx {
--note-split-background-color: var(--center-pane-background-color-bgfx);
}
/* #endregion */
/* Matches when the left pane is collapsed */
@@ -72,9 +94,21 @@ body.layout-vertical #horizontal-main-container.left-pane-hidden #launcher-pane.
border-inline-end: 2px solid var(--left-pane-collapsed-border-color);
}
body.background-effects.zen #root-widget {
--main-background-color: transparent;
--root-background: transparent;
/*
* Zen mode
*/
@keyframes zen-formatting-toolbar-entrance {
from {
transform: translateY(200%);
} to {
transform: translateY(0);
}
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
position: relative;
animation: zen-formatting-toolbar-entrance 300ms ease-out;
}
/*
@@ -1171,23 +1205,18 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
* CENTER PANE
*/
#center-pane {
background: var(--main-background-color);
}
.vertical-layout #center-pane {
/* The first visible note split */
.vertical-layout #center-pane .note-split:not(.visible ~ .visible) {
border-radius: var(--center-pane-border-radius) 0 0 0;
}
.note-split {
#center-pane .note-split {
padding-top: 2px;
animation: note-entrance 100ms linear;
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
background-color: var(--note-split-background-color, var(--main-background-color));
}
.split-note-container-widget > .gutter {
background: var(--root-background) !important;
transition: background 150ms ease-out;
body:not(.background-effects) #center-pane .note-split {
animation: note-entrance 100ms linear;
}
/*
@@ -1200,9 +1229,9 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
@keyframes note-entrance {
from {
opacity: 0;
filter: opacity(0);
} to {
opacity: 1;
filter: opacity(1);
}
}
@@ -1328,8 +1357,7 @@ div.promoted-attribute-cell {
--pa-card-padding-inline-end: 2px;
--input-background-color: transparent;
box-shadow: 1px 1px 2px var(--promoted-attribute-card-shadow-color);
box-shadow: var(--promoted-attribute-card-shadow);
display: inline-flex;
margin: 0;
border-radius: 8px;
@@ -1716,7 +1744,7 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
*/
#right-pane {
background: var(--main-background-color);
background: var(--right-pane-background-color);
}
#right-pane div.card-header {

View File

@@ -520,9 +520,7 @@
"max_content_width": {
"max_width_unit": "بكسل",
"title": "عرض المحتوى",
"reload_button": "اعادة تحميل الواجهة",
"max_width_label": "اقصى عرض للمحتوى",
"reload_description": "تغييرات من خيارات المظهر"
"max_width_label": "اقصى عرض للمحتوى"
},
"native_title_bar": {
"enabled": "مفعل",
@@ -716,6 +714,7 @@
"backup_database_now": "نسخ اختياطي لقاعدة البيانات الان"
},
"etapi": {
"wiki": "ويكي",
"created": "تم الأنشاء",
"actions": "أجراءات",
"title": "ETAPI",

View File

@@ -1107,9 +1107,6 @@
"title": "内容宽度",
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
"max_width_label": "内容最大宽度(像素)",
"apply_changes_description": "要应用内容宽度更改,请点击",
"reload_button": "重载前端",
"reload_description": "来自外观选项的更改",
"max_width_unit": "像素"
},
"native_title_bar": {
@@ -1289,6 +1286,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI 是一个 REST API用于以编程方式访问 Trilium 实例,而无需 UI。",
"see_more": "有关更多详细信息,请参见 {{- link_to_wiki}} 和 {{- link_to_openapi_spec}} 或 {{- link_to_swagger_ui}}。",
"wiki": "维基",
"openapi_spec": "ETAPI OpenAPI 规范",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "创建新的 ETAPI 令牌",
"existing_tokens": "现有令牌",
"no_tokens_yet": "目前还没有令牌。点击上面的按钮创建一个。",

View File

@@ -1104,9 +1104,6 @@
"title": "Inhaltsbreite",
"default_description": "Trilium begrenzt standardmäßig die maximale Inhaltsbreite, um die Lesbarkeit für maximierte Bildschirme auf Breitbildschirmen zu verbessern.",
"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",
"max_width_unit": "Pixel"
},
"native_title_bar": {
@@ -1286,6 +1283,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI ist eine REST-API, die für den programmgesteuerten Zugriff auf die Trilium-Instanz ohne Benutzeroberfläche verwendet wird.",
"see_more": "Weitere Details können im {{- link_to_wiki}} und in der {{- link_to_openapi_spec}} oder der {{- link_to_swagger_ui }} gefunden werden.",
"wiki": "Wiki",
"openapi_spec": "ETAPI OpenAPI-Spezifikation",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Erstelle ein neues ETAPI-Token",
"existing_tokens": "Vorhandene Token",
"no_tokens_yet": "Es sind noch keine Token vorhanden. Klicke auf die Schaltfläche oben, um eine zu erstellen.",

View File

@@ -1107,10 +1107,7 @@
"title": "Content Width",
"default_description": "Trilium by default limits max content width to improve readability for maximized screens on wide screens.",
"max_width_label": "Max content width",
"max_width_unit": "pixels",
"apply_changes_description": "To apply content width changes, click on",
"reload_button": "reload frontend",
"reload_description": "changes from appearance options"
"max_width_unit": "pixels"
},
"native_title_bar": {
"title": "Native Title Bar (requires app restart)",

View File

@@ -1107,10 +1107,7 @@
"title": "Ancho del contenido",
"default_description": "Trilium limita de forma predeterminada el ancho máximo del contenido para mejorar la legibilidad de ventanas maximizadas en pantallas anchas.",
"max_width_label": "Ancho máximo del contenido en píxeles",
"max_width_unit": "píxeles",
"apply_changes_description": "Para aplicar cambios en el ancho del contenido, haga clic en",
"reload_button": "recargar la interfaz",
"reload_description": "cambios desde las opciones de apariencia"
"max_width_unit": "píxeles"
},
"native_title_bar": {
"title": "Barra de título nativa (requiere reiniciar la aplicación)",
@@ -1446,6 +1443,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI es una REST API que se utiliza para acceder a la instancia de Trilium mediante programación, sin interfaz de usuario.",
"see_more": "Véa más detalles en el {{- link_to_wiki}} y el {{- link_to_openapi_spec}} o el {{- link_to_swagger_ui }}.",
"wiki": "wiki",
"openapi_spec": "Especificación ETAPI OpenAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Crear nuevo token ETAPI",
"existing_tokens": "Tokens existentes",
"no_tokens_yet": "Aún no hay tokens. Dé clic en el botón de arriba para crear uno.",

View File

@@ -1106,9 +1106,6 @@
"title": "Largeur du contenu",
"default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.",
"max_width_label": "Largeur maximale du contenu en pixels",
"apply_changes_description": "Pour appliquer les modifications de largeur du contenu, cliquez sur",
"reload_button": "recharger l'interface",
"reload_description": "changements par rapport aux options d'apparence",
"max_width_unit": "Pixels"
},
"native_title_bar": {
@@ -1288,6 +1285,8 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI est une API REST utilisée pour accéder à l'instance Trilium par programme, sans interface utilisateur.",
"wiki": "wiki",
"openapi_spec": "Spec ETAPI OpenAPI",
"create_token": "Créer un nouveau jeton ETAPI",
"existing_tokens": "Jetons existants",
"no_tokens_yet": "Il n'y a pas encore de jetons. Cliquez sur le bouton ci-dessus pour en créer un.",
@@ -1304,7 +1303,9 @@
"delete_token": "Supprimer/désactiver ce token",
"rename_token_title": "Renommer le jeton",
"rename_token_message": "Veuillez saisir le nom du nouveau jeton",
"delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?"
"delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?",
"see_more": "Voir plus de détails dans le {{- link_to_wiki}} et le {{- link_to_openapi_spec}} ou le {{- link_to_swagger_ui }}.",
"swagger_ui": "Interface utilisateur ETAPI Swagger"
},
"options_widget": {
"options_status": "Statut des options",

View File

@@ -132,6 +132,10 @@
"new_token_message": "Inserisci il nome del nuovo token",
"title": "ETAPI",
"description": "ETAPI è un'API REST utilizzata per accedere alle istanze di Trilium in modo programmatico, senza interfaccia utente.",
"see_more": "Per maggiori dettagli consulta {{- link_to_wiki}} e {{- link_to_openapi_spec}} o {{- link_to_swagger_ui}}.",
"wiki": "wiki",
"openapi_spec": "Specifiche ETAPI OpenAPI",
"swagger_ui": "Interfaccia utente ETAPI Swagger",
"create_token": "Crea un nuovo token ETAPI",
"existing_tokens": "Token esistenti",
"no_tokens_yet": "Non ci sono ancora token. Clicca sul pulsante qui sopra per crearne uno.",
@@ -1569,10 +1573,7 @@
"title": "Larghezza del contenuto",
"default_description": "Per impostazione predefinita, Trilium limita la larghezza massima del contenuto per migliorare la leggibilità sugli schermi più grandi.",
"max_width_label": "Larghezza massima del contenuto",
"max_width_unit": "pixel",
"apply_changes_description": "Per applicare le modifiche alla larghezza del contenuto, fare clic su",
"reload_button": "ricarica frontend",
"reload_description": "modifiche dalle opzioni di aspetto"
"max_width_unit": "pixel"
},
"native_title_bar": {
"title": "Barra del titolo nativa (richiede il riavvio dell'app)",

View File

@@ -657,6 +657,10 @@
"created": "作成日時",
"title": "ETAPI",
"description": "ETAPI は、Trilium インスタンスに UI なしでプログラム的にアクセスするための REST API です。",
"see_more": "詳細は{{- link_to_wiki}}と{{- link_to_openapi_spec}}または{{- link_to_swagger_ui }}を参照してください。",
"wiki": "wiki",
"openapi_spec": "ETAPI OpenAPIの仕様",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "新しくETAPIトークンを作成",
"existing_tokens": "既存のトークン",
"no_tokens_yet": "トークンはまだありません。上のボタンをクリックして作成してください。",
@@ -830,13 +834,10 @@
"theme_defined": "テーマが定義されました"
},
"max_content_width": {
"reload_button": "フロントエンドをリロード",
"title": "コンテンツ幅",
"default_description": "Triliumは、ワイドスクリーンで最大化された画面での可読性を向上させるために、デフォルトでコンテンツの最大幅を制限しています。",
"max_width_label": "最大コンテンツ幅",
"max_width_unit": "ピクセル",
"apply_changes_description": "コンテンツ幅の変更を適用するには、クリックしてください",
"reload_description": "外観設定から変更"
"max_width_unit": "ピクセル"
},
"theme": {
"title": "アプリのテーマ",

View File

@@ -1464,10 +1464,7 @@
"title": "Szerokość zawartości",
"default_description": "Trilium domyślnie ogranicza maksymalną szerokość zawartości, aby poprawić czytelność na zmaksymalizowanych ekranach o dużej szerokości.",
"max_width_label": "Maksymalna szerokość zawartości",
"max_width_unit": "piksele",
"apply_changes_description": "Aby zastosować zmiany szerokości zawartości, kliknij na",
"reload_button": "przeładuj frontend",
"reload_description": "zmiany z opcji wyglądu"
"max_width_unit": "piksele"
},
"native_title_bar": {
"title": "Natywny pasek tytułu (wymaga ponownego uruchomienia aplikacji)",
@@ -1663,6 +1660,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI to interfejs API REST używany do programowego dostępu do instancji Trilium, bez interfejsu użytkownika.",
"see_more": "Zobacz więcej szczegółów w {{- link_to_wiki}} oraz w {{- link_to_openapi_spec}} lub {{- link_to_swagger_ui }}.",
"wiki": "wiki",
"openapi_spec": "specyfikacja ETAPI OpenAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Utwórz nowy token ETAPI",
"existing_tokens": "Istniejące tokeny",
"no_tokens_yet": "Nie ma jeszcze żadnych tokenów. Kliknij przycisk powyżej, aby utworzyć jeden.",

View File

@@ -1082,10 +1082,7 @@
"title": "Largura do Conteúdo",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em ecrãs largos.",
"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"
"max_width_unit": "pixels"
},
"native_title_bar": {
"title": "Barra de Título Nativa (requer recarregar a app)",
@@ -1422,6 +1419,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI é uma API REST usada para aceder a instância do Trilium programaticamente, sem interface gráfica.",
"see_more": "Veja mais pormenores no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.",
"wiki": "wiki",
"openapi_spec": "Especificação OpenAPI do ETAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Criar token ETAPI",
"existing_tokens": "Tokens existentes",
"no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.",

View File

@@ -1304,9 +1304,6 @@
"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",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide."
},
"native_title_bar": {
@@ -1932,6 +1929,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI é uma API REST usada para acessar a instância do Trilium programaticamente, sem interface gráfica.",
"see_more": "Veja mais detalhes no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.",
"wiki": "wiki",
"openapi_spec": "Especificação OpenAPI do ETAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Criar novo token ETAPI",
"existing_tokens": "Tokens existentes",
"no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.",

View File

@@ -507,13 +507,17 @@
"new_token_message": "Introduceți denumirea noului token",
"new_token_title": "Token ETAPI nou",
"no_tokens_yet": "Nu există încă token-uri. Clic pe butonul de deasupra pentru a crea una.",
"openapi_spec": "Specificația OpenAPI pentru ETAPI",
"swagger_ui": "UI-ul Swagger pentru ETAPI",
"rename_token": "Redenumește token-ul",
"rename_token_message": "Introduceți denumirea noului token",
"rename_token_title": "Redenumire token",
"see_more": "Vedeți mai multe detalii în {{- link_to_wiki}} și în {{- link_to_openapi_spec}} sau în {{- link_to_swagger_ui }}.",
"title": "ETAPI",
"token_created_message": "Copiați token-ul creat în clipboard. Trilium stochează token-ul ca hash așadar această valoare poate fi văzută doar acum.",
"token_created_title": "Token ETAPI creat",
"token_name": "Denumire token"
"token_name": "Denumire token",
"wiki": "wiki"
},
"execute_script": {
"example_1": "De exemplu, pentru a adăuga un șir de caractere la titlul unei notițe, se poate folosi acest mic script:",
@@ -796,12 +800,9 @@
"modal_body_text": "Din cauza limitărilor la nivel de navigator, nu este posibilă citirea clipboard-ului din JavaScript. Inserați Markdown-ul pentru a-l importa în caseta de mai jos și dați clic pe butonul Import"
},
"max_content_width": {
"apply_changes_description": "Pentru a aplica schimbările de lățime a conținutului, dați click pe",
"default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.",
"max_width_label": "Lungimea maximă a conținutului",
"max_width_unit": "pixeli",
"reload_button": "reîncarcă interfața",
"reload_description": "schimbări din opțiunile de afișare",
"title": "Lățime conținut"
},
"mobile_detail_menu": {

View File

@@ -1203,11 +1203,8 @@
"max_content_width": {
"max_width_unit": "пикселей",
"title": "Ширина контентной области",
"reload_button": "перезагрузить интерфейс",
"default_description": "Trilium по умолчанию ограничивает максимальную ширину контента, чтобы улучшить читаемость на широких экранах.",
"max_width_label": "Максимальная ширина контентной области",
"apply_changes_description": "Чтобы применить изменения, нажмите на",
"reload_description": "изменения в параметрах внешнего вида"
"max_width_label": "Максимальная ширина контентной области"
},
"native_title_bar": {
"enabled": "включено",
@@ -1440,6 +1437,7 @@
},
"etapi": {
"title": "ETAPI",
"wiki": "вики",
"created": "Создано",
"actions": "Действия",
"existing_tokens": "Существующие токены",
@@ -1447,7 +1445,10 @@
"default_token_name": "новый токен",
"rename_token_title": "Переименовать токен",
"description": "ETAPI — это REST API, используемый для программного доступа к экземпляру Trilium без пользовательского интерфейса.",
"see_more": "Более подробную информацию смотрите в {{- link_to_wiki}} и {{- link_to_openapi_spec}} или {{- link_to_swagger_ui }}.",
"create_token": "Создать новый токен ETAPI",
"openapi_spec": "Спецификация ETAPI OpenAPI",
"swagger_ui": "Пользовательский интерфейс ETAPI Swagger",
"new_token_title": "Новый токен ETAPI",
"token_created_title": "Создан токен ETAPI",
"rename_token": "Переименовать этот токен",

View File

@@ -1104,9 +1104,6 @@
"title": "內容寬度",
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
"max_width_label": "內容最大寬度(像素)",
"apply_changes_description": "要套用內容寬度更改,請點擊",
"reload_button": "重新載入前端",
"reload_description": "來自外觀選項的更改",
"max_width_unit": "像素"
},
"native_title_bar": {
@@ -1281,6 +1278,8 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI 是一個 REST API用於以編程方式訪問 Trilium 實例,而無需 UI。",
"wiki": "維基",
"openapi_spec": "ETAPI OpenAPI 規範",
"create_token": "新增 ETAPI 令牌",
"existing_tokens": "現有令牌",
"no_tokens_yet": "目前還沒有令牌。點擊上面的按鈕新增一個。",
@@ -1297,7 +1296,9 @@
"delete_token": "刪除 / 停用此令牌",
"rename_token_title": "重新命名令牌",
"rename_token_message": "請輸入新的令牌名稱",
"delete_token_confirmation": "您確定要刪除 ETAPI 令牌 \"{{name}}\" 嗎?"
"delete_token_confirmation": "您確定要刪除 ETAPI 令牌 \"{{name}}\" 嗎?",
"see_more": "有關更多詳細資訊,請參閱 {{- link_to_wiki}} 和 {{- link_to_openapi_spec}} 或 {{- link_to_swagger_ui}}。",
"swagger_ui": "ETAPI Swagger UI"
},
"options_widget": {
"options_status": "選項狀態",

View File

@@ -1204,10 +1204,7 @@
"title": "Ширина вмісту",
"default_description": "Trilium за замовчуванням обмежує максимальну ширину вмісту, щоб поліпшити читабельність на широкоформатних екранах у режимі максимального розширення.",
"max_width_label": "Максимальна ширина вмісту",
"max_width_unit": "пікселів",
"apply_changes_description": "Щоб застосувати зміни ширини вмісту, натисніть на",
"reload_button": "перезавантажити інтерфейс",
"reload_description": "зміни в параметрах зовнішнього вигляду"
"max_width_unit": "пікселів"
},
"native_title_bar": {
"title": "Нативний рядок заголовка (потрібен перезапуск)",
@@ -1402,6 +1399,10 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI — це REST API, який використовується для програмного доступу до екземпляра Trilium без інтерфейсу користувача.",
"see_more": "Див. докладнішу інформацію у {{- link_to_wiki}} та {{- link_to_openapi_spec}} або {{- link_to_swagger_ui }}.",
"wiki": "вікі",
"openapi_spec": "ETAPI OpenAPI spec",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Створити новий токен ETAPI",
"existing_tokens": "Існуючі токени",
"no_tokens_yet": "Токенів поки що немає. Натисніть кнопку вище, щоб створити його.",

View File

@@ -23,6 +23,24 @@ export class CssVarReader {
return (!isNaN(number.valueOf()) ? number.valueOf() : defaultValue)
}
asBoolean(defaultValue?: boolean) {
let value = this.value.toLocaleLowerCase().trim();
let result: boolean | undefined;
switch (value) {
case "true":
case "1":
result = true;
break;
case "false":
case "0":
result = false;
break;
}
return (result !== undefined) ? result : defaultValue;
}
asEnum<T>(enumType: T, defaultValue?: T[keyof T]): T[keyof T] | undefined {
let result: T[keyof T] | undefined;

View File

@@ -6,7 +6,7 @@
.floating-buttons-children,
.show-floating-buttons {
position: absolute;
top: var(--floating-buttons-vert-offset, 10px);
top: var(--floating-buttons-vert-offset, 14px);
inset-inline-end: var(--floating-buttons-horiz-offset, 10px);
display: flex;
flex-direction: row;

View File

@@ -1,5 +1,6 @@
.note-list-widget {
min-height: 0;
max-width: var(--max-content-width); /* Inherited from .note-split */
overflow: auto;
contain: none !important;
}

View File

@@ -1,9 +1,10 @@
import { EventData } from "../../components/app_context.js";
import { LOCALES } from "@triliumnext/commons";
import { readCssVar } from "../../utils/css-var.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";
import { LOCALES } from "@triliumnext/commons";
/**
* The root container is the top-most widget/container, from which the entire layout derives.
@@ -30,9 +31,11 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
window.visualViewport?.addEventListener("resize", () => this.#onMobileResize());
}
this.#setMaxContentWidth(options.getInt("maxContentWidth") ?? 0);
this.#setMotion(options.is("motionEnabled"));
this.#setShadows(options.is("shadowsEnabled"));
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
this.#setThemeCapabilities();
this.#setLocaleAndDirection(options.get("locale"));
return super.render();
@@ -50,14 +53,24 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
if (loadResults.isOptionReloaded("backdropEffectsEnabled")) {
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
}
if (loadResults.isOptionReloaded("maxContentWidth")) {
this.#setMaxContentWidth(options.getInt("maxContentWidth") ?? 0);
}
}
#onMobileResize() {
const currentViewportHeight = getViewportHeight();
const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight);
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
}
#setMaxContentWidth(width: number) {
width = Math.max(width, 640);
document.body.style.setProperty("--preferred-max-content-width", `${width}px`);
}
#setMotion(enabled: boolean) {
document.body.classList.toggle("motion-disabled", !enabled);
jQuery.fx.off = !enabled;
@@ -71,6 +84,15 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
document.body.classList.toggle("backdrop-effects-disabled", !enabled);
}
#setThemeCapabilities() {
// Supports background effects
const useBgfx = readCssVar(document.documentElement, "allow-background-effects")
.asBoolean(false);
document.body.classList.toggle("theme-supports-background-effects", useBgfx);
}
#setLocaleAndDirection(locale: string) {
const correspondingLocale = LOCALES.find(l => l.id === locale);
document.body.lang = locale;

View File

@@ -57,17 +57,19 @@ const TPL = /*html*/`\
}
</style>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">
<!-- This is where the first child will be injected -->
<div class="quick-edit-dialog-wrapper">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">
<!-- This is where the first child will be injected -->
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- This is where all but the first child will be injected. -->
<div class="modal-body">
<!-- This is where all but the first child will be injected. -->
</div>
</div>
</div>
</div>
@@ -79,6 +81,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
private noteContext: NoteContext;
private $modalHeader!: JQuery<HTMLElement>;
private $modalBody!: JQuery<HTMLElement>;
private $wrapper!: JQuery<HTMLDivElement>;
constructor() {
super();
@@ -93,6 +96,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
const $newWidget = $(TPL);
this.$modalHeader = $newWidget.find(".modal-title");
this.$modalBody = $newWidget.find(".modal-body");
this.$wrapper = $newWidget.find(".quick-edit-dialog-wrapper");
const children = this.$widget.children();
this.$modalHeader.append(children[0]);
@@ -112,6 +116,21 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
}
});
const colorClass = this.noteContext.note?.getColorClass();
const wrapperElement = this.$wrapper.get(0)!;
if (colorClass) {
wrapperElement.className = "quick-edit-dialog-wrapper " + colorClass;
} else {
wrapperElement.className = "quick-edit-dialog-wrapper";
}
const customHue = getComputedStyle(wrapperElement).getPropertyValue("--custom-color-hue");
if (customHue) {
/* Apply the tinted-dialog class only if the custom color CSS class specifies a hue */
wrapperElement.classList.add("tinted-quick-edit-dialog");
}
const activeEl = document.activeElement;
if (activeEl && "blur" in activeEl) {
(activeEl as HTMLElement).blur();

View File

@@ -39,6 +39,7 @@ const TPL = /*html*/`
<div class="note-detail">
<style>
.note-detail {
max-width: var(--max-content-width); /* Inherited from .note-split */
font-family: var(--detail-font-family);
font-size: var(--detail-font-size);
}

View File

@@ -52,6 +52,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
const note = this.noteContext?.note;
if (!note) {
this.$widget.addClass("bgfx");
return;
}
@@ -61,7 +62,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
this.$widget.addClass(utils.getNoteTypeClass(note.type));
this.$widget.addClass(utils.getMimeTypeClass(note.mime));
this.$widget.toggleClass("bgfx", note.isOptions());
this.$widget.toggleClass("protected", note.isProtected);
const noteLanguage = note?.getLabelValue("language");
@@ -70,7 +71,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
}
#isFullWidthNote(note: FNote) {
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
if (["code", "image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
return true;
}

View File

@@ -299,10 +299,6 @@ function MaxContentWidth() {
/>
</FormGroup>
</Column>
<p>
{t("max_content_width.apply_changes_description")} <Button text={t("max_content_width.reload_button")} size="micro" onClick={reloadFrontendApp} />
</p>
</OptionsSection>
)
}

View File

@@ -97,7 +97,7 @@ function TokenList({ tokens }: { tokens: EtapiToken[] }) {
return (
tokens.length ? (
<div style={{ overflow: "auto", height: "500px"}}>
<div style={{ overflow: "auto"}}>
<table className="table table-stripped">
<thead>
<tr>

View File

@@ -28,12 +28,6 @@
<%- include("./partials/windowGlobal.ejs", locals) %>
<style>
.note-split {
max-width: <%= maxContentWidth %>px;
}
</style>
<!-- Required for match the PWA's top bar color with the theme -->
<!-- This works even when the user directly changes --root-background in CSS -->
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>

View File

@@ -57,7 +57,6 @@ function index(req: Request, res: Response) {
isDev,
isMainWindow: view === "mobile" ? true : !req.query.extraWindow,
isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
maxContentWidth: Math.max(640, parseInt(options.maxContentWidth)),
triliumVersion: packageJson.version,
assetPath,
appPath,

View File

@@ -1,37 +1 @@
{
"get-started": {
"title": "Для начала",
"desktop_title": "Установите приложение для ПК (v{{version}})",
"architecture": "Архитектура:",
"older_releases": "См. старые релизы",
"server_title": "Настройка сервера для работы с нескольких устройств"
},
"hero_section": {
"title": "Упорядочите свои мысли. Создайте личную базу знаний.",
"subtitle": "Trilium - это open-source решение для ведение заметок и организации личной базы знаний. Используйте его локально на своём ПК, или синхронизируйтесь с собственным сервером, чтобы ваши заметки всегда были с вами.",
"get_started": "Для начала",
"github": "GitHub",
"dockerhub": "Docker Hub",
"screenshot_alt": "Скриншот приложения Trilium Notes для ПК"
},
"organization_benefits": {
"title": "Структура",
"note_structure_title": "Структура заметки",
"note_structure_description": "Строки могут распологаться иерархически. Не нужно постоянно создавать папки, так как каждая заметка может содержать вложенные под-заметки. Одну и ту же заметку можно добавить сразу в несколько мест в иерархии.",
"attributes_title": "Ярлыки и связи заметок",
"hoisting_title": "Рабочие пространства и хосты",
"hoisting_description": "Легко разделяйте заметки на личные и рабочие, группируя их в рабочей области. Благодаря этому в вашем дереве будет отображаться только определённый набор заметок."
},
"productivity_benefits": {
"revisions_content": "Заметки периодически сохраняются в фоне, ревизии могут быть использованы для просмотра или отмены случайных изменений. Ревизии также можно создавать самостоятельно.",
"sync_title": "Синхронизация",
"protected_notes_title": "Защищённые заметки",
"jump_to_title": "Бастрый поиск и команды",
"search_title": "Глубокий поиск",
"web_clipper_content": "Перемещайте целые веб-страницы (или скриншоты) в Trilium с помощью браузерного расширения web clipper."
},
"note_types": {
"title": "Несколько способов представления вашей информации",
"text_title": "Текстовые заметки"
}
}
{}

1016
docs/ARCHITECTURE.md vendored

File diff suppressed because it is too large Load Diff

736
docs/DATABASE.md vendored
View File

@@ -1,736 +0,0 @@
# Trilium Database Architecture
> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [Database Schema](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/)
## Overview
Trilium uses **SQLite** as its embedded database engine, providing a reliable, file-based storage system that requires no separate database server. The database stores all notes, their relationships, metadata, and configuration.
## Database File
**Location:**
- Desktop: `~/.local/share/trilium-data/document.db` (Linux/Mac) or `%APPDATA%/trilium-data/document.db` (Windows)
- Server: Configured via `TRILIUM_DATA_DIR` environment variable
- Docker: Mounted volume at `/home/node/trilium-data/`
**Characteristics:**
- Single-file database
- Embedded (no server required)
- ACID compliant
- Cross-platform
- Supports up to 281 TB database size
- Efficient for 100k+ notes
## Database Driver
**Library:** `better-sqlite3`
**Why better-sqlite3:**
- Native performance (C++ bindings)
- Synchronous API (simpler code)
- Prepared statements
- Transaction support
- Type safety
**Usage:**
```typescript
// apps/server/src/services/sql.ts
import Database from 'better-sqlite3'
const db = new Database('document.db')
const stmt = db.prepare('SELECT * FROM notes WHERE noteId = ?')
const note = stmt.get(noteId)
```
## Schema Overview
Schema location: `apps/server/src/assets/db/schema.sql`
**Entity Tables:**
- `notes` - Core note data
- `branches` - Tree relationships
- `attributes` - Metadata (labels/relations)
- `revisions` - Version history
- `attachments` - File attachments
- `blobs` - Binary content storage
**System Tables:**
- `options` - Application configuration
- `entity_changes` - Change tracking for sync
- `recent_notes` - Recently accessed notes
- `etapi_tokens` - API authentication tokens
- `user_data` - User credentials
- `sessions` - Web session storage
## Entity Tables
### Notes Table
```sql
CREATE TABLE notes (
noteId TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL DEFAULT "note",
isProtected INT NOT NULL DEFAULT 0,
type TEXT NOT NULL DEFAULT 'text',
mime TEXT NOT NULL DEFAULT 'text/html',
blobId TEXT DEFAULT NULL,
isDeleted INT NOT NULL DEFAULT 0,
deleteId TEXT DEFAULT NULL,
dateCreated TEXT NOT NULL,
dateModified TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
utcDateModified TEXT NOT NULL
);
-- Indexes for performance
CREATE INDEX IDX_notes_title ON notes (title);
CREATE INDEX IDX_notes_type ON notes (type);
CREATE INDEX IDX_notes_dateCreated ON notes (dateCreated);
CREATE INDEX IDX_notes_dateModified ON notes (dateModified);
CREATE INDEX IDX_notes_utcDateModified ON notes (utcDateModified);
CREATE INDEX IDX_notes_blobId ON notes (blobId);
```
**Field Descriptions:**
| Field | Type | Description |
|-------|------|-------------|
| `noteId` | TEXT | Unique identifier (UUID or custom) |
| `title` | TEXT | Note title (displayed in tree) |
| `isProtected` | INT | 1 if encrypted, 0 if not |
| `type` | TEXT | Note type: text, code, file, image, etc. |
| `mime` | TEXT | MIME type: text/html, application/json, etc. |
| `blobId` | TEXT | Reference to content in blobs table |
| `isDeleted` | INT | Soft delete flag |
| `deleteId` | TEXT | Unique delete operation ID |
| `dateCreated` | TEXT | Creation date (local timezone) |
| `dateModified` | TEXT | Last modified (local timezone) |
| `utcDateCreated` | TEXT | Creation date (UTC) |
| `utcDateModified` | TEXT | Last modified (UTC) |
**Note Types:**
- `text` - Rich text with HTML
- `code` - Source code
- `file` - Binary file
- `image` - Image file
- `search` - Saved search
- `render` - Custom HTML rendering
- `relation-map` - Relationship diagram
- `canvas` - Excalidraw drawing
- `mermaid` - Mermaid diagram
- `book` - Container for documentation
- `web-view` - Embedded web page
- `mindmap` - Mind map
- `geomap` - Geographical map
### Branches Table
```sql
CREATE TABLE branches (
branchId TEXT NOT NULL PRIMARY KEY,
noteId TEXT NOT NULL,
parentNoteId TEXT NOT NULL,
notePosition INTEGER NOT NULL,
prefix TEXT,
isExpanded INTEGER NOT NULL DEFAULT 0,
isDeleted INTEGER NOT NULL DEFAULT 0,
deleteId TEXT DEFAULT NULL,
utcDateModified TEXT NOT NULL
);
-- Indexes
CREATE INDEX IDX_branches_noteId_parentNoteId ON branches (noteId, parentNoteId);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
```
**Field Descriptions:**
| Field | Type | Description |
|-------|------|-------------|
| `branchId` | TEXT | Unique identifier for this branch |
| `noteId` | TEXT | Child note ID |
| `parentNoteId` | TEXT | Parent note ID |
| `notePosition` | INT | Sort order among siblings |
| `prefix` | TEXT | Optional prefix text (e.g., "Chapter 1:") |
| `isExpanded` | INT | Tree expansion state |
| `isDeleted` | INT | Soft delete flag |
| `deleteId` | TEXT | Delete operation ID |
| `utcDateModified` | TEXT | Last modified (UTC) |
**Key Concepts:**
- **Cloning:** A note can have multiple branches (multiple parents)
- **Position:** Siblings ordered by `notePosition`
- **Prefix:** Display text before note title in tree
- **Soft Delete:** Allows sync before permanent deletion
### Attributes Table
```sql
CREATE TABLE attributes (
attributeId TEXT NOT NULL PRIMARY KEY,
noteId TEXT NOT NULL,
type TEXT NOT NULL,
name TEXT NOT NULL,
value TEXT DEFAULT '' NOT NULL,
position INT DEFAULT 0 NOT NULL,
utcDateModified TEXT NOT NULL,
isDeleted INT NOT NULL,
deleteId TEXT DEFAULT NULL,
isInheritable INT DEFAULT 0 NULL
);
-- Indexes
CREATE INDEX IDX_attributes_name_value ON attributes (name, value);
CREATE INDEX IDX_attributes_noteId ON attributes (noteId);
CREATE INDEX IDX_attributes_value ON attributes (value);
```
**Field Descriptions:**
| Field | Type | Description |
|-------|------|-------------|
| `attributeId` | TEXT | Unique identifier |
| `noteId` | TEXT | Note this attribute belongs to |
| `type` | TEXT | 'label' or 'relation' |
| `name` | TEXT | Attribute name |
| `value` | TEXT | Attribute value (text for labels, noteId for relations) |
| `position` | INT | Display order |
| `utcDateModified` | TEXT | Last modified (UTC) |
| `isDeleted` | INT | Soft delete flag |
| `deleteId` | TEXT | Delete operation ID |
| `isInheritable` | INT | Inherited by child notes |
**Attribute Types:**
**Labels** (key-value pairs):
```sql
-- Example: #priority=high
INSERT INTO attributes (attributeId, noteId, type, name, value)
VALUES ('attr1', 'note123', 'label', 'priority', 'high')
```
**Relations** (links to other notes):
```sql
-- Example: ~author=[[noteId]]
INSERT INTO attributes (attributeId, noteId, type, name, value)
VALUES ('attr2', 'note123', 'relation', 'author', 'author-note-id')
```
**Special Attributes:**
- `#run=frontendStartup` - Execute script on frontend load
- `#run=backendStartup` - Execute script on backend load
- `#customWidget` - Custom widget implementation
- `#iconClass` - Custom tree icon
- `#cssClass` - CSS class for note
- `#sorted` - Auto-sort children
- `#hideChildrenOverview` - Don't show child list
### Revisions Table
```sql
CREATE TABLE revisions (
revisionId TEXT NOT NULL PRIMARY KEY,
noteId TEXT NOT NULL,
type TEXT DEFAULT '' NOT NULL,
mime TEXT DEFAULT '' NOT NULL,
title TEXT NOT NULL,
isProtected INT NOT NULL DEFAULT 0,
blobId TEXT DEFAULT NULL,
utcDateLastEdited TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
utcDateModified TEXT NOT NULL,
dateLastEdited TEXT NOT NULL,
dateCreated TEXT NOT NULL
);
-- Indexes
CREATE INDEX IDX_revisions_noteId ON revisions (noteId);
CREATE INDEX IDX_revisions_utcDateCreated ON revisions (utcDateCreated);
CREATE INDEX IDX_revisions_utcDateLastEdited ON revisions (utcDateLastEdited);
CREATE INDEX IDX_revisions_blobId ON revisions (blobId);
```
**Revision Strategy:**
- Automatic revision created on note modification
- Configurable interval (default: daily max)
- Stores complete note snapshot
- Allows reverting to previous versions
- Can be disabled with `#disableVersioning`
### Attachments Table
```sql
CREATE TABLE attachments (
attachmentId TEXT NOT NULL PRIMARY KEY,
ownerId TEXT NOT NULL,
role TEXT NOT NULL,
mime TEXT NOT NULL,
title TEXT NOT NULL,
isProtected INT NOT NULL DEFAULT 0,
position INT DEFAULT 0 NOT NULL,
blobId TEXT DEFAULT NULL,
dateModified TEXT NOT NULL,
utcDateModified TEXT NOT NULL,
utcDateScheduledForErasureSince TEXT DEFAULT NULL,
isDeleted INT NOT NULL,
deleteId TEXT DEFAULT NULL
);
-- Indexes
CREATE INDEX IDX_attachments_ownerId_role ON attachments (ownerId, role);
CREATE INDEX IDX_attachments_blobId ON attachments (blobId);
```
**Attachment Roles:**
- `file` - Regular file attachment
- `image` - Image file
- `cover-image` - Note cover image
- Custom roles for specific purposes
### Blobs Table
```sql
CREATE TABLE blobs (
blobId TEXT NOT NULL PRIMARY KEY,
content TEXT NULL DEFAULT NULL,
dateModified TEXT NOT NULL,
utcDateModified TEXT NOT NULL
);
```
**Blob Usage:**
- Stores actual content (text or binary)
- Referenced by notes, revisions, attachments
- Deduplication via hash-based blobId
- TEXT type stores both text and binary (base64)
**Content Types:**
- **Text notes:** HTML content
- **Code notes:** Plain text source code
- **Binary notes:** Base64 encoded data
- **Protected notes:** Encrypted content
## System Tables
### Options Table
```sql
CREATE TABLE options (
name TEXT NOT NULL PRIMARY KEY,
value TEXT NOT NULL,
isSynced INTEGER DEFAULT 0 NOT NULL,
utcDateModified TEXT NOT NULL
);
```
**Key Options:**
- `documentId` - Unique installation ID
- `dbVersion` - Schema version
- `syncVersion` - Sync protocol version
- `passwordVerificationHash` - Password verification
- `encryptedDataKey` - Encryption key (encrypted)
- `theme` - UI theme
- Various feature flags and settings
**Synced Options:**
- `isSynced = 1` - Synced across devices
- `isSynced = 0` - Local to this installation
### Entity Changes Table
```sql
CREATE TABLE entity_changes (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
entityName TEXT NOT NULL,
entityId TEXT NOT NULL,
hash TEXT NOT NULL,
isErased INT NOT NULL,
changeId TEXT NOT NULL,
componentId TEXT NOT NULL,
instanceId TEXT NOT NULL,
isSynced INTEGER NOT NULL,
utcDateChanged TEXT NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX IDX_entityChanges_entityName_entityId
ON entity_changes (entityName, entityId);
CREATE INDEX IDX_entity_changes_changeId ON entity_changes (changeId);
```
**Purpose:** Track all entity modifications for synchronization
**Entity Types:**
- `notes`
- `branches`
- `attributes`
- `revisions`
- `attachments`
- `options`
- `etapi_tokens`
### Recent Notes Table
```sql
CREATE TABLE recent_notes (
noteId TEXT NOT NULL PRIMARY KEY,
notePath TEXT NOT NULL,
utcDateCreated TEXT NOT NULL
);
```
**Purpose:** Track recently accessed notes for quick access
### Sessions Table
```sql
CREATE TABLE sessions (
sid TEXT PRIMARY KEY,
sess TEXT NOT NULL,
expired TEXT NOT NULL
);
```
**Purpose:** HTTP session storage for web interface
### User Data Table
```sql
CREATE TABLE user_data (
tmpID INT PRIMARY KEY,
username TEXT,
email TEXT,
userIDEncryptedDataKey TEXT,
userIDVerificationHash TEXT,
salt TEXT,
derivedKey TEXT,
isSetup TEXT DEFAULT "false"
);
```
**Purpose:** Store user authentication credentials
### ETAPI Tokens Table
```sql
CREATE TABLE etapi_tokens (
etapiTokenId TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
tokenHash TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
utcDateModified TEXT NOT NULL,
isDeleted INT NOT NULL DEFAULT 0
);
```
**Purpose:** API token authentication for external access
## Data Relationships
```
┌──────────────┐
│ Notes │
└───┬──────────┘
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌───────────┐
│Branches│ │Attributes│ │Attachments│
└────────┘ └──────────┘ └─────┬─────┘
│ │
│ │
│ ┌──────────┐ │
└──────▶│ Blobs │◀────────┘
└──────────┘
┌────┴─────┐
│Revisions │
└──────────┘
```
**Relationships:**
- Notes ↔ Branches (many-to-many via noteId)
- Notes → Attributes (one-to-many)
- Notes → Blobs (one-to-one)
- Notes → Revisions (one-to-many)
- Notes → Attachments (one-to-many)
- Attachments → Blobs (one-to-one)
- Revisions → Blobs (one-to-one)
## Database Access Patterns
### Direct SQL Access
**Location:** `apps/server/src/services/sql.ts`
```typescript
// Execute query (returns rows)
const notes = sql.getRows('SELECT * FROM notes WHERE type = ?', ['text'])
// Execute query (returns single row)
const note = sql.getRow('SELECT * FROM notes WHERE noteId = ?', [noteId])
// Execute statement (no return)
sql.execute('UPDATE notes SET title = ? WHERE noteId = ?', [title, noteId])
// Insert
sql.insert('notes', {
noteId: 'new-note-id',
title: 'New Note',
type: 'text',
// ...
})
// Transactions
sql.transactional(() => {
sql.execute('UPDATE ...')
sql.execute('INSERT ...')
})
```
### Entity-Based Access (Recommended)
**Via Becca Cache:**
```typescript
// Get entity from cache
const note = becca.getNote(noteId)
// Modify and save
note.title = 'Updated Title'
note.save() // Writes to database
// Create new
const newNote = becca.createNote({
parentNoteId: 'root',
title: 'New Note',
type: 'text',
content: 'Hello World'
})
// Delete
note.markAsDeleted()
```
## Database Migrations
**Location:** `apps/server/src/migrations/`
**Migration Files:**
- Format: `XXXX_migration_name.sql` or `XXXX_migration_name.js`
- Executed in numerical order
- Version tracked in `options.dbVersion`
**SQL Migration Example:**
```sql
-- 0280_add_new_column.sql
ALTER TABLE notes ADD COLUMN newField TEXT DEFAULT NULL;
UPDATE options SET value = '280' WHERE name = 'dbVersion';
```
**JavaScript Migration Example:**
```javascript
// 0285_complex_migration.js
module.exports = () => {
const notes = sql.getRows('SELECT * FROM notes WHERE type = ?', ['old-type'])
for (const note of notes) {
sql.execute('UPDATE notes SET type = ? WHERE noteId = ?',
['new-type', note.noteId])
}
}
```
**Migration Process:**
1. Server checks `dbVersion` on startup
2. Compares with latest migration number
3. Executes pending migrations in order
4. Updates `dbVersion` after each
5. Restarts if migrations ran
## Database Maintenance
### Backup
**Full Backup:**
```bash
# Copy database file
cp document.db document.db.backup
# Or use Trilium's backup feature
# Settings → Backup
```
**Automatic Backups:**
- Daily backup (configurable)
- Stored in `backup/` directory
- Retention policy (keep last N backups)
### Vacuum
**Purpose:** Reclaim unused space, defragment
```sql
VACUUM;
```
**When to vacuum:**
- After deleting many notes
- Database file size larger than expected
- Performance degradation
### Integrity Check
```sql
PRAGMA integrity_check;
```
**Result:** "ok" or list of errors
### Consistency Checks
**Built-in Consistency Checks:**
Location: `apps/server/src/services/consistency_checks.ts`
- Orphaned branches
- Missing parent notes
- Circular dependencies
- Invalid entity references
- Blob reference integrity
**Run Checks:**
```typescript
// Via API
POST /api/consistency-check
// Or from backend script
api.runConsistencyChecks()
```
## Performance Optimization
### Indexes
**Existing Indexes:**
- `notes.title` - Fast title searches
- `notes.type` - Filter by type
- `notes.dateCreated/Modified` - Time-based queries
- `branches.noteId_parentNoteId` - Tree navigation
- `attributes.name_value` - Attribute searches
**Query Optimization:**
```sql
-- Use indexed columns in WHERE clause
SELECT * FROM notes WHERE type = 'text' -- Uses index
-- Avoid functions on indexed columns
SELECT * FROM notes WHERE LOWER(title) = 'test' -- No index
-- Better
SELECT * FROM notes WHERE title = 'Test' -- Uses index
```
### Connection Settings
```typescript
// apps/server/src/services/sql.ts
const db = new Database('document.db', {
// Enable WAL mode for better concurrency
verbose: console.log
})
db.pragma('journal_mode = WAL')
db.pragma('synchronous = NORMAL')
db.pragma('cache_size = -64000') // 64MB cache
db.pragma('temp_store = MEMORY')
```
**WAL Mode Benefits:**
- Better concurrency (readers don't block writers)
- Faster commits
- More robust
### Query Performance
**Use EXPLAIN QUERY PLAN:**
```sql
EXPLAIN QUERY PLAN
SELECT * FROM notes
WHERE type = 'text'
AND dateCreated > '2025-01-01'
```
**Analyze slow queries:**
- Check index usage
- Avoid SELECT *
- Use prepared statements
- Batch operations in transactions
## Database Size Management
**Typical Sizes:**
- 1,000 notes: ~5-10 MB
- 10,000 notes: ~50-100 MB
- 100,000 notes: ~500 MB - 1 GB
**Size Reduction Strategies:**
1. **Delete old revisions**
2. **Remove large attachments**
3. **Vacuum database**
4. **Compact blobs**
5. **Archive old notes**
**Blob Deduplication:**
- Blobs identified by content hash
- Identical content shares one blob
- Automatic deduplication on insert
## Security Considerations
### Protected Notes Encryption
**Encryption Process:**
```typescript
// Encrypt blob content
const encryptedContent = encrypt(content, dataKey)
blob.content = encryptedContent
// Store encrypted
sql.insert('blobs', { blobId, content: encryptedContent })
```
**Encryption Details:**
- Algorithm: AES-256-CBC
- Key derivation: PBKDF2 (10,000 iterations)
- Per-note encryption
- Master key encrypted with user password
### SQL Injection Prevention
**Always use parameterized queries:**
```typescript
// GOOD - Safe from SQL injection
sql.execute('SELECT * FROM notes WHERE title = ?', [userInput])
// BAD - Vulnerable to SQL injection
sql.execute(`SELECT * FROM notes WHERE title = '${userInput}'`)
```
### Database File Protection
**File Permissions:**
- Owner read/write only
- No group/other access
- Located in user-specific directory
---
**See Also:**
- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture
- [Database Schema Files](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/)
- [Migration Scripts](../apps/server/src/migrations/)

View File

@@ -1,155 +0,0 @@
# Trilium Technical Documentation - Quick Reference
> **Start here:** [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) - Complete index of all documentation
## 📖 Documentation Files
| Document | Description | Size | Lines |
|----------|-------------|------|-------|
| [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) | Main index and navigation hub | 13KB | 423 |
| [ARCHITECTURE.md](ARCHITECTURE.md) | Complete system architecture | 30KB | 1,016 |
| [DATABASE.md](DATABASE.md) | Database schema and operations | 19KB | 736 |
| [SYNCHRONIZATION.md](SYNCHRONIZATION.md) | Sync protocol and implementation | 14KB | 583 |
| [SCRIPTING.md](SCRIPTING.md) | User scripting system guide | 17KB | 734 |
| [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) | Security implementation details | 19KB | 834 |
**Total:** 112KB of comprehensive documentation across 4,326 lines!
## 🎯 Quick Access by Role
### 👤 End Users
- **Getting Started:** [User Guide](User%20Guide/User%20Guide/)
- **Scripting:** [SCRIPTING.md](SCRIPTING.md)
- **Sync Setup:** [SYNCHRONIZATION.md](SYNCHRONIZATION.md)
### 💻 Developers
- **Architecture:** [ARCHITECTURE.md](ARCHITECTURE.md)
- **Development Setup:** [Developer Guide](Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
- **Database:** [DATABASE.md](DATABASE.md)
### 🔒 Security Auditors
- **Security:** [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md)
- **Encryption:** [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption)
- **Auth:** [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication)
### 🏗️ System Architects
- **Overall Design:** [ARCHITECTURE.md](ARCHITECTURE.md)
- **Cache System:** [ARCHITECTURE.md#three-layer-cache-system](ARCHITECTURE.md#three-layer-cache-system)
- **Entity Model:** [ARCHITECTURE.md#entity-system](ARCHITECTURE.md#entity-system)
### 🔧 DevOps Engineers
- **Server Installation:** [User Guide - Server Installation](User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- **Docker:** [Developer Guide - Docker](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Docker.md)
- **Sync Server:** [SYNCHRONIZATION.md#sync-server-configuration](SYNCHRONIZATION.md#sync-server-configuration)
### 📊 Database Administrators
- **Schema:** [DATABASE.md#database-schema](DATABASE.md#database-schema)
- **Maintenance:** [DATABASE.md#database-maintenance](DATABASE.md#database-maintenance)
- **Performance:** [DATABASE.md#performance-optimization](DATABASE.md#performance-optimization)
## 🔍 Quick Topic Finder
### Core Concepts
- **Becca Cache:** [ARCHITECTURE.md#1-becca-backend-cache](ARCHITECTURE.md#1-becca-backend-cache)
- **Froca Cache:** [ARCHITECTURE.md#2-froca-frontend-cache](ARCHITECTURE.md#2-froca-frontend-cache)
- **Entity System:** [ARCHITECTURE.md#entity-system](ARCHITECTURE.md#entity-system)
- **Widget System:** [ARCHITECTURE.md#widget-based-ui](ARCHITECTURE.md#widget-based-ui)
### Database
- **Schema Overview:** [DATABASE.md#schema-overview](DATABASE.md#schema-overview)
- **Notes Table:** [DATABASE.md#notes-table](DATABASE.md#notes-table)
- **Branches Table:** [DATABASE.md#branches-table](DATABASE.md#branches-table)
- **Migrations:** [DATABASE.md#database-migrations](DATABASE.md#database-migrations)
### Synchronization
- **Sync Protocol:** [SYNCHRONIZATION.md#sync-protocol](SYNCHRONIZATION.md#sync-protocol)
- **Conflict Resolution:** [SYNCHRONIZATION.md#conflict-resolution](SYNCHRONIZATION.md#conflict-resolution)
- **Entity Changes:** [SYNCHRONIZATION.md#entity-changes](SYNCHRONIZATION.md#entity-changes)
### Scripting
- **Frontend Scripts:** [SCRIPTING.md#frontend-scripts](SCRIPTING.md#frontend-scripts)
- **Backend Scripts:** [SCRIPTING.md#backend-scripts](SCRIPTING.md#backend-scripts)
- **Script Examples:** [SCRIPTING.md#script-examples](SCRIPTING.md#script-examples)
- **API Reference:** [SCRIPTING.md#script-api](SCRIPTING.md#script-api)
### Security
- **Authentication:** [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication)
- **Encryption:** [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption)
- **Input Sanitization:** [SECURITY_ARCHITECTURE.md#input-sanitization](SECURITY_ARCHITECTURE.md#input-sanitization)
- **Best Practices:** [SECURITY_ARCHITECTURE.md#security-best-practices](SECURITY_ARCHITECTURE.md#security-best-practices)
## 📚 Learning Paths
### New to Trilium Development
1. Read [ARCHITECTURE.md](ARCHITECTURE.md) - System overview
2. Setup environment: [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
3. Explore [DATABASE.md](DATABASE.md) - Understand data model
4. Check [Developer Guide](Developer%20Guide/Developer%20Guide/)
### Want to Create Scripts
1. Read [SCRIPTING.md](SCRIPTING.md) - Complete guide
2. Check [Script API](Script%20API/) - API reference
3. Review examples: [SCRIPTING.md#script-examples](SCRIPTING.md#script-examples)
4. Explore [Advanced Showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases)
### Setting Up Sync
1. Understand protocol: [SYNCHRONIZATION.md](SYNCHRONIZATION.md)
2. Configure server: [SYNCHRONIZATION.md#sync-server-configuration](SYNCHRONIZATION.md#sync-server-configuration)
3. Setup clients: [SYNCHRONIZATION.md#client-setup](SYNCHRONIZATION.md#client-setup)
4. Troubleshoot: [SYNCHRONIZATION.md#troubleshooting](SYNCHRONIZATION.md#troubleshooting)
### Security Review
1. Read threat model: [SECURITY_ARCHITECTURE.md#threat-model](SECURITY_ARCHITECTURE.md#threat-model)
2. Review authentication: [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication)
3. Check encryption: [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption)
4. Verify best practices: [SECURITY_ARCHITECTURE.md#security-best-practices](SECURITY_ARCHITECTURE.md#security-best-practices)
## 🗺️ Documentation Map
```
docs/
├── TECHNICAL_DOCUMENTATION.md ← START HERE (Index)
├── Core Technical Docs
│ ├── ARCHITECTURE.md (System design)
│ ├── DATABASE.md (Data layer)
│ ├── SYNCHRONIZATION.md (Sync system)
│ ├── SCRIPTING.md (User scripting)
│ └── SECURITY_ARCHITECTURE.md (Security)
├── Developer Guide/
│ └── Developer Guide/ (Development setup)
├── User Guide/
│ └── User Guide/ (End-user docs)
└── Script API/ (API reference)
```
## 💡 Tips for Reading Documentation
1. **Start with the index:** [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) provides an overview
2. **Use search:** Press Ctrl+F / Cmd+F to find specific topics
3. **Follow links:** Documents are cross-referenced for easy navigation
4. **Code examples:** Most docs include practical code examples
5. **See Also sections:** Check bottom of each doc for related resources
## 🔗 External Resources
- **Website:** https://triliumnotes.org
- **Online Docs:** https://docs.triliumnotes.org
- **GitHub:** https://github.com/TriliumNext/Trilium
- **Discussions:** https://github.com/TriliumNext/Trilium/discussions
- **Matrix Chat:** https://matrix.to/#/#triliumnext:matrix.org
## 🤝 Contributing to Documentation
Found an error or want to improve the docs? See:
- [Contributing Guide](../README.md#-contribute)
- [Documentation Standards](TECHNICAL_DOCUMENTATION.md#documentation-conventions)
---
**Version:** 0.99.3
**Last Updated:** November 2025
**Maintained by:** TriliumNext Team

181
docs/README-ru.md vendored
View File

@@ -11,19 +11,19 @@
# Trilium Notes
![Спонсоры GitHub](https://img.shields.io/github/sponsors/eliandoran) ![Меценаты
LiberaPay ](https://img.shields.io/liberapay/patrons/ElianDoran)\
![Загрузок Docker](https://img.shields.io/docker/pulls/triliumnext/trilium)
![Загрузок GitHub (all assets, all
![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran)
![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)\
![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/trilium)
![GitHub Downloads (all assets, all
releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\
[![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
[![Процесс
перевода](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[![Translation
status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[Английский](./README.md) | [Китайский (Упрощенный)](./docs/README-ZH_CN.md) |
[Китайский (Традиционный)](./docs/README-ZH_TW.md) |
[Русский](./docs/README-ru.md) | [Японский](./docs/README-ja.md) |
[Итальянский](./docs/README-it.md) | [Испанский](./docs/README-es.md)
[English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) |
[Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README-ru.md)
| [Japanese](./docs/README-ja.md) | [Italian](./docs/README-it.md) |
[Spanish](./docs/README-es.md)
Trilium Notes это приложение для заметок с иерархической структурой,
ориентированное на создание больших персональных баз знаний.
@@ -33,38 +33,38 @@ Trilium Notes это приложение для заметок с иера
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
## ⏬ Загрузка
- [Последний релиз](https://github.com/TriliumNext/Trilium/releases/latest)
стабильная версия, подойдёт для большинства пользователей.
- [Ночной билд](https://github.com/TriliumNext/Trilium/releases/tag/nightly)
нестабильная разрабатываемая версия, ежедневно получает новые функции и
исправления.
## ⏬ Download
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest)
stable version, recommended for most users.
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly)
unstable development version, updated daily with the latest features and
fixes.
## 📚 Документация
**Полная документация по адресу
**Visit our comprehensive documentation at
[docs.triliumnotes.org](https://docs.triliumnotes.org/)**
Документация доступна в нескольких форматах:
- **Онлайн Документация**: Полная документация доступна по адресу:
Our documentation is available in multiple formats:
- **Online Documentation**: Browse the full documentation at
[docs.triliumnotes.org](https://docs.triliumnotes.org/)
- **Справка в приложении**: Нажмите`F1` в Trilium для доступа к этой
документации прямо в приложении
- **In-App Help**: Press `F1` within Trilium to access the same documentation
directly in the application
- **GitHub**: Navigate through the [User
Guide](./docs/User%20Guide/User%20Guide/) in this repository
### Важные Ссылки
- [Руководство по началу работы](https://docs.triliumnotes.org/)
- [Инструкция по
установке](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Установка
Docker](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Обновление
### Quick Links
- [Getting Started Guide](https://docs.triliumnotes.org/)
- [Installation
Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker
Setup](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Upgrading
TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Основные идеи и
возможности](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Шаблоны Персональный Базы
Знаний](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
- [Basic Concepts and
Features](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Patterns of Personal Knowledge
Base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
## 🎁 Возможности
@@ -88,11 +88,11 @@ Trilium Notes это приложение для заметок с иера
* Специальные [атрибуты](https://triliumnext.github.io/Docs/Wiki/attributes)
позволяют гибко организовать структуру, используются для поиска и продвинутого
[скриптинга](https://triliumnext.github.io/Docs/Wiki/scripts)
* Интерфейс доступен на Английском, Немецком, Испанском, Французском, Румынском
и Китайском (упрощённом и традиционном)
* Интеграция [OpenID and TOTP
* UI available in English, German, Spanish, French, Romanian, and Chinese
(simplified and traditional)
* Direct [OpenID and TOTP
integration](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/Multi-Factor%20Authentication.md)
для более безопасного входа
for more secure login
* [Синхронизация](https://triliumnext.github.io/Docs/Wiki/synchronization)
заметок со своим сервером
* there's a [3rd party service for hosting synchronisation
@@ -223,20 +223,20 @@ installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
## 💻 Участвуйте в разработке
### Переводы
### Translations
Если вы являетесь носителем языка, помогите нам перевести Trilium, перейдя на
нашу [страницу Weblate](https://hosted.weblate.org/engage/trilium/).
If you are a native speaker, help us translate Trilium by heading over to our
[Weblate page](https://hosted.weblate.org/engage/trilium/).
Что сделано на данный момент:
Here's the language coverage we have so far:
[![Статус
перевода](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/)
[![Translation
status](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/)
### Код
### Code
Скачайте репозиторий, установите зависимости с помощью `pnpm`, затем запустите
сервер (доступен по адресу http://localhost:8080):
Download the repository, install dependencies using `pnpm` and then run the
server (available at http://localhost:8080):
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
@@ -244,10 +244,10 @@ pnpm install
pnpm run server:start
```
### Документация
### Documentation
Скачайте репозиторий, установите зависимости с помощью `pnpm`, затем запустите
окружение, необходимое для редактирование документации:
Download the repository, install dependencies using `pnpm` and then run the
environment required to edit the documentation:
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
@@ -255,9 +255,9 @@ pnpm install
pnpm edit-docs:edit-docs
```
### Сборка исполняемого файла
Скачайте репозиторий, установите зависимости с помощью `pnpm`, затем соберите
приложение для Windows:
### Building the Executable
Download the repository, install dependencies using `pnpm` and then build the
desktop app for Windows:
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
@@ -265,10 +265,10 @@ pnpm install
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
```
Для получения подробностей, смотрите [документы
разработки](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
For more details, see the [development
docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
### Документация для разработчиков
### Developer Documentation
Please view the [documentation
guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
@@ -277,49 +277,48 @@ described in the "Discuss with us" section above.
## 👏 Благодарности
* [zadam](https://github.com/zadam) за оригинальный концепт и реализацию
приложения.
* [Sarah Hussein](https://github.com/Sarah-Hussein) за создание иконки
приложения.
* [nriver](https://github.com/nriver) за работу по интернационализации.
* [zadam](https://github.com/zadam) for the original concept and implementation
of the application.
* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the
application icon.
* [nriver](https://github.com/nriver) for his work on internationalization.
* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas.
* [antoniotejada](https://github.com/nriver) за оригинальный виджет подсветки
синтаксиса.
* [Dosu](https://dosu.dev/) за обеспечение автоматических ответов на вопросы и
обсуждения GitHub.
* [Tabler Icons](https://tabler.io/icons) за системные иконки.
* [antoniotejada](https://github.com/nriver) for the original syntax highlight
widget.
* [Dosu](https://dosu.dev/) for providing us with the automated responses to
GitHub issues and discussions.
* [Tabler Icons](https://tabler.io/icons) for the system tray icons.
Trilium не существовал бы без технологий, лежащих в его основе:
Trilium would not be possible without the technologies behind it:
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - визуальный редактор
текстовых заметок. Мы благодарны за предоставленный нам набор дополнительный
функций.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - редактор кода с
поддержкой огромного количества языков.
* [Excalidraw](https://github.com/excalidraw/excalidraw) - бесконечная белая
доска, используемая в заметках Canvas.
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - обеспечивает
функционирование ментальной карты.
* [Leaflet](https://github.com/Leaflet/Leaflet) - отображение географических
карт.
* [Tabulator](https://github.com/olifolkerd/tabulator) - интерактивные таблицы,
используемые в коллекциях.
* [FancyTree](https://github.com/mar10/fancytree) - многофункциональная
библиотека деревьев, не имеющая себе равных.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - библиотека визуальных связей.
Используется в [картах
связей](https://triliumnext.github.io/Docs/Wiki/relation-map.html) и [картах
ссылок](https://triliumnext.github.io/Docs/Wiki/note-map.html#link-map)
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - the visual editor behind
text notes. We are grateful for being offered a set of the premium features.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with
support for huge amount of languages.
* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite
whiteboard used in Canvas notes.
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the
mind map functionality.
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical
maps.
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive
table used in collections.
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library
without real competition.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library.
Used in [relation
maps](https://triliumnext.github.io/Docs/Wiki/relation-map.html) and [link
maps](https://triliumnext.github.io/Docs/Wiki/note-map.html#link-map)
## 🤝 Поддержка
## 🤝 Support
На создание и поддержку Trilium затрачены [сотни часов
работы](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Ваша
поддержка помогает ему оставаться open-source, улучшает функции и покрывает
расходы, такие как хостинг.
Trilium is built and maintained with [hundreds of hours of
work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your
support keeps it open-source, improves features, and covers costs such as
hosting.
Вы также можете поддержать главного разработчика приложения
([eliandoran](https://github.com/eliandoran)) с помощью:
Consider supporting the main developer
([eliandoran](https://github.com/eliandoran)) of the application via:
- [GitHub Sponsors](https://github.com/sponsors/eliandoran)
- [PayPal](https://paypal.me/eliandoran)

22
docs/README.md vendored
View File

@@ -1,17 +1,4 @@
# Trilium Notes Documentation
## 📚 Technical Documentation
**NEW:** Comprehensive technical and architectural documentation is now available!
- **[Technical Documentation Index](TECHNICAL_DOCUMENTATION.md)** - Complete index to all technical docs
- **[Architecture Overview](ARCHITECTURE.md)** - System design and core patterns
- **[Database Architecture](DATABASE.md)** - Complete database documentation
- **[Synchronization](SYNCHRONIZATION.md)** - Sync protocol and implementation
- **[Scripting System](SCRIPTING.md)** - User scripting guide and API
- **[Security Architecture](SECURITY_ARCHITECTURE.md)** - Security implementation details
## 📖 User Documentation
# Trilium Notes
Please see the [main documentation](index.md) or visit one of our translated versions:
@@ -22,11 +9,4 @@ Please see the [main documentation](index.md) or visit one of our translated ver
- [简体中文](README-ZH_CN.md)
- [繁體中文](README-ZH_TW.md)
## 🔧 Developer Documentation
- [Developer Guide](Developer%20Guide/Developer%20Guide/) - Development environment and contribution guide
- [Script API](Script%20API/) - Complete scripting API reference
## 🔗 Additional Resources
For the full application README, please visit our [GitHub repository](https://github.com/triliumnext/trilium).

734
docs/SCRIPTING.md vendored
View File

@@ -1,734 +0,0 @@
# Trilium Scripting System
> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [Script API Documentation](Script%20API/)
## Overview
Trilium features a **powerful scripting system** that allows users to extend and customize the application without modifying source code. Scripts are written in JavaScript and can execute both in the **frontend (browser)** and **backend (Node.js)** contexts.
## Script Types
### Frontend Scripts
**Location:** Attached to notes with `#run=frontendStartup` attribute
**Execution Context:** Browser environment
**Access:**
- Trilium Frontend API
- Browser APIs (DOM, localStorage, etc.)
- Froca (frontend cache)
- UI widgets
- No direct file system access
**Lifecycle:**
- `frontendStartup` - Run once when Trilium loads
- `frontendReload` - Run on every note context change
**Example:**
```javascript
// Attach to note with #run=frontendStartup
const api = window.api
// Add custom button to toolbar
api.addButtonToToolbar({
title: 'My Button',
icon: 'star',
action: () => {
api.showMessage('Hello from frontend!')
}
})
```
### Backend Scripts
**Location:** Attached to notes with `#run=backendStartup` attribute
**Execution Context:** Node.js server environment
**Access:**
- Trilium Backend API
- Node.js APIs (fs, http, etc.)
- Becca (backend cache)
- Database (SQL)
- External libraries (via require)
**Lifecycle:**
- `backendStartup` - Run once when server starts
- Event handlers (custom events)
**Example:**
```javascript
// Attach to note with #run=backendStartup
const api = require('@triliumnext/api')
// Listen for note creation
api.dayjs // Example: access dayjs library
api.onNoteCreated((note) => {
if (note.title.includes('TODO')) {
note.setLabel('priority', 'high')
}
})
```
### Render Scripts
**Location:** Attached to notes with `#customWidget` or similar attributes
**Purpose:** Custom note rendering/widgets
**Example:**
```javascript
// Custom widget for a note
class MyWidget extends api.NoteContextAwareWidget {
doRender() {
this.$widget = $('<div>')
.text('Custom widget content')
return this.$widget
}
}
module.exports = MyWidget
```
## Script API
### Frontend API
**Location:** `apps/client/src/services/frontend_script_api.ts`
**Global Access:** `window.api`
**Key Methods:**
```typescript
// Note Operations
api.getNote(noteId) // Get note object
api.getBranch(branchId) // Get branch object
api.getActiveNote() // Currently displayed note
api.openNote(noteId, activateNote) // Open note in UI
// UI Operations
api.showMessage(message) // Show toast notification
api.showDialog() // Show modal dialog
api.confirm(message) // Show confirmation dialog
api.prompt(message, defaultValue) // Show input prompt
// Tree Operations
api.getTree() // Get note tree structure
api.expandTree(noteId) // Expand tree branch
api.collapseTree(noteId) // Collapse tree branch
// Search
api.searchForNotes(searchQuery) // Search notes
api.searchForNote(searchQuery) // Get single note
// Navigation
api.openTabWithNote(noteId) // Open note in new tab
api.closeActiveTab() // Close current tab
api.activateNote(noteId) // Switch to note
// Attributes
api.getAttribute(noteId, type, name) // Get attribute
api.getAttributes(noteId, type, name) // Get all matching attributes
// Custom Widgets
api.addButtonToToolbar(def) // Add toolbar button
api.addCustomWidget(def) // Add custom widget
// Events
api.runOnNoteOpened(callback) // Note opened event
api.runOnNoteContentChange(callback) // Content changed event
// Utilities
api.dayjs // Date/time library
api.formatDate(date) // Format date
api.log(message) // Console log
```
### Backend API
**Location:** `apps/server/src/services/backend_script_api.ts`
**Access:** `require('@triliumnext/api')` or global `api`
**Key Methods:**
```typescript
// Note Operations
api.getNote(noteId) // Get note from Becca
api.getNoteWithContent(noteId) // Get note with content
api.createNote(parentNoteId, title) // Create new note
api.deleteNote(noteId) // Delete note
// Branch Operations
api.getBranch(branchId) // Get branch
api.createBranch(noteId, parentNoteId) // Create branch (clone)
// Attribute Operations
api.getAttribute(noteId, type, name) // Get attribute
api.createAttribute(noteId, type, name, value) // Create attribute
// Database Access
api.sql.getRow(query, params) // Execute SQL query (single row)
api.sql.getRows(query, params) // Execute SQL query (multiple rows)
api.sql.execute(query, params) // Execute SQL statement
// Events
api.onNoteCreated(callback) // Note created event
api.onNoteUpdated(callback) // Note updated event
api.onNoteDeleted(callback) // Note deleted event
api.onAttributeCreated(callback) // Attribute created event
// Search
api.searchForNotes(searchQuery) // Search notes
// Date/Time
api.dayjs // Date/time library
api.now() // Current date/time
// Logging
api.log(message) // Log message
api.error(message) // Log error
// External Communication
api.axios // HTTP client library
// Utilities
api.backup.backupNow() // Trigger backup
api.export.exportSubtree(noteId) // Export notes
```
## Script Attributes
### Execute Attributes
- `#run=frontendStartup` - Execute on frontend startup
- `#run=backendStartup` - Execute on backend startup
- `#run=hourly` - Execute every hour
- `#run=daily` - Execute daily
### Widget Attributes
- `#customWidget` - Custom note widget
- `#widget` - Standard widget integration
### Other Attributes
- `#disableVersioning` - Disable automatic versioning for this note
- `#hideChildrenOverview` - Hide children in overview
- `#iconClass` - Custom icon for note
## Entity Classes
### Frontend Entities
**FNote** (`apps/client/src/entities/fnote.ts`)
```typescript
class FNote {
noteId: string
title: string
type: string
mime: string
// Relationships
getParentNotes(): FNote[]
getChildNotes(): FNote[]
getBranches(): FBranch[]
// Attributes
getAttribute(type, name): FAttribute
getAttributes(type?, name?): FAttribute[]
hasLabel(name): boolean
getLabelValue(name): string
// Content
getContent(): Promise<string>
// Navigation
open(): void
}
```
**FBranch**
```typescript
class FBranch {
branchId: string
noteId: string
parentNoteId: string
prefix: string
notePosition: number
getNote(): FNote
getParentNote(): FNote
}
```
**FAttribute**
```typescript
class FAttribute {
attributeId: string
noteId: string
type: 'label' | 'relation'
name: string
value: string
getNote(): FNote
getTargetNote(): FNote // For relations
}
```
### Backend Entities
**BNote** (`apps/server/src/becca/entities/bnote.ts`)
```typescript
class BNote {
noteId: string
title: string
type: string
mime: string
isProtected: boolean
// Content
getContent(): string | Buffer
setContent(content: string | Buffer): void
// Relationships
getParentNotes(): BNote[]
getChildNotes(): BNote[]
getBranches(): BBranch[]
// Attributes
getAttribute(type, name): BAttribute
getAttributes(type?, name?): BAttribute[]
setLabel(name, value): BAttribute
setRelation(name, targetNoteId): BAttribute
hasLabel(name): boolean
getLabelValue(name): string
// Operations
save(): void
markAsDeleted(): void
}
```
**BBranch**
```typescript
class BBranch {
branchId: string
noteId: string
parentNoteId: string
prefix: string
notePosition: number
getNote(): BNote
getParentNote(): BNote
save(): void
}
```
**BAttribute**
```typescript
class BAttribute {
attributeId: string
noteId: string
type: 'label' | 'relation'
name: string
value: string
getNote(): BNote
getTargetNote(): BNote // For relations
save(): void
}
```
## Script Examples
### Frontend Examples
**1. Custom Toolbar Button**
```javascript
// #run=frontendStartup
api.addButtonToToolbar({
title: 'Export to PDF',
icon: 'file-export',
action: async () => {
const note = api.getActiveNote()
if (note) {
await api.runOnBackend('exportToPdf', [note.noteId])
api.showMessage('Export started')
}
}
})
```
**2. Auto-Save Reminder**
```javascript
// #run=frontendStartup
let saveTimer
api.runOnNoteContentChange(() => {
clearTimeout(saveTimer)
saveTimer = setTimeout(() => {
api.showMessage('Remember to save your work!')
}, 300000) // 5 minutes
})
```
**3. Note Statistics Widget**
```javascript
// #customWidget
class StatsWidget extends api.NoteContextAwareWidget {
doRender() {
this.$widget = $('<div class="stats-widget">')
return this.$widget
}
async refreshWithNote(note) {
const content = await note.getContent()
const words = content.split(/\s+/).length
const chars = content.length
this.$widget.html(`
<div>Words: ${words}</div>
<div>Characters: ${chars}</div>
`)
}
}
module.exports = StatsWidget
```
### Backend Examples
**1. Auto-Tagging on Note Creation**
```javascript
// #run=backendStartup
api.onNoteCreated((note) => {
// Auto-tag TODO notes
if (note.title.includes('TODO')) {
note.setLabel('type', 'todo')
note.setLabel('priority', 'normal')
}
// Auto-tag meeting notes by date
if (note.title.match(/Meeting \d{4}-\d{2}-\d{2}/)) {
note.setLabel('type', 'meeting')
const dateMatch = note.title.match(/(\d{4}-\d{2}-\d{2})/)
if (dateMatch) {
note.setLabel('date', dateMatch[1])
}
}
})
```
**2. Daily Backup Reminder**
```javascript
// #run=daily
const todayNote = api.getTodayNote()
todayNote.setLabel('backupDone', 'false')
// Create reminder note
api.createNote(todayNote.noteId, '🔔 Backup Reminder', {
content: 'Remember to verify today\'s backup!',
type: 'text'
})
```
**3. External API Integration**
```javascript
// #run=backendStartup
api.onNoteCreated(async (note) => {
// Sync new notes to external service
if (note.hasLabel('sync-external')) {
try {
await api.axios.post('https://external-api.com/sync', {
noteId: note.noteId,
title: note.title,
content: note.getContent()
})
note.setLabel('lastSync', api.dayjs().format())
} catch (error) {
api.log('Sync failed: ' + error.message)
}
}
})
```
**4. Database Cleanup**
```javascript
// #run=weekly
// Clean up old revisions
const cutoffDate = api.dayjs().subtract(90, 'days').format()
const oldRevisions = api.sql.getRows(`
SELECT revisionId FROM revisions
WHERE utcDateCreated < ?
`, [cutoffDate])
api.log(`Deleting ${oldRevisions.length} old revisions`)
for (const row of oldRevisions) {
api.sql.execute('DELETE FROM revisions WHERE revisionId = ?', [row.revisionId])
}
```
## Script Storage
**Storage Location:** Scripts are stored as regular notes
**Identifying Scripts:**
- Have `#run` attribute or `#customWidget` attribute
- Type is typically `code` with MIME `application/javascript`
**Script Note Structure:**
```
📁 Scripts (folder note)
├── 📜 Frontend Scripts
│ ├── Custom Toolbar Button (#run=frontendStartup)
│ └── Statistics Widget (#customWidget)
└── 📜 Backend Scripts
├── Auto-Tagger (#run=backendStartup)
└── Daily Backup (#run=daily)
```
## Script Execution
### Frontend Execution
**Timing:**
1. Trilium frontend loads
2. Froca cache initializes
3. Script notes with `#run=frontendStartup` are found
4. Scripts execute in dependency order
**Isolation:**
- Each script runs in separate context
- Shared `window.api` object
- Can access global window object
### Backend Execution
**Timing:**
1. Server starts
2. Becca cache loads
3. Script notes with `#run=backendStartup` are found
4. Scripts execute in dependency order
**Isolation:**
- Each script is a separate module
- Can require Node.js modules
- Shared `api` global
### Error Handling
**Frontend:**
```javascript
try {
// Script code
} catch (error) {
api.showError('Script error: ' + error.message)
console.error(error)
}
```
**Backend:**
```javascript
try {
// Script code
} catch (error) {
api.log('Script error: ' + error.message)
console.error(error)
}
```
## Security Considerations
### Frontend Scripts
**Risks:**
- Can access all notes via Froca
- Can manipulate DOM
- Can make API calls
- Limited by browser security model
**Mitigations:**
- User must trust scripts they add
- Scripts run with user privileges
- No access to file system
### Backend Scripts
**Risks:**
- Full Node.js access
- Can execute system commands
- Can access file system
- Can make network requests
**Mitigations:**
- Scripts are user-created (trusted)
- Single-user model (no privilege escalation)
- Review scripts before adding `#run` attribute
### Best Practices
1. **Review script code** before adding execution attributes
2. **Use specific attributes** rather than wildcard searches
3. **Avoid eval()** and dynamic code execution
4. **Validate inputs** in scripts
5. **Handle errors** gracefully
6. **Log important actions** for audit trail
## Performance Considerations
### Optimization Tips
**1. Cache Results:**
```javascript
// Bad: Re-query on every call
function getConfig() {
return api.getNote('config').getContent()
}
// Good: Cache the result
let cachedConfig
function getConfig() {
if (!cachedConfig) {
cachedConfig = api.getNote('config').getContent()
}
return cachedConfig
}
```
**2. Use Efficient Queries:**
```javascript
// Bad: Load all notes and filter
const todos = api.searchForNotes('#type=todo')
// Good: Use specific search
const todos = api.searchForNotes('#type=todo #status=pending')
```
**3. Batch Operations:**
```javascript
// Bad: Save after each change
notes.forEach(note => {
note.title = 'Updated'
note.save()
})
// Good: Batch changes
notes.forEach(note => {
note.title = 'Updated'
})
// Save happens in batch
```
**4. Debounce Event Handlers:**
```javascript
let timeout
api.runOnNoteContentChange(() => {
clearTimeout(timeout)
timeout = setTimeout(() => {
// Process change
}, 500)
})
```
## Debugging Scripts
### Frontend Debugging
**Browser DevTools:**
```javascript
console.log('Debug info:', data)
debugger // Breakpoint
```
**Trilium Log:**
```javascript
api.log('Script executed')
```
### Backend Debugging
**Console Output:**
```javascript
console.log('Backend debug:', data)
api.log('Script log message')
```
**Inspect Becca:**
```javascript
api.log('Note count:', Object.keys(api.becca.notes).length)
```
## Advanced Topics
### Custom Note Types
Scripts can implement custom note type handlers:
```javascript
// Register custom type
api.registerNoteType({
type: 'mytype',
mime: 'application/x-mytype',
renderNote: (note) => {
// Custom rendering
}
})
```
### External Libraries
**Frontend:**
```javascript
// Load external library
const myLib = await import('https://cdn.example.com/lib.js')
```
**Backend:**
```javascript
// Use Node.js require
const fs = require('fs')
const axios = require('axios')
```
### State Persistence
**Frontend:**
```javascript
// Use localStorage
localStorage.setItem('myScript:data', JSON.stringify(data))
const data = JSON.parse(localStorage.getItem('myScript:data'))
```
**Backend:**
```javascript
// Store in special note
const stateNote = api.getNote('script-state-note')
stateNote.setContent(JSON.stringify(data))
const data = JSON.parse(stateNote.getContent())
```
---
**See Also:**
- [Script API Documentation](Script%20API/) - Complete API reference
- [Advanced Showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases) - Example scripts
- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture

View File

@@ -1,834 +0,0 @@
# Trilium Security Architecture
> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [SECURITY.md](../SECURITY.md)
## Overview
Trilium implements a **defense-in-depth security model** with multiple layers of protection for user data. The security architecture covers authentication, authorization, encryption, input sanitization, and secure communication.
## Security Principles
1. **Data Privacy**: User data is protected at rest and in transit
2. **Encryption**: Per-note encryption for sensitive content
3. **Authentication**: Multiple authentication methods supported
4. **Authorization**: Single-user model with granular protected sessions
5. **Input Validation**: All user input sanitized
6. **Secure Defaults**: Security features enabled by default
7. **Transparency**: Open source allows security audits
## Threat Model
### Threats Considered
1. **Unauthorized Access**
- Physical access to device
- Network eavesdropping
- Stolen credentials
- Session hijacking
2. **Data Exfiltration**
- Malicious scripts
- XSS attacks
- SQL injection
- CSRF attacks
3. **Data Corruption**
- Malicious modifications
- Database tampering
- Sync conflicts
4. **Privacy Leaks**
- Unencrypted backups
- Search indexing
- Temporary files
- Memory dumps
### Out of Scope
- Nation-state attackers
- Zero-day vulnerabilities in dependencies
- Hardware vulnerabilities (Spectre, Meltdown)
- Physical access with unlimited time
- Quantum computing attacks
## Authentication
### Password Authentication
**Implementation:** `apps/server/src/services/password.ts`
**Password Storage:**
```typescript
// Password is never stored directly
const salt = crypto.randomBytes(32)
const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256')
const verificationHash = crypto.createHash('sha256')
.update(derivedKey)
.digest('hex')
// Store only salt and verification hash
sql.insert('user_data', {
salt: salt.toString('hex'),
derivedKey: derivedKey.toString('hex') // Used for encryption
})
sql.insert('options', {
name: 'passwordVerificationHash',
value: verificationHash
})
```
**Password Requirements:**
- Minimum length: 4 characters (configurable)
- No maximum length
- All characters allowed
- Can be changed by user
**Login Process:**
```typescript
// 1. User submits password
POST /api/login/password
Body: { password: "user-password" }
// 2. Server derives key
const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256')
// 3. Verify against stored hash
const verificationHash = crypto.createHash('sha256')
.update(derivedKey)
.digest('hex')
if (verificationHash === storedHash) {
// 4. Create session
req.session.loggedIn = true
req.session.regenerate()
}
```
### TOTP (Two-Factor Authentication)
**Implementation:** `apps/server/src/routes/api/login.ts`
**Setup Process:**
```typescript
// 1. Generate secret
const secret = speakeasy.generateSecret({
name: `Trilium (${username})`,
length: 32
})
// 2. Store encrypted secret
const encryptedSecret = encrypt(secret.base32, dataKey)
sql.insert('options', {
name: 'totpSecret',
value: encryptedSecret
})
// 3. Generate QR code
const qrCodeUrl = secret.otpauth_url
```
**Verification:**
```typescript
// User submits TOTP token
POST /api/login/totp
Body: { token: "123456" }
// Verify token
const secret = decrypt(encryptedSecret, dataKey)
const verified = speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 1 // Allow 1 time step tolerance
})
```
### OpenID Connect
**Implementation:** `apps/server/src/routes/api/login.ts`
**Supported Providers:**
- Any OpenID Connect compatible provider
- Google, GitHub, Auth0, etc.
**Flow:**
```typescript
// 1. Redirect to provider
GET /api/login/openid
// 2. Provider redirects back with code
GET /api/login/openid/callback?code=...
// 3. Exchange code for tokens
const tokens = await openidClient.callback(redirectUri, req.query)
// 4. Verify ID token
const claims = tokens.claims()
// 5. Create session
req.session.loggedIn = true
```
### Session Management
**Session Storage:** SQLite database (sessions table)
**Session Configuration:**
```typescript
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: false,
rolling: true,
cookie: {
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
httpOnly: true,
secure: isHttps,
sameSite: 'lax'
},
store: new SqliteStore({
db: db,
table: 'sessions'
})
}))
```
**Session Invalidation:**
- Automatic timeout after inactivity
- Manual logout clears session
- Server restart invalidates all sessions (optional)
## Authorization
### Single-User Model
**Desktop:**
- Single user (owner of device)
- No multi-user support
- Full access to all notes
**Server:**
- Single user per installation
- Authentication required for all operations
- No user roles or permissions
### Protected Sessions
**Purpose:** Temporary access to encrypted (protected) notes
**Implementation:** `apps/server/src/services/protected_session.ts`
**Workflow:**
```typescript
// 1. User enters password for protected notes
POST /api/protected-session/enter
Body: { password: "protected-password" }
// 2. Derive encryption key
const protectedDataKey = deriveKey(password)
// 3. Verify password (decrypt known encrypted value)
const decrypted = decrypt(testValue, protectedDataKey)
if (decrypted === expectedValue) {
// 4. Store in memory (not in session)
protectedSessionHolder.setProtectedDataKey(protectedDataKey)
// 5. Set timeout
setTimeout(() => {
protectedSessionHolder.clearProtectedDataKey()
}, timeout)
}
```
**Protected Session Timeout:**
- Default: 10 minutes (configurable)
- Extends on activity
- Cleared on browser close
- Separate from main session
### API Authorization
**Internal API:**
- Requires authenticated session
- CSRF token validation
- Same-origin policy
**ETAPI (External API):**
- Token-based authentication
- No session required
- Rate limiting
## Encryption
### Note Encryption
**Encryption Algorithm:** AES-256-CBC
**Key Hierarchy:**
```
User Password
↓ (PBKDF2)
Data Key (for protected notes)
↓ (AES-256)
Protected Note Content
```
**Encryption Process:**
```typescript
// 1. Generate IV (initialization vector)
const iv = crypto.randomBytes(16)
// 2. Encrypt content
const cipher = crypto.createCipheriv('aes-256-cbc', dataKey, iv)
let encrypted = cipher.update(content, 'utf8', 'base64')
encrypted += cipher.final('base64')
// 3. Prepend IV to encrypted content
const encryptedBlob = iv.toString('base64') + ':' + encrypted
// 4. Store in database
sql.insert('blobs', {
blobId: blobId,
content: encryptedBlob
})
```
**Decryption Process:**
```typescript
// 1. Split IV and encrypted content
const [ivBase64, encryptedData] = encryptedBlob.split(':')
const iv = Buffer.from(ivBase64, 'base64')
// 2. Decrypt
const decipher = crypto.createDecipheriv('aes-256-cbc', dataKey, iv)
let decrypted = decipher.update(encryptedData, 'base64', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
```
**Protected Note Metadata:**
- Title is NOT encrypted (for tree display)
- Type and MIME are NOT encrypted
- Content IS encrypted
- Attributes CAN be encrypted (optional)
### Data Key Management
**Master Data Key:**
```typescript
// Generated once during setup
const dataKey = crypto.randomBytes(32) // 256 bits
// Encrypted with derived key from user password
const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256')
const encryptedDataKey = encrypt(dataKey, derivedKey)
// Stored in database
sql.insert('options', {
name: 'encryptedDataKey',
value: encryptedDataKey.toString('hex')
})
```
**Key Rotation:**
- Not currently supported
- Requires re-encrypting all protected notes
- Planned for future version
### Transport Encryption
**HTTPS:**
- Required for server installations (recommended)
- TLS 1.2+ only
- Strong cipher suites preferred
- Certificate validation enabled
**Desktop:**
- Local communication (no network)
- No HTTPS required
### Backup Encryption
**Database Backups:**
- Protected notes remain encrypted in backup
- Backup file should be protected separately
- Consider encrypting backup storage location
## Input Sanitization
### XSS Prevention
**HTML Sanitization:**
Location: `apps/client/src/services/dompurify.ts`
```typescript
import DOMPurify from 'dompurify'
// Configure DOMPurify
DOMPurify.setConfig({
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'div', ...],
ALLOWED_ATTR: ['href', 'title', 'class', 'id', ...],
ALLOW_DATA_ATTR: false
})
// Sanitize HTML before rendering
const cleanHtml = DOMPurify.sanitize(userHtml)
```
**CKEditor Configuration:**
```typescript
// apps/client/src/widgets/type_widgets/text_type_widget.ts
ClassicEditor.create(element, {
// Restrict allowed content
htmlSupport: {
allow: [
{ name: /./, attributes: true, classes: true, styles: true }
],
disallow: [
{ name: 'script' },
{ name: 'iframe', attributes: /^(?!src$).*/ }
]
}
})
```
**Content Security Policy:**
```typescript
// apps/server/src/main.ts
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: blob:;"
)
next()
})
```
### SQL Injection Prevention
**Parameterized Queries:**
```typescript
// GOOD - Safe from SQL injection
const notes = sql.getRows(
'SELECT * FROM notes WHERE title = ?',
[userInput]
)
// BAD - Vulnerable to SQL injection
const notes = sql.getRows(
`SELECT * FROM notes WHERE title = '${userInput}'`
)
```
**ORM Usage:**
```typescript
// Entity-based access prevents SQL injection
const note = becca.getNote(noteId)
note.title = userInput // Sanitized by entity
note.save() // Parameterized query
```
### CSRF Prevention
**CSRF Token Validation:**
Location: `apps/server/src/routes/middleware/csrf.ts`
```typescript
// Generate CSRF token
const csrfToken = crypto.randomBytes(32).toString('hex')
req.session.csrfToken = csrfToken
// Validate on state-changing requests
app.use((req, res, next) => {
if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
const token = req.headers['x-csrf-token']
if (token !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' })
}
}
next()
})
```
**Client-Side:**
```typescript
// apps/client/src/services/server.ts
const csrfToken = getCsrfToken()
fetch('/api/notes', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
```
### File Upload Validation
**Validation:**
```typescript
// apps/server/src/routes/api/attachments.ts
const allowedMimeTypes = [
'image/jpeg',
'image/png',
'application/pdf',
// ...
]
if (!allowedMimeTypes.includes(file.mimetype)) {
throw new Error('File type not allowed')
}
// Validate file size
const maxSize = 100 * 1024 * 1024 // 100 MB
if (file.size > maxSize) {
throw new Error('File too large')
}
// Sanitize filename
const sanitizedFilename = path.basename(file.originalname)
.replace(/[^a-z0-9.-]/gi, '_')
```
## Network Security
### HTTPS Configuration
**Server Setup:**
```typescript
// apps/server/src/main.ts
const httpsOptions = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert')
}
https.createServer(httpsOptions, app).listen(443)
```
**Certificate Validation:**
- Require valid certificates in production
- Self-signed certificates allowed for development
- Certificate pinning not implemented
### Secure Headers
```typescript
// apps/server/src/main.ts
app.use((req, res, next) => {
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'SAMEORIGIN')
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff')
// XSS protection
res.setHeader('X-XSS-Protection', '1; mode=block')
// Referrer policy
res.setHeader('Referrer-Policy', 'same-origin')
// HTTPS upgrade
if (req.secure) {
res.setHeader('Strict-Transport-Security', 'max-age=31536000')
}
next()
})
```
### Rate Limiting
**API Rate Limiting:**
```typescript
// apps/server/src/routes/middleware/rate_limit.ts
const rateLimit = require('express-rate-limit')
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1000, // Limit each IP to 1000 requests per window
message: 'Too many requests from this IP'
})
app.use('/api/', apiLimiter)
```
**Login Rate Limiting:**
```typescript
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 5 failed attempts
skipSuccessfulRequests: true
})
app.post('/api/login/password', loginLimiter, loginHandler)
```
## Data Security
### Secure Data Deletion
**Soft Delete:**
```typescript
// Mark as deleted (sync first)
note.isDeleted = 1
note.deleteId = generateUUID()
note.save()
// Entity change tracked for sync
addEntityChange('notes', noteId, note)
```
**Hard Delete (Erase):**
```typescript
// After sync completed
sql.execute('DELETE FROM notes WHERE noteId = ?', [noteId])
sql.execute('DELETE FROM branches WHERE noteId = ?', [noteId])
sql.execute('DELETE FROM attributes WHERE noteId = ?', [noteId])
// Mark entity change as erased
sql.execute('UPDATE entity_changes SET isErased = 1 WHERE entityId = ?', [noteId])
```
**Blob Cleanup:**
```typescript
// Find orphaned blobs (not referenced by any note/revision/attachment)
const orphanedBlobs = sql.getRows(`
SELECT blobId FROM blobs
WHERE blobId NOT IN (SELECT blobId FROM notes WHERE blobId IS NOT NULL)
AND blobId NOT IN (SELECT blobId FROM revisions WHERE blobId IS NOT NULL)
AND blobId NOT IN (SELECT blobId FROM attachments WHERE blobId IS NOT NULL)
`)
// Delete orphaned blobs
for (const blob of orphanedBlobs) {
sql.execute('DELETE FROM blobs WHERE blobId = ?', [blob.blobId])
}
```
### Memory Security
**Protected Data in Memory:**
- Protected data keys stored in memory only
- Cleared on timeout
- Not written to disk
- Not in session storage
**Memory Cleanup:**
```typescript
// Clear sensitive data
const clearSensitiveData = () => {
protectedDataKey = null
// Force garbage collection if available
if (global.gc) {
global.gc()
}
}
```
### Temporary Files
**Secure Temporary Files:**
```typescript
const tempDir = os.tmpdir()
const tempFile = path.join(tempDir, `trilium-${crypto.randomBytes(16).toString('hex')}`)
// Write temp file
fs.writeFileSync(tempFile, data, { mode: 0o600 }) // Owner read/write only
// Clean up after use
fs.unlinkSync(tempFile)
```
## Dependency Security
### Vulnerability Scanning
**Tools:**
- `npm audit` - Check for known vulnerabilities
- Renovate bot - Automatic dependency updates
- GitHub Dependabot alerts
**Process:**
```bash
# Check for vulnerabilities
npm audit
# Fix automatically
npm audit fix
# Manual review for breaking changes
npm audit fix --force
```
### Dependency Pinning
**package.json:**
```json
{
"dependencies": {
"express": "4.18.2", // Exact version
"better-sqlite3": "^9.2.2" // Compatible versions
}
}
```
**pnpm Overrides:**
```json
{
"pnpm": {
"overrides": {
"lodash@<4.17.21": ">=4.17.21", // Force minimum version
"axios@<0.21.2": ">=0.21.2"
}
}
}
```
### Patch Management
**pnpm Patches:**
```bash
# Create patch
pnpm patch @ckeditor/ckeditor5
# Edit files in temporary directory
# ...
# Generate patch file
pnpm patch-commit /tmp/ckeditor5-patch
# Patch applied automatically on install
```
## Security Best Practices
### For Users
1. **Strong Passwords**
- Use unique password for Trilium
- Enable TOTP 2FA
- Protect password manager
2. **Protected Notes**
- Use for sensitive information
- Set reasonable session timeout
- Don't leave sessions unattended
3. **Backups**
- Regular backups to secure location
- Encrypt backup storage
- Test backup restoration
4. **Server Setup**
- Use HTTPS only
- Keep software updated
- Firewall configuration
- Use reverse proxy (nginx, Caddy)
5. **Scripts**
- Review scripts before using
- Be cautious with external scripts
- Understand script permissions
### For Developers
1. **Code Review**
- Review all security-related changes
- Test authentication/authorization changes
- Validate input sanitization
2. **Testing**
- Write security tests
- Test edge cases
- Penetration testing
3. **Dependencies**
- Regular updates
- Audit new dependencies
- Monitor security advisories
4. **Secrets**
- No secrets in source code
- Use environment variables
- Secure key generation
## Security Auditing
### Logs
**Security Events Logged:**
- Login attempts (success/failure)
- Protected session access
- Password changes
- ETAPI token usage
- Failed CSRF validations
**Log Location:**
- Desktop: Console output
- Server: Log files or stdout
### Monitoring
**Metrics to Monitor:**
- Failed login attempts
- API error rates
- Unusual database changes
- Large exports/imports
## Incident Response
### Security Issue Reporting
**Process:**
1. Email security@triliumnext.com
2. Include vulnerability details
3. Provide reproduction steps
4. Allow reasonable disclosure time
**Response:**
1. Acknowledge within 48 hours
2. Investigate and validate
3. Develop fix
4. Coordinate disclosure
5. Release patch
### Breach Response
**If Compromised:**
1. Change password immediately
2. Review recent activity
3. Check for unauthorized changes
4. Restore from backup if needed
5. Update security settings
## Future Security Enhancements
**Planned:**
- Hardware security key support (U2F/WebAuthn)
- End-to-end encryption for sync
- Zero-knowledge architecture option
- Encryption key rotation
- Audit log enhancements
- Per-note access controls
**Under Consideration:**
- Multi-user support with permissions
- Blockchain-based sync verification
- Homomorphic encryption for search
- Quantum-resistant encryption
---
**See Also:**
- [SECURITY.md](../SECURITY.md) - Security policy
- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture
- [Protected Notes Guide](https://triliumnext.github.io/Docs/Wiki/protected-notes)

View File

@@ -1,583 +0,0 @@
# Trilium Synchronization Architecture
> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [User Guide: Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization)
## Overview
Trilium implements a sophisticated **bidirectional synchronization system** that allows users to sync their note databases across multiple devices (desktop clients and server instances). The sync protocol is designed to handle:
- Concurrent modifications across devices
- Conflict resolution
- Partial sync (only changed entities)
- Protected note synchronization
- Efficient bandwidth usage
## Sync Architecture
```
┌─────────────┐ ┌─────────────┐
│ Desktop 1 │ │ Desktop 2 │
│ (Client) │ │ (Client) │
└──────┬──────┘ └──────┬──────┘
│ │
│ WebSocket/HTTP │
│ │
▼ ▼
┌────────────────────────────────────────────────┐
│ Sync Server │
│ ┌──────────────────────────────────────┐ │
│ │ Sync Service │ │
│ │ - Entity Change Management │ │
│ │ - Conflict Resolution │ │
│ │ - Version Tracking │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ ┌──────┴───────┐ │
│ │ Database │ │
│ │ (entity_changes)│ │
│ └──────────────┘ │
└────────────────────────────────────────────────┘
```
## Core Concepts
### Entity Changes
Every modification to any entity (note, branch, attribute, etc.) creates an **entity change** record:
```sql
entity_changes (
id, -- Auto-increment ID
entityName, -- 'notes', 'branches', 'attributes', etc.
entityId, -- ID of the changed entity
hash, -- Content hash for integrity
isErased, -- If entity was erased (deleted permanently)
changeId, -- Unique change identifier
componentId, -- Installation identifier
instanceId, -- Process instance identifier
isSynced, -- Whether synced to server
utcDateChanged -- When change occurred
)
```
**Key Properties:**
- **changeId**: Globally unique identifier (UUID) for the change
- **componentId**: Unique per Trilium installation (persists across restarts)
- **instanceId**: Unique per process (changes on restart)
- **hash**: SHA-256 hash of entity data for integrity verification
### Sync Versions
Each Trilium installation tracks:
- **Local sync version**: Highest change ID seen locally
- **Server sync version**: Highest change ID on server
- **Entity versions**: Last sync version for each entity type
### Change Tracking
**When an entity is modified:**
```typescript
// apps/server/src/services/entity_changes.ts
function addEntityChange(entityName, entityId, entity) {
const hash = calculateHash(entity)
const changeId = generateUUID()
sql.insert('entity_changes', {
entityName,
entityId,
hash,
changeId,
componentId: config.componentId,
instanceId: config.instanceId,
isSynced: 0,
utcDateChanged: now()
})
}
```
**Entity modification triggers:**
- Note content update
- Note metadata change
- Branch creation/deletion/reorder
- Attribute addition/removal
- Options modification
## Sync Protocol
### Sync Handshake
**Step 1: Client Initiates Sync**
```typescript
// Client sends current sync version
POST /api/sync/check
{
"sourceId": "client-component-id",
"maxChangeId": 12345
}
```
**Step 2: Server Responds with Status**
```typescript
// Server checks for changes
Response:
{
"entityChanges": 567, // Changes on server
"maxChangeId": 12890, // Server's max change ID
"outstandingPushCount": 23 // Client changes not yet synced
}
```
**Step 3: Decision**
- If `entityChanges > 0`: Pull changes from server
- If `outstandingPushCount > 0`: Push changes to server
- Both can happen in sequence
### Pull Sync (Server → Client)
**Client Requests Changes:**
```typescript
POST /api/sync/pull
{
"sourceId": "client-component-id",
"lastSyncedChangeId": 12345
}
```
**Server Responds:**
```typescript
Response:
{
"notes": [
{ noteId: "abc", title: "New Note", ... }
],
"branches": [...],
"attributes": [...],
"revisions": [...],
"attachments": [...],
"entityChanges": [
{ entityName: "notes", entityId: "abc", changeId: "...", ... }
],
"maxChangeId": 12890
}
```
**Client Processing:**
1. Apply entity changes to local database
2. Update Froca cache
3. Update local sync version
4. Trigger UI refresh
### Push Sync (Client → Server)
**Client Sends Changes:**
```typescript
POST /api/sync/push
{
"sourceId": "client-component-id",
"entities": [
{
"entity": {
"noteId": "xyz",
"title": "Modified Note",
...
},
"entityChange": {
"changeId": "change-uuid",
"entityName": "notes",
...
}
}
]
}
```
**Server Processing:**
1. Validate changes
2. Check for conflicts
3. Apply changes to database
4. Update Becca cache
5. Mark as synced
6. Broadcast to other connected clients via WebSocket
**Conflict Detection:**
```typescript
// Check if entity was modified on server since client's last sync
const serverEntity = becca.getNote(noteId)
const serverLastModified = serverEntity.utcDateModified
if (serverLastModified > clientSyncVersion) {
// CONFLICT!
resolveConflict(serverEntity, clientEntity)
}
```
## Conflict Resolution
### Conflict Types
**1. Content Conflict**
- Both client and server modified same note content
- **Resolution**: Last-write-wins based on `utcDateModified`
**2. Structure Conflict**
- Branch moved/deleted on one side, modified on other
- **Resolution**: Tombstone records, reconciliation
**3. Attribute Conflict**
- Same attribute modified differently
- **Resolution**: Last-write-wins
### Conflict Resolution Strategy
**Last-Write-Wins:**
```typescript
if (clientEntity.utcDateModified > serverEntity.utcDateModified) {
// Client wins, apply client changes
applyClientChange(clientEntity)
} else {
// Server wins, reject client change
// Client will pull server version on next sync
}
```
**Tombstone Records:**
- Deleted entities leave tombstone in `entity_changes`
- Prevents re-sync of deleted items
- `isErased = 1` for permanent deletions
### Protected Notes Sync
**Challenge:** Encrypted content can't be synced without password
**Solution:**
1. **Protected session required**: User must unlock protected notes
2. **Encrypted sync**: Content synced in encrypted form
3. **Hash verification**: Integrity checked without decryption
4. **Lazy decryption**: Only decrypt when accessed
**Sync Flow:**
```typescript
// Client side
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
// Skip protected notes if session not active
continue
}
// Server side
if (note.isProtected) {
// Sync encrypted blob
// Don't decrypt for sync
syncEncryptedBlob(note.blobId)
}
```
## Sync States
### Connection States
- **Connected**: WebSocket connection active
- **Disconnected**: No connection to sync server
- **Syncing**: Actively transferring data
- **Conflict**: Sync paused due to conflict
### Entity Sync States
Each entity can be in:
- **Synced**: In sync with server
- **Pending**: Local changes not yet pushed
- **Conflict**: Conflicting changes detected
### UI Indicators
```typescript
// apps/client/src/widgets/sync_status.ts
class SyncStatusWidget {
showSyncStatus() {
if (isConnected && allSynced) {
showIcon('synced')
} else if (isSyncing) {
showIcon('syncing-spinner')
} else if (hasConflicts) {
showIcon('conflict-warning')
} else {
showIcon('not-synced')
}
}
}
```
## Performance Optimizations
### Incremental Sync
Only entities changed since last sync are transferred:
```sql
SELECT * FROM entity_changes
WHERE id > :lastSyncedChangeId
ORDER BY id ASC
LIMIT 1000
```
### Batch Processing
Changes sent in batches to reduce round trips:
```typescript
const BATCH_SIZE = 1000
const changes = getUnsyncedChanges(BATCH_SIZE)
await syncBatch(changes)
```
### Hash-Based Change Detection
```typescript
// Only sync if hash differs
const localHash = calculateHash(localEntity)
const serverHash = getServerHash(entityId)
if (localHash !== serverHash) {
syncEntity(localEntity)
}
```
### Compression
Large payloads compressed before transmission:
```typescript
// Server sends compressed response
res.setHeader('Content-Encoding', 'gzip')
res.send(gzip(syncData))
```
## Error Handling
### Network Errors
**Retry Strategy:**
```typescript
const RETRY_DELAYS = [1000, 2000, 5000, 10000, 30000]
async function syncWithRetry(attempt = 0) {
try {
await performSync()
} catch (error) {
if (attempt < RETRY_DELAYS.length) {
setTimeout(() => {
syncWithRetry(attempt + 1)
}, RETRY_DELAYS[attempt])
}
}
}
```
### Sync Integrity Checks
**Hash Verification:**
```typescript
// Verify entity hash matches
const calculatedHash = calculateHash(entity)
const receivedHash = entityChange.hash
if (calculatedHash !== receivedHash) {
throw new Error('Hash mismatch - data corruption detected')
}
```
**Consistency Checks:**
- Orphaned branches detection
- Missing parent notes
- Invalid entity references
- Circular dependencies
## Sync Server Configuration
### Server Setup
**Required Options:**
```javascript
{
"syncServerHost": "https://sync.example.com",
"syncServerTimeout": 60000,
"syncProxy": "" // Optional HTTP proxy
}
```
**Authentication:**
- Username/password or
- Sync token (generated on server)
### Client Setup
**Desktop Client:**
```javascript
// Settings → Sync
{
"syncServerHost": "https://sync.example.com",
"username": "user@example.com",
"password": "********"
}
```
**Test Connection:**
```typescript
POST /api/sync/test
Response: { "success": true }
```
## Sync API Endpoints
Located at: `apps/server/src/routes/api/sync.ts`
**Endpoints:**
- `POST /api/sync/check` - Check sync status
- `POST /api/sync/pull` - Pull changes from server
- `POST /api/sync/push` - Push changes to server
- `POST /api/sync/finished` - Mark sync complete
- `POST /api/sync/test` - Test connection
- `GET /api/sync/stats` - Sync statistics
## WebSocket Sync Updates
Real-time sync via WebSocket:
```typescript
// Server broadcasts change to all connected clients
ws.broadcast('entity-change', {
entityName: 'notes',
entityId: 'abc123',
changeId: 'change-uuid',
sourceId: 'originating-component-id'
})
// Client receives and applies
ws.on('entity-change', (data) => {
if (data.sourceId !== myComponentId) {
froca.processEntityChange(data)
}
})
```
## Sync Scheduling
### Automatic Sync
**Desktop:**
- Sync on startup
- Periodic sync (configurable interval, default: 60s)
- Sync before shutdown
**Server:**
- Sync on entity modification
- WebSocket push to connected clients
### Manual Sync
User can trigger:
- Full sync
- Sync now
- Sync specific subtree
## Troubleshooting
### Common Issues
**Sync stuck:**
```sql
-- Reset sync state
UPDATE entity_changes SET isSynced = 0;
DELETE FROM options WHERE name LIKE 'sync%';
```
**Hash mismatch:**
- Data corruption detected
- Re-sync from backup
- Check database integrity
**Conflict loop:**
- Manual intervention required
- Export conflicting notes
- Choose winning version
- Re-sync
### Sync Diagnostics
**Check sync status:**
```typescript
GET /api/sync/stats
Response: {
"unsyncedChanges": 0,
"lastSyncDate": "2025-11-02T12:00:00Z",
"syncVersion": 12890
}
```
**Entity change log:**
```sql
SELECT * FROM entity_changes
WHERE isSynced = 0
ORDER BY id DESC;
```
## Security Considerations
### Encrypted Sync
- Protected notes synced encrypted
- No plain text over network
- Server cannot read protected content
### Authentication
- Username/password over HTTPS only
- Sync tokens for token-based auth
- Session cookies with CSRF protection
### Authorization
- Users can only sync their own data
- No cross-user sync support
- Sync server validates ownership
## Performance Metrics
**Typical Sync Performance:**
- 1000 changes: ~2-5 seconds
- 10000 changes: ~20-50 seconds
- Initial full sync (100k notes): ~5-10 minutes
**Factors:**
- Network latency
- Database size
- Number of protected notes
- Attachment sizes
## Future Improvements
**Planned Enhancements:**
- Differential sync (binary diff)
- Peer-to-peer sync (no central server)
- Multi-server sync
- Partial sync (subtree only)
- Sync over Tor/I2P
---
**See Also:**
- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture
- [Sync User Guide](https://triliumnext.github.io/Docs/Wiki/synchronization)
- [Sync API Source](../apps/server/src/routes/api/sync.ts)

View File

@@ -1,423 +0,0 @@
# Trilium Notes - Technical Documentation Index
Welcome to the comprehensive technical and architectural documentation for Trilium Notes. This index provides quick access to all technical documentation resources.
## 📚 Core Architecture Documentation
### [ARCHITECTURE.md](ARCHITECTURE.md)
**Main technical architecture document** covering the complete system design.
**Topics Covered:**
- High-level architecture overview
- Monorepo structure and organization
- Core architecture patterns (Becca, Froca, Shaca)
- Entity system and data model
- Widget-based UI architecture
- Frontend and backend architecture
- API architecture (Internal, ETAPI, WebSocket)
- Build system and tooling
- Testing strategy
- Security overview
**Audience:** Developers, architects, contributors
---
### [DATABASE.md](DATABASE.md)
**Complete database architecture and schema documentation.**
**Topics Covered:**
- SQLite database structure
- Entity tables (notes, branches, attributes, revisions, attachments, blobs)
- System tables (options, entity_changes, sessions)
- Data relationships and integrity
- Database access patterns
- Migrations and versioning
- Performance optimization
- Backup and maintenance
- Security considerations
**Audience:** Backend developers, database administrators
---
### [SYNCHRONIZATION.md](SYNCHRONIZATION.md)
**Detailed synchronization protocol and implementation.**
**Topics Covered:**
- Sync architecture overview
- Entity change tracking
- Sync protocol (handshake, pull, push)
- Conflict resolution strategies
- Protected notes synchronization
- Performance optimizations
- Error handling and retry logic
- Sync server configuration
- WebSocket real-time updates
- Troubleshooting guide
**Audience:** Advanced users, sync server administrators, contributors
---
### [SCRIPTING.md](SCRIPTING.md)
**Comprehensive guide to the Trilium scripting system.**
**Topics Covered:**
- Script types (frontend, backend, render)
- Frontend API reference
- Backend API reference
- Entity classes (FNote, BNote, etc.)
- Script examples and patterns
- Script storage and execution
- Security considerations
- Performance optimization
- Debugging techniques
- Advanced topics
**Audience:** Power users, script developers, plugin creators
---
### [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md)
**In-depth security architecture and implementation.**
**Topics Covered:**
- Security principles and threat model
- Authentication methods (password, TOTP, OpenID)
- Session management
- Authorization and protected sessions
- Encryption (notes, transport, backups)
- Input sanitization (XSS, SQL injection, CSRF)
- Network security (HTTPS, headers, rate limiting)
- Data security and secure deletion
- Dependency security
- Security best practices
- Incident response
**Audience:** Security engineers, administrators, auditors
---
## 🔧 Developer Documentation
### [Developer Guide](Developer%20Guide/Developer%20Guide/)
Collection of developer-focused documentation for contributing to Trilium.
**Key Documents:**
- [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - Setting up development environment
- [Project Structure](Developer%20Guide/Developer%20Guide/Project%20Structure.md) - Monorepo organization
- [Development and Architecture](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/) - Various development topics
**Topics Include:**
- Local development setup
- Building and deployment
- Adding new note types
- Database schema details
- Internationalization
- Icons and UI customization
- Docker development
- Troubleshooting
**Audience:** Contributors, developers
---
## 📖 User Documentation
### [User Guide](User%20Guide/User%20Guide/)
Comprehensive end-user documentation for using Trilium.
**Key Sections:**
- Installation & Setup
- Basic Concepts and Features
- Note Types
- Advanced Usage
- Synchronization
- Import/Export
**Audience:** End users, administrators
---
### [Script API](Script%20API/)
Complete API reference for user scripting.
**Coverage:**
- Frontend API methods
- Backend API methods
- Entity properties and methods
- Event handlers
- Utility functions
**Audience:** Script developers, power users
---
## 🚀 Quick Start Guides
### For Users
1. [Installation Guide](User%20Guide/User%20Guide/Installation%20&%20Setup/) - Get Trilium running
2. [Basic Concepts](User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/) - Learn the fundamentals
3. [Scripting Guide](SCRIPTING.md) - Extend Trilium with scripts
### For Developers
1. [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - Setup development environment
2. [Architecture Overview](ARCHITECTURE.md) - Understand the system
3. [Contributing Guide](../README.md#-contribute) - Start contributing
### For Administrators
1. [Server Installation](User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md) - Deploy Trilium server
2. [Synchronization Setup](SYNCHRONIZATION.md) - Configure sync
3. [Security Best Practices](SECURITY_ARCHITECTURE.md#security-best-practices) - Secure your installation
---
## 🔍 Documentation by Topic
### Architecture & Design
- [Overall Architecture](ARCHITECTURE.md)
- [Monorepo Structure](ARCHITECTURE.md#monorepo-structure)
- [Three-Layer Cache System](ARCHITECTURE.md#three-layer-cache-system)
- [Entity System](ARCHITECTURE.md#entity-system)
- [Widget-Based UI](ARCHITECTURE.md#widget-based-ui)
### Data & Storage
- [Database Architecture](DATABASE.md)
- [Entity Tables](DATABASE.md#entity-tables)
- [Data Relationships](DATABASE.md#data-relationships)
- [Blob Storage](DATABASE.md#blobs-table)
- [Database Migrations](DATABASE.md#database-migrations)
### Synchronization
- [Sync Architecture](SYNCHRONIZATION.md#sync-architecture)
- [Sync Protocol](SYNCHRONIZATION.md#sync-protocol)
- [Conflict Resolution](SYNCHRONIZATION.md#conflict-resolution)
- [Protected Notes Sync](SYNCHRONIZATION.md#protected-notes-sync)
- [WebSocket Sync](SYNCHRONIZATION.md#websocket-sync-updates)
### Security
- [Authentication](SECURITY_ARCHITECTURE.md#authentication)
- [Encryption](SECURITY_ARCHITECTURE.md#encryption)
- [Input Sanitization](SECURITY_ARCHITECTURE.md#input-sanitization)
- [Network Security](SECURITY_ARCHITECTURE.md#network-security)
- [Security Best Practices](SECURITY_ARCHITECTURE.md#security-best-practices)
### Scripting & Extensibility
- [Script Types](SCRIPTING.md#script-types)
- [Frontend API](SCRIPTING.md#frontend-api)
- [Backend API](SCRIPTING.md#backend-api)
- [Script Examples](SCRIPTING.md#script-examples)
- [Custom Widgets](SCRIPTING.md#render-scripts)
### Frontend
- [Client Architecture](ARCHITECTURE.md#frontend-architecture)
- [Widget System](ARCHITECTURE.md#widget-based-ui)
- [Event System](ARCHITECTURE.md#event-system)
- [Froca Cache](ARCHITECTURE.md#2-froca-frontend-cache)
- [UI Components](ARCHITECTURE.md#ui-components)
### Backend
- [Server Architecture](ARCHITECTURE.md#backend-architecture)
- [Service Layer](ARCHITECTURE.md#service-layer)
- [Route Structure](ARCHITECTURE.md#route-structure)
- [Becca Cache](ARCHITECTURE.md#1-becca-backend-cache)
- [Middleware](ARCHITECTURE.md#middleware)
### Build & Deploy
- [Build System](ARCHITECTURE.md#build-system)
- [Package Manager](ARCHITECTURE.md#package-manager-pnpm)
- [Build Tools](ARCHITECTURE.md#build-tools)
- [Docker](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Docker.md)
- [Deployment](Developer%20Guide/Developer%20Guide/Building%20and%20deployment/)
### Testing
- [Testing Strategy](ARCHITECTURE.md#testing-strategy)
- [Test Organization](ARCHITECTURE.md#test-organization)
- [E2E Testing](ARCHITECTURE.md#e2e-testing)
---
## 📋 Reference Documentation
### File Locations
```
trilium/
├── apps/
│ ├── client/ # Frontend application
│ ├── server/ # Backend server
│ ├── desktop/ # Electron app
│ └── ...
├── packages/
│ ├── commons/ # Shared code
│ ├── ckeditor5/ # Rich text editor
│ └── ...
├── docs/
│ ├── ARCHITECTURE.md # Main architecture doc
│ ├── DATABASE.md # Database documentation
│ ├── SYNCHRONIZATION.md # Sync documentation
│ ├── SCRIPTING.md # Scripting guide
│ ├── SECURITY_ARCHITECTURE.md # Security documentation
│ ├── Developer Guide/ # Developer docs
│ ├── User Guide/ # User docs
│ └── Script API/ # API reference
└── ...
```
### Key Source Files
- **Backend Entry:** `apps/server/src/main.ts`
- **Frontend Entry:** `apps/client/src/desktop.ts` / `apps/client/src/index.ts`
- **Becca Cache:** `apps/server/src/becca/becca.ts`
- **Froca Cache:** `apps/client/src/services/froca.ts`
- **Database Schema:** `apps/server/src/assets/db/schema.sql`
- **Backend API:** `apps/server/src/services/backend_script_api.ts`
- **Frontend API:** `apps/client/src/services/frontend_script_api.ts`
### Important Directories
- **Entities:** `apps/server/src/becca/entities/`
- **Widgets:** `apps/client/src/widgets/`
- **Services:** `apps/server/src/services/`
- **Routes:** `apps/server/src/routes/`
- **Migrations:** `apps/server/src/migrations/`
- **Tests:** Various `*.spec.ts` files throughout
---
## 🎯 Common Tasks
### Understanding the Codebase
1. Read [ARCHITECTURE.md](ARCHITECTURE.md) for overview
2. Explore [Monorepo Structure](ARCHITECTURE.md#monorepo-structure)
3. Review [Entity System](ARCHITECTURE.md#entity-system)
4. Check [Key Files](ARCHITECTURE.md#key-files-for-understanding-architecture)
### Adding Features
1. Review relevant architecture documentation
2. Check [Developer Guide](Developer%20Guide/Developer%20Guide/)
3. Follow existing patterns in codebase
4. Write tests
5. Update documentation
### Debugging Issues
1. Check [Troubleshooting](Developer%20Guide/Developer%20Guide/Troubleshooting/)
2. Review [Database](DATABASE.md) for data issues
3. Check [Synchronization](SYNCHRONIZATION.md) for sync issues
4. Review [Security](SECURITY_ARCHITECTURE.md) for auth issues
### Performance Optimization
1. [Database Performance](DATABASE.md#performance-optimization)
2. [Cache Optimization](ARCHITECTURE.md#caching-system)
3. [Build Optimization](ARCHITECTURE.md#build-system)
4. [Script Performance](SCRIPTING.md#performance-considerations)
---
## 🔗 External Resources
### Official Links
- **Website:** https://triliumnotes.org
- **Documentation:** https://docs.triliumnotes.org
- **GitHub:** https://github.com/TriliumNext/Trilium
- **Discussions:** https://github.com/TriliumNext/Trilium/discussions
- **Matrix Chat:** https://matrix.to/#/#triliumnext:matrix.org
### Community Resources
- **Awesome Trilium:** https://github.com/Nriver/awesome-trilium
- **TriliumRocks:** https://trilium.rocks/
- **Wiki:** https://triliumnext.github.io/Docs/Wiki/
### Related Projects
- **TriliumDroid:** https://github.com/FliegendeWurst/TriliumDroid
- **Web Clipper:** Included in main repository
---
## 📝 Documentation Conventions
### Document Structure
- Overview section
- Table of contents
- Main content with headings
- Code examples where relevant
- "See Also" references
### Code Examples
```typescript
// TypeScript examples with comments
const example = 'value'
```
```sql
-- SQL examples with formatting
SELECT * FROM notes WHERE noteId = ?
```
### Cross-References
- Use relative links: `[text](path/to/file.md)`
- Reference sections: `[text](file.md#section)`
- External links: Full URLs
### Maintenance
- Review on major releases
- Update for architectural changes
- Add examples for new features
- Keep API references current
---
## 🤝 Contributing to Documentation
### What to Document
- New features and APIs
- Architecture changes
- Migration guides
- Performance tips
- Security considerations
### How to Contribute
1. Edit markdown files in `docs/`
2. Follow existing structure and style
3. Include code examples
4. Test links and formatting
5. Submit pull request
### Documentation Standards
- Clear, concise language
- Complete code examples
- Proper markdown formatting
- Cross-references to related docs
- Updated version numbers
---
## 📅 Version Information
- **Documentation Version:** 0.99.3
- **Last Updated:** November 2025
- **Trilium Version:** 0.99.3+
- **Next Review:** When major architectural changes occur
---
## 💡 Getting Help
### For Users
- [User Guide](User%20Guide/User%20Guide/)
- [GitHub Discussions](https://github.com/TriliumNext/Trilium/discussions)
- [Matrix Chat](https://matrix.to/#/#triliumnext:matrix.org)
### For Developers
- [Developer Guide](Developer%20Guide/Developer%20Guide/)
- [Architecture Docs](ARCHITECTURE.md)
- [GitHub Issues](https://github.com/TriliumNext/Trilium/issues)
### For Contributors
- [Contributing Guidelines](../README.md#-contribute)
- [Code of Conduct](../CODE_OF_CONDUCT)
- [Developer Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
---
**Maintained by:** TriliumNext Team
**License:** AGPL-3.0-only
**Repository:** https://github.com/TriliumNext/Trilium