Compare commits

..

2 Commits

Author SHA1 Message Date
Elian Doran
63c54010eb docs(guide): update html docs with sharing 2025-10-06 19:30:01 +03:00
Elian Doran
cec868459a Merge remote-tracking branch 'origin/main' into kev/share-html 2025-10-06 19:26:54 +03:00
90 changed files with 748 additions and 3857 deletions

View File

@@ -118,7 +118,6 @@ jobs:
- name: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages
if: github.repository == ${{ vars.REPO_MAIN }}
with:
project_name: "trilium-docs"
comment_body: "📚 Documentation preview is ready"

View File

@@ -11,9 +11,6 @@ on:
paths:
- "apps/website/**"
release:
types: [ released ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest

View File

@@ -167,7 +167,7 @@ Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blo
## 👏 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.
* [Larsa](https://github.com/LarsaSara) 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.

View File

@@ -35,7 +35,7 @@
"chore:generate-openapi": "tsx bin/generate-openapi.js"
},
"devDependencies": {
"@playwright/test": "1.56.0",
"@playwright/test": "1.55.1",
"@stylistic/eslint-plugin": "5.4.0",
"@types/express": "5.0.3",
"@types/node": "22.18.8",

View File

@@ -3,7 +3,16 @@ import linkContextMenuService from "../menus/link_context_menu.js";
import appContext, { type NoteCommandData } from "../components/app_context.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
// Be consistent with `allowedSchemes` in `src\services\html_sanitizer.ts`
// TODO: Deduplicate with server once we can.
export const ALLOWED_PROTOCOLS = [
'http', 'https', 'ftp', 'ftps', 'mailto', 'data', 'evernote', 'file', 'facetime', 'gemini', 'git',
'gopher', 'imap', 'irc', 'irc6', 'jabber', 'jar', 'lastfm', 'ldap', 'ldaps', 'magnet', 'message',
'mumble', 'nfs', 'onenote', 'pop', 'rmi', 's3', 'sftp', 'skype', 'sms', 'spotify', 'steam', 'svn', 'udp',
'view-source', 'vlc', 'vnc', 'ws', 'wss', 'xmpp', 'jdbc', 'slack', 'tel', 'smb', 'zotero', 'geo',
'mid'
];
function getNotePathFromUrl(url: string) {
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);

View File

@@ -1,395 +1,5 @@
{
"about": {
"title": "حول تريليوم للملاحظات",
"homepage": "الصفحة الرئيسية:",
"app_version": "نسخة التطبيق:",
"sync_version": "اصدار المزامنه:",
"build_date": "تاريخ الانشاء:",
"build_revision": "مراجعة الاصدار:",
"data_directory": "مجلد البيانات:",
"db_version": "اصدار قاعدة البيانات:"
},
"toast": {
"critical-error": {
"title": "خطأ فادح"
}
},
"add_link": {
"add_link": "أضافة رابط",
"note": "ملاحظة",
"search_note": "البحث عن الملاحظة بالاسم",
"link_title": "عنوان الرابط",
"button_add_link": "اضافة رابط"
},
"branch_prefix": {
"edit_branch_prefix": "تعديل بادئة الفرع",
"prefix": "البادئة: ",
"save": "حفظ"
},
"bulk_actions": {
"bulk_actions": "اجراءات جماعية",
"available_actions": "الاجراءات المتاحة",
"chosen_actions": "الأجراءات المختارة",
"execute_bulk_actions": "تنفيذ الأجراءات الجماعية",
"bulk_actions_executed": "تم تنفيذ الاجراءات الجماعية بنجاح،",
"none_yet": "لايوجد أجراء بعد... اضف اجراء بالنقر على احد الأجراءات المتاحة اعلاه.",
"relations": "العلاقات",
"notes": "الملاحظات",
"other": "أخرى",
"affected_notes": "الملاحظات المتأثرة"
},
"upload_attachments": {
"options": "خيارات",
"upload": "تحميل"
},
"attribute_detail": {
"name": "الاسم",
"value": "قيمة",
"promoted": "تمت ترقيته",
"promoted_alias": "اسم مستعار",
"label_type": "نوع",
"text": "نص",
"date": "تاريخ",
"time": "وقت",
"precision": "دفة",
"digits": "رقم",
"delete": "حذف",
"color_type": "لون",
"multiplicity": "تعددية",
"number": "عدد",
"boolean": "منطقي",
"url": "عنوان الويب",
"inheritable": "قابل للوراثة"
},
"rename_label": {
"to": "الى"
},
"move_note": {
"to": "الى"
},
"add_relation": {
"to": "الى"
},
"rename_relation": {
"to": "الى"
},
"update_relation_target": {
"to": "الى"
},
"attachments_actions": {
"download": "تنزيل"
},
"calendar": {
"week": "أسبوع",
"month": "شهر",
"year": "سنة",
"list": "قائمة",
"today": "اليوم",
"mon": "الأثنين",
"tue": "الثلاثاء",
"wed": "الأربعاء",
"thu": "الخميس",
"fri": "الجمعة",
"sat": "السبت",
"sun": "الأحد",
"january": "يناير",
"march": "مارس",
"april": "ابريل",
"may": "مايو",
"june": "يونيو",
"july": "يوليو",
"august": "أغسطس",
"september": "سبتمبر",
"october": "اكتوبر",
"november": "نوفمبر",
"december": "ديسمبر"
},
"global_menu": {
"menu": "القائمة",
"options": "خيارات",
"advanced": "متقدمة",
"logout": "تسجيل خروج",
"zoom": "تكبير/تصغير"
},
"zpetne_odkazy": {
"relation": "العلاقة"
},
"note_icon": {
"category": "الفئة:",
"search": "بحث:"
},
"basic_properties": {
"language": "اللغة",
"editable": "قابل للتعديل"
},
"book_properties": {
"list": "قائمة",
"expand": "توسيع",
"calendar": "التقويم",
"table": "جدول",
"board": "لوحة",
"grid": "خطوط شبكة",
"collapse": "طي"
},
"file_properties": {
"download": "تنزيل",
"open": "فتح",
"title": "ملف"
},
"image_properties": {
"download": "تنزيل",
"open": "فتح",
"title": "صورة"
},
"note_info_widget": {
"created": "انشاء",
"type": "نوع",
"modified": "معدل",
"calculate": "حساب"
},
"note_paths": {
"search": "بحث",
"archived": "مؤرشف"
},
"script_executor": {
"query": "استعلام",
"script": "برنامج نصي"
},
"search_definition": {
"ancestor": "السلف",
"limit": "الحد الاقصى",
"action": "أجراء",
"search_button": "بحث",
"debug": "تصحيح الاخطاء"
},
"ancestor": {
"label": "السلف",
"depth_label": "العمق"
},
"limit": {
"limit": "الحد الاقصى"
},
"debug": {
"debug": "تصحيح الاخطاء"
},
"order_by": {
"title": "عنوان",
"desc": "تنازلي"
},
"search_string": {
"search_prefix": "بحث:"
},
"sync": {
"title": "مزامنة"
},
"fonts": {
"fonts": "خطوط",
"size": "حجم",
"serif": "خط ومزخرف",
"monospace": "خط بعرض ثابت"
},
"confirm": {
"confirmation": "تأكيد",
"cancel": "الغاء",
"ok": "نعم"
},
"delete_notes": {
"close": "غلق",
"cancel": "الغاء",
"ok": "نعم"
},
"export": {
"close": "غلق",
"export": "تصدير",
"export_note_title": "تصدير الملاحظة",
"export_status": "حالة التصدير"
},
"help": {
"troubleshooting": "أستكشاف الاخطاء واصلاحها",
"other": "أخرى",
"title": "ورقة المراجعة السريعة",
"noteNavigation": "التنقل بين الملاحظات",
"collapseExpand": "طي/توسيع العقدة",
"notSet": "غير محدد",
"collapseSubTree": "طي الشجرة الفرعية",
"tabShortcuts": "أختصارات التبويب",
"creatingNotes": "انشاء الملاحظات",
"selectNote": "تحديد الملاحظة"
},
"import": {
"options": "خيارات",
"import": "استيراد"
},
"include_note": {
"label_note": "ملاحظة"
},
"info": {
"closeButton": "أغلاق",
"okButton": "نعم"
},
"markdown_import": {
"import_button": "أستيراد"
},
"note_type_chooser": {
"templates": "قوالب"
},
"prompt": {
"title": "ترقية",
"ok": "نعم",
"defaultTitle": "ترقية"
},
"protected_session_password": {
"close_label": "أغلاق"
},
"revisions": {
"delete_button": "حذف",
"download_button": "تنزيل",
"restore_button": "أستعادة",
"preview": "معاينة:"
},
"sort_child_notes": {
"title": "عنوان",
"ascending": "تصاعدي",
"descending": "تنازلي",
"folders": "مجلدات",
"sort": "فرز"
},
"recent_changes": {
"undelete_link": "الغاء الحذف"
},
"edited_notes": {
"deleted": "(حذف)"
},
"note_properties": {
"info": "معلومات"
},
"backend_log": {
"refresh": "تحديث"
},
"max_content_width": {
"max_width_unit": "بكسل"
},
"native_title_bar": {
"enabled": "مفعل",
"disabled": "معطل"
},
"theme": {
"theme_label": "السمة",
"layout": "تخطيط",
"layout-vertical-title": "عمودي",
"layout-horizontal-title": "أفقي"
},
"ui-performance": {
"title": "أداء"
},
"ai_llm": {
"progress": "تقدم",
"openai_tab": "OpenAI",
"actions": "أجراءات",
"retry": "أعد المحاولة",
"reprocessing_index": "جار اعادة البناء...",
"never": "ابدٱ",
"agent": {
"processing": "جار المعالجة...",
"thinking": "جار التفكير...",
"loading": "جار التحميل...",
"generating": "جار الانشاء..."
},
"name": "الذكاء الأصطناعي",
"openai": "OpenAI",
"sources": "مصادر"
},
"code_auto_read_only_size": {
"unit": "حروف"
},
"code-editor-options": {
"title": "محرر"
},
"images": {
"images_section_title": "صور",
"max_image_dimensions_unit": "بكسل"
},
"revisions_snapshot_limit": {
"snapshot_number_limit_unit": "لقطات"
},
"search_engine": {
"bing": "Bing",
"duckduckgo": "DuckDuckGo",
"google": "جوجل",
"save_button": "حفظ"
},
"heading_style": {
"plain": "بسيط"
},
"text_auto_read_only_size": {
"unit": "حروف"
},
"i18n": {
"language": "لغة",
"sunday": "الأحد",
"monday": "الأثنين"
},
"backup": {
"path": "مسار"
},
"etapi": {
"wiki": "ويكي",
"created": "تم الأنشاء",
"actions": "أجراءات"
},
"password": {
"heading": "كلمة السر",
"wiki": "ويكي"
},
"shortcuts": {
"shortcuts": "أختصارات",
"description": "الوصف"
},
"sync_2": {
"timeout_unit": "ميلي ثانية",
"note": "ملاحظة",
"save": "حفظ",
"help": "المساعدة"
},
"api_log": {
"close": "أغلاق"
},
"bookmark_switch": {
"bookmark": "علامة مرجعية"
},
"editability_select": {
"auto": "تلقائي",
"read_only": "قراءة-فقط"
},
"tab_row": {
"close": "اغلاق"
},
"toc": {
"options": "خيارات"
},
"tasks": {
"due": {
"yesterday": "أمس"
}
},
"code_theme": {
"title": "المظهر"
},
"table_view": {
"sort-column-ascending": "تصاعدي",
"sort-column-descending": "تنازلي",
"new-column-relation": "العلاقة"
},
"modal": {
"close": "اغلاق"
},
"call_to_action": {
"dismiss": "تجاهل"
},
"units": {
"percentage": "%"
},
"clone_to": {
"prefix_optional": "بادئة (اختياري)"
"title": "حول تريليوم للملاحظات"
}
}

View File

@@ -646,8 +646,7 @@
"about": "About Trilium Notes",
"logout": "Logout",
"show-cheatsheet": "Show Cheatsheet",
"toggle-zen-mode": "Zen Mode",
"update_available": "Version {{latestVersion}} is available, click to download."
"toggle-zen-mode": "Zen Mode"
},
"zen_mode": {
"button_exit": "Exit Zen Mode"

View File

@@ -1,8 +0,0 @@
{
"about": {
"title": "Tentang Trilium Notes",
"homepage": "Halaman utama:",
"app_version": "Versi Aplikasi:",
"db_version": "Versi DB:"
}
}

View File

@@ -67,8 +67,7 @@
"switch_to_mobile_version": "モバイル版に切り替え",
"switch_to_desktop_version": "デスクトップ版に切り替え",
"configure_launchbar": "ランチャーバーの設定",
"show_shared_notes_subtree": "共有ノートのサブツリーを表示",
"update_available": "バージョン {{latestVersion}} が利用可能です。クリックしてダウンロードしてください。"
"show_shared_notes_subtree": "共有ノートのサブツリーを表示"
},
"left_pane_toggle": {
"show_panel": "パネルを表示",
@@ -252,12 +251,12 @@
"help": {
"title": "チートシート",
"noteNavigation": "ノートナビゲーション",
"collapseExpand": "ノードを折りたたむ / 展開",
"collapseExpand": "ノードの格納/展開",
"goBackForwards": "履歴を戻る/進む",
"scrollToActiveNote": "アクティブノートまでスクロール",
"jumpToParentNote": "親ノートへ移動",
"collapseWholeTree": "すべてのノートツリーを折りたたむ",
"collapseSubTree": "サブツリーを折りたたむ",
"collapseWholeTree": "すべてのノートツリーを格納",
"collapseSubTree": "サブツリーを格納",
"tabShortcuts": "タブショートカット",
"newTabNoteLink": "ノートのリンクをクリックすると、新しいタブで開く",
"newTabWithActivationNoteLink": "ノートのリンクをクリックすると、新しいタブで開き、アクティブにします",
@@ -516,9 +515,9 @@
"book_properties": {
"grid": "グリッド",
"list": "リスト",
"collapse_all_notes": "すべてのノートを折りたたむ",
"collapse_all_notes": "すべてのノートを格納",
"expand_all_children": "すべての子を展開",
"collapse": "折りたたむ",
"collapse": "格納",
"expand": "展開",
"book_properties": "コレクションプロパティ",
"invalid_view_type": "無効なビュータイプ '{{type}}'",
@@ -1291,7 +1290,7 @@
"cut": "カット",
"copy": "コピー",
"copy-link": "リンクをコピー",
"paste": "貼り付け",
"paste": "ペースト",
"paste-as-plain-text": "プレーンテキストで貼り付け",
"search_online": "{{searchEngine}} で \"{{term}}\" を検索"
},

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
<KeyboardActionMenuItem command="showHelp" icon="bx bx-help-circle" text={t("global_menu.show_help")} />
<KeyboardActionMenuItem command="showCheatsheet" icon="bx bxs-keyboard" text={t("global_menu.show-cheatsheet")} />
<MenuItem command="openAboutDialog" icon="bx bx-info-circle" text={t("global_menu.about")} />
{isUpdateAvailable && <MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")} icon="bx bx-sync" text={t("global_menu.update_available", { latestVersion })} /> }
{isUpdateAvailable && <MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")} icon="bx bx-sync" text={`Version ${latestVersion} is available, click to download.`} /> }
{!isElectron() && <BrowserOnlyOptions />}
</Dropdown>
)

View File

@@ -23,6 +23,8 @@
flex-shrink: 0;
border: 2px solid transparent;
border-radius: 8px;
padding: 0.5em;
padding-bottom: 0;
background-color: var(--accented-background-color);
transition: border-color 0.2s ease;
overflow-y: auto;
@@ -102,7 +104,7 @@
.board-view-container .board-column > .board-column-content {
flex-grow: 1;
overflow: scroll;
padding: 0.5em;
padding-bottom: 0.5em;
}
.board-view-container .board-column h3:hover .edit-icon,
@@ -128,10 +130,6 @@
font-size: var(--card-font-size);
}
.board-view-container .board-note:first-of-type {
margin-top: 0;
}
.board-view-container :focus {
outline: 3px solid var(--input-focus-outline-color);
outline-offset: 0;

View File

@@ -71,7 +71,6 @@ export const LOCALE_MAPPINGS: Record<LOCALE_IDS, (() => Promise<{ default: Local
ro: () => import("@fullcalendar/core/locales/ro"),
ru: () => import("@fullcalendar/core/locales/ru"),
ja: () => import("@fullcalendar/core/locales/ja"),
pt: () => import("@fullcalendar/core/locales/pt"),
"pt_br": () => import("@fullcalendar/core/locales/pt-br"),
uk: () => import("@fullcalendar/core/locales/uk"),
en: null

View File

@@ -1,4 +1,5 @@
import { ALLOWED_PROTOCOLS, MIME_TYPE_AUTO } from "@triliumnext/commons";
import { ALLOWED_PROTOCOLS } from "../../../services/link.js";
import { MIME_TYPE_AUTO } from "@triliumnext/commons";
import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS } from "@triliumnext/ckeditor5";
import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
import options from "../../../services/options.js";

View File

@@ -90,10 +90,6 @@ const config: ForgeConfig = {
base: "org.electronjs.Electron2.BaseApp",
baseVersion: "24.08",
baseFlatpakref: "https://flathub.org/repo/flathub.flatpakrepo",
finishArgs: [
"--socket=fallback-x11",
"--socket=wayland"
],
modules: [
{
name: "zypak",

View File

@@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.99.1",
"appVersion": "0.98.1",
"files": [
{
"isClone": false,
@@ -60,13 +60,6 @@
"value": "dayGridMonth",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "dateTemplate",
"value": "bRQvb9VCkc3t",
"isInheritable": false,
"position": 50
}
],
"dataFileName": "Journal.dat",
@@ -82,7 +75,7 @@
"title": "Trilium Demo",
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"isExpanded": true,
"type": "text",
"mime": "text/html",
"attributes": [
@@ -6040,68 +6033,6 @@
]
}
]
},
{
"isClone": false,
"noteId": "fhNlr1V1o3d8",
"notePath": [
"root",
"fhNlr1V1o3d8"
],
"title": "Miscellaneous",
"notePosition": 30,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bx-dots-horizontal-rounded",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"attachments": [],
"dirFileName": "Miscellaneous",
"children": [
{
"isClone": false,
"noteId": "bRQvb9VCkc3t",
"notePath": [
"root",
"fhNlr1V1o3d8",
"bRQvb9VCkc3t"
],
"title": "Day Note Template",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bx-notepad",
"isInheritable": false,
"position": 10
},
{
"type": "label",
"name": "excludeFromNoteMap",
"value": "",
"isInheritable": false,
"position": 20
}
],
"format": "html",
"dataFileName": "Day Note Template.html",
"attachments": []
}
]
}
]
},

View File

@@ -637,12 +637,6 @@
</li>
</ul>
</li>
<li>Miscellaneous
<ul>
<li><a href="root/Miscellaneous/Day%20Note%20Template.html" target="detail">Day Note Template</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>

View File

@@ -1,24 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Day Note Template</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Day Note Template</h1>
<div class="ck-content">
<h2>☑️ Tasks</h2>
<ul>
<li data-list-item-id="e4b26220d6ce48997f1116dc1d1d83dc0">[…]</li>
</ul>
</div>
</div>
</body>
</html>

View File

@@ -23,18 +23,10 @@
alert("Hello world");
}</code></pre>
<p>For larger pieces of code it is better to use a code note, which uses
a fully-fledged code editor (CodeMirror). For an example of a code note,

View File

@@ -18,10 +18,6 @@
# This script opens 4 terminal windows.
@@ -30,38 +26,18 @@
i="0"
while [ $i -lt 4 ]
do
xterm &amp;
@@ -70,22 +46,10 @@ do
i=$[$i+1]
done</code></pre>
</div>
</div>

View File

@@ -110,7 +110,7 @@
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.0",
"openai": "6.2.0",
"openai": "6.1.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",

Binary file not shown.

View File

@@ -1,151 +1 @@
{
"keyboard_actions": {
"open-jump-to-note-dialog": "فتح مربع الحوار \"الانتقال الى الملاحظة\"",
"open-command-palette": "فتح لوحة الاوامر",
"quick-search": "تفعيل شريط البحث السريع",
"collapse-tree": "طي جميع الملاحظات",
"collapse-subtree": "طي الفروع التابعة للملاحظة الحالية",
"sort-child-notes": "فرز الملاحظات الفرعية",
"creating-and-moving-notes": "انشاء الملاحظات ونقلها",
"create-note-after": "انشاء ملاحظة بعد الملاحظة الحالية",
"create-note-into": "انشاء ملاحظة فرعية تابعة للملاحظة الحالية",
"delete-note": "حذف ملاحظة",
"move-note-up": "نقل الملاحظة للاعلى",
"move-note-down": "نثل الملاحظة للاسفل",
"clone-notes-to": "استنساخ الملاحظات المحددة",
"move-notes-to": "نقل الملاحظات للمحددة",
"note-clipboard": "حافظة الملاحظات",
"copy-notes-to-clipboard": "نسخ الملاحظات المحددة الى الحافظة",
"paste-notes-from-clipboard": "لصق الملاحظا من الحافظة الى الملاحظة الحالية",
"cut-notes-to-clipboard": "قص الملاحظات المحددة الى الحافظة",
"select-all-notes-in-parent": "تحديد جميع الملاحظات من مستوى الملاحظة الحالي",
"back-in-note-history": "الانتقال الى الملاحظة السابقة في السجل",
"forward-in-note-history": "الانتقال الى الملاحظة التالية في السجل",
"scroll-to-active-note": "تمرير شجرة الملاحظات الى الملاحظة النشطة",
"search-in-subtree": "البحث عن الملاحظات في الشجرة الفرعية للملاحظة النشطة",
"expand-subtree": "توسيع الشجرة الفرعية للملاحظة الحالية",
"create-note-into-inbox": "انشاء ملاحظة في صندوق الوارد (اذا كان معرفا) او في ملاحظة اليوم",
"move-note-up-in-hierarchy": "نقل الملاحظة للاعلى في التسلسل الهرمي",
"move-note-down-in-hierarchy": "نقل الملاحظة للاسفل في التسلسل الهرمي",
"edit-note-title": "الانتقال من شجرة الملاحظات إلى تفاصيل الملاحظة وتحرير العنوان",
"edit-branch-prefix": "عرض مربع حوار \"تعديل بادئة الفرع\"",
"add-note-above-to-the-selection": "اضافة ملاحظة فوق الملاحظة المحددة",
"add-note-below-to-selection": "اضافة ملاحظة اسفل الملاحظة المحددة",
"duplicate-subtree": "استنساخ الشجرة الفرعية",
"tabs-and-windows": "التبويبات والنوافذ",
"open-new-tab": "فتح تبويب جديد",
"close-active-tab": "غلق التبويب النشط",
"reopen-last-tab": "اعادة فتح اخر تبويب مغلق",
"activate-next-tab": "تنشيط التبويب الموجود على اليمين",
"activate-previous-tab": "تنشيط التبويب الموجود على اليسار",
"open-new-window": "فتح نافذة جديدة فارغة",
"first-tab": "تنشيط التبويب الاول في القائمة",
"second-tab": "تنشيط التبويب الثاني في القائمة",
"third-tab": "تنشيط التبويب الثالث في الثائمة",
"fourth-tab": "تنشيط التبويب الرابع في القائمة",
"fifth-tab": "تنشيط التبويب الخامس في القائمة",
"sixth-tab": "تنشيط التبويب السادس في القائمة",
"seventh-tab": "تنشيط التبويب السابع في القائمة",
"eight-tab": "تنشيط التبويب الثامن في القائمة",
"ninth-tab": "تنشيط التبويب التاسع في القائمة",
"last-tab": "تنشيط التبويب الاخير في القائمة",
"other": "أخرى",
"dialogs": "مربعات الحوار"
},
"setup_sync-from-server": {
"note": "ملاحظة:",
"password": "كلمة السر",
"password-placeholder": "كلمة السر",
"back": "رجوع",
"server-host-placeholder": "https://<hostname>:<port>",
"proxy-server-placeholder": "https://<hostname>:<port>"
},
"weekdays": {
"monday": "الأثنين",
"tuesday": "الثلاثاء",
"wednesday": "الاربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت",
"sunday": "الأحد"
},
"months": {
"january": "يناير",
"february": "فبراير",
"march": "مارس",
"april": "ابريل",
"may": "مايو",
"june": "يونيو",
"july": "يوليو",
"august": "أغسطس",
"september": "سبتمبر",
"october": "أكتوبر",
"november": "نوفمبر",
"december": "ديسمبر"
},
"special_notes": {
"search_prefix": "بحث:"
},
"hidden-subtree": {
"calendar-title": "تقويم",
"bookmarks-title": "العلامات المرجعية",
"settings-title": "أعدادات",
"options-title": "خيارات",
"appearance-title": "المظهر",
"shortcuts-title": "أختصارات",
"images-title": "صور",
"password-title": "كلمة السر",
"backup-title": "نسخة أحتياطية",
"sync-title": "مزامنة",
"other": "أخرى",
"advanced-title": "متقدم",
"inbox-title": "صندوق الوارد",
"spacer-title": "فاصل",
"spellcheck-title": "التدقيق الاملائي",
"multi-factor-authentication-title": "المصادقة متعددة العوامل"
},
"tray": {
"bookmarks": "العلامات المرجعية"
},
"modals": {
"error_title": "خطأ"
},
"share_theme": {
"search_placeholder": "بحث...",
"subpages": "الصفحات الفرعية:",
"expand": "توسيع"
},
"hidden_subtree_templates": {
"description": "الوصف",
"calendar": "التقويم",
"table": "جدول",
"geolocation": "الموقع الجغرافي",
"board": "لوحة",
"status": "الحالة",
"board_status_done": "تمت"
},
"login": {
"title": "تسجيل الدخول",
"password": "كلمة السر",
"button": "تسجيل الدخول"
},
"set_password": {
"password": "كلمة السر"
},
"setup": {
"next": "التالي",
"title": "تثبيت"
},
"setup_sync-from-desktop": {
"step6-here": "هنا"
},
"setup_sync-in-progress": {
"outstanding-items-default": "غير متوفر"
},
"share_page": {
"parent": "الأصل:"
},
"notes": {
"duplicate-note-suffix": "(مكرر)"
}
}
{}

View File

@@ -1,8 +0,0 @@
{
"keyboard_actions": {
"back-in-note-history": "Navigasi ke catatan sebelumnya di history",
"forward-in-note-history": "Navigasi ke catatan selanjutnya di history",
"open-jump-to-note-dialog": "Buka dialog \"Menuju ke catatan\"",
"open-command-palette": "Buka palet perintah"
}
}

View File

@@ -1,26 +1,26 @@
{
"keyboard_actions": {
"back-in-note-history": "Navegar para a nota anterior no histórico",
"forward-in-note-history": "Navegar para a nota seguinte no histórico",
"forward-in-note-history": "Navegar para a próxima nota no histórico",
"open-jump-to-note-dialog": "Abrir diálogo \"Ir para nota\"",
"open-command-palette": "Abrir paleta de comandos",
"scroll-to-active-note": "Rolar a árvore de notas até a nota atual",
"quick-search": "Ativar barra de pesquisa rápida",
"search-in-subtree": "Pesquisar notas na sub-árvore da nota atual",
"expand-subtree": "Expandir sub-árvore da nota atual",
"collapse-tree": "Colapsar a árvore de notas completa",
"collapse-subtree": "Colapsar sub-árvore da nota atual",
"search-in-subtree": "Pesquisar notas na subárvore da nota atual",
"expand-subtree": "Expandir subárvore da nota atual",
"collapse-tree": "Colapsar a árvore completa de notas",
"collapse-subtree": "Colapsar subárvore da nota atual",
"sort-child-notes": "Ordenar notas filhas",
"creating-and-moving-notes": "A criar e mover notas",
"create-note-after": "Criar nota após nota atual",
"create-note-into": "Criar nota como sub-nota da nota atual",
"create-note-into-inbox": "Criar uma nota na caixa de entrada (se definida) ou na nota do dia",
"create-note-into": "Criar nota como subnota da nota atual",
"create-note-into-inbox": "Crie uma nota na caixa de entrada (se definida) ou na nota do dia",
"delete-note": "Apagar nota",
"move-note-up": "Mover nota para cima",
"move-note-down": "Mover nota para baixo",
"move-note-up-in-hierarchy": "Mover nota para cima na hierarquia",
"move-note-down-in-hierarchy": "Mover nota para baixo na hierarquia",
"edit-note-title": "Saltar da árvore para os pormenores da nota e editar o título",
"edit-note-title": "Pular da árvore para os pormenores da nota e editar o título",
"edit-branch-prefix": "Exibir o diálogo \"Editar prefixo da ramificação\"",
"clone-notes-to": "Clonar notas selecionadas",
"move-notes-to": "Mover notas selecionadas",
@@ -31,36 +31,36 @@
"select-all-notes-in-parent": "Selecionar todas as notas do nível atual da nota",
"add-note-above-to-the-selection": "Adicionar nota acima à seleção",
"add-note-below-to-selection": "Adicionar nota abaixo à seleção",
"duplicate-subtree": "Duplicar subárvore",
"tabs-and-windows": "Separadores & Janelas",
"open-new-tab": "Abre novo separador",
"close-active-tab": "Fechar separador ativo",
"reopen-last-tab": "Reabre o último separador fechado",
"activate-next-tab": "Ativa separador à direita",
"activate-previous-tab": "Ativa separador à esquerda",
"duplicate-subtree": "Duplicar subárvores",
"tabs-and-windows": "Guias & Janelas",
"open-new-tab": "Abre nova guia",
"close-active-tab": "Fecha guia ativa",
"reopen-last-tab": "Reabre a última guia fechada",
"activate-next-tab": "Ativa guia à direita",
"activate-previous-tab": "Ativa guia à esquerda",
"open-new-window": "Abre nova janela vazia",
"toggle-tray": "Mostrar/ocultar a aplicação na bandeja do sistema",
"first-tab": "Ativar o primeiro separador na lista",
"second-tab": "Ativa o segundo separador na lista",
"third-tab": "Ativar o terceiro separador na lista",
"fourth-tab": "Ativar o quarto separador na lista",
"fifth-tab": "Ativar o quinto separador na lista",
"sixth-tab": "Ativar o sexto separador na lista",
"seventh-tab": "Ativar o sétimo separador na lista",
"eight-tab": "Ativar o oitavo separador na lista",
"ninth-tab": "Ativar o novo separador na lista",
"last-tab": "Ativar o último separador na lista",
"toggle-tray": "Mostrar/ocultar a aplicação da bandeja do sistema",
"first-tab": "Ativa a primeira guia na lista",
"second-tab": "Ativa a segunda guia na lista",
"third-tab": "Ativa a terceira guia na lista",
"fourth-tab": "Ativa a quarta guia na lista",
"fifth-tab": "Ativa a quinta guia na lista",
"sixth-tab": "Ativa a sexta guia na lista",
"seventh-tab": "Ativa a sétima guia na lista",
"eight-tab": "Ativa a oitava guia na lista",
"ninth-tab": "Ativa a nona guia na lista",
"last-tab": "Ativa a última guia na lista",
"dialogs": "Diálogos",
"show-note-source": "Exibe o diálogo \"origem da nota\"",
"show-options": "Abrir página de configurações",
"show-revisions": "Exibe diálogo \"revisões de nota\"",
"show-recent-changes": "Exibe o diálogo \"alterações recentes\"",
"show-sql-console": "Exibe a página \"consola SQL\"",
"show-backend-log": "Exibe a página \"registo do backend\"",
"show-help": "Exibir o guia de utilizador integrado",
"show-cheatsheet": "Exibir um modal com atalhos de teclado",
"show-note-source": "Exibe o diálogo de origem da nota",
"show-options": "Mostrar página de configurações",
"show-revisions": "Exibe diálogo de revisões de nota",
"show-recent-changes": "Exibe o diálogo de alterações recentes",
"show-sql-console": "Exibe a página do console SQL",
"show-backend-log": "Exibe a página do backend",
"show-help": "Exibir Ajuda integrada / colinha",
"show-cheatsheet": "Exibir um modal com operações comuns de teclado",
"text-note-operations": "Operações de nota de texto",
"add-link-to-text": "Abrir diálogo para adicionar ligação ao texto",
"add-link-to-text": "Abrir diálogo e adicionar ligação ao texto",
"follow-link-under-cursor": "Seguir a ligação sob o cursor",
"insert-date-and-time-to-text": "Inserir data e hora atual no texto",
"paste-markdown-into-text": "Colar Markdown da área de transferência na nota de texto",

View File

@@ -1,5 +1,4 @@
import { BNote } from "../../services/backend_script_entrypoint";
import cls from "../../services/cls";
import { buildNote } from "../../test/becca_easy_mocking";
import { processContent } from "./clipper";
@@ -7,9 +6,7 @@ let note!: BNote;
describe("processContent", () => {
beforeAll(() => {
note = buildNote({
content: "Hi there"
});
note = buildNote({});
note.saveAttachment = () => {};
vi.mock("../../services/image.js", () => ({
default: {
@@ -24,29 +21,29 @@ describe("processContent", () => {
});
it("processes basic note", () => {
const processed = cls.init(() => processContent([], note, "<p>Hello world.</p>"));
const processed = processContent([], note, "<p>Hello world.</p>");
expect(processed).toStrictEqual("<p>Hello world.</p>")
});
it("processes plain text", () => {
const processed = cls.init(() => processContent([], note, "Hello world."));
const processed = processContent([], note, "Hello world.");
expect(processed).toStrictEqual("<p>Hello world.</p>")
});
it("replaces images", () => {
const processed = cls.init(() => processContent(
const processed = processContent(
[{"imageId":"OKZxZA3MonZJkwFcEhId","src":"inline.png","dataUrl":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAF0lEQVQoU2P8DwQMBADjqKLRIGAgKggAzHs/0SoYCGwAAAAASUVORK5CYII="}],
note, `<img src="OKZxZA3MonZJkwFcEhId">`
));
);
expect(processed).toStrictEqual(`<img src="api/attachments/foo/image/encodedTitle" >`);
});
it("skips over non-data images", () => {
for (const url of [ "foo", "" ]) {
const processed = cls.init(() => processContent(
const processed = processContent(
[{"imageId":"OKZxZA3MonZJkwFcEhId","src":"inline.png","dataUrl": url}],
note, `<img src="OKZxZA3MonZJkwFcEhId">`
));
);
expect(processed).toStrictEqual(`<img src="OKZxZA3MonZJkwFcEhId" >`);
}
});

View File

@@ -3,7 +3,7 @@ import { beforeAll, describe, expect, it, vi, beforeEach, afterEach } from "vite
import supertest from "supertest";
import config from "../../services/config.js";
import { refreshAuth } from "../../services/auth.js";
import { sleepFor } from "@triliumnext/commons";
import type { WebSocket } from 'ws';
// Mock the CSRF protection middleware to allow tests to pass
vi.mock("../csrf_protection.js", () => ({
@@ -16,8 +16,7 @@ vi.mock("../../services/ws.js", () => ({
default: {
sendMessageToAllClients: vi.fn(),
sendTransactionEntityChangesToAllClients: vi.fn(),
setLastSyncedPush: vi.fn(),
syncFailed() {}
setLastSyncedPush: vi.fn()
}
}));
@@ -72,11 +71,7 @@ vi.mock("../../services/options.js", () => ({
getOptionMap: vi.fn(() => new Map()),
createOption: vi.fn(),
getOption: vi.fn(() => '0'),
getOptionOrNull: vi.fn(() => null),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionOrNull: vi.fn(() => null)
}
}));
@@ -86,7 +81,7 @@ async function loginWithSession(app: Application) {
.post("/login")
.send({ password: "demo1234" })
.expect(302);
const setCookieHeader = response.headers["set-cookie"][0];
expect(setCookieHeader).toBeTruthy();
return setCookieHeader;
@@ -96,14 +91,14 @@ async function loginWithSession(app: Application) {
async function getCsrfToken(app: Application, sessionCookie: string) {
const response = await supertest(app)
.get("/")
.expect(200);
const csrfTokenMatch = response.text.match(/csrfToken: '([^']+)'/);
if (csrfTokenMatch) {
return csrfTokenMatch[1];
}
throw new Error("CSRF token not found in response");
}
@@ -159,7 +154,7 @@ describe("LLM API Tests", () => {
expect(response.body).toHaveProperty('sessions');
expect(Array.isArray(response.body.sessions)).toBe(true);
if (response.body.sessions.length > 0) {
expect(response.body.sessions[0]).toMatchObject({
id: expect.any(String),
@@ -176,18 +171,18 @@ describe("LLM API Tests", () => {
// Create a chat first if we don't have one
const createResponse = await supertest(app)
.post("/api/llm/chat")
.send({
title: "Test Retrieval Chat"
})
.expect(200);
createdChatId = createResponse.body.id;
}
const response = await supertest(app)
.get(`/api/llm/chat/${createdChatId}`)
.expect(200);
expect(response.body).toMatchObject({
@@ -207,7 +202,7 @@ describe("LLM API Tests", () => {
title: "Test Update Chat"
})
.expect(200);
createdChatId = createResponse.body.id;
}
@@ -229,7 +224,7 @@ describe("LLM API Tests", () => {
it("should return 404 for non-existent chat session", async () => {
await supertest(app)
.get("/api/llm/chat/nonexistent-chat-id")
.expect(404);
});
});
@@ -245,7 +240,7 @@ describe("LLM API Tests", () => {
title: "Message Test Chat"
})
.expect(200);
testChatId = createResponse.body.id;
});
@@ -265,10 +260,10 @@ describe("LLM API Tests", () => {
// The response depends on whether AI is actually configured
// We should get either a successful response or an error about AI not being configured
expect([200, 400, 500]).toContain(response.status);
// All responses should have some body
expect(response.body).toBeDefined();
// Either success with response or error
if (response.body.response) {
expect(response.body).toMatchObject({
@@ -315,10 +310,10 @@ describe("LLM API Tests", () => {
beforeEach(async () => {
// Reset all mocks
vi.clearAllMocks();
// Import options service to access mock
const options = (await import("../../services/options.js")).default;
// Setup default mock behaviors
(options.getOptionBool as any).mockReturnValue(true); // AI enabled
mockAiServiceManager.getOrCreateAnyService.mockResolvedValue({});
@@ -326,7 +321,7 @@ describe("LLM API Tests", () => {
model: 'test-model',
provider: 'test-provider'
});
// Create a fresh chat for each test
const mockChat = {
id: 'streaming-test-chat',
@@ -336,15 +331,15 @@ describe("LLM API Tests", () => {
};
mockChatStorage.createChat.mockResolvedValue(mockChat);
mockChatStorage.getChat.mockResolvedValue(mockChat);
const createResponse = await supertest(app)
.post("/api/llm/chat")
.send({
title: "Streaming Test Chat"
})
.expect(200);
testChatId = createResponse.body.id;
});
@@ -363,7 +358,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Tell me a short story",
useAdvancedContext: false,
@@ -377,17 +372,17 @@ describe("LLM API Tests", () => {
success: true,
message: "Streaming initiated successfully"
});
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify WebSocket messages were sent
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
thinking: undefined
});
// Verify streaming chunks were sent
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -395,7 +390,7 @@ describe("LLM API Tests", () => {
content: 'Hello',
done: false
});
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -407,7 +402,7 @@ describe("LLM API Tests", () => {
it("should handle empty content for streaming", async () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "",
useAdvancedContext: false,
@@ -424,7 +419,7 @@ describe("LLM API Tests", () => {
it("should handle whitespace-only content for streaming", async () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: " \n\t ",
useAdvancedContext: false,
@@ -441,7 +436,7 @@ describe("LLM API Tests", () => {
it("should handle invalid chat ID for streaming", async () => {
const response = await supertest(app)
.post("/api/llm/chat/invalid-chat-id/messages/stream")
.send({
content: "Hello",
useAdvancedContext: false,
@@ -472,7 +467,7 @@ describe("LLM API Tests", () => {
// Verify mention content is included
expect(input.query).toContain('Tell me about this note');
expect(input.query).toContain('Root note content for testing');
const callback = input.streamCallback;
await callback('The root note contains', false, {});
await callback(' important information.', true, {});
@@ -480,7 +475,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Tell me about this note",
useAdvancedContext: true,
@@ -498,12 +493,11 @@ describe("LLM API Tests", () => {
success: true,
message: "Streaming initiated successfully"
});
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify thinking message was sent
await sleepFor(1_000);
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -523,7 +517,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "What is the meaning of life?",
useAdvancedContext: false,
@@ -531,10 +525,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify thinking messages
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -542,7 +536,7 @@ describe("LLM API Tests", () => {
thinking: 'Analyzing the question...',
done: false
});
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -570,7 +564,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "What is 2 + 2?",
useAdvancedContext: false,
@@ -578,10 +572,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify tool execution message
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -603,7 +597,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "This will fail",
useAdvancedContext: false,
@@ -611,10 +605,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200); // Still returns 200
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify error message was sent via WebSocket
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -631,7 +625,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Hello AI",
useAdvancedContext: false,
@@ -639,10 +633,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify error message about AI being disabled
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -661,7 +655,7 @@ describe("LLM API Tests", () => {
await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Save this response",
useAdvancedContext: false,
@@ -686,10 +680,10 @@ describe("LLM API Tests", () => {
});
// Send multiple requests rapidly
const promises = Array.from({ length: 3 }, (_, i) =>
const promises = Array.from({ length: 3 }, (_, i) =>
supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: `Request ${i + 1}`,
useAdvancedContext: false,
@@ -698,7 +692,7 @@ describe("LLM API Tests", () => {
);
const responses = await Promise.all(promises);
// All should succeed
responses.forEach(response => {
expect(response.status).toBe(200);
@@ -722,7 +716,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Generate large response",
useAdvancedContext: false,
@@ -730,10 +724,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify multiple chunks were sent
const streamCalls = (ws.sendMessageToAllClients as any).mock.calls.filter(
call => call[0].type === 'llm-stream' && call[0].content
@@ -747,7 +741,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post("/api/llm/chat")
.set('Content-Type', 'application/json')
.send('{ invalid json }');
expect([400, 500]).toContain(response.status);
@@ -756,7 +750,7 @@ describe("LLM API Tests", () => {
it("should handle missing required fields", async () => {
const response = await supertest(app)
.post("/api/llm/chat")
.send({
// Missing required fields
});
@@ -768,7 +762,7 @@ describe("LLM API Tests", () => {
it("should handle invalid parameter types", async () => {
const response = await supertest(app)
.post("/api/llm/chat")
.send({
title: "Test Chat",
temperature: "invalid", // Should be number
@@ -792,4 +786,4 @@ describe("LLM API Tests", () => {
}
}
});
});
});

View File

@@ -9,13 +9,13 @@ import { changeLanguage } from "./i18n.js";
import { deferred } from "./utils.js";
describe("Hidden Subtree", () => {
beforeAll(async () => {
sql_init.initializeDb();
await sql_init.dbReady;
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
});
describe("Launcher movement persistence", () => {
beforeAll(async () => {
sql_init.initializeDb();
await sql_init.dbReady;
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
});
it("should persist launcher movement between visible and available after integrity check", () => {
// Move backend log to visible launchers.
const backendLogBranch = becca.getBranchFromChildAndParent("_lbBackendLog", "_lbAvailableLaunchers");
@@ -119,14 +119,4 @@ describe("Hidden Subtree", () => {
await done;
});
});
describe("Hidden subtree", () => {
it("cleans up exclude from note map at the root", async () => {
const hiddenSubtree = becca.getNoteOrThrow("_hidden");
cls.init(() => hiddenSubtree.addLabel("excludeFromNoteMap"));
expect(hiddenSubtree.hasLabel("excludeFromNoteMap")).toBeTruthy();
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
expect(hiddenSubtree.hasLabel("excludeFromNoteMap")).toBeFalsy();
});
});
});

View File

@@ -40,8 +40,8 @@ function buildHiddenSubtreeDefinition(helpSubtree: HiddenSubtreeItem[]): HiddenS
// we want to keep the hidden subtree always last, otherwise there will be problems with e.g., keyboard navigation
// over tree when it's in the middle
notePosition: 999_999_999,
enforceAttributes: true,
attributes: [
{ type: "label", name: "excludeFromNoteMap", isInheritable: true },
{ type: "label", name: "docName", value: "hidden" }
],
children: [
@@ -441,15 +441,6 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree
}
}
// Enforce attribute structure if needed.
if (item.enforceAttributes) {
for (const attribute of note.getAttributes()) {
if (!attrs.some(a => a.name === attribute.name)) {
attribute.markAsDeleted();
}
}
}
for (const attr of attrs) {
const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name;

View File

@@ -1,7 +1,17 @@
import sanitizeHtml from "sanitize-html";
import { sanitizeUrl } from "@braintree/sanitize-url";
import optionService from "./options.js";
import { ALLOWED_PROTOCOLS, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
// Be consistent with `ALLOWED_PROTOCOLS` in `src\public\app\services\link.js`
// TODO: Deduplicate with client once we can.
export const ALLOWED_PROTOCOLS = [
'http', 'https', 'ftp', 'ftps', 'mailto', 'data', 'evernote', 'file', 'facetime', 'gemini', 'git',
'gopher', 'imap', 'irc', 'irc6', 'jabber', 'jar', 'lastfm', 'ldap', 'ldaps', 'magnet', 'message',
'mumble', 'nfs', 'onenote', 'pop', 'rmi', 's3', 'sftp', 'skype', 'sms', 'spotify', 'steam', 'svn', 'udp',
'view-source', 'vlc', 'vnc', 'ws', 'wss', 'xmpp', 'jdbc', 'slack', 'tel', 'smb', 'zotero', 'geo',
'mid'
];
// intended mainly as protection against XSS via import
// secondarily, it (partly) protects against "CSS takeover"

View File

@@ -1,7 +1,6 @@
import { LOCALES } from "@triliumnext/commons";
import { readFileSync } from "fs";
import { join } from "path";
import { DAYJS_LOADER } from "./i18n";
describe("i18n", () => {
it("translations are valid JSON", () => {
@@ -16,13 +15,4 @@ describe("i18n", () => {
.not.toThrow();
}
});
it("all dayjs locales are valid", async () => {
for (const locale of LOCALES) {
const dayjsLoader = DAYJS_LOADER[locale.id];
expect(dayjsLoader, `Locale ${locale.id} missing.`).toBeDefined();
await dayjsLoader();
}
});
});

View File

@@ -7,7 +7,7 @@ import hidden_subtree from "./hidden_subtree.js";
import { LOCALES, type Locale, type LOCALE_IDS } from "@triliumnext/commons";
import dayjs, { Dayjs } from "dayjs";
export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs/locale/en.js")>> = {
const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs/locale/en.js")>> = {
"ar": () => import("dayjs/locale/ar.js"),
"cn": () => import("dayjs/locale/zh-cn.js"),
"de": () => import("dayjs/locale/de.js"),
@@ -19,7 +19,6 @@ export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs
"ja": () => import("dayjs/locale/ja.js"),
"ku": () => import("dayjs/locale/ku.js"),
"pt_br": () => import("dayjs/locale/pt-br.js"),
"pt": () => import("dayjs/locale/pt.js"),
"ro": () => import("dayjs/locale/ro.js"),
"ru": () => import("dayjs/locale/ru.js"),
"tw": () => import("dayjs/locale/zh-tw.js"),

View File

@@ -12,11 +12,7 @@ import type { AIService, ChatCompletionOptions, Message } from './ai_interface.j
vi.mock('../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -114,26 +110,26 @@ describe('AIServiceManager', () => {
describe('getSelectedProviderAsync', () => {
it('should return the selected provider', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce('openai');
const result = await manager.getSelectedProviderAsync();
expect(result).toBe('openai');
expect(configHelpers.getSelectedProvider).toHaveBeenCalled();
});
it('should return null if no provider is selected', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce(null);
const result = await manager.getSelectedProviderAsync();
expect(result).toBeNull();
});
it('should handle errors and return null', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockRejectedValueOnce(new Error('Config error'));
const result = await manager.getSelectedProviderAsync();
expect(result).toBeNull();
});
});
@@ -145,9 +141,9 @@ describe('AIServiceManager', () => {
errors: [],
warnings: []
});
const result = await manager.validateConfiguration();
expect(result).toBeNull();
});
@@ -157,9 +153,9 @@ describe('AIServiceManager', () => {
errors: ['Missing API key', 'Invalid model'],
warnings: []
});
const result = await manager.validateConfiguration();
expect(result).toContain('There are issues with your AI configuration');
expect(result).toContain('Missing API key');
expect(result).toContain('Invalid model');
@@ -171,9 +167,9 @@ describe('AIServiceManager', () => {
errors: [],
warnings: ['Model not optimal']
});
const result = await manager.validateConfiguration();
expect(result).toBeNull();
});
});
@@ -182,21 +178,21 @@ describe('AIServiceManager', () => {
it('should create and return the selected provider service', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce('openai');
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key');
const mockService = {
isAvailable: vi.fn().mockReturnValue(true),
generateChatCompletion: vi.fn()
};
vi.mocked(OpenAIService).mockImplementationOnce(() => mockService as any);
const result = await manager.getOrCreateAnyService();
expect(result).toBe(mockService);
});
it('should throw error if no provider is selected', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce(null);
await expect(manager.getOrCreateAnyService()).rejects.toThrow(
'No AI provider is selected'
);
@@ -205,7 +201,7 @@ describe('AIServiceManager', () => {
it('should throw error if selected provider is not available', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce('openai');
vi.mocked(options.getOption).mockReturnValueOnce(''); // No API key
await expect(manager.getOrCreateAnyService()).rejects.toThrow(
'Selected AI provider (openai) is not available'
);
@@ -215,17 +211,17 @@ describe('AIServiceManager', () => {
describe('isAnyServiceAvailable', () => {
it('should return true if any provider is available', () => {
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key');
const result = manager.isAnyServiceAvailable();
expect(result).toBe(true);
});
it('should return false if no providers are available', () => {
vi.mocked(options.getOption).mockReturnValue('');
const result = manager.isAnyServiceAvailable();
expect(result).toBe(false);
});
});
@@ -236,18 +232,18 @@ describe('AIServiceManager', () => {
.mockReturnValueOnce('openai-key')
.mockReturnValueOnce('anthropic-key')
.mockReturnValueOnce(''); // No Ollama URL
const result = manager.getAvailableProviders();
expect(result).toEqual(['openai', 'anthropic']);
});
it('should include already created services', () => {
// Mock that OpenAI has API key configured
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key');
const result = manager.getAvailableProviders();
expect(result).toContain('openai');
});
});
@@ -259,23 +255,23 @@ describe('AIServiceManager', () => {
it('should generate completion with selected provider', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce('openai');
// Mock the getAvailableProviders to include openai
vi.mocked(options.getOption)
.mockReturnValueOnce('test-api-key') // for availability check
.mockReturnValueOnce('') // for anthropic
.mockReturnValueOnce('') // for ollama
.mockReturnValueOnce('test-api-key'); // for service creation
const mockResponse = { content: 'Hello response' };
const mockService = {
isAvailable: vi.fn().mockReturnValue(true),
generateChatCompletion: vi.fn().mockResolvedValueOnce(mockResponse)
};
vi.mocked(OpenAIService).mockImplementationOnce(() => mockService as any);
const result = await manager.generateChatCompletion(messages);
expect(result).toBe(mockResponse);
expect(mockService.generateChatCompletion).toHaveBeenCalledWith(messages, {});
});
@@ -287,28 +283,28 @@ describe('AIServiceManager', () => {
modelId: 'gpt-4',
fullIdentifier: 'openai:gpt-4'
});
// Mock the getAvailableProviders to include openai
vi.mocked(options.getOption)
.mockReturnValueOnce('test-api-key') // for availability check
.mockReturnValueOnce('') // for anthropic
.mockReturnValueOnce('') // for ollama
.mockReturnValueOnce('test-api-key'); // for service creation
const mockResponse = { content: 'Hello response' };
const mockService = {
isAvailable: vi.fn().mockReturnValue(true),
generateChatCompletion: vi.fn().mockResolvedValueOnce(mockResponse)
};
vi.mocked(OpenAIService).mockImplementationOnce(() => mockService as any);
const result = await manager.generateChatCompletion(messages, {
model: 'openai:gpt-4'
const result = await manager.generateChatCompletion(messages, {
model: 'openai:gpt-4'
});
expect(result).toBe(mockResponse);
expect(mockService.generateChatCompletion).toHaveBeenCalledWith(
messages,
messages,
{ model: 'gpt-4' }
);
});
@@ -321,7 +317,7 @@ describe('AIServiceManager', () => {
it('should throw error if no provider selected', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce(null);
await expect(manager.generateChatCompletion(messages)).rejects.toThrow(
'No AI provider is selected'
);
@@ -334,13 +330,13 @@ describe('AIServiceManager', () => {
modelId: 'claude-3',
fullIdentifier: 'anthropic:claude-3'
});
// Mock that openai is available
vi.mocked(options.getOption)
.mockReturnValueOnce('test-api-key') // for availability check
.mockReturnValueOnce('') // for anthropic
.mockReturnValueOnce(''); // for ollama
await expect(
manager.generateChatCompletion(messages, { model: 'anthropic:claude-3' })
).rejects.toThrow(
@@ -352,9 +348,9 @@ describe('AIServiceManager', () => {
describe('getAIEnabledAsync', () => {
it('should return AI enabled status', async () => {
vi.mocked(configHelpers.isAIEnabled).mockResolvedValueOnce(true);
const result = await manager.getAIEnabledAsync();
expect(result).toBe(true);
expect(configHelpers.isAIEnabled).toHaveBeenCalled();
});
@@ -363,9 +359,9 @@ describe('AIServiceManager', () => {
describe('getAIEnabled', () => {
it('should return AI enabled status synchronously', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(true);
const result = manager.getAIEnabled();
expect(result).toBe(true);
expect(options.getOptionBool).toHaveBeenCalledWith('aiEnabled');
});
@@ -374,17 +370,17 @@ describe('AIServiceManager', () => {
describe('initialize', () => {
it('should initialize if AI is enabled', async () => {
vi.mocked(configHelpers.isAIEnabled).mockResolvedValueOnce(true);
await manager.initialize();
expect(configHelpers.isAIEnabled).toHaveBeenCalled();
});
it('should not initialize if AI is disabled', async () => {
vi.mocked(configHelpers.isAIEnabled).mockResolvedValueOnce(false);
await manager.initialize();
expect(configHelpers.isAIEnabled).toHaveBeenCalled();
});
});
@@ -392,36 +388,36 @@ describe('AIServiceManager', () => {
describe('getService', () => {
it('should return service for specified provider', async () => {
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key');
const mockService = {
isAvailable: vi.fn().mockReturnValue(true),
generateChatCompletion: vi.fn()
};
vi.mocked(OpenAIService).mockImplementationOnce(() => mockService as any);
const result = await manager.getService('openai');
expect(result).toBe(mockService);
});
it('should return selected provider service if no provider specified', async () => {
vi.mocked(configHelpers.getSelectedProvider).mockResolvedValueOnce('anthropic');
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key');
const mockService = {
isAvailable: vi.fn().mockReturnValue(true),
generateChatCompletion: vi.fn()
};
vi.mocked(AnthropicService).mockImplementationOnce(() => mockService as any);
const result = await manager.getService();
expect(result).toBe(mockService);
});
it('should throw error if specified provider not available', async () => {
vi.mocked(options.getOption).mockReturnValueOnce(''); // No API key
await expect(manager.getService('openai')).rejects.toThrow(
'Specified provider openai is not available'
);
@@ -431,17 +427,17 @@ describe('AIServiceManager', () => {
describe('getSelectedProvider', () => {
it('should return selected provider synchronously', () => {
vi.mocked(options.getOption).mockReturnValueOnce('anthropic');
const result = manager.getSelectedProvider();
expect(result).toBe('anthropic');
});
it('should return default provider if none selected', () => {
vi.mocked(options.getOption).mockReturnValueOnce('');
const result = manager.getSelectedProvider();
expect(result).toBe('openai');
});
});
@@ -450,18 +446,18 @@ describe('AIServiceManager', () => {
it('should return true if provider service is available', () => {
// Mock that OpenAI has API key configured
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key');
const result = manager.isProviderAvailable('openai');
expect(result).toBe(true);
});
it('should return false if provider service not created', () => {
// Mock that OpenAI has no API key configured
vi.mocked(options.getOption).mockReturnValueOnce('');
const result = manager.isProviderAvailable('openai');
expect(result).toBe(false);
});
});
@@ -471,13 +467,13 @@ describe('AIServiceManager', () => {
// Since getProviderMetadata only returns metadata for the current active provider,
// and we don't have a current provider set, it should return null
const result = manager.getProviderMetadata('openai');
expect(result).toBeNull();
});
it('should return null for non-existing provider', () => {
const result = manager.getProviderMetadata('openai');
expect(result).toBeNull();
});
});
@@ -489,4 +485,4 @@ describe('AIServiceManager', () => {
expect(manager).toBeDefined();
});
});
});
});

View File

@@ -15,11 +15,7 @@ vi.mock('../../log.js', () => ({
vi.mock('../../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -70,14 +66,14 @@ describe('RestChatService', () => {
beforeEach(async () => {
vi.clearAllMocks();
// Get mocked modules
mockOptions = (await import('../../options.js')).default;
mockAiServiceManager = (await import('../ai_service_manager.js')).default;
mockChatStorageService = (await import('../chat_storage_service.js')).default;
restChatService = (await import('./rest_chat_service.js')).default;
// Setup mock request and response
mockReq = {
params: {},
@@ -85,7 +81,7 @@ describe('RestChatService', () => {
query: {},
method: 'POST'
};
mockRes = {
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
@@ -244,7 +240,7 @@ describe('RestChatService', () => {
it('should handle GET request with stream parameter', async () => {
mockReq.method = 'GET';
mockReq.query = {
mockReq.query = {
stream: 'true',
useAdvancedContext: 'true',
showThinking: 'false'
@@ -423,4 +419,4 @@ describe('RestChatService', () => {
expect(mockChatStorageService.getChat).toHaveBeenCalledWith('chat-123');
});
});
});
});

View File

@@ -18,11 +18,7 @@ vi.mock('./configuration_manager.js', () => ({
vi.mock('../../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -46,26 +42,26 @@ describe('configuration_helpers', () => {
describe('getSelectedProvider', () => {
it('should return the selected provider', async () => {
vi.mocked(optionService.getOption).mockReturnValueOnce('openai');
const result = await configHelpers.getSelectedProvider();
expect(result).toBe('openai');
expect(optionService.getOption).toHaveBeenCalledWith('aiSelectedProvider');
});
it('should return null if no provider is selected', async () => {
vi.mocked(optionService.getOption).mockReturnValueOnce('');
const result = await configHelpers.getSelectedProvider();
expect(result).toBeNull();
});
it('should handle invalid provider and return null', async () => {
vi.mocked(optionService.getOption).mockReturnValueOnce('invalid-provider');
const result = await configHelpers.getSelectedProvider();
expect(result).toBe('invalid-provider' as ProviderType);
});
});
@@ -73,7 +69,7 @@ describe('configuration_helpers', () => {
describe('parseModelIdentifier', () => {
it('should parse model identifier directly', () => {
const result = configHelpers.parseModelIdentifier('openai:gpt-4');
expect(result).toStrictEqual({
provider: 'openai',
modelId: 'gpt-4',
@@ -83,7 +79,7 @@ describe('configuration_helpers', () => {
it('should handle model without provider', () => {
const result = configHelpers.parseModelIdentifier('gpt-4');
expect(result).toStrictEqual({
modelId: 'gpt-4',
fullIdentifier: 'gpt-4'
@@ -92,7 +88,7 @@ describe('configuration_helpers', () => {
it('should handle empty model string', () => {
const result = configHelpers.parseModelIdentifier('');
expect(result).toStrictEqual({
modelId: '',
fullIdentifier: ''
@@ -102,7 +98,7 @@ describe('configuration_helpers', () => {
// Tests for special characters in model names
it('should handle model names with periods', () => {
const result = configHelpers.parseModelIdentifier('gpt-4.1-turbo-preview');
expect(result).toStrictEqual({
modelId: 'gpt-4.1-turbo-preview',
fullIdentifier: 'gpt-4.1-turbo-preview'
@@ -111,7 +107,7 @@ describe('configuration_helpers', () => {
it('should handle model names with provider prefix and periods', () => {
const result = configHelpers.parseModelIdentifier('openai:gpt-4.1-turbo');
expect(result).toStrictEqual({
provider: 'openai',
modelId: 'gpt-4.1-turbo',
@@ -121,7 +117,7 @@ describe('configuration_helpers', () => {
it('should handle model names with multiple colons', () => {
const result = configHelpers.parseModelIdentifier('custom:model:v1.2:latest');
expect(result).toStrictEqual({
modelId: 'custom:model:v1.2:latest',
fullIdentifier: 'custom:model:v1.2:latest'
@@ -130,7 +126,7 @@ describe('configuration_helpers', () => {
it('should handle Ollama model names with colons', () => {
const result = configHelpers.parseModelIdentifier('ollama:llama3.1:70b-instruct-q4_K_M');
expect(result).toStrictEqual({
provider: 'ollama',
modelId: 'llama3.1:70b-instruct-q4_K_M',
@@ -140,7 +136,7 @@ describe('configuration_helpers', () => {
it('should handle model names with slashes', () => {
const result = configHelpers.parseModelIdentifier('library/mistral:7b-instruct');
expect(result).toStrictEqual({
modelId: 'library/mistral:7b-instruct',
fullIdentifier: 'library/mistral:7b-instruct'
@@ -150,7 +146,7 @@ describe('configuration_helpers', () => {
it('should handle complex model names with special characters', () => {
const complexName = 'org/model-v1.2.3:tag@version#variant';
const result = configHelpers.parseModelIdentifier(complexName);
expect(result).toStrictEqual({
modelId: complexName,
fullIdentifier: complexName
@@ -159,7 +155,7 @@ describe('configuration_helpers', () => {
it('should handle model names with @ symbols', () => {
const result = configHelpers.parseModelIdentifier('claude-3.5-sonnet@20241022');
expect(result).toStrictEqual({
modelId: 'claude-3.5-sonnet@20241022',
fullIdentifier: 'claude-3.5-sonnet@20241022'
@@ -169,7 +165,7 @@ describe('configuration_helpers', () => {
it('should not modify or encode special characters', () => {
const specialChars = 'model!@#$%^&*()_+-=[]{}|;:\'",.<>?/~`';
const result = configHelpers.parseModelIdentifier(specialChars);
expect(result).toStrictEqual({
modelId: specialChars,
fullIdentifier: specialChars
@@ -180,7 +176,7 @@ describe('configuration_helpers', () => {
describe('createModelConfig', () => {
it('should create model config directly', () => {
const result = configHelpers.createModelConfig('gpt-4', 'openai');
expect(result).toStrictEqual({
provider: 'openai',
modelId: 'gpt-4',
@@ -190,7 +186,7 @@ describe('configuration_helpers', () => {
it('should handle model with provider prefix', () => {
const result = configHelpers.createModelConfig('openai:gpt-4');
expect(result).toStrictEqual({
provider: 'openai',
modelId: 'gpt-4',
@@ -200,7 +196,7 @@ describe('configuration_helpers', () => {
it('should fallback to openai provider when none specified', () => {
const result = configHelpers.createModelConfig('gpt-4');
expect(result).toStrictEqual({
provider: 'openai',
modelId: 'gpt-4',
@@ -212,27 +208,27 @@ describe('configuration_helpers', () => {
describe('getDefaultModelForProvider', () => {
it('should return default model for provider', async () => {
vi.mocked(optionService.getOption).mockReturnValue('gpt-4');
const result = await configHelpers.getDefaultModelForProvider('openai');
expect(result).toBe('gpt-4');
expect(optionService.getOption).toHaveBeenCalledWith('openaiDefaultModel');
});
it('should return undefined if no default model', async () => {
vi.mocked(optionService.getOption).mockReturnValue('');
const result = await configHelpers.getDefaultModelForProvider('anthropic');
expect(result).toBeUndefined();
expect(optionService.getOption).toHaveBeenCalledWith('anthropicDefaultModel');
});
it('should handle ollama provider', async () => {
vi.mocked(optionService.getOption).mockReturnValue('llama2');
const result = await configHelpers.getDefaultModelForProvider('ollama');
expect(result).toBe('llama2');
expect(optionService.getOption).toHaveBeenCalledWith('ollamaDefaultModel');
});
@@ -241,27 +237,27 @@ describe('configuration_helpers', () => {
it('should handle OpenAI model names with periods', async () => {
const modelName = 'gpt-4.1-turbo-preview';
vi.mocked(optionService.getOption).mockReturnValue(modelName);
const result = await configHelpers.getDefaultModelForProvider('openai');
expect(result).toBe(modelName);
});
it('should handle Anthropic model names with periods and @ symbols', async () => {
const modelName = 'claude-3.5-sonnet@20241022';
vi.mocked(optionService.getOption).mockReturnValue(modelName);
const result = await configHelpers.getDefaultModelForProvider('anthropic');
expect(result).toBe(modelName);
});
it('should handle Ollama model names with colons and slashes', async () => {
const modelName = 'library/llama3.1:70b-instruct-q4_K_M';
vi.mocked(optionService.getOption).mockReturnValue(modelName);
const result = await configHelpers.getDefaultModelForProvider('ollama');
expect(result).toBe(modelName);
});
});
@@ -272,9 +268,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('https://api.openai.com') // openaiBaseUrl
.mockReturnValueOnce('gpt-4'); // openaiDefaultModel
const result = await configHelpers.getProviderSettings('openai');
expect(result).toStrictEqual({
apiKey: 'test-key',
baseUrl: 'https://api.openai.com',
@@ -287,9 +283,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('anthropic-key') // anthropicApiKey
.mockReturnValueOnce('https://api.anthropic.com') // anthropicBaseUrl
.mockReturnValueOnce('claude-3'); // anthropicDefaultModel
const result = await configHelpers.getProviderSettings('anthropic');
expect(result).toStrictEqual({
apiKey: 'anthropic-key',
baseUrl: 'https://api.anthropic.com',
@@ -301,9 +297,9 @@ describe('configuration_helpers', () => {
vi.mocked(optionService.getOption)
.mockReturnValueOnce('http://localhost:11434') // ollamaBaseUrl
.mockReturnValueOnce('llama2'); // ollamaDefaultModel
const result = await configHelpers.getProviderSettings('ollama');
expect(result).toStrictEqual({
baseUrl: 'http://localhost:11434',
defaultModel: 'llama2'
@@ -312,7 +308,7 @@ describe('configuration_helpers', () => {
it('should return empty object for unknown provider', async () => {
const result = await configHelpers.getProviderSettings('unknown' as ProviderType);
expect(result).toStrictEqual({});
});
});
@@ -320,18 +316,18 @@ describe('configuration_helpers', () => {
describe('isAIEnabled', () => {
it('should return true if AI is enabled', async () => {
vi.mocked(optionService.getOptionBool).mockReturnValue(true);
const result = await configHelpers.isAIEnabled();
expect(result).toBe(true);
expect(optionService.getOptionBool).toHaveBeenCalledWith('aiEnabled');
});
it('should return false if AI is disabled', async () => {
vi.mocked(optionService.getOptionBool).mockReturnValue(false);
const result = await configHelpers.isAIEnabled();
expect(result).toBe(false);
expect(optionService.getOptionBool).toHaveBeenCalledWith('aiEnabled');
});
@@ -343,9 +339,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.isProviderConfigured('openai');
expect(result).toBe(true);
});
@@ -354,9 +350,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('') // openaiApiKey (empty)
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.isProviderConfigured('openai');
expect(result).toBe(false);
});
@@ -365,9 +361,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('anthropic-key') // anthropicApiKey
.mockReturnValueOnce('') // anthropicBaseUrl
.mockReturnValueOnce(''); // anthropicDefaultModel
const result = await configHelpers.isProviderConfigured('anthropic');
expect(result).toBe(true);
});
@@ -375,15 +371,15 @@ describe('configuration_helpers', () => {
vi.mocked(optionService.getOption)
.mockReturnValueOnce('http://localhost:11434') // ollamaBaseUrl
.mockReturnValueOnce(''); // ollamaDefaultModel
const result = await configHelpers.isProviderConfigured('ollama');
expect(result).toBe(true);
});
it('should return false for unknown provider', async () => {
const result = await configHelpers.isProviderConfigured('unknown' as ProviderType);
expect(result).toBe(false);
});
});
@@ -395,17 +391,17 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.getAvailableSelectedProvider();
expect(result).toBe('openai');
});
it('should return null if no provider selected', async () => {
vi.mocked(optionService.getOption).mockReturnValueOnce('');
const result = await configHelpers.getAvailableSelectedProvider();
expect(result).toBeNull();
});
@@ -415,9 +411,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('') // openaiApiKey (empty)
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.getAvailableSelectedProvider();
expect(result).toBeNull();
});
});
@@ -431,9 +427,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce('gpt-4'); // openaiDefaultModel
const result = await configHelpers.validateConfiguration();
expect(result).toStrictEqual({
isValid: true,
errors: [],
@@ -443,9 +439,9 @@ describe('configuration_helpers', () => {
it('should return warning when AI is disabled', async () => {
vi.mocked(optionService.getOptionBool).mockReturnValue(false);
const result = await configHelpers.validateConfiguration();
expect(result).toStrictEqual({
isValid: true,
errors: [],
@@ -456,9 +452,9 @@ describe('configuration_helpers', () => {
it('should return error when no provider selected', async () => {
vi.mocked(optionService.getOptionBool).mockReturnValue(true);
vi.mocked(optionService.getOption).mockReturnValue(''); // no aiSelectedProvider
const result = await configHelpers.validateConfiguration();
expect(result).toStrictEqual({
isValid: false,
errors: ['No AI provider selected'],
@@ -473,9 +469,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('') // openaiApiKey (empty)
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.validateConfiguration();
expect(result).toStrictEqual({
isValid: true,
errors: [],
@@ -499,9 +495,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.getValidModelConfig('openai');
expect(result).toStrictEqual({
model: modelName,
provider: 'openai'
@@ -515,9 +511,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('anthropic-key') // anthropicApiKey
.mockReturnValueOnce('') // anthropicBaseUrl
.mockReturnValueOnce(''); // anthropicDefaultModel
const result = await configHelpers.getValidModelConfig('anthropic');
expect(result).toStrictEqual({
model: modelName,
provider: 'anthropic'
@@ -530,9 +526,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce(modelName) // ollamaDefaultModel
.mockReturnValueOnce('http://localhost:11434') // ollamaBaseUrl
.mockReturnValueOnce(''); // ollamaDefaultModel
const result = await configHelpers.getValidModelConfig('ollama');
expect(result).toStrictEqual({
model: modelName,
provider: 'ollama'
@@ -549,9 +545,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.getSelectedModelConfig();
expect(result).toStrictEqual({
model: modelName,
provider: 'openai'
@@ -566,9 +562,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // openaiApiKey
.mockReturnValueOnce('') // openaiBaseUrl
.mockReturnValueOnce(''); // openaiDefaultModel
const result = await configHelpers.getSelectedModelConfig();
expect(result).toStrictEqual({
model: modelName,
provider: 'openai'
@@ -582,9 +578,9 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce(modelName) // ollamaDefaultModel
.mockReturnValueOnce('http://localhost:11434') // ollamaBaseUrl
.mockReturnValueOnce(''); // ollamaDefaultModel
const result = await configHelpers.getSelectedModelConfig();
expect(result).toStrictEqual({
model: modelName,
provider: 'ollama'
@@ -599,13 +595,13 @@ describe('configuration_helpers', () => {
.mockReturnValueOnce('test-key') // anthropicApiKey
.mockReturnValueOnce('') // anthropicBaseUrl
.mockReturnValueOnce(''); // anthropicDefaultModel
const result = await configHelpers.getSelectedModelConfig();
expect(result).toStrictEqual({
model: modelName,
provider: 'anthropic'
});
});
});
});
});

View File

@@ -10,11 +10,7 @@ import { PROVIDER_CONSTANTS } from '../constants/provider_constants.js';
vi.mock('../../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -83,7 +79,7 @@ describe('AnthropicService', () => {
beforeEach(() => {
vi.clearAllMocks();
// Get the mocked Anthropic instance before creating the service
const AnthropicMock = vi.mocked(Anthropic);
mockAnthropicInstance = {
@@ -126,9 +122,9 @@ describe('AnthropicService', () => {
})
}
};
AnthropicMock.mockImplementation(() => mockAnthropicInstance);
service = new AnthropicService();
});
@@ -148,26 +144,26 @@ describe('AnthropicService', () => {
it('should return true when AI is enabled and API key exists', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(true); // AI enabled
vi.mocked(options.getOption).mockReturnValueOnce('test-api-key'); // API key
const result = service.isAvailable();
expect(result).toBe(true);
});
it('should return false when AI is disabled', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(false); // AI disabled
const result = service.isAvailable();
expect(result).toBe(false);
});
it('should return false when no API key', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(true); // AI enabled
vi.mocked(options.getOption).mockReturnValueOnce(''); // No API key
const result = service.isAvailable();
expect(result).toBe(false);
});
});
@@ -194,9 +190,9 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
const result = await service.generateChatCompletion(messages);
expect(result).toEqual({
text: 'Hello! How can I help you today?',
provider: 'Anthropic',
@@ -218,11 +214,11 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
const createSpy = vi.spyOn(mockAnthropicInstance.messages, 'create');
await service.generateChatCompletion(messages);
const calledParams = createSpy.mock.calls[0][0] as any;
expect(calledParams.messages).toEqual([
{ role: 'user', content: 'Hello' }
@@ -239,12 +235,12 @@ describe('AnthropicService', () => {
onChunk: vi.fn()
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
const result = await service.generateChatCompletion(messages);
// Wait for chunks to be processed
await new Promise(resolve => setTimeout(resolve, 100));
// Check that the result exists (streaming logic is complex, so we just verify basic structure)
expect(result).toBeDefined();
expect(result).toHaveProperty('text');
@@ -260,7 +256,7 @@ describe('AnthropicService', () => {
properties: {}
}
}];
const mockOptions = {
apiKey: 'test-key',
baseUrl: 'https://api.anthropic.com',
@@ -271,7 +267,7 @@ describe('AnthropicService', () => {
tool_choice: { type: 'any' }
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
// Mock response with tool use
mockAnthropicInstance.messages.create.mockResolvedValueOnce({
id: 'msg_123',
@@ -291,9 +287,9 @@ describe('AnthropicService', () => {
output_tokens: 25
}
});
const result = await service.generateChatCompletion(messages);
expect(result).toEqual({
text: '',
provider: 'Anthropic',
@@ -316,7 +312,7 @@ describe('AnthropicService', () => {
it('should throw error if service not available', async () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(false); // AI disabled
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'Anthropic service is not available'
);
@@ -330,12 +326,12 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
// Mock API error
mockAnthropicInstance.messages.create.mockRejectedValueOnce(
new Error('API Error: Invalid API key')
);
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'API Error: Invalid API key'
);
@@ -351,15 +347,15 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
// Spy on Anthropic constructor
const AnthropicMock = vi.mocked(Anthropic);
AnthropicMock.mockClear();
// Create new service to trigger client creation
const newService = new AnthropicService();
await newService.generateChatCompletion(messages);
expect(AnthropicMock).toHaveBeenCalledWith({
apiKey: 'test-key',
baseURL: 'https://api.anthropic.com',
@@ -378,15 +374,15 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
// Spy on Anthropic constructor
const AnthropicMock = vi.mocked(Anthropic);
AnthropicMock.mockClear();
// Create new service to trigger client creation
const newService = new AnthropicService();
await newService.generateChatCompletion(messages);
expect(AnthropicMock).toHaveBeenCalledWith({
apiKey: 'test-key',
baseURL: 'https://api.anthropic.com',
@@ -405,7 +401,7 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
// Mock response with mixed content
mockAnthropicInstance.messages.create.mockResolvedValueOnce({
id: 'msg_123',
@@ -424,9 +420,9 @@ describe('AnthropicService', () => {
output_tokens: 25
}
});
const result = await service.generateChatCompletion(messages);
expect(result.text).toBe('Here is the result: The calculation is complete.');
expect(result.tool_calls).toHaveLength(1);
expect(result.tool_calls![0].function.name).toBe('calculate');
@@ -435,8 +431,8 @@ describe('AnthropicService', () => {
it('should handle tool results in messages', async () => {
const messagesWithToolResult: Message[] = [
{ role: 'user', content: 'Calculate 5 + 3' },
{
role: 'assistant',
{
role: 'assistant',
content: '',
tool_calls: [{
id: 'call_123',
@@ -444,13 +440,13 @@ describe('AnthropicService', () => {
function: { name: 'calculate', arguments: '{"x": 5, "y": 3}' }
}]
},
{
role: 'tool',
{
role: 'tool',
content: '8',
tool_call_id: 'call_123'
}
];
const mockOptions = {
apiKey: 'test-key',
baseUrl: 'https://api.anthropic.com',
@@ -458,11 +454,11 @@ describe('AnthropicService', () => {
stream: false
};
vi.mocked(providers.getAnthropicOptions).mockReturnValueOnce(mockOptions);
const createSpy = vi.spyOn(mockAnthropicInstance.messages, 'create');
await service.generateChatCompletion(messagesWithToolResult);
const formattedMessages = (createSpy.mock.calls[0][0] as any).messages;
expect(formattedMessages).toHaveLength(3);
expect(formattedMessages[2]).toEqual({
@@ -475,4 +471,4 @@ describe('AnthropicService', () => {
});
});
});
});
});

View File

@@ -10,11 +10,7 @@ import options from '../../options.js';
vi.mock('../../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -86,7 +82,7 @@ describe('LLM Model Selection with Special Characters', () => {
// Spy on getOpenAIOptions to verify model name is passed correctly
const getOpenAIOptionsSpy = vi.spyOn(providers, 'getOpenAIOptions');
try {
await service.generateChatCompletion([{ role: 'user', content: 'test' }], opts);
} catch (error) {
@@ -112,7 +108,7 @@ describe('LLM Model Selection with Special Characters', () => {
};
const getOpenAIOptionsSpy = vi.spyOn(providers, 'getOpenAIOptions');
try {
await service.generateChatCompletion([{ role: 'user', content: 'test' }], opts);
} catch (error) {
@@ -131,7 +127,7 @@ describe('LLM Model Selection with Special Characters', () => {
};
const getOpenAIOptionsSpy = vi.spyOn(providers, 'getOpenAIOptions');
const openaiOptions = providers.getOpenAIOptions(opts);
expect(openaiOptions.model).toBe(modelName);
});
@@ -157,7 +153,7 @@ describe('LLM Model Selection with Special Characters', () => {
});
const service = new OpenAIService();
// Access the private openai client through the service
const client = (service as any).getClient('test-key');
const createSpy = vi.spyOn(client.chat.completions, 'create');
@@ -217,7 +213,7 @@ describe('LLM Model Selection with Special Characters', () => {
});
const service = new AnthropicService();
// Access the private anthropic client
const client = (service as any).getClient('test-key');
const createSpy = vi.spyOn(client.messages, 'create');
@@ -282,7 +278,7 @@ describe('LLM Model Selection with Special Characters', () => {
const ollamaOptions = await providers.getOllamaOptions(opts);
expect(ollamaOptions.model).toBe(modelName);
// Also test with model specified in options
const optsWithModel: ChatCompletionOptions = {
model: 'another/model:v2.0@beta',
@@ -374,7 +370,7 @@ describe('LLM Model Selection with Special Characters', () => {
describe('Integration with REST API', () => {
it('should pass model names correctly through REST chat service', async () => {
const modelName = 'gpt-4.1-turbo-preview@latest';
// Mock the configuration helpers
vi.doMock('../config/configuration_helpers.js', () => ({
getSelectedModelConfig: vi.fn().mockResolvedValue({
@@ -386,8 +382,8 @@ describe('LLM Model Selection with Special Characters', () => {
const { getSelectedModelConfig } = await import('../config/configuration_helpers.js');
const config = await getSelectedModelConfig();
expect(config?.model).toBe(modelName);
});
});
});
});

View File

@@ -9,11 +9,7 @@ import { Ollama } from 'ollama';
vi.mock('../../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -138,7 +134,7 @@ describe('OllamaService', () => {
beforeEach(() => {
vi.clearAllMocks();
// Create the mock instance before creating the service
const OllamaMock = vi.mocked(Ollama);
mockOllamaInstance = {
@@ -195,11 +191,11 @@ describe('OllamaService', () => {
]
})
};
OllamaMock.mockImplementation(() => mockOllamaInstance);
service = new OllamaService();
// Replace the formatter with a mock after construction
(service as any).formatter = {
formatMessages: vi.fn().mockReturnValue([
@@ -235,26 +231,26 @@ describe('OllamaService', () => {
it('should return true when AI is enabled and base URL exists', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(true); // AI enabled
vi.mocked(options.getOption).mockReturnValueOnce('http://localhost:11434'); // Base URL
const result = service.isAvailable();
expect(result).toBe(true);
});
it('should return false when AI is disabled', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(false); // AI disabled
const result = service.isAvailable();
expect(result).toBe(false);
});
it('should return false when no base URL', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(true); // AI enabled
vi.mocked(options.getOption).mockReturnValueOnce(''); // No base URL
const result = service.isAvailable();
expect(result).toBe(false);
});
});
@@ -279,9 +275,9 @@ describe('OllamaService', () => {
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const result = await service.generateChatCompletion(messages);
expect(result).toEqual({
text: 'Hello! How can I help you today?',
provider: 'ollama',
@@ -300,12 +296,12 @@ describe('OllamaService', () => {
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const result = await service.generateChatCompletion(messages);
// Wait for chunks to be processed
await new Promise(resolve => setTimeout(resolve, 100));
// For streaming, we expect a different response structure
expect(result).toBeDefined();
expect(result).toHaveProperty('text');
@@ -314,7 +310,7 @@ describe('OllamaService', () => {
it('should handle tools when enabled', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockTools = [{
name: 'test_tool',
description: 'Test tool',
@@ -324,7 +320,7 @@ describe('OllamaService', () => {
required: []
}
}];
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
@@ -333,18 +329,18 @@ describe('OllamaService', () => {
tools: mockTools
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
const chatSpy = vi.spyOn(mockOllamaInstance, 'chat');
await service.generateChatCompletion(messages);
const calledParams = chatSpy.mock.calls[0][0] as any;
expect(calledParams.tools).toEqual(mockTools);
});
it('should throw error if service not available', async () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(false); // AI disabled
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'Ollama service is not available'
);
@@ -354,14 +350,14 @@ describe('OllamaService', () => {
vi.mocked(options.getOption)
.mockReturnValueOnce('') // Empty base URL for ollamaBaseUrl
.mockReturnValue(''); // Ensure all subsequent calls return empty
const mockOptions = {
baseUrl: '',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'Ollama service is not available'
);
@@ -369,19 +365,19 @@ describe('OllamaService', () => {
it('should handle API errors', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
// Mock API error
mockOllamaInstance.chat.mockRejectedValueOnce(
new Error('Connection refused')
);
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'Connection refused'
);
@@ -389,30 +385,30 @@ describe('OllamaService', () => {
it('should create client with custom fetch for debugging', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
// Spy on Ollama constructor
const OllamaMock = vi.mocked(Ollama);
OllamaMock.mockClear();
// Create new service to trigger client creation
const newService = new OllamaService();
// Replace the formatter with a mock for the new service
(newService as any).formatter = {
formatMessages: vi.fn().mockReturnValue([
{ role: 'user', content: 'Hello' }
])
};
await newService.generateChatCompletion(messages);
expect(OllamaMock).toHaveBeenCalledWith({
host: 'http://localhost:11434',
fetch: expect.any(Function)
@@ -421,7 +417,7 @@ describe('OllamaService', () => {
it('should handle tool execution feedback', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
@@ -430,7 +426,7 @@ describe('OllamaService', () => {
tools: [{ name: 'test_tool', description: 'Test', parameters: {} }]
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
// Mock response with tool call (arguments should be a string for Ollama)
mockOllamaInstance.chat.mockResolvedValueOnce({
message: {
@@ -446,9 +442,9 @@ describe('OllamaService', () => {
},
done: true
});
const result = await service.generateChatCompletion(messages);
expect(result.tool_calls).toEqual([{
id: 'call_123',
type: 'function',
@@ -461,14 +457,14 @@ describe('OllamaService', () => {
it('should handle mixed text and tool content', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
// Mock response with both text and tool calls
mockOllamaInstance.chat.mockResolvedValueOnce({
message: {
@@ -484,30 +480,30 @@ describe('OllamaService', () => {
},
done: true
});
const result = await service.generateChatCompletion(messages);
expect(result.text).toBe('Let me help you with that.');
expect(result.tool_calls).toHaveLength(1);
});
it('should format messages using the formatter', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
const formattedMessages = [{ role: 'user', content: 'Hello' }];
(service as any).formatter.formatMessages.mockReturnValueOnce(formattedMessages);
const chatSpy = vi.spyOn(mockOllamaInstance, 'chat');
await service.generateChatCompletion(messages);
expect((service as any).formatter.formatMessages).toHaveBeenCalled();
expect(chatSpy).toHaveBeenCalledWith(
expect.objectContaining({
@@ -518,23 +514,23 @@ describe('OllamaService', () => {
it('should handle network errors gracefully', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
// Mock network error
global.fetch = vi.fn().mockRejectedValueOnce(
new Error('Network error')
);
mockOllamaInstance.chat.mockRejectedValueOnce(
new Error('fetch failed')
);
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'fetch failed'
);
@@ -542,19 +538,19 @@ describe('OllamaService', () => {
it('should validate model availability', async () => {
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'nonexistent-model',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValueOnce(mockOptions);
// Mock model not found error
mockOllamaInstance.chat.mockRejectedValueOnce(
new Error('model "nonexistent-model" not found')
);
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'model "nonexistent-model" not found'
);
@@ -565,23 +561,23 @@ describe('OllamaService', () => {
it('should reuse existing client', async () => {
vi.mocked(options.getOptionBool).mockReturnValue(true);
vi.mocked(options.getOption).mockReturnValue('http://localhost:11434');
const mockOptions = {
baseUrl: 'http://localhost:11434',
model: 'llama2',
stream: false
};
vi.mocked(providers.getOllamaOptions).mockResolvedValue(mockOptions);
const OllamaMock = vi.mocked(Ollama);
OllamaMock.mockClear();
// Make two calls
await service.generateChatCompletion([{ role: 'user', content: 'Hello' }]);
await service.generateChatCompletion([{ role: 'user', content: 'Hi' }]);
// Should only create client once
expect(OllamaMock).toHaveBeenCalledTimes(1);
});
});
});
});

View File

@@ -2,17 +2,13 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { OpenAIService } from './openai_service.js';
import options from '../../options.js';
import * as providers from './providers.js';
import type { Message } from '../ai_interface.js';
import type { ChatCompletionOptions, Message } from '../ai_interface.js';
// Mock dependencies
vi.mock('../../options.js', () => ({
default: {
getOption: vi.fn(),
getOptionBool: vi.fn(),
getOptionInt: vi.fn(name => {
if (name === "protectedSessionTimeout") return Number.MAX_SAFE_INTEGER;
return 0;
})
getOptionBool: vi.fn()
}
}));
@@ -57,17 +53,17 @@ describe('OpenAIService', () => {
describe('isAvailable', () => {
it('should return true when base checks pass', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(true); // AI enabled
const result = service.isAvailable();
expect(result).toBe(true);
});
it('should return false when AI is disabled', () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(false); // AI disabled
const result = service.isAvailable();
expect(result).toBe(false);
});
});
@@ -93,7 +89,7 @@ describe('OpenAIService', () => {
enableTools: false
};
vi.mocked(providers.getOpenAIOptions).mockReturnValueOnce(mockOptions);
// Mock the getClient method to return our mock client
const mockCompletion = {
id: 'chatcmpl-123',
@@ -124,9 +120,9 @@ describe('OpenAIService', () => {
};
vi.spyOn(service as any, 'getClient').mockReturnValue(mockClient);
const result = await service.generateChatCompletion(messages);
expect(result).toEqual({
text: 'Hello! How can I help you today?',
model: 'gpt-3.5-turbo',
@@ -148,7 +144,7 @@ describe('OpenAIService', () => {
stream: true
};
vi.mocked(providers.getOpenAIOptions).mockReturnValueOnce(mockOptions);
// Mock the streaming response
const mockStream = {
[Symbol.asyncIterator]: async function* () {
@@ -166,7 +162,7 @@ describe('OpenAIService', () => {
};
}
};
const mockClient = {
chat: {
completions: {
@@ -176,9 +172,9 @@ describe('OpenAIService', () => {
};
vi.spyOn(service as any, 'getClient').mockReturnValue(mockClient);
const result = await service.generateChatCompletion(messages);
expect(result).toHaveProperty('stream');
expect(result.text).toBe('');
expect(result.model).toBe('gpt-3.5-turbo');
@@ -187,7 +183,7 @@ describe('OpenAIService', () => {
it('should throw error if service not available', async () => {
vi.mocked(options.getOptionBool).mockReturnValueOnce(false); // AI disabled
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'OpenAI service is not available'
);
@@ -201,7 +197,7 @@ describe('OpenAIService', () => {
stream: false
};
vi.mocked(providers.getOpenAIOptions).mockReturnValueOnce(mockOptions);
const mockClient = {
chat: {
completions: {
@@ -211,7 +207,7 @@ describe('OpenAIService', () => {
};
vi.spyOn(service as any, 'getClient').mockReturnValue(mockClient);
await expect(service.generateChatCompletion(messages)).rejects.toThrow(
'API Error: Invalid API key'
);
@@ -226,7 +222,7 @@ describe('OpenAIService', () => {
parameters: {}
}
}];
const mockOptions = {
apiKey: 'test-key',
baseUrl: 'https://api.openai.com/v1',
@@ -237,7 +233,7 @@ describe('OpenAIService', () => {
tool_choice: 'auto'
};
vi.mocked(providers.getOpenAIOptions).mockReturnValueOnce(mockOptions);
const mockCompletion = {
id: 'chatcmpl-123',
object: 'chat.completion',
@@ -267,9 +263,9 @@ describe('OpenAIService', () => {
};
vi.spyOn(service as any, 'getClient').mockReturnValue(mockClient);
await service.generateChatCompletion(messages);
const createCall = mockClient.chat.completions.create.mock.calls[0][0];
expect(createCall.tools).toEqual(mockTools);
expect(createCall.tool_choice).toBe('auto');
@@ -285,7 +281,7 @@ describe('OpenAIService', () => {
tools: [{ type: 'function' as const, function: { name: 'test', description: 'test' } }]
};
vi.mocked(providers.getOpenAIOptions).mockReturnValueOnce(mockOptions);
const mockCompletion = {
id: 'chatcmpl-123',
object: 'chat.completion',
@@ -323,9 +319,9 @@ describe('OpenAIService', () => {
};
vi.spyOn(service as any, 'getClient').mockReturnValue(mockClient);
const result = await service.generateChatCompletion(messages);
expect(result).toEqual({
text: '',
model: 'gpt-3.5-turbo',
@@ -346,4 +342,4 @@ describe('OpenAIService', () => {
});
});
});
});
});

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { StreamProcessor, createStreamHandler, processProviderStream, extractStreamStats, performProviderHealthCheck } from './stream_handler.js';
import type { StreamProcessingOptions, StreamChunk } from './stream_handler.js';
import type { StreamProcessingOptions, StreamChunk, ProviderStreamOptions } from './stream_handler.js';
// Mock the log module
vi.mock('../../log.js', () => ({
@@ -86,7 +86,7 @@ describe('StreamProcessor', () => {
it('should handle callback errors gracefully', async () => {
const errorCallback = vi.fn().mockRejectedValue(new Error('Callback error'));
// Should not throw
await expect(StreamProcessor.sendChunkToCallback(errorCallback, 'test', false, {}, 1))
.resolves.toBeUndefined();
@@ -127,7 +127,7 @@ describe('StreamProcessor', () => {
it('should handle final callback errors gracefully', async () => {
const errorCallback = vi.fn().mockRejectedValue(new Error('Final callback error'));
await expect(StreamProcessor.sendFinalCallback(errorCallback, 'test'))
.resolves.toBeUndefined();
});
@@ -297,8 +297,8 @@ describe('processProviderStream', () => {
it('should handle tool calls in stream', async () => {
const chunks = [
{ message: { content: 'Using tool...' } },
{
message: {
{
message: {
tool_calls: [
{ id: 'call_1', function: { name: 'calculator', arguments: '{"x": 5}' } }
]
@@ -573,8 +573,8 @@ describe('Streaming edge cases and concurrency', () => {
it('should handle mixed content and tool calls', async () => {
const chunks = [
{ message: { content: 'Let me calculate that...' } },
{
message: {
{
message: {
content: '',
tool_calls: [{ id: '1', function: { name: 'calc' } }]
}
@@ -599,4 +599,4 @@ describe('Streaming edge cases and concurrency', () => {
expect(result.completeText).toBe('Let me calculate that...The answer is 42.');
expect(result.toolCalls).toHaveLength(1);
});
});
});

View File

@@ -1,6 +1,9 @@
"use strict";
import log from "./log.js";
import dataEncryptionService from "./encryption/data_encryption.js";
import options from "./options.js";
import ws from "./ws.js";
let dataKey: Buffer | null = null;
@@ -12,11 +15,11 @@ function getDataKey() {
return dataKey;
}
export function resetDataKey() {
function resetDataKey() {
dataKey = null;
}
export function isProtectedSessionAvailable() {
function isProtectedSessionAvailable() {
return !!dataKey;
}
@@ -54,8 +57,15 @@ function touchProtectedSession() {
}
}
export function getLastProtectedSessionOperationDate() {
return lastProtectedSessionOperationDate;
function checkProtectedSessionExpiration() {
const protectedSessionTimeout = options.getOptionInt("protectedSessionTimeout");
if (isProtectedSessionAvailable() && lastProtectedSessionOperationDate && Date.now() - lastProtectedSessionOperationDate > protectedSessionTimeout * 1000) {
resetDataKey();
log.info("Expiring protected session");
ws.reloadFrontend("leaving protected session");
}
}
export default {
@@ -65,5 +75,6 @@ export default {
encrypt,
decrypt,
decryptString,
touchProtectedSession
touchProtectedSession,
checkProtectedSessionExpiration
};

View File

@@ -4,11 +4,9 @@ import sqlInit from "./sql_init.js";
import config from "./config.js";
import log from "./log.js";
import attributeService from "../services/attributes.js";
import protectedSessionService from "../services/protected_session.js";
import hiddenSubtreeService from "./hidden_subtree.js";
import type BNote from "../becca/entities/bnote.js";
import options from "./options.js";
import { getLastProtectedSessionOperationDate, isProtectedSessionAvailable, resetDataKey } from "./protected_session.js";
import ws from "./ws.js";
function getRunAtHours(note: BNote): number[] {
try {
@@ -66,15 +64,5 @@ sqlInit.dbReady.then(() => {
);
}
setInterval(() => checkProtectedSessionExpiration(), 1);
setInterval(() => protectedSessionService.checkProtectedSessionExpiration(), 30000);
});
function checkProtectedSessionExpiration() {
const protectedSessionTimeout = options.getOptionInt("protectedSessionTimeout");
const lastProtectedSessionOperationDate = getLastProtectedSessionOperationDate();
if (isProtectedSessionAvailable() && lastProtectedSessionOperationDate && Date.now() - lastProtectedSessionOperationDate > protectedSessionTimeout * 1000) {
resetDataKey();
log.info("Expiring protected session");
ws.reloadFrontend("leaving protected session");
}
}

View File

@@ -9,7 +9,6 @@ describe("Share API test", () => {
let cannotSetHeadersCount = 0;
beforeAll(async () => {
vi.useFakeTimers();
const buildApp = (await import("../app.js")).default;
app = await buildApp();
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {

View File

@@ -1,7 +1,6 @@
import utils from "../services/utils.js";
import BNote from "../becca/entities/bnote.js";
import BAttribute from "../becca/entities/battribute.js";
import BBranch from "../becca/entities/bbranch.js";
type AttributeDefinitions = { [key in `#${string}`]: string; };
type RelationDefinitions = { [key in `~${string}`]: string; };
@@ -10,7 +9,6 @@ interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
id?: string | undefined;
title?: string;
content?: string;
children?: NoteDefinition[];
}
/**
@@ -53,18 +51,6 @@ export function buildNote(noteDef: NoteDefinition) {
note.getContent = () => noteDef.content!;
}
// Handle children
if (noteDef.children) {
for (const childDef of noteDef.children) {
const childNote = buildNote(childDef);
new BBranch({
noteId: childNote.noteId,
parentNoteId: note.noteId,
branchId: `${note.noteId}_${childNote.noteId}`
});
}
}
// Handle labels and relations.
let position = 0;
for (const [ key, value ] of Object.entries(noteDef)) {

View File

@@ -27,6 +27,6 @@ export default defineConfig(() => ({
provider: 'v8' as const,
reporter: [ "text", "html" ]
},
pool: "vmForks"
pool: "threads"
},
}));

View File

@@ -211,8 +211,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

View File

@@ -202,8 +202,7 @@ pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
* [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.
* [Larsa](https://github.com/LarsaSara) 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

29
docs/README-ar.md vendored
View File

@@ -1,4 +1,4 @@
# ملاحظات تريليوم
# Trilium Notes
![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran)
![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)\
@@ -22,12 +22,12 @@ quick overview:
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
## 📚توثيق
## 📚 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
@@ -35,8 +35,8 @@ quick overview:
- **GitHub**: Navigate through the [User
Guide](./docs/User%20Guide/User%20Guide/) in this repository
### روابط سريعة
- [دليل البدء السريع](https://docs.triliumnotes.org/)
### Quick Links
- [Getting Started Guide](https://docs.triliumnotes.org/)
- [Installation
Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker
@@ -48,7 +48,7 @@ quick overview:
- [Patterns of Personal Knowledge
Base](https://triliumnext.github.io/Docs/Wiki/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
@@ -135,7 +135,7 @@ compatible with the latest zadam/trilium version of
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!
@@ -157,7 +157,7 @@ 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.
@@ -179,7 +179,7 @@ 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).
@@ -194,7 +194,7 @@ 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
@@ -203,7 +203,7 @@ installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
## 💻 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/).
@@ -213,7 +213,7 @@ 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):
@@ -224,7 +224,7 @@ pnpm install
pnpm run server:start
```
### التوثيق
### Documentation
Download the repository, install dependencies using `pnpm` and then run the
environment required to edit the documentation:
@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-ca.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-cs.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-de.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-el.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-es.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-fa.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-fi.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-fr.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-hr.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-hu.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

315
docs/README-id.md vendored
View File

@@ -1,315 +0,0 @@
# 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)
![Unduhan GitHub (semua aset, semua
rilis)](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)
[![Status
terjemahan](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[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 adalah aplikasi pencatatan hierarkis lintas platform yang gratis
dan sumber terbuka dengan fokus untuk mengembangkan pengetahuan pribadi yang
luas.
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for
quick overview:
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
## 📚 Dokumentasi
**Kunjungi dokumentasi lengkap kami di
[docs.triliumnotes.org](https://docs.triliumnotes.org/)**
Dokumentasi kami tersedia dalam berbagai format:
- **Dokumentasi Online**: Telusuri dokumentasi lengkap di
[docs.triliumnotes.org](https://docs.triliumnotes.org/)
- **Bantuan Dalam Aplikasi**: Tekan `F1` di dalam Trilium untuk mengakses
dokumentasi yang sama langsung di aplikasi
- **GitHub**: Navigasi melalui [Panduan
Pengguna](./docs/User%20Guide/User%20Guide/) di repositori ini
### Tautan Cepat
- [Panduan Memulai](https://docs.triliumnotes.org/)
- [Petunjuk
Instalasi](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Pengaturan
Docker](./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)
- [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)
## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed
into multiple places in the tree (see
[cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
* Rich WYSIWYG note editor including e.g. tables, images and
[math](https://triliumnext.github.io/Docs/Wiki/text-notes) with markdown
[autoformat](https://triliumnext.github.io/Docs/Wiki/text-notes#autoformat)
* Support for editing [notes with source
code](https://triliumnext.github.io/Docs/Wiki/code-notes), including syntax
highlighting
* Fast and easy [navigation between
notes](https://triliumnext.github.io/Docs/Wiki/note-navigation), full text
search and [note
hoisting](https://triliumnext.github.io/Docs/Wiki/note-hoisting)
* Seamless [note
versioning](https://triliumnext.github.io/Docs/Wiki/note-revisions)
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/attributes) can be
used for note organization, querying and advanced
[scripting](https://triliumnext.github.io/Docs/Wiki/scripts)
* 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
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization)
with self-hosted sync server
* there's a [3rd party service for hosting synchronisation
server](https://trilium.cc/paid-hosting)
* [Sharing](https://triliumnext.github.io/Docs/Wiki/sharing) (publishing) notes
to public internet
* Strong [note
encryption](https://triliumnext.github.io/Docs/Wiki/protected-notes) with
per-note granularity
* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type
"canvas")
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map) and
[link maps](https://triliumnext.github.io/Docs/Wiki/link-map) for visualizing
notes and their relations
* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/)
* [Geo maps](./docs/User%20Guide/User%20Guide/Note%20Types/Geo%20Map.md) with
location pins and GPX tracks
* [Scripting](https://triliumnext.github.io/Docs/Wiki/scripts) - see [Advanced
showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases)
* [REST API](https://triliumnext.github.io/Docs/Wiki/etapi) for automation
* Scales well in both usability and performance upwards of 100 000 notes
* Touch optimized [mobile
frontend](https://triliumnext.github.io/Docs/Wiki/mobile-frontend) for
smartphones and tablets
* Built-in [dark theme](https://triliumnext.github.io/Docs/Wiki/themes), support
for user themes
* [Evernote](https://triliumnext.github.io/Docs/Wiki/evernote-import) and
[Markdown import & export](https://triliumnext.github.io/Docs/Wiki/markdown)
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/web-clipper) for easy
saving of web content
* Customizable UI (sidebar buttons, user-defined widgets, ...)
* [Metrics](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics.md), along
with a [Grafana
Dashboard](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics/grafana-dashboard.json)
✨ 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://triliumnext.github.io/Docs/Wiki/server-installation).
## 💻 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://triliumnext.github.io/Docs/Wiki/relation-map.html) and [link
maps](https://triliumnext.github.io/Docs/Wiki/note-map.html#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.

3
docs/README-it.md vendored
View File

@@ -260,8 +260,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

2
docs/README-ja.md vendored
View File

@@ -215,7 +215,7 @@ pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
## 👏 シャウトアウト
* [zadam](https://github.com/zadam) アプリケーションのオリジナルのコンセプトと実装に対して感謝します。
* [Sarah Hussein](https://github.com/Sarah-Hussein) アプリケーションアイコンをデザイン。
* [Larsa](https://github.com/LarsaSara) アプリケーションアイコンをデザイン。
* [nriver](https://github.com/nriver) 国際化への取り組み。
* [Thomas Frei](https://github.com/thfrei) Canvasへのオリジナルな取り組み。
* [antoniotejada](https://github.com/nriver) オリジナルの構文ハイライトウィジェット。

3
docs/README-ko.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-md.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-nl.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-pl.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-pt.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

5
docs/README-ro.md vendored
View File

@@ -153,7 +153,7 @@ sugestii sau probleme aveți!
## 🏗 Procesul de instalare
### Windows / macOS
### Windows / MacOS
Descărcați release-ul binar pentru platforma dvs. de pe pagina [ultimului
release](https://github.com/TriliumNext/Trilium/releases/latest), dezarhivați și
@@ -263,8 +263,7 @@ legăturile descrise în secțiunea „Discutați cu noi” de mai sus.
* [zadam](https://github.com/zadam) pentru conceptul și implementarea originală
a aplicației.
* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the
application icon.
* [Larsa](https://github.com/LarsaSara) pentru pictograma aplicației.
* [nriver](https://github.com/nriver) pentru sistemul de internaționalizare.
* [Thomas Frei](https://github.com/thfrei) pentru munca sa originală pentru
notițele de tip schiță.

5
docs/README-ru.md vendored
View File

@@ -261,8 +261,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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
@@ -308,7 +307,7 @@ Consider supporting the main developer
## 🔑 Лицензия
Copyright 2017-2025 zadam, Elian Doran и другие авторы
Copyright 2017-2025 zadam, Elian Doran, and other contributors
Эта программа является бесплатным программным обеспечением: вы можете
распространять и/или изменять ее в соответствии с условиями GNU Affero General

3
docs/README-sl.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-sr.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-tr.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-uk.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

3
docs/README-vi.md vendored
View File

@@ -259,8 +259,7 @@ described in the "Discuss with us" section above.
* [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.
* [Larsa](https://github.com/LarsaSara) 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

View File

@@ -200,7 +200,7 @@
# '/build/source/apps/desktop/node_modules/better-sqlite3/build/node_gyp_bins'
preBuildCommands = ''
export npm_config_nodedir=${electron.headers}
pnpm postinstall
pnpm postinstall || true
'';
buildTask = "desktop:build";
mainProgram = "trilium";

View File

@@ -37,7 +37,7 @@
"private": true,
"devDependencies": {
"@electron/rebuild": "4.0.1",
"@playwright/test": "1.56.0",
"@playwright/test": "1.55.1",
"@triliumnext/server": "workspace:*",
"@types/express": "5.0.3",
"@types/node": "22.18.8",
@@ -59,7 +59,7 @@
"tslib": "2.8.1",
"tsx": "4.20.6",
"typescript": "~5.9.0",
"typescript-eslint": "8.46.0",
"typescript-eslint": "8.45.0",
"upath": "2.0.1",
"vite": "7.1.9",
"vite-plugin-dts": "~4.5.0",
@@ -79,7 +79,7 @@
"url": "https://github.com/TriliumNext/Trilium/issues"
},
"homepage": "https://triliumnotes.org",
"packageManager": "pnpm@10.18.1",
"packageManager": "pnpm@10.18.0",
"pnpm": {
"patchedDependencies": {
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",
@@ -90,7 +90,7 @@
"mermaid": "11.12.0",
"preact": "10.27.2",
"roughjs": "4.6.6",
"@types/express-serve-static-core": "5.1.0",
"@types/express-serve-static-core": "5.0.7",
"flat@<5.0.1": ">=5.0.1",
"debug@>=3.2.0 <3.2.7": ">=3.2.7",
"nanoid@<3.3.8": ">=3.3.8",

View File

@@ -24,8 +24,8 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "4.1.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/eslint-plugin": "~8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -25,8 +25,8 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "4.1.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/eslint-plugin": "~8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -27,8 +27,8 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "4.1.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/eslint-plugin": "~8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -28,8 +28,8 @@
"@ckeditor/ckeditor5-dev-utils": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "4.1.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/eslint-plugin": "~8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -30,12 +30,7 @@ export default class MathCommand extends Command {
mathtex = writer.createElement(
display ? 'mathtex-display' : 'mathtex-inline',
{
...Object.fromEntries(selection.getAttributes()),
equation,
type,
display
}
{ equation, type, display }
);
} else {
const selection = this.editor.model.document.selection;
@@ -45,7 +40,7 @@ export default class MathCommand extends Command {
display ? 'mathtex-display' : 'mathtex-inline',
{
// Inherit all attributes from selection (e.g. color, background color, size).
...Object.fromEntries(selection.getAttributes()),
...Object.fromEntries( selection.getAttributes() ),
equation,
type: outputType,
display,

View File

@@ -27,8 +27,8 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "4.1.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/eslint-plugin": "~8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -49,9 +49,4 @@ export interface HiddenSubtreeItem {
* the user moves it around.
*/
enforceBranches?: boolean;
/**
* If set to true, then the attributes of this note will be checked. Any owned attribute that does not match the
* definitions will be removed.
*/
enforceAttributes?: boolean;
}

View File

@@ -17,7 +17,6 @@ const UNSORTED_LOCALES: Locale[] = [
{ id: "fr", name: "Français", electronLocale: "fr" },
{ id: "ja", name: "日本語", electronLocale: "ja" },
{ id: "pt_br", name: "Português (Brasil)", electronLocale: "pt_BR" },
{ id: "pt", name: "Português (Portugal)", electronLocale: "pt_PT" },
{ id: "ro", name: "Română", electronLocale: "ro" },
{ id: "ru", name: "Русский", electronLocale: "ru" },
{ id: "tw", name: "繁體中文", electronLocale: "zh_TW" },

View File

@@ -1,22 +1,98 @@
// Default list of allowed HTML tags
export const SANITIZER_DEFAULT_ALLOWED_TAGS = [
"h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "p", "a", "ul", "ol", "li", "b", "i", "strong", "em",
"strike", "s", "del", "abbr", "code", "hr", "br", "div", "table", "thead", "caption", "tbody", "tfoot",
"tr", "th", "td", "pre", "section", "img", "figure", "figcaption", "span", "label", "input", "details",
"summary", "address", "aside", "footer", "header", "hgroup", "main", "nav", "dl", "dt", "menu", "bdi",
"bdo", "dfn", "kbd", "mark", "q", "time", "var", "wbr", "area", "map", "track", "video", "audio", "picture",
"del", "ins",
// for ENEX import
"en-media",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"blockquote",
"p",
"a",
"ul",
"ol",
"li",
"b",
"i",
"strong",
"em",
"strike",
"s",
"del",
"abbr",
"code",
"hr",
"br",
"div",
"table",
"thead",
"caption",
"tbody",
"tfoot",
"tr",
"th",
"td",
"pre",
"section",
"img",
"figure",
"figcaption",
"span",
"label",
"input",
"details",
"summary",
"address",
"aside",
"footer",
"header",
"hgroup",
"main",
"nav",
"dl",
"dt",
"menu",
"bdi",
"bdo",
"dfn",
"kbd",
"mark",
"q",
"time",
"var",
"wbr",
"area",
"map",
"track",
"video",
"audio",
"picture",
"del",
"ins",
"en-media", // for ENEX import
// Additional tags (https://github.com/TriliumNext/Trilium/issues/567)
"acronym", "article", "big", "button", "cite", "col", "colgroup", "data", "dd", "fieldset", "form", "legend",
"meter", "noscript", "option", "progress", "rp", "samp", "small", "sub", "sup", "template", "textarea", "tt"
"acronym",
"article",
"big",
"button",
"cite",
"col",
"colgroup",
"data",
"dd",
"fieldset",
"form",
"legend",
"meter",
"noscript",
"option",
"progress",
"rp",
"samp",
"small",
"sub",
"sup",
"template",
"textarea",
"tt"
] as const;
export const ALLOWED_PROTOCOLS = [
'http', 'https', 'ftp', 'ftps', 'mailto', 'data', 'evernote', 'file', 'facetime', 'gemini', 'git',
'gopher', 'imap', 'irc', 'irc6', 'jabber', 'jar', 'lastfm', 'ldap', 'ldaps', 'magnet', 'message',
'mumble', 'nfs', 'onenote', 'pop', 'rmi', 's3', 'sftp', 'skype', 'sms', 'spotify', 'steam', 'svn', 'udp',
'view-source', 'vlc', 'vnc', 'ws', 'wss', 'xmpp', 'jdbc', 'slack', 'tel', 'smb', 'zotero', 'geo',
'mid', 'obsidian'
];

View File

@@ -54,11 +54,3 @@ export function trimIndentation(strings: TemplateStringsArray, ...values: any[])
}
return output.join("\n");
}
export function flushPromises() {
return new Promise(setImmediate);
}
export function sleepFor(duration: number) {
return new Promise(resolve => setTimeout(resolve, duration));
}

View File

@@ -24,8 +24,8 @@
"devDependencies": {
"@digitak/esrun": "3.2.26",
"@types/swagger-ui": "5.21.1",
"@typescript-eslint/eslint-plugin": "8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/eslint-plugin": "8.45.0",
"@typescript-eslint/parser": "8.45.0",
"dotenv": "17.2.3",
"esbuild": "0.25.10",
"eslint": "9.37.0",

260
pnpm-lock.yaml generated
View File

@@ -8,7 +8,7 @@ overrides:
mermaid: 11.12.0
preact: 10.27.2
roughjs: 4.6.6
'@types/express-serve-static-core': 5.1.0
'@types/express-serve-static-core': 5.0.7
flat@<5.0.1: '>=5.0.1'
debug@>=3.2.0 <3.2.7: '>=3.2.7'
nanoid@<3.3.8: '>=3.3.8'
@@ -41,8 +41,8 @@ importers:
specifier: 4.0.1
version: 4.0.1
'@playwright/test':
specifier: 1.56.0
version: 1.56.0
specifier: 1.55.1
version: 1.55.1
'@triliumnext/server':
specifier: workspace:*
version: link:apps/server
@@ -107,8 +107,8 @@ importers:
specifier: ~5.9.0
version: 5.9.3
typescript-eslint:
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
upath:
specifier: 2.0.1
version: 2.0.1
@@ -694,8 +694,8 @@ importers:
specifier: 0.6.0
version: 0.6.0
openai:
specifier: 6.2.0
version: 6.2.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.24.4)
specifier: 6.1.0
version: 6.1.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.24.4)
rand-token:
specifier: 1.0.1
version: 1.0.1
@@ -837,14 +837,14 @@ importers:
specifier: 4.1.0
version: 4.1.0(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.18.8)(bufferutil@4.0.9)(esbuild@0.25.10)(utf-8-validate@6.0.5)
'@typescript-eslint/eslint-plugin':
specifier: ~8.46.0
version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: ~8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@vitest/browser':
specifier: 3.2.4
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/coverage-istanbul':
specifier: 3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -897,14 +897,14 @@ importers:
specifier: 4.1.0
version: 4.1.0(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.18.8)(bufferutil@4.0.9)(esbuild@0.25.10)(utf-8-validate@6.0.5)
'@typescript-eslint/eslint-plugin':
specifier: ~8.46.0
version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: ~8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@vitest/browser':
specifier: 3.2.4
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/coverage-istanbul':
specifier: 3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -957,14 +957,14 @@ importers:
specifier: 4.1.0
version: 4.1.0(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.18.8)(bufferutil@4.0.9)(esbuild@0.25.10)(utf-8-validate@6.0.5)
'@typescript-eslint/eslint-plugin':
specifier: ~8.46.0
version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: ~8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@vitest/browser':
specifier: 3.2.4
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/coverage-istanbul':
specifier: 3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -1024,14 +1024,14 @@ importers:
specifier: 4.1.0
version: 4.1.0(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.18.8)(bufferutil@4.0.9)(esbuild@0.25.10)(utf-8-validate@6.0.5)
'@typescript-eslint/eslint-plugin':
specifier: ~8.46.0
version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: ~8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@vitest/browser':
specifier: 3.2.4
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/coverage-istanbul':
specifier: 3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -1091,14 +1091,14 @@ importers:
specifier: 4.1.0
version: 4.1.0(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.18.8)(bufferutil@4.0.9)(esbuild@0.25.10)(utf-8-validate@6.0.5)
'@typescript-eslint/eslint-plugin':
specifier: ~8.46.0
version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: ~8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@vitest/browser':
specifier: 3.2.4
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/coverage-istanbul':
specifier: 3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -1315,11 +1315,11 @@ importers:
specifier: 5.21.1
version: 5.21.1
'@typescript-eslint/eslint-plugin':
specifier: 8.46.0
version: 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.0
version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
dotenv:
specifier: 17.2.3
version: 17.2.3
@@ -3372,8 +3372,8 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@playwright/test@1.56.0':
resolution: {integrity: sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==}
'@playwright/test@1.55.1':
resolution: {integrity: sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==}
engines: {node: '>=18'}
hasBin: true
@@ -4715,8 +4715,8 @@ packages:
'@types/express-http-proxy@1.6.7':
resolution: {integrity: sha512-CEp9pbnwVI1RzN9PXc+KESMxwUW5r1O7tkWb5h7Wg/YAIf+KulD/zKev8fbbn+Ljt0Yvs8MXwV2W6Id+cKxe2Q==}
'@types/express-serve-static-core@5.1.0':
resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==}
'@types/express-serve-static-core@5.0.7':
resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==}
'@types/express-session@1.18.2':
resolution: {integrity: sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==}
@@ -4989,11 +4989,11 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/eslint-plugin@8.46.0':
resolution: {integrity: sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==}
'@typescript-eslint/eslint-plugin@8.45.0':
resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.46.0
'@typescript-eslint/parser': ^8.45.0
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
@@ -5004,8 +5004,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/parser@8.46.0':
resolution: {integrity: sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==}
'@typescript-eslint/parser@8.45.0':
resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -5023,8 +5023,8 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/project-service@8.46.0':
resolution: {integrity: sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==}
'@typescript-eslint/project-service@8.45.0':
resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
@@ -5037,8 +5037,8 @@ packages:
resolution: {integrity: sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/scope-manager@8.46.0':
resolution: {integrity: sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==}
'@typescript-eslint/scope-manager@8.45.0':
resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.40.0':
@@ -5059,12 +5059,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/tsconfig-utils@8.46.0':
resolution: {integrity: sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/type-utils@8.40.0':
resolution: {integrity: sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -5072,8 +5066,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/type-utils@8.46.0':
resolution: {integrity: sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==}
'@typescript-eslint/type-utils@8.45.0':
resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -5091,10 +5085,6 @@ packages:
resolution: {integrity: sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/types@8.46.0':
resolution: {integrity: sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.40.0':
resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -5107,8 +5097,8 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/typescript-estree@8.46.0':
resolution: {integrity: sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==}
'@typescript-eslint/typescript-estree@8.45.0':
resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
@@ -5127,8 +5117,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.46.0':
resolution: {integrity: sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==}
'@typescript-eslint/utils@8.45.0':
resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -5142,8 +5132,8 @@ packages:
resolution: {integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/visitor-keys@8.46.0':
resolution: {integrity: sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==}
'@typescript-eslint/visitor-keys@8.45.0':
resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
@@ -10106,8 +10096,8 @@ packages:
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
engines: {node: '>=18'}
openai@6.2.0:
resolution: {integrity: sha512-qqjzHls7F5xkXNGy9P1Ei1rorI5LWupUUFWP66zPU8FlZbiITX8SFcHMKNZg/NATJ0LpIZcMUFxSwQmdeQPwSw==}
openai@6.1.0:
resolution: {integrity: sha512-5sqb1wK67HoVgGlsPwcH2bUbkg66nnoIYKoyV9zi5pZPqh7EWlmSrSDjAh4O5jaIg/0rIlcDKBtWvZBuacmGZg==}
hasBin: true
peerDependencies:
ws: ^8.18.0
@@ -10438,13 +10428,13 @@ packages:
pkg-types@2.1.0:
resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==}
playwright-core@1.56.0:
resolution: {integrity: sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==}
playwright-core@1.55.1:
resolution: {integrity: sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==}
engines: {node: '>=18'}
hasBin: true
playwright@1.56.0:
resolution: {integrity: sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==}
playwright@1.55.1:
resolution: {integrity: sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==}
engines: {node: '>=18'}
hasBin: true
@@ -13022,8 +13012,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
typescript-eslint@8.46.0:
resolution: {integrity: sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==}
typescript-eslint@8.45.0:
resolution: {integrity: sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -14724,8 +14714,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.0.0
'@ckeditor/ckeditor5-watchdog': 47.0.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@@ -14917,8 +14905,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.0.0
ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-multi-root@47.0.0':
dependencies:
@@ -14941,8 +14927,6 @@ snapshots:
'@ckeditor/ckeditor5-table': 47.0.0
'@ckeditor/ckeditor5-utils': 47.0.0
ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-emoji@47.0.0':
dependencies:
@@ -15114,8 +15098,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.0.0
ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-icons@47.0.0': {}
@@ -15502,6 +15484,8 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.0.0
'@ckeditor/ckeditor5-utils': 47.0.0
ckeditor5: 47.0.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-special-characters@47.0.0':
dependencies:
@@ -17567,9 +17551,9 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@playwright/test@1.56.0':
'@playwright/test@1.55.1':
dependencies:
playwright: 1.56.0
playwright: 1.55.1
'@polka/url@1.0.0-next.29': {}
@@ -18793,7 +18777,7 @@ snapshots:
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 5.1.0
'@types/express-serve-static-core': 5.0.7
'@types/node': 22.18.8
'@types/connect@3.4.38':
@@ -18964,7 +18948,7 @@ snapshots:
dependencies:
'@types/express': 5.0.3
'@types/express-serve-static-core@5.1.0':
'@types/express-serve-static-core@5.0.7':
dependencies:
'@types/node': 22.18.8
'@types/qs': 6.14.0
@@ -18978,14 +18962,14 @@ snapshots:
'@types/express@4.17.23':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 5.1.0
'@types/express-serve-static-core': 5.0.7
'@types/qs': 6.14.0
'@types/serve-static': 1.15.9
'@types/express@5.0.3':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 5.1.0
'@types/express-serve-static-core': 5.0.7
'@types/serve-static': 1.15.9
'@types/fs-extra@11.0.4':
@@ -19286,14 +19270,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.46.0
'@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.0
'@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.45.0
'@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.45.0
eslint: 9.37.0(jiti@2.6.1)
graphemer: 1.4.0
ignore: 7.0.5
@@ -19315,12 +19299,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.46.0
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.0
'@typescript-eslint/scope-manager': 8.45.0
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.45.0
debug: 4.4.3(supports-color@6.0.0)
eslint: 9.37.0(jiti@2.6.1)
typescript: 5.9.3
@@ -19345,10 +19329,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.46.0(typescript@5.9.3)':
'@typescript-eslint/project-service@8.45.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.46.0(typescript@5.9.3)
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3)
'@typescript-eslint/types': 8.45.0
debug: 4.4.3(supports-color@6.0.0)
typescript: 5.9.3
transitivePeerDependencies:
@@ -19364,10 +19348,10 @@ snapshots:
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/visitor-keys': 8.44.1
'@typescript-eslint/scope-manager@8.46.0':
'@typescript-eslint/scope-manager@8.45.0':
dependencies:
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/visitor-keys': 8.46.0
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/visitor-keys': 8.45.0
'@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.3)':
dependencies:
@@ -19381,10 +19365,6 @@ snapshots:
dependencies:
typescript: 5.9.3
'@typescript-eslint/tsconfig-utils@8.46.0(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.40.0
@@ -19397,11 +19377,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/type-utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
debug: 4.4.3(supports-color@6.0.0)
eslint: 9.37.0(jiti@2.6.1)
ts-api-utils: 2.1.0(typescript@5.9.3)
@@ -19415,8 +19395,6 @@ snapshots:
'@typescript-eslint/types@8.45.0': {}
'@typescript-eslint/types@8.46.0': {}
'@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.40.0(typescript@5.9.3)
@@ -19449,12 +19427,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@8.46.0(typescript@5.9.3)':
'@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.46.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.46.0(typescript@5.9.3)
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/visitor-keys': 8.46.0
'@typescript-eslint/project-service': 8.45.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3)
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/visitor-keys': 8.45.0
debug: 4.4.3(supports-color@6.0.0)
fast-glob: 3.3.3
is-glob: 4.0.3
@@ -19487,12 +19465,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1))
'@typescript-eslint/scope-manager': 8.46.0
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.45.0
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
eslint: 9.37.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
@@ -19508,9 +19486,9 @@ snapshots:
'@typescript-eslint/types': 8.44.1
eslint-visitor-keys: 4.2.1
'@typescript-eslint/visitor-keys@8.46.0':
'@typescript-eslint/visitor-keys@8.45.0':
dependencies:
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/types': 8.45.0
eslint-visitor-keys: 4.2.1
'@ungap/structured-clone@1.3.0': {}
@@ -19546,7 +19524,7 @@ snapshots:
- bufferutil
- utf-8-validate
'@vitest/browser@3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))':
'@vitest/browser@3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))':
dependencies:
'@testing-library/dom': 10.4.0
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0)
@@ -19558,7 +19536,7 @@ snapshots:
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.8)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@19.0.2)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
optionalDependencies:
playwright: 1.56.0
playwright: 1.55.1
webdriverio: 9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
transitivePeerDependencies:
- bufferutil
@@ -19599,7 +19577,7 @@ snapshots:
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.8)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@19.0.2)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
optionalDependencies:
'@vitest/browser': 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/browser': 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
transitivePeerDependencies:
- supports-color
@@ -25939,7 +25917,7 @@ snapshots:
is-inside-container: 1.0.0
wsl-utils: 0.1.0
openai@6.2.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.24.4):
openai@6.1.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.24.4):
optionalDependencies:
ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
zod: 3.24.4
@@ -26273,11 +26251,11 @@ snapshots:
exsolve: 1.0.5
pathe: 2.0.3
playwright-core@1.56.0: {}
playwright-core@1.55.1: {}
playwright@1.56.0:
playwright@1.55.1:
dependencies:
playwright-core: 1.56.0
playwright-core: 1.55.1
optionalDependencies:
fsevents: 2.3.2
@@ -29456,12 +29434,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
typescript-eslint@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3):
typescript-eslint@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.37.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
@@ -29828,7 +29806,7 @@ snapshots:
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 22.18.8
'@vitest/browser': 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.56.0)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/browser': 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.18.8)(typescript@5.9.3))(playwright@1.55.1)(utf-8-validate@6.0.5)(vite@7.1.9(@types/node@22.18.8)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))
'@vitest/ui': 3.2.4(vitest@3.2.4)
happy-dom: 19.0.2
jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
@@ -29994,7 +29972,7 @@ snapshots:
'@types/bonjour': 3.5.13
'@types/connect-history-api-fallback': 1.5.4
'@types/express': 4.17.23
'@types/express-serve-static-core': 5.1.0
'@types/express-serve-static-core': 5.0.7
'@types/serve-index': 1.9.4
'@types/serve-static': 1.15.9
'@types/sockjs': 0.3.36

View File

@@ -23,7 +23,7 @@ function copyNativeDependencies(projectRoot: string) {
cpSync(sourcePath, destPath, { recursive: true, dereference: true });
}
async function rebuildNativeDependencies(projectRoot: string) {
function rebuildNativeDependencies(projectRoot: string) {
const electronVersion = determineElectronVersion(projectRoot);
if (!electronVersion) {
@@ -35,7 +35,7 @@ async function rebuildNativeDependencies(projectRoot: string) {
console.log(`Rebuilding ${projectRoot} with ${electronVersion} for ${targetArch}...`);
const resolvedPath = resolve(projectRoot);
await rebuild({
rebuild({
projectRootPath: resolvedPath,
buildPath: resolvedPath,
electronVersion,
@@ -64,5 +64,5 @@ function determineElectronVersion(projectRoot: string) {
for (const projectRoot of [ "apps/desktop", "apps/edit-docs" ]) {
copyNativeDependencies(projectRoot);
await rebuildNativeDependencies(projectRoot);
rebuildNativeDependencies(projectRoot);
}