Compare commits

..

3 Commits

42 changed files with 585 additions and 1067 deletions

View File

@@ -17,12 +17,12 @@
}, },
"dependencies": { "dependencies": {
"@excalidraw/excalidraw": "0.18.0", "@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.20", "@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.20", "@fullcalendar/daygrid": "6.1.19",
"@fullcalendar/interaction": "6.1.20", "@fullcalendar/interaction": "6.1.19",
"@fullcalendar/list": "6.1.20", "@fullcalendar/list": "6.1.19",
"@fullcalendar/multimonth": "6.1.20", "@fullcalendar/multimonth": "6.1.19",
"@fullcalendar/timegrid": "6.1.20", "@fullcalendar/timegrid": "6.1.19",
"@maplibre/maplibre-gl-leaflet": "0.1.3", "@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0", "@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1", "@mind-elixir/node-menu": "5.0.1",
@@ -75,7 +75,7 @@
"@types/leaflet-gpx": "1.3.8", "@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12", "@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.2", "@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1", "@types/tabulator-tables": "6.3.0",
"copy-webpack-plugin": "13.0.1", "copy-webpack-plugin": "13.0.1",
"happy-dom": "20.0.11", "happy-dom": "20.0.11",
"script-loader": "0.7.2", "script-loader": "0.7.2",

View File

@@ -2012,10 +2012,8 @@ body.zen .shared-info-widget,
body.zen .ribbon-container:not(:has(.classic-toolbar-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:has(.classic-toolbar-widget) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)), body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget,
body.zen .title-row .icon-action, body.zen .title-row .icon-action,
body.zen .note-badges > *:not(.read-only-badge),
body.zen .ribbon-button-container,
body.zen .inline-title,
body.zen .promoted-attributes-widget, body.zen .promoted-attributes-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt), body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button, body.zen .action-button,
@@ -2038,11 +2036,11 @@ body.zen #launcher-pane {
} }
body.zen .title-row { body.zen .title-row {
display: block !important;
height: unset !important; height: unset !important;
-webkit-app-region: drag; -webkit-app-region: drag;
padding-inline-start: env(titlebar-area-x); padding-inline-start: env(titlebar-area-x);
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em); padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em);
border-bottom: none !important;
} }
body.zen .floating-buttons { body.zen .floating-buttons {
@@ -2062,6 +2060,8 @@ body.zen .floating-buttons-children .button-widget {
body.zen .note-title-widget, body.zen .note-title-widget,
body.zen .note-title-widget input { body.zen .note-title-widget input {
font-size: 1rem !important; font-size: 1rem !important;
background: transparent !important;
pointer-events: none;
} }
body.zen #detail-container { body.zen #detail-container {

View File

@@ -234,9 +234,6 @@
--right-pane-item-hover-background: #ffffff26; --right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white; --right-pane-item-hover-color: white;
--bottom-panel-background-color: #11111180;
--bottom-panel-title-bar-background-color: #3F3F3F80;
--scrollbar-thumb-color: #fdfdfd5c; --scrollbar-thumb-color: #fdfdfd5c;
--scrollbar-thumb-hover-color: #ffffff7d; --scrollbar-thumb-hover-color: #ffffff7d;
--scrollbar-background-color: transparent; --scrollbar-background-color: transparent;

View File

@@ -232,9 +232,6 @@
--right-pane-item-hover-background: #00000013; --right-pane-item-hover-background: #00000013;
--right-pane-item-hover-color: inherit; --right-pane-item-hover-color: inherit;
--bottom-panel-background-color: #0000000a;
--bottom-panel-title-bar-background-color: #00000017;
--scrollbar-thumb-color: #0000005c; --scrollbar-thumb-color: #0000005c;
--scrollbar-thumb-hover-color: #00000066; --scrollbar-thumb-hover-color: #00000066;
--scrollbar-background-color: transparent; --scrollbar-background-color: transparent;

View File

@@ -128,12 +128,6 @@ body.backdrop-effects-disabled {
font-size: 0.9rem !important; font-size: 0.9rem !important;
} }
.dropdown-menu.tn-dropdown-menu-scrollable {
/* Note: scrollable dropdowns does not support submenus */
max-height: 90vh;
overflow: scroll;
}
body.desktop .dropdown-menu::before, body.desktop .dropdown-menu::before,
:root .ck.ck-dropdown__panel::before, :root .ck.ck-dropdown__panel::before,
:root .excalidraw .popover::before, :root .excalidraw .popover::before,

View File

@@ -653,8 +653,7 @@ body a.tn-link:focus-visible,
} }
body a.tn-link:hover, body a.tn-link:hover,
.use-tn-links a:hover, .use-tn-links a:hover {
.use-tn-links a.ck-widget_selected {
box-shadow: 0 0 0 4px var(--link-hover-background); box-shadow: 0 0 0 4px var(--link-hover-background);
--background: var(--link-hover-background); --background: var(--link-hover-background);
color: var(--link-hover-color); color: var(--link-hover-color);

View File

@@ -670,19 +670,6 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
color: var(--main-text-color); color: var(--main-text-color);
} }
/* Links */
.ck-content a.ck-widget {
outline: none;
}
.ck-content a.ck-widget.ck-widget_selected,
.ck-content a.ck-link_selected {
outline: 2px solid var(--input-focus-outline-color);
outline-offset: 2px;
background: var(--link-hover-background);
}
/* Reference link */ /* Reference link */
.ck-content a.reference-link, .ck-content a.reference-link,
@@ -692,11 +679,7 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
text-decoration: none; text-decoration: none;
} }
.ck-content a.reference-link.use-note-color > span { .ck-content a.reference-link > span {
color: var(--custom-color, inherit);
}
.ck-content a.reference-link:hover > span {
text-decoration: underline; text-decoration: underline;
} }

View File

@@ -1230,7 +1230,7 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
margin-bottom: 2px; margin-bottom: 2px;
} }
body.layout-vertical #rest-pane > .classic-toolbar-widget { body.vertical-layout #rest-pane > .classic-toolbar-widget {
border-start-start-radius: var(--center-pane-border-radius); border-start-start-radius: var(--center-pane-border-radius);
} }

View File

@@ -1 +0,0 @@
{}

View File

@@ -818,8 +818,7 @@
}, },
"inherited_attribute_list": { "inherited_attribute_list": {
"title": "Inherited Attributes", "title": "Inherited Attributes",
"no_inherited_attributes": "No inherited attributes.", "no_inherited_attributes": "No inherited attributes."
"none": "none"
}, },
"note_info_widget": { "note_info_widget": {
"note_id": "Note ID", "note_id": "Note ID",
@@ -2204,9 +2203,6 @@
"note_paths_title": "Note paths", "note_paths_title": "Note paths",
"code_note_switcher": "Change language mode" "code_note_switcher": "Change language mode"
}, },
"attributes_panel": {
"title": "Note Attributes"
},
"right_pane": { "right_pane": {
"empty_message": "Nothing to show for this note", "empty_message": "Nothing to show for this note",
"empty_button": "Hide the panel", "empty_button": "Hide the panel",

View File

@@ -1934,10 +1934,7 @@
}, },
"highlights_list_2": { "highlights_list_2": {
"title": "Lista wyróżnień", "title": "Lista wyróżnień",
"options": "Opcje", "options": "Opcje"
"modal_title": "Konfiguracja listy wyróżnień",
"menu_configure": "Konfiguracja listy wyróżnień...",
"no_highlights": "Nie znaleziono wyróżnień."
}, },
"quick-search": { "quick-search": {
"placeholder": "Szybkie wyszukiwanie", "placeholder": "Szybkie wyszukiwanie",

View File

@@ -483,7 +483,7 @@
"workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если будет установлен фокус на рабочую область с этим шаблоном", "workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если будет установлен фокус на рабочую область с этим шаблоном",
"workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство", "workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство",
"workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства", "workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства",
"hide_highlight_widget": "Скрыть виджет «Акценты»", "hide_highlight_widget": "Скрыть виджет «Выделенное»",
"is_owned_by_note": "принадлежит заметке", "is_owned_by_note": "принадлежит заметке",
"and_more": "... и ещё {{count}}.", "and_more": "... и ещё {{count}}.",
"app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium.", "app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium.",
@@ -750,8 +750,7 @@
}, },
"toc": { "toc": {
"table_of_contents": "Оглавление", "table_of_contents": "Оглавление",
"options": "Параметры", "options": "Параметры"
"no_headings": "Заголовки не найдены."
}, },
"note_tree": { "note_tree": {
"hide-archived-notes": "Скрыть архивные заметки", "hide-archived-notes": "Скрыть архивные заметки",
@@ -1563,13 +1562,7 @@
}, },
"highlights_list_2": { "highlights_list_2": {
"options": "Параметры", "options": "Параметры",
"title": "Акценты", "title": "Список выделенного"
"modal_title": "Настроить акценты",
"menu_configure": "Настроить акценты...",
"no_highlights": "Акценты в тексте не найдены.",
"title_with_count_one": "{{count}} акцент",
"title_with_count_few": "{{count}} акцента",
"title_with_count_many": "{{count}} акцентов"
}, },
"include_note": { "include_note": {
"dialog_title": "Вставить заметку", "dialog_title": "Вставить заметку",
@@ -1762,15 +1755,15 @@
"enable_tray": "Включить отображение иконки в системном трее (чтобы изменения вступили в силу, необходимо перезапустить Trilium)" "enable_tray": "Включить отображение иконки в системном трее (чтобы изменения вступили в силу, необходимо перезапустить Trilium)"
}, },
"highlights_list": { "highlights_list": {
"title": "Акценты", "title": "Список выделенного",
"bold": "Жирный текст", "bold": "Жирный текст",
"italic": "Наклонный текст", "italic": "Наклонный текст",
"underline": "Подчеркнутый текст", "underline": "Подчеркнутый текст",
"color": "Цветной текст", "color": "Цветной текст",
"description": "Вы можете настроить список акцентов, отображаемый на правой панели:", "description": "Вы можете настроить список выделенного, отображаемый на правой панели:",
"bg_color": "Текст с заливкой фона", "bg_color": "Текст с заливкой фона",
"visibility_title": "Видимость списка акцентов", "visibility_title": "Видимость списка выделений",
"visibility_description": "Вы можете скрыть виджет списка акцентов, добавив атрибут #hideHighlightWidget к заметке.", "visibility_description": "Вы можете скрыть виджет списка выделенного, добавив атрибут #hideHighlightWidget к заметке.",
"shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделенного) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")." "shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделенного) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")."
}, },
"custom_date_time_format": { "custom_date_time_format": {
@@ -1815,7 +1808,7 @@
"edit_this_note": "Редактировать заметку" "edit_this_note": "Редактировать заметку"
}, },
"show_highlights_list_widget_button": { "show_highlights_list_widget_button": {
"show_highlights_list": "Показать список акцентов" "show_highlights_list": "Показать список выделенного"
}, },
"zen_mode": { "zen_mode": {
"button_exit": "Покинуть режим \"дзен\"" "button_exit": "Покинуть режим \"дзен\""
@@ -2210,11 +2203,5 @@
}, },
"popup-editor": { "popup-editor": {
"maximize": "Переключить на полный редактор" "maximize": "Переключить на полный редактор"
},
"right_pane": {
"custom_widget_go_to_source": "Исходный код",
"toggle": "Переключить панель справа",
"empty_button": "Скрыть панель",
"empty_message": "Нечего отобразить для текущей заметки"
} }
} }

View File

@@ -691,12 +691,7 @@
"convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?", "convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?",
"print_pdf": "匯出為 PDF…", "print_pdf": "匯出為 PDF…",
"open_note_on_server": "在伺服器上開啟筆記", "open_note_on_server": "在伺服器上開啟筆記",
"view_revisions": "筆記歷史版本...", "view_revisions": "筆記歷史版本..."
"advanced": "進階",
"export_as_image": "匯出為圖片",
"export_as_image_png": "PNG (點陣)",
"export_as_image_svg": "SVG (向量)",
"note_map": "筆記地圖"
}, },
"onclick_button": { "onclick_button": {
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式" "no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
@@ -795,7 +790,7 @@
"file_type": "檔案類型", "file_type": "檔案類型",
"file_size": "檔案大小", "file_size": "檔案大小",
"download": "下載", "download": "下載",
"open": "以外部程式打開", "open": "打開",
"upload_new_revision": "上傳新版本", "upload_new_revision": "上傳新版本",
"upload_success": "已上傳新檔案版本。", "upload_success": "已上傳新檔案版本。",
"upload_failed": "新檔案版本上傳失敗。", "upload_failed": "新檔案版本上傳失敗。",
@@ -826,9 +821,7 @@
"note_size_info": "筆記大小提供了該筆記儲存需求的粗略估計。它考慮了筆記及其歷史的內容。", "note_size_info": "筆記大小提供了該筆記儲存需求的粗略估計。它考慮了筆記及其歷史的內容。",
"calculate": "計算", "calculate": "計算",
"subtree_size": "(子階層大小: {{size}}, 共計 {{count}} 個筆記)", "subtree_size": "(子階層大小: {{size}}, 共計 {{count}} 個筆記)",
"title": "筆記資訊", "title": "筆記資訊"
"mime": "MIME 類型",
"show_similar_notes": "顯示相似筆記"
}, },
"note_map": { "note_map": {
"open_full": "展開顯示", "open_full": "展開顯示",
@@ -891,8 +884,7 @@
"search_parameters": "搜尋參數", "search_parameters": "搜尋參數",
"unknown_search_option": "未知的搜尋選項 {{searchOptionName}}", "unknown_search_option": "未知的搜尋選項 {{searchOptionName}}",
"search_note_saved": "搜尋筆記已儲存至 {{- notePathTitle}}", "search_note_saved": "搜尋筆記已儲存至 {{- notePathTitle}}",
"actions_executed": "已執行操作。", "actions_executed": "已執行操作。"
"view_options": "查看選項:"
}, },
"similar_notes": { "similar_notes": {
"title": "相似筆記", "title": "相似筆記",
@@ -1511,11 +1503,7 @@
}, },
"highlights_list_2": { "highlights_list_2": {
"title": "高亮列表", "title": "高亮列表",
"options": "選項", "options": "選項"
"title_with_count_one": "{{count}} 處高亮",
"modal_title": "設定高亮列表",
"menu_configure": "設定高亮列表…",
"no_highlights": "未找到高亮內容。"
}, },
"quick-search": { "quick-search": {
"placeholder": "快速搜尋", "placeholder": "快速搜尋",
@@ -1551,13 +1539,8 @@
}, },
"note_title": { "note_title": {
"placeholder": "請輸入筆記標題...", "placeholder": "請輸入筆記標題...",
"created_on": "建立於 <Value />", "created_on": "建立於 {{date}}",
"last_modified": "修改於 <Value />", "last_modified": "最後修改於 {{date}}"
"note_type_switcher_label": "從 {{type}} 切換至:",
"note_type_switcher_others": "其他筆記類型",
"note_type_switcher_templates": "模板",
"note_type_switcher_collection": "集合",
"edited_notes": "編輯過的筆記"
}, },
"search_result": { "search_result": {
"no_notes_found": "沒有找到符合搜尋條件的筆記。", "no_notes_found": "沒有找到符合搜尋條件的筆記。",
@@ -1586,8 +1569,7 @@
}, },
"toc": { "toc": {
"table_of_contents": "目錄", "table_of_contents": "目錄",
"options": "選項", "options": "選項"
"no_headings": "無標題。"
}, },
"watched_file_update_status": { "watched_file_update_status": {
"file_last_modified": "檔案 <code class=\"file-path\"></code> 最後修改時間為 <span class=\"file-last-modified\"></span>。", "file_last_modified": "檔案 <code class=\"file-path\"></code> 最後修改時間為 <span class=\"file-last-modified\"></span>。",
@@ -1961,9 +1943,8 @@
"unknown_widget": "未知元件:\"{{id}}\"。" "unknown_widget": "未知元件:\"{{id}}\"。"
}, },
"note_language": { "note_language": {
"not_set": "設定語言", "not_set": "設定",
"configure-languages": "設定語言…", "configure-languages": "設定語言…"
"help-on-languages": "設定內容語言說明…"
}, },
"content_language": { "content_language": {
"title": "內文語言", "title": "內文語言",
@@ -2030,7 +2011,7 @@
"book_properties_config": { "book_properties_config": {
"hide-weekends": "隱藏週末", "hide-weekends": "隱藏週末",
"display-week-numbers": "顯示週數", "display-week-numbers": "顯示週數",
"map-style": "地圖樣式", "map-style": "地圖樣式",
"max-nesting-depth": "最大嵌套深度:", "max-nesting-depth": "最大嵌套深度:",
"raster": "柵格", "raster": "柵格",
"vector_light": "向量(淺色)", "vector_light": "向量(淺色)",
@@ -2087,19 +2068,14 @@
"next_theme_title": "試用新 Trilium 主題", "next_theme_title": "試用新 Trilium 主題",
"next_theme_message": "您正在使用舊版主題,要試用新主題嗎?", "next_theme_message": "您正在使用舊版主題,要試用新主題嗎?",
"next_theme_button": "試用新主題", "next_theme_button": "試用新主題",
"dismiss": "關閉", "dismiss": "關閉"
"new_layout_title": "新版面配置",
"new_layout_button": "更多資訊"
}, },
"settings": { "settings": {
"related_settings": "相關設定" "related_settings": "相關設定"
}, },
"settings_appearance": { "settings_appearance": {
"related_code_blocks": "文字筆記中程式碼區塊的配色方案", "related_code_blocks": "文字筆記中程式碼區塊的配色方案",
"related_code_notes": "程式碼筆記的配色方案", "related_code_notes": "程式碼筆記的配色方案"
"ui": "使用者介面",
"ui_old_layout": "舊版面配置",
"ui_new_layout": "新版面配置"
}, },
"units": { "units": {
"percentage": "%" "percentage": "%"
@@ -2159,40 +2135,6 @@
"read_only_explicit": "唯讀", "read_only_explicit": "唯讀",
"read_only_auto": "自動唯讀", "read_only_auto": "自動唯讀",
"shared_publicly": "公開分享", "shared_publicly": "公開分享",
"shared_locally": "本地分享", "shared_locally": "本地分享"
"read_only_explicit_description": "此筆記已被手動設定為唯讀。\n點擊以臨時編輯。",
"read_only_temporarily_disabled": "臨時編輯",
"shared_copy_to_clipboard": "複製連結至剪貼簿",
"shared_open_in_browser": "在瀏覽器中打開連結",
"shared_unshare": "取消分享",
"clipped_note": "網頁擷取",
"execute_script": "運行腳本",
"execute_sql": "運行 SQL"
},
"breadcrumb": {
"hoisted_badge": "聚焦",
"hoisted_badge_title": "取消聚焦",
"workspace_badge": "工作空間",
"scroll_to_top_title": "跳轉至筆記開頭",
"create_new_note": "新增子筆記",
"empty_hide_archived_notes": "隱藏已歸檔的筆記"
},
"status_bar": {
"language_title": "更改內容語言",
"note_info_title": "查看筆記資訊(如日期、筆記大小)",
"backlinks_one": "{{count}} 個反連結",
"backlinks_title_one": "查看反連結",
"attachments_one": "{{count}} 個附件",
"attachments_title_one": "在新分頁中查看附件",
"attributes_one": "{{count}} 個屬性",
"attributes_title": "自有屬性及繼承屬性",
"note_paths_one": "{{count}} 條路徑",
"note_paths_title": "筆記路徑",
"code_note_switcher": "更改語言模式"
},
"right_pane": {
"empty_button": "隱藏面板",
"toggle": "切換右側面板",
"custom_widget_go_to_source": "跳轉至原始碼"
} }
} }

View File

@@ -16,8 +16,6 @@
} }
a.tn-link { a.tn-link {
--link-hover-background: var(--icon-button-hover-background);
color: var(--custom-color, inherit); color: var(--custom-color, inherit);
&:hover { &:hover {
@@ -48,19 +46,6 @@
overflow: hidden; overflow: hidden;
display: block; display: block;
flex-shrink: 2; flex-shrink: 2;
font-weight: normal;
}
}
.icon-action {
font-size: .9rem !important;
.bxs-chevron-right {
transform: translateY(8%);
&::before {
opacity: .75;
}
} }
} }
@@ -87,15 +72,6 @@
color: var(--custom-color, inherit) !important; color: var(--custom-color, inherit) !important;
} }
.dropdown .breadcrumb-child-list {
/* Icon */
li > span:first-child {
opacity: .75;
padding-inline-end: 4px;
translate: none;
};
}
a.breadcrumb-last-item, a.breadcrumb-last-item,
a.breadcrumb-last-item:visited { a.breadcrumb-last-item:visited {
text-decoration: none; text-decoration: none;

View File

@@ -189,11 +189,10 @@ interface BreadcrumbSeparatorProps {
function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) { function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) {
return ( return (
<Dropdown <Dropdown
text={<Icon icon="bx bxs-chevron-right" />} text={<Icon icon="bx bx-chevron-right" />}
noSelectButtonStyle noSelectButtonStyle
buttonClassName="icon-action" buttonClassName="icon-action"
hideToggleArrow hideToggleArrow
dropdownContainerClassName="tn-dropdown-menu-scrollable"
dropdownOptions={{ popperConfig: { strategy: "fixed", placement: "top" } }} dropdownOptions={{ popperConfig: { strategy: "fixed", placement: "top" } }}
> >
<BreadcrumbSeparatorDropdownContent {...props} /> <BreadcrumbSeparatorDropdownContent {...props} />

View File

@@ -1,12 +1,12 @@
.component.status-bar { .component.status-bar {
contain: none; contain: none;
border-top: 1px solid var(--main-border-color); border-top: 1px solid var(--main-border-color);
background-color: var(--left-pane-background-color);
> .status-bar-main-row { > .status-bar-main-row {
min-height: 28px; min-height: 28px;
display: flex; display: flex;
align-items: center; align-items: center;
background-color: var(--left-pane-background-color);
padding-inline: 0.25em; padding-inline: 0.25em;
font-size: 0.85em; font-size: 0.85em;
@@ -155,6 +155,11 @@
} }
} }
.dropdown-code-note-switcher {
max-height: 90vh;
overflow: scroll;
}
.backlinks-widget > .dropdown-menu { .backlinks-widget > .dropdown-menu {
--menu-padding-size: .9em; --menu-padding-size: .9em;
@@ -243,82 +248,16 @@
> .attribute-list { > .attribute-list {
font-size: 0.9em; font-size: 0.9em;
padding: 0.5em 0.75em;
.attributes-panel-label { .inherited-attributes-widget > div {
opacity: .5;
margin-inline-end: 4px;
font-weight: 600;
}
.inherited-attributes-widget {
display: inline;
> div {
display: inline;
padding: 0; padding: 0;
font-size: 0.9em;
} }
}
.attribute-list-editor-wrapper {
display: flex;
flex-direction: column-reverse;
padding-bottom: 0 !important;
.attribute-list-editor { .attribute-list-editor {
padding-block: 0 !important; padding: 0 !important;
padding-inline: 0 100px !important ;
} }
.attribute-errors {
padding: 4px 0;
color: var(--dropdown-item-icon-destructive-color);
font-style: italic;
}
.ck.ck-editor__editable::after {
/* Remove a hidden spinner that causes overflow */
display: none;
}
}
}
div.similar-notes-widget div.similar-notes-wrapper {
max-height: unset;
}
button.select-button:not(:focus-visible) {
outline: none;
} }
} }
.bottom-panel {
margin: 0 !important;
padding: 0;
.bottom-panel-title-bar {
display: flex;
padding: 6px 12px;
background: var(--bottom-panel-title-bar-background-color);
justify-content: space-between;
align-items: center;
.bottom-panel-title-bar-caption {
text-transform: uppercase;
letter-spacing: .3pt;
font-weight: 600;
font-size: .85em;
}
}
.bottom-panel-content {
border-bottom: 1px solid var(--main-border-color);
background: var(--bottom-panel-background-color);
padding: 8px 12px;
max-height: 40vh;
overflow-y: auto;
}
}

View File

@@ -266,15 +266,11 @@ function NoteInfoValue({ text, title, value }: { text: string; title?: string, v
); );
} }
function SimilarNotesPane({ note, similarNotesShown, setSimilarNotesShown }: NoteInfoContext) { function SimilarNotesPane({ note, similarNotesShown }: NoteInfoContext) {
return (similarNotesShown && return (similarNotesShown &&
<BottomPanel title={t("similar_notes.title")} <div className="similar-notes-pane">
className="similar-notes-pane"
visible={similarNotesShown}
setVisible={setSimilarNotesShown}
>
<SimilarNotesTab note={note} /> <SimilarNotesTab note={note} />
</BottomPanel> </div>
); );
} }
//#endregion //#endregion
@@ -371,20 +367,15 @@ function AttributesPane({ note, noteContext, attributesShown, setAttributesShown
}), [ api ])); }), [ api ]));
return (context && return (context &&
<BottomPanel title={t("attributes_panel.title")} <div className={clsx("attribute-list", !attributesShown && "hidden-ext")}>
className="attribute-list" <InheritedAttributesTab {...context} />
visible={attributesShown}
setVisible={setAttributesShown}>
<span class="attributes-panel-label">{t("inherited_attribute_list.title")}</span>
<InheritedAttributesTab {...context} emptyListString="inherited_attribute_list.none" />
<AttributeEditor <AttributeEditor
{...context} {...context}
api={api} api={api}
ntxId={noteContext.ntxId} ntxId={noteContext.ntxId}
/> />
</BottomPanel> </div>
); );
} }
//#endregion //#endregion
@@ -431,7 +422,7 @@ function CodeNoteSwitcher({ note }: StatusBarContext) {
icon="bx bx-code-curly" icon="bx bx-code-curly"
text={correspondingMimeType?.title} text={correspondingMimeType?.title}
title={t("status_bar.code_note_switcher")} title={t("status_bar.code_note_switcher")}
dropdownContainerClassName="dropdown-code-note-switcher tn-dropdown-menu-scrollable" dropdownContainerClassName="dropdown-code-note-switcher"
> >
<NoteTypeCodeNoteList <NoteTypeCodeNoteList
currentMimeType={currentNoteMime} currentMimeType={currentNoteMime}
@@ -448,26 +439,3 @@ function CodeNoteSwitcher({ note }: StatusBarContext) {
); );
} }
//#endregion //#endregion
//#region Bottom panel
interface BottomPanelParams {
children: ComponentChildren;
title: string;
visible: boolean;
setVisible?: (visible: boolean) => void;
className?: string;
}
function BottomPanel({ children, title, visible, setVisible, className }: BottomPanelParams) {
return <div className={clsx("bottom-panel", className, {"hidden-ext": !visible})}>
<div className="bottom-panel-title-bar">
<span className="bottom-panel-title-bar-caption">{title}</span>
<button class="icon-action bx bx-x" onClick={() => setVisible?.(false)} />
</div>
<div class={clsx("bottom-panel-content")}>
{children}
</div>
</div>;
}
//#endregion

View File

@@ -9,11 +9,7 @@ import RawHtml from "../react/RawHtml";
import { joinElements } from "../react/react_utils"; import { joinElements } from "../react/react_utils";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail"; import AttributeDetailWidget from "../attribute_widgets/attribute_detail";
type InheritedAttributesTabArgs = Pick<TabContext, "note" | "componentId"> & { export default function InheritedAttributesTab({ note, componentId }: Pick<TabContext, "note" | "componentId">) {
emptyListString?: string;
}
export default function InheritedAttributesTab({ note, componentId, emptyListString }: InheritedAttributesTabArgs) {
const [ inheritedAttributes, setInheritedAttributes ] = useState<FAttribute[]>(); const [ inheritedAttributes, setInheritedAttributes ] = useState<FAttribute[]>();
const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget()); const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget());
@@ -67,7 +63,7 @@ export default function InheritedAttributesTab({ note, componentId, emptyListStr
/> />
)), " ") )), " ")
) : ( ) : (
<>{t(emptyListString ?? "inherited_attribute_list.no_inherited_attributes")}</> <>{t("inherited_attribute_list.no_inherited_attributes")}</>
)} )}
</div> </div>

View File

@@ -283,7 +283,6 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
return ( return (
<> <>
{!hidden && <div {!hidden && <div
className="attribute-list-editor-wrapper"
ref={wrapperRef} ref={wrapperRef}
style="position: relative; padding-top: 10px; padding-bottom: 10px" style="position: relative; padding-top: 10px; padding-bottom: 10px"
onKeyDown={(e) => { onKeyDown={(e) => {
@@ -297,7 +296,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
setTimeout(() => save(), 100); setTimeout(() => save(), 100);
} }
}} }}
> <div style="position: relative;"> >
<CKEditor <CKEditor
apiRef={editorRef} apiRef={editorRef}
className="attribute-list-editor" className="attribute-list-editor"
@@ -398,7 +397,6 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
}} }}
/> />
</div> </div>
</div>
{ error && ( { error && (
<div className="attribute-errors"> <div className="attribute-errors">

Binary file not shown.

View File

@@ -82,7 +82,7 @@
"toggle-note-paths": "Перейти к путям заметки", "toggle-note-paths": "Перейти к путям заметки",
"toggle-similar-notes": "Перейти к похожим заметкам", "toggle-similar-notes": "Перейти к похожим заметкам",
"other": "Прочее", "other": "Прочее",
"toggle-right-pane": "Включить/выключить отображение правой панели, которая включает оглавление и акценты", "toggle-right-pane": "Включить/выключить отображение правой панели, которая включает оглавление и выделенные фрагменты",
"print-active-note": "Распечатать активную заметку", "print-active-note": "Распечатать активную заметку",
"open-note-externally": "Открыть заметку как файл с помощью приложения по умолчанию", "open-note-externally": "Открыть заметку как файл с помощью приложения по умолчанию",
"render-active-note": "Отрендерить (повторно отрендерить) активную заметку", "render-active-note": "Отрендерить (повторно отрендерить) активную заметку",

View File

@@ -108,7 +108,7 @@ function loginSync(req: Request) {
const givenHash = req.body.hash; const givenHash = req.body.hash;
if (expectedHash !== givenHash) { if (!utils.constantTimeCompare(expectedHash, givenHash)) {
return [400, { message: "Sync login credentials are incorrect. It looks like you're trying to sync two different initialized documents which is not possible." }]; return [400, { message: "Sync login credentials are incorrect. It looks like you're trying to sync two different initialized documents which is not possible." }];
} }

View File

@@ -1,3 +1,4 @@
import crypto from "crypto";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
import optionService from "../services/options.js"; import optionService from "../services/options.js";
import myScryptService from "../services/encryption/my_scrypt.js"; import myScryptService from "../services/encryption/my_scrypt.js";
@@ -160,7 +161,11 @@ function verifyPassword(submittedPassword: string) {
const guess_hashed = myScryptService.getVerificationHash(submittedPassword); const guess_hashed = myScryptService.getVerificationHash(submittedPassword);
return guess_hashed.equals(hashed_password); // Use constant-time comparison to prevent timing attacks
if (hashed_password.length !== guess_hashed.length) {
return false;
}
return crypto.timingSafeEqual(guess_hashed, hashed_password);
} }
function sendLoginError(req: Request, res: Response, errorType: 'password' | 'totp' = 'password') { function sendLoginError(req: Request, res: Response, errorType: 'password' | 'totp' = 'password') {

View File

@@ -1,5 +1,5 @@
import myScryptService from "./my_scrypt.js"; import myScryptService from "./my_scrypt.js";
import utils from "../utils.js"; import utils, { constantTimeCompare } from "../utils.js";
import dataEncryptionService from "./data_encryption.js"; import dataEncryptionService from "./data_encryption.js";
import sql from "../sql.js"; import sql from "../sql.js";
import sqlInit from "../sql_init.js"; import sqlInit from "../sql_init.js";
@@ -87,8 +87,7 @@ function verifyOpenIDSubjectIdentifier(subjectIdentifier: string) {
return undefined; return undefined;
} }
console.log("Matches: " + givenHash === savedHash); return constantTimeCompare(givenHash, savedHash as string);
return givenHash === savedHash;
} }
function setDataKey( function setDataKey(

View File

@@ -1,6 +1,6 @@
import optionService from "../options.js"; import optionService from "../options.js";
import myScryptService from "./my_scrypt.js"; import myScryptService from "./my_scrypt.js";
import { toBase64 } from "../utils.js"; import { toBase64, constantTimeCompare } from "../utils.js";
import dataEncryptionService from "./data_encryption.js"; import dataEncryptionService from "./data_encryption.js";
function verifyPassword(password: string) { function verifyPassword(password: string) {
@@ -12,7 +12,7 @@ function verifyPassword(password: string) {
return false; return false;
} }
return givenPasswordHash === dbPasswordHash; return constantTimeCompare(givenPasswordHash, dbPasswordHash);
} }
function setDataKey(password: string, plainTextDataKey: string | Buffer) { function setDataKey(password: string, plainTextDataKey: string | Buffer) {

View File

@@ -1,6 +1,7 @@
import crypto from 'crypto'; import crypto from 'crypto';
import optionService from '../options.js'; import optionService from '../options.js';
import sql from '../sql.js'; import sql from '../sql.js';
import { constantTimeCompare } from '../utils.js';
function isRecoveryCodeSet() { function isRecoveryCodeSet() {
return optionService.getOptionBool('encryptedRecoveryCodes'); return optionService.getOptionBool('encryptedRecoveryCodes');
@@ -55,13 +56,22 @@ function verifyRecoveryCode(recoveryCodeGuess: string) {
const recoveryCodes = getRecoveryCodes(); const recoveryCodes = getRecoveryCodes();
let loginSuccess = false; let loginSuccess = false;
recoveryCodes.forEach((recoveryCode) => { let matchedCode: string | null = null;
if (recoveryCodeGuess === recoveryCode) {
removeRecoveryCode(recoveryCode); // Check ALL codes to prevent timing attacks - do not short-circuit
for (const recoveryCode of recoveryCodes) {
if (constantTimeCompare(recoveryCodeGuess, recoveryCode)) {
matchedCode = recoveryCode;
loginSuccess = true; loginSuccess = true;
return; // Continue checking all codes to maintain constant time
} }
}); }
// Remove the matched code only after checking all codes
if (matchedCode) {
removeRecoveryCode(matchedCode);
}
return loginSuccess; return loginSuccess;
} }

View File

@@ -1,6 +1,6 @@
import optionService from "../options.js"; import optionService from "../options.js";
import myScryptService from "./my_scrypt.js"; import myScryptService from "./my_scrypt.js";
import { randomSecureToken, toBase64 } from "../utils.js"; import { randomSecureToken, toBase64, constantTimeCompare } from "../utils.js";
import dataEncryptionService from "./data_encryption.js"; import dataEncryptionService from "./data_encryption.js";
import type { OptionNames } from "@triliumnext/commons"; import type { OptionNames } from "@triliumnext/commons";
@@ -18,7 +18,7 @@ function verifyTotpSecret(secret: string): boolean {
return false; return false;
} }
return givenSecretHash === dbSecretHash; return constantTimeCompare(givenSecretHash, dbSecretHash);
} }
function setTotpSecret(secret: string) { function setTotpSecret(secret: string) {

View File

@@ -1,5 +1,5 @@
import becca from "../becca/becca.js"; import becca from "../becca/becca.js";
import { fromBase64, randomSecureToken } from "./utils.js"; import { fromBase64, randomSecureToken, constantTimeCompare } from "./utils.js";
import BEtapiToken from "../becca/entities/betapi_token.js"; import BEtapiToken from "../becca/entities/betapi_token.js";
import crypto from "crypto"; import crypto from "crypto";
@@ -83,15 +83,16 @@ function isValidAuthHeader(auth: string | undefined) {
return false; return false;
} }
return etapiToken.tokenHash === authTokenHash; return constantTimeCompare(etapiToken.tokenHash, authTokenHash);
} else { } else {
// Check ALL tokens to prevent timing attacks - do not short-circuit
let isValid = false;
for (const etapiToken of becca.getEtapiTokens()) { for (const etapiToken of becca.getEtapiTokens()) {
if (etapiToken.tokenHash === authTokenHash) { if (constantTimeCompare(etapiToken.tokenHash, authTokenHash)) {
return true; isValid = true;
} }
} }
return isValid;
return false;
} }
} }

View File

@@ -74,6 +74,36 @@ export function hmac(secret: any, value: any) {
return hmac.digest("base64"); return hmac.digest("base64");
} }
/**
* Constant-time string comparison to prevent timing attacks.
* Uses crypto.timingSafeEqual to ensure comparison time is independent
* of how many characters match.
*
* @param a First string to compare
* @param b Second string to compare
* @returns true if strings are equal, false otherwise
* @note Returns false for null/undefined/non-string inputs. Empty strings are considered equal.
*/
export function constantTimeCompare(a: string | null | undefined, b: string | null | undefined): boolean {
// Handle null/undefined/non-string cases safely
if (typeof a !== "string" || typeof b !== "string") {
return false;
}
const bufA = Buffer.from(a, "utf-8");
const bufB = Buffer.from(b, "utf-8");
// If lengths differ, we still do a constant-time comparison
// to avoid leaking length information through timing
if (bufA.length !== bufB.length) {
// Compare bufA against itself to maintain constant time behavior
crypto.timingSafeEqual(bufA, bufA);
return false;
}
return crypto.timingSafeEqual(bufA, bufB);
}
export function hash(text: string) { export function hash(text: string) {
text = text.normalize(); text = text.normalize();
@@ -486,6 +516,7 @@ function slugify(text: string) {
export default { export default {
compareVersions, compareVersions,
constantTimeCompare,
crash, crash,
envToBoolean, envToBoolean,
escapeHtml, escapeHtml,

View File

@@ -1,35 +0,0 @@
{
"get-started": {
"title": "Започнете тук",
"desktop_title": "Свалете десктоп апликацията (v{{version}})",
"architecture": "Архитектура:",
"older_releases": "Вижте по-стари версии",
"server_title": "Конфигурирайте сървър за достъп от различни устройства"
},
"hero_section": {
"title": "Организирайте Вашите идеи. Създайте Вашата лична база от знания.",
"subtitle": "Trilium е решение с отворен код за създаване на бележки и организация на лична база от знания. Изполвайте приложението на Вашия десктоп, или го синхоранизирайте чрез сървар хостнат от Вас за да имате дотъп до бележките си навсякъде.",
"get_started": "Започнете тук",
"github": "GitHub",
"screenshot_alt": "Скриншот от Trilium Notes десктоп програмата"
},
"organization_benefits": {
"title": "Организация",
"note_structure_title": "Структура на бележката",
"note_structure_description": "Бележките може да бъдат подредени йерархично. Няма нужа от папки, понеже всяка бележка може да съдържа под-бележка. Една бележка може да бъде добавена на няколко места в йерархията.",
"attributes_title": "Етикети и връзки на бележката",
"attributes_description": "Използвайте връзки между бележките или добавете етикети за по-лесна кетегоризация. Изплозвайте избрани атрибуте за да въведете стуктурирана информация, която може да бъде използвана в таблици, табла.",
"hoisting_title": "Работни плотове",
"hoisting_description": "Лесно разделяйте лични от работни бележки като ги групирате в работен плот, който ще накара дървото от бележки да показва само специфичен сет от бележки."
},
"productivity_benefits": {
"title": "Продуктивност и безопасност",
"revisions_title": "Ревизии на бележката",
"revisions_content": "Бележките периодично се запазват и ревизиите може да се ползват за ревю или за да се отмени нежелана промяна. Ревизиите също могат да бъдат създавани ръчно.",
"sync_title": "Синхронизация",
"sync_content": "Използвайте лично хостната или качена в клауда инстанция за да синхронизирате Вашите бележки на няколко устройства, и за да ги достъпите от мобилно устройство използвайки ПУА(Прогресивен Уеб Апп).",
"protected_notes_title": "Защитени бележки",
"protected_notes_content": "Защитете чуствителна лична информация като криптирате съобщенията и ги заключите с парола.",
"jump_to_title": "Бързо търсене и команди"
}
}

View File

@@ -125,11 +125,5 @@
"geomap_description": "커스터마이징이 가능한 마커로 휴가를 계획하거나 관심 지점을 지도에 직접 표시하세요. 기록된 GPX 트랙을 표시하여 여정을 추적할 수도 있습니다.", "geomap_description": "커스터마이징이 가능한 마커로 휴가를 계획하거나 관심 지점을 지도에 직접 표시하세요. 기록된 GPX 트랙을 표시하여 여정을 추적할 수도 있습니다.",
"presentation_title": "프레젠테이션", "presentation_title": "프레젠테이션",
"presentation_description": "슬라이드에 정보를 정리하고, 매끄러운 전환 효과와 함께 전체 화면으로 발표하세요. 슬라이드를 PDF로 내보내 간편하게 공유할 수도 있습니다." "presentation_description": "슬라이드에 정보를 정리하고, 매끄러운 전환 효과와 함께 전체 화면으로 발표하세요. 슬라이드를 PDF로 내보내 간편하게 공유할 수도 있습니다."
},
"faq": {
"mobile_question": "모바일 앱이 있나요?",
"mobile_answer": "현재 공식적인 모바일 앱은 없습니다. 하지만, 서버 인스턴스를 가지고 있다면 웹 브라우저를 이용해 접근하거나 PWA로 설치할 수 있습니다. 안드로이드에는 (데스크탑 클라이언트처럼)오프라인에서도 작동하는 TriliumDroid라는 비공식 앱이 있습니다.",
"database_question": "어디에 데이터가 저장되나요?",
"server_question": "Trilium을 사용하기 위해 서버가 필요한가요?"
} }
} }

337
docs/README-bg.md vendored
View File

@@ -1,337 +0,0 @@
<div align="center">
<sup>Special thanks to:</sup><br />
<a href="https://go.warp.dev/Trilium" target="_blank">
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-03.png"><br />
Warp, built for coding with multiple AI agents<br />
</a>
<sup>Available for macOS, Linux and Windows</sup>
</div>
<hr />
# Trilium Notes
![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)
[![Translation
status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
<!-- translate:off -->
<!-- LANGUAGE SWITCHER -->
[Chinese (Simplified Han script)](./README-ZH_CN.md) | [Chinese (Traditional Han
script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md)
| [German](./README-de.md) | [Greek](./README-el.md) | [Italian](./README-it.md)
| [Japanese](./README-ja.md) | [Romanian](./README-ro.md) |
[Spanish](./README-es.md)
<!-- translate:on -->
Trilium Notes is a free and open-source, cross-platform hierarchical note taking
application with focus on building large personal knowledge bases.
<img src="./app.png" alt="Trilium Screenshot" width="1000">
## ⏬ 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.
## 📚 Documentation
**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/)
- **In-App Help**: Press `F1` within Trilium to access the same documentation
directly in the application
- **GitHub**: Navigate through the [User Guide](./User%20Guide/User%20Guide/) in
this repository
### Quick Links
- [Getting Started Guide](https://docs.triliumnotes.org/)
- [Installation Instructions](https://docs.triliumnotes.org/user-guide/setup)
- [Docker
Setup](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker)
- [Upgrading
TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading)
- [Basic Concepts and
Features](https://docs.triliumnotes.org/user-guide/concepts/notes)
- [Patterns of Personal Knowledge
Base](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge)
## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed
into multiple places in the tree (see
[cloning](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning))
* Rich WYSIWYG note editor including e.g. tables, images and
[math](https://docs.triliumnotes.org/user-guide/note-types/text) with markdown
[autoformat](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting)
* Support for editing [notes with source
code](https://docs.triliumnotes.org/user-guide/note-types/code), including
syntax highlighting
* Fast and easy [navigation between
notes](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation),
full text search and [note
hoisting](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting)
* Seamless [note
versioning](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions)
* Note
[attributes](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes)
can be used for note organization, querying and advanced
[scripting](https://docs.triliumnotes.org/user-guide/scripts)
* UI available in English, German, Spanish, French, Romanian, and Chinese
(simplified and traditional)
* Direct [OpenID and TOTP
integration](https://docs.triliumnotes.org/user-guide/setup/server/mfa) for
more secure login
* [Synchronization](https://docs.triliumnotes.org/user-guide/setup/synchronization)
with self-hosted sync server
* there are [3rd party services for hosting synchronisation
server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
* [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing)
(publishing) notes to public internet
* Strong [note
encryption](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes)
with per-note granularity
* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type
"canvas")
* [Relation
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
[note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map)
for visualizing notes and their relations
* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/)
* [Geo maps](https://docs.triliumnotes.org/user-guide/collections/geomap) with
location pins and GPX tracks
* [Scripting](https://docs.triliumnotes.org/user-guide/scripts) - see [Advanced
showcases](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases)
* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) for
automation
* Scales well in both usability and performance upwards of 100 000 notes
* Touch optimized [mobile
frontend](https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) for
smartphones and tablets
* Built-in [dark
theme](https://docs.triliumnotes.org/user-guide/concepts/themes), support for
user themes
* [Evernote](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote)
and [Markdown import &
export](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown)
* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) for
easy saving of web content
* Customizable UI (sidebar buttons, user-defined widgets, ...)
* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics),
along with a Grafana Dashboard.
✨ Check out the following third-party resources/communities for more TriliumNext
related goodies:
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party
themes, scripts, plugins and more.
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
## ❓Why TriliumNext?
The original Trilium developer ([Zadam](https://github.com/zadam)) has
graciously given the Trilium repository to the community project which resides
at https://github.com/TriliumNext
### ⬆Migrating from Zadam/Trilium?
There are no special migration steps to migrate from a zadam/Trilium instance to
a TriliumNext/Trilium instance. Simply [install
TriliumNext/Trilium](#-installation) as usual and it will use your existing
database.
Versions up to and including
[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are
compatible with the latest zadam/trilium version of
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later
versions of TriliumNext/Trilium have their sync versions incremented which
prevents direct migration.
## 💬 Discuss with us
Feel free to join our official conversations. We would love to hear what
features, suggestions, or issues you may have!
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous
discussions.)
- The `General` Matrix room is also bridged to
[XMPP](xmpp:discuss@trilium.thisgreat.party?join)
- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For
asynchronous discussions.)
- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug
reports and feature requests.)
## 🏗 Installation
### Windows / MacOS
Download the binary release for your platform from the [latest release
page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package
and run the `trilium` executable.
### Linux
If your distribution is listed in the table below, use your distribution's
package.
[![Packaging
status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions)
You may also download the binary release for your platform from the [latest
release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the
package and run the `trilium` executable.
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
### Browser (any OS)
If you use a server installation (see below), you can directly access the web
interface (which is almost identical to the desktop app).
Currently only the latest versions of Chrome & Firefox are supported (and
tested).
### Mobile
To use TriliumNext on a mobile device, you can use a mobile web browser to
access the mobile interface of a server installation (see below).
See issue https://github.com/TriliumNext/Trilium/issues/4962 for more
information on mobile app support.
If you prefer a native Android app, you can use
[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid).
Report bugs and missing features at [their
repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to
disable automatic updates on your server installation (see below) when using
TriliumDroid since the sync version must match between Trilium and TriliumDroid.
### Server
To install TriliumNext on your own server (including via Docker from
[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server
installation docs](https://docs.triliumnotes.org/user-guide/setup/server).
## 💻 Contribute
### Translations
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:
[![Translation
status](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/)
### Code
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
pnpm install
pnpm run server:start
```
### Documentation
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
pnpm install
pnpm edit-docs:edit-docs
```
### Building the Executable
Download the repository, install dependencies using `pnpm` and then build the
desktop app for Windows:
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
pnpm install
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
```
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)
for details. If you have more questions, feel free to reach out via the links
described in the "Discuss with us" section above.
## 👏 Shoutouts
* [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) 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 would not be possible without the technologies behind it:
* [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://docs.triliumnotes.org/user-guide/note-types/relation-map) and
[link
maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)
## 🤝 Support
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.
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)
- [Buy Me a Coffee](https://buymeacoffee.com/eliandoran)
## 🔑 License
Copyright 2017-2025 zadam, Elian Doran, and other contributors
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.

View File

@@ -69,7 +69,7 @@
"tslib": "2.8.1", "tslib": "2.8.1",
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "~5.9.0", "typescript": "~5.9.0",
"typescript-eslint": "8.50.1", "typescript-eslint": "8.50.0",
"upath": "2.0.1", "upath": "2.0.1",
"vite": "7.3.0", "vite": "7.3.0",
"vite-plugin-dts": "~4.5.0", "vite-plugin-dts": "~4.5.0",

View File

@@ -21,11 +21,11 @@
"ckeditor5-metadata.json" "ckeditor5-metadata.json"
], ],
"devDependencies": { "devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.2.3", "@ckeditor/ckeditor5-dev-build-tools": "54.2.2",
"@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1", "@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "~8.50.0", "@typescript-eslint/eslint-plugin": "~8.50.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.50.0",
"@vitest/browser": "4.0.16", "@vitest/browser": "4.0.16",
"@vitest/coverage-istanbul": "4.0.16", "@vitest/coverage-istanbul": "4.0.16",
"ckeditor5": "47.3.0", "ckeditor5": "47.3.0",

View File

@@ -22,11 +22,11 @@
"ckeditor5-metadata.json" "ckeditor5-metadata.json"
], ],
"devDependencies": { "devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.2.3", "@ckeditor/ckeditor5-dev-build-tools": "54.2.2",
"@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1", "@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "~8.50.0", "@typescript-eslint/eslint-plugin": "~8.50.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.50.0",
"@vitest/browser": "4.0.16", "@vitest/browser": "4.0.16",
"@vitest/coverage-istanbul": "4.0.16", "@vitest/coverage-istanbul": "4.0.16",
"ckeditor5": "47.3.0", "ckeditor5": "47.3.0",

View File

@@ -24,11 +24,11 @@
"ckeditor5-metadata.json" "ckeditor5-metadata.json"
], ],
"devDependencies": { "devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.2.3", "@ckeditor/ckeditor5-dev-build-tools": "54.2.2",
"@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1", "@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "~8.50.0", "@typescript-eslint/eslint-plugin": "~8.50.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.50.0",
"@vitest/browser": "4.0.16", "@vitest/browser": "4.0.16",
"@vitest/coverage-istanbul": "4.0.16", "@vitest/coverage-istanbul": "4.0.16",
"ckeditor5": "47.3.0", "ckeditor5": "47.3.0",

View File

@@ -24,11 +24,11 @@
"ckeditor5-metadata.json" "ckeditor5-metadata.json"
], ],
"devDependencies": { "devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.2.3", "@ckeditor/ckeditor5-dev-build-tools": "54.2.2",
"@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1", "@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "~8.50.0", "@typescript-eslint/eslint-plugin": "~8.50.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.50.0",
"@vitest/browser": "4.0.16", "@vitest/browser": "4.0.16",
"@vitest/coverage-istanbul": "4.0.16", "@vitest/coverage-istanbul": "4.0.16",
"ckeditor5": "47.3.0", "ckeditor5": "47.3.0",

View File

@@ -24,11 +24,11 @@
"ckeditor5-metadata.json" "ckeditor5-metadata.json"
], ],
"devDependencies": { "devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.2.3", "@ckeditor/ckeditor5-dev-build-tools": "54.2.2",
"@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1", "@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "~8.50.0", "@typescript-eslint/eslint-plugin": "~8.50.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.50.0",
"@vitest/browser": "4.0.16", "@vitest/browser": "4.0.16",
"@vitest/coverage-istanbul": "4.0.16", "@vitest/coverage-istanbul": "4.0.16",
"ckeditor5": "47.3.0", "ckeditor5": "47.3.0",

View File

@@ -16,7 +16,7 @@
"@codemirror/lang-xml": "6.1.0", "@codemirror/lang-xml": "6.1.0",
"@codemirror/legacy-modes": "6.5.2", "@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.5.11", "@codemirror/search": "6.5.11",
"@codemirror/view": "6.39.6", "@codemirror/view": "6.39.4",
"@fsegurai/codemirror-theme-abcdef": "6.2.3", "@fsegurai/codemirror-theme-abcdef": "6.2.3",
"@fsegurai/codemirror-theme-abyss": "6.2.3", "@fsegurai/codemirror-theme-abyss": "6.2.3",
"@fsegurai/codemirror-theme-android-studio": "6.2.3", "@fsegurai/codemirror-theme-android-studio": "6.2.3",

View File

@@ -32,8 +32,8 @@
"devDependencies": { "devDependencies": {
"@digitak/esrun": "3.2.26", "@digitak/esrun": "3.2.26",
"@triliumnext/ckeditor5": "workspace:*", "@triliumnext/ckeditor5": "workspace:*",
"@typescript-eslint/eslint-plugin": "8.50.1", "@typescript-eslint/eslint-plugin": "8.50.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.50.0",
"dotenv": "17.2.3", "dotenv": "17.2.3",
"esbuild": "0.27.2", "esbuild": "0.27.2",
"eslint": "9.39.2", "eslint": "9.39.2",

612
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff