Compare commits

...

80 Commits

Author SHA1 Message Date
Jin
64f191b83b Merge branch 'main' into feat/clean-share-url 2025-10-07 23:39:38 +01:00
Elian Doran
31c8e96d70 chore(forge): set wayland socket for flatpak build 2025-10-07 18:38:13 +03:00
Elian Doran
1191421388 fix(flake): bettersqlite3 dependency not build properly 2025-10-07 18:27:13 +03:00
Elian Doran
831a184c2a test(server): try to use fake timers to avoid flaky test in the CI 2025-10-07 15:34:58 +03:00
Elian Doran
c671f91bca test(server): fix test failures 2025-10-07 14:27:05 +03:00
Elian Doran
fa436c7ce6 test(server): switch to vmForks due to better error reporting 2025-10-07 14:25:58 +03:00
Elian Doran
d1367286c9 test(server): fix unrejected failure in test 2025-10-07 14:20:06 +03:00
Elian Doran
d10b0fa823 fix(hidden_subtree): builtin templates have #excludeFromNoteMap (closes #7187) 2025-10-07 12:20:29 +03:00
Elian Doran
545c8648b7 feat(demo): add a template with #excludeFromNoteMap (closes #3547) 2025-10-07 11:49:36 +03:00
Elian Doran
d6e9acc149 fix(ckeditor5-math): equation not maintaining styles (closes #7211) 2025-10-07 11:14:56 +03:00
Elian Doran
26e14aff7b Translations update from Hosted Weblate (#7219) 2025-10-07 10:17:34 +03:00
Elian Doran
280ec5b406 Translated using Weblate (Portuguese)
Currently translated at 0.8% (1 of 115 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pt/
2025-10-07 09:17:03 +02:00
Miguel Dias
26081ffd36 Translated using Weblate (Portuguese)
Currently translated at 0.1% (2 of 1605 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt/
2025-10-07 09:17:02 +02:00
Miguel Dias
f106cbf6c0 Translated using Weblate (Portuguese)
Currently translated at 99.7% (380 of 381 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pt/
2025-10-07 09:17:02 +02:00
green
f3877a52ab Translated using Weblate (Japanese)
Currently translated at 100.0% (1605 of 1605 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-10-07 09:17:01 +02:00
Hosted Weblate
5becf60a63 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-10-07 09:17:00 +02:00
Elian Doran
2ea96dc8f8 Revert "fix(deps): update dependency mind-elixir to v5.3.1" (#7220) 2025-10-07 10:16:54 +03:00
Elian Doran
e48724662e Revert "fix(deps): update dependency mind-elixir to v5.3.1" 2025-10-07 10:16:42 +03:00
Elian Doran
745ce7de76 fix(deps): update dependency mind-elixir to v5.3.1 (#7202) 2025-10-07 10:16:16 +03:00
Elian Doran
997217861c chore(deps): update pnpm to v10.18.1 (#7212) 2025-10-07 10:15:49 +03:00
Elian Doran
44f8e8b833 chore(deps): update dependency @playwright/test to v1.56.0 (#7213) 2025-10-07 10:15:25 +03:00
Elian Doran
99e2b63ff0 chore(deps): update dependency @types/express-serve-static-core to v5.1.0 (#7214) 2025-10-07 10:15:08 +03:00
Elian Doran
0139d90ac7 chore(deps): update dependency openai to v6.2.0 (#7215) 2025-10-07 10:14:55 +03:00
Elian Doran
5b7484c27c chore(deps): update typescript-eslint monorepo to v8.46.0 (#7216) 2025-10-07 10:14:46 +03:00
renovate[bot]
71e64be44c fix(deps): update dependency mind-elixir to v5.3.1 2025-10-07 07:05:43 +00:00
renovate[bot]
639651329a chore(deps): update typescript-eslint monorepo to v8.46.0 2025-10-07 07:04:54 +00:00
renovate[bot]
a7a0d3584a chore(deps): update dependency openai to v6.2.0 2025-10-07 07:04:10 +00:00
renovate[bot]
f765441f1e chore(deps): update dependency @types/express-serve-static-core to v5.1.0 2025-10-07 07:03:35 +00:00
renovate[bot]
10cd5bf130 chore(deps): update dependency @playwright/test to v1.56.0 2025-10-07 07:02:53 +00:00
renovate[bot]
d93c5dfeea chore(deps): update pnpm to v10.18.1 2025-10-07 07:02:08 +00:00
Elian Doran
b58aac1298 fix(client): invalid import after moving protocol config 2025-10-07 09:59:33 +03:00
Elian Doran
d662718a4a feat(client): allow opening obsidian: links (closes #7207) 2025-10-06 20:05:42 +03:00
Elian Doran
bebd3d430b refactor(commons): deduplicate allowed protocols 2025-10-06 20:04:07 +03:00
Elian Doran
2fca995725 docs(guide): update html docs with sharing 2025-10-06 19:58:33 +03:00
Elian Doran
caa1ea12f1 feat: Add shareHtml relation for custom HTML snippets in shared pages (#7157) 2025-10-06 19:32:17 +03:00
Elian Doran
f25e4ea391 Translations update from Hosted Weblate (#7210) 2025-10-06 19:22:42 +03:00
Hosted Weblate
05b433edb5 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-10-06 15:55:10 +00:00
Kevin Leutzinger
b8851565eb docs: clarify shareHtmlLocation goes on snippet note 2025-10-01 03:53:40 -04:00
Kevin Leutzinger
888d0d1084 add docs about shareHtml and shareHtmlLocation 2025-10-01 03:47:49 -04:00
Kevin Leutzinger
0a25d4db0d shareHTML to shareHtml 2025-10-01 03:30:16 -04:00
Kevin Leutzinger
d483b6e840 add shareHTML relation 2025-09-30 23:06:20 -04:00
JYC333
979fbe2e76 Merge branch 'main' into feat/clean-share-url 2025-08-13 18:28:27 +02:00
Jin
ea5564c6e6 docs: ✏️ update docs 2025-06-26 23:48:05 +02:00
Jin
6ab8750726 feat: 🎸 update translation 2025-06-26 23:15:23 +02:00
Jin
923eabd750 feat: 🎸 Allow multi-segement path 2025-06-26 23:08:54 +02:00
Jin
c52d6a6384 Merge branch 'main' into feat/clean-share-url 2025-06-26 22:53:35 +02:00
Jin
519b9648c6 test: 💍 Fix test for path input 2025-06-26 22:30:24 +02:00
Jin
b25fd6ca3a feat: 🎸 update custom share path without restart server 2025-06-26 22:30:24 +02:00
Panagiotis Papadopoulos
2bd8c215ff Merge branch 'develop' into feat/clean-share-url 2025-05-12 20:38:28 +02:00
Panagiotis Papadopoulos
34e7901de9 refactor: remove "cleanUrl" related string for now
that should be part of a later PR
2025-04-25 09:12:29 +02:00
Panagiotis Papadopoulos
30a191cedf fix(share_settings): disallow "/" as share root for now as it is not working
this will be handled by cleanUrl PR later on
2025-04-25 08:53:13 +02:00
Panagiotis Papadopoulos
128d8907c3 chore(share_settings): add a TODO hint for currently active bug 2025-04-25 08:48:28 +02:00
Panagiotis Papadopoulos
3b1d7d045e feat: improve example and wording for share_path_description
needs to currently mention, that a server restart is required
2025-04-25 08:40:43 +02:00
Panagiotis Papadopoulos
0ae9a29e0d refactor: remove "cleanUrl" related code for now
that should be part of a later PR
2025-04-25 08:38:48 +02:00
Panagiotis Papadopoulos
f4b5ed73ad refactor(options_init): remove sharePath normalization
normalization is happening in the share settings options widget in the meantime, making it more obvious to the user
2025-04-25 08:31:27 +02:00
Panagiotis Papadopoulos
43166dbeb5 refactor: remove "cleanUrl" related code for now
that should be part of a later PR
2025-04-25 08:27:01 +02:00
Panagiotis Papadopoulos
00b5aef890 feat(share_settings): add support for adding "/" as sharePath 2025-04-21 10:45:41 +02:00
Panagiotis Papadopoulos
1b7266f083 chore(share_settings): remove unnecessary comment 2025-04-21 10:25:09 +02:00
Panagiotis Papadopoulos
d1d4b47111 test(share_settings): add initial test for normalizeSharePathInput
it currently fails trying to import the class though
a "manual" importing of the function did pass all checks though

-> will need to investigate why importing the class does not work like that
2025-04-21 09:15:48 +02:00
Panagiotis Papadopoulos
c90364bd76 feat(share_settings): improve sharePath input handling
* add normalization helper to ensure string does not start with multiple trailing slashes or ends with a trailing slash
* fall back to default "/share" when empty string is entered
2025-04-21 09:10:21 +02:00
Panagiotis Papadopoulos
6dc687ef43 feat(share_settings): improve checkShareRoot
* add rudimental error handling
* add handling of special case, where one has multiple shared notes with a #shareRoot label
* refactor styling into auxiliary setCheckShareRootStyle function
2025-04-21 07:54:24 +02:00
Panagiotis Papadopoulos
0e31aab1ab refactor(share_settings): use this.$shareRootCheck instead of creating new local $button variable 2025-04-20 19:24:58 +02:00
Panagiotis Papadopoulos
b0030f89b7 feat(share_settings): group options that belong together logically 2025-04-20 19:21:38 +02:00
Panagiotis Papadopoulos
9f0a0238cc fix(share_settings): stop runnning checkShareRoot on init and on redirectBareDomain change
→ that is what the shareRootCheck button is there for
2025-04-20 19:19:30 +02:00
Panagiotis Papadopoulos
dabdfaddec fix(share/routes): remove unnecessary redirects that cause loops 2025-04-20 10:40:20 +02:00
Panagiotis Papadopoulos
a8901e6dc8 chore: revert back unnecessary changes from unclean merge
c9d151289c
2025-04-18 11:53:56 +02:00
Panagiotis Papadopoulos
ab901a5d32 refactor(share_settings): get rid of save() method
there's no need to execute PUT requests for *all* Share Settings, when any option changes

moved the code to inside the "change" event handlers
2025-04-18 02:05:40 +02:00
Panagiotis Papadopoulos
56fc2d9b30 fix(share_settings): fix not being able to set share path
caused by having other several save operation inside the save() method (which doesn't even make sense to begin with, as far as I can tell)

moving it to inside the "change" event handler allows us to set and store a custom share path again
2025-04-18 02:00:23 +02:00
Panagiotis Papadopoulos
df45fa2e1e fix(share/routes): fix redirect loop
commented out for now, to make sure we get back to it – not sure if it was suppossed to have any special other reason to exist
2025-04-18 01:49:34 +02:00
Panagiotis Papadopoulos
9a11fc13d7 fix(share_settings): fix missing class in redirect-bare-domain input 2025-04-18 01:07:54 +02:00
Panagiotis Papadopoulos
d72a0d3c69 fix(services/auth): fix crash on clean DB startup when options are not set 2025-04-18 00:15:25 +02:00
Panagiotis Papadopoulos
0be508ed70 fix(share/routes): fix crash on clean DB startup when sharePath option is not set 2025-04-18 00:10:29 +02:00
Panagiotis Papadopoulos
37f3a9b19d Merge branch 'develop' into feat/clean-share-url 2025-04-17 23:43:18 +02:00
Jin
81d2fbc057 fix: 🐛 add back missing translation 2025-04-11 17:54:54 +02:00
Jin
1aecf66cbe Merge branch 'develop' into feat/clean-share-url 2025-04-10 21:47:44 +02:00
Jin
fbe7d64e00 Merge branch 'develop' into feat/clean-share-url 2025-04-04 19:06:10 +02:00
matt wilkie
c9d151289c conflicts fixed - Merge remote-tracking branch 'origin/develop'
1st time using Meld. I'm not sure if the conflict resolve is clean
2025-03-08 09:49:06 -07:00
matt wilkie
bba2f6db64 wip: another attempt (and use translation syntax this time) 2025-03-08 09:35:52 -07:00
matt wilkie
6725f81a00 WIP: options page works, but routing broken when logged out 2025-02-26 03:10:55 -07:00
matt wilkie
8487c8cd03 WIP: allow no share path url prefix at all (not working yet) 2025-02-25 14:51:29 -07:00
55 changed files with 14219 additions and 13872 deletions

View File

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

View File

@@ -3,16 +3,7 @@ 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";
// 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'
];
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
function getNotePathFromUrl(url: string) {
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);

View File

@@ -1743,9 +1743,17 @@
"show_login_link": "在共享主题中显示登录链接",
"show_login_link_description": "在共享页面底部添加登录链接",
"check_share_root": "检查共享根状态",
"check_share_root_error": "检查共享根状态时发生意外错误,请检查日志以获取更多信息。",
"share_note_title": "'{{noteTitle}}'",
"share_root_found": "共享根笔记 '{{noteTitle}}' 已准备好",
"share_root_not_found": "未找到带有 #shareRoot 标签的笔记",
"share_root_not_shared": "笔记 '{{noteTitle}}' 具有 #shareRoot 标签,但未共享"
"share_root_not_shared": "笔记 '{{noteTitle}}' 具有 #shareRoot 标签,但未共享",
"share_root_multiple_found": "找到多个具有 #shareRoot 标签的共享笔记:{{- foundNoteTitles}}。将使用笔记 {{- activeNoteTitle}} 作为共享根笔记。",
"share_path": "共享路径",
"share_path_description": "共享笔记的 URL 前缀(例如 '/share' --> '/share/noteId' 或 '/custom-path' --> '/custom-path/noteId')。支持多级嵌套(例如 '/custom-path/sub-path' --> '/custom-path/sub-path/noteId')。刷新页面以应用更改。",
"share_path_placeholder": "/share 或 /custom-path",
"share_subtree": "共享子树",
"share_subtree_description": "共享整个子树,而不是仅共享笔记"
},
"time_selector": {
"invalid_input": "输入的时间值不是有效数字。",

View File

@@ -1907,9 +1907,17 @@
"show_login_link": "Show Login link in Share theme",
"show_login_link_description": "Add a login link to the Share page footer",
"check_share_root": "Check Share Root Status",
"check_share_root_error": "An unexpected error happened while checking the Share Root Status, please check the logs for more information.",
"share_note_title": "'{{noteTitle}}'",
"share_root_found": "Share root note '{{noteTitle}}' is ready",
"share_root_not_found": "No note with #shareRoot label found",
"share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not shared"
"share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not Shared",
"share_root_multiple_found": "Found multiple shared notes with a #shareRoot label: {{- foundNoteTitles}}. The note {{- activeNoteTitle}} will be used as shared root note.",
"share_path": "Share path",
"share_path_description": "The url prefix for shared notes (e.g. '/share' --> '/share/noteId' or '/custom-path' --> '/custom-path/noteId'). Multiple levels of nesting are supported (e.g. '/custom-path/sub-path' --> '/custom-path/sub-path/noteId'). Refresh the page to apply the changes.",
"share_path_placeholder": "/share or /custom-path",
"share_subtree": "Share subtree",
"share_subtree_description": "Share the entire subtree, not just the note"
},
"time_selector": {
"invalid_input": "The entered time value is not a valid number.",

View File

@@ -251,12 +251,12 @@
"help": {
"title": "チートシート",
"noteNavigation": "ノートナビゲーション",
"collapseExpand": "ノードの格納/展開",
"collapseExpand": "ノードを折りたたむ / 展開",
"goBackForwards": "履歴を戻る/進む",
"scrollToActiveNote": "アクティブノートまでスクロール",
"jumpToParentNote": "親ノートへ移動",
"collapseWholeTree": "すべてのノートツリーを格納",
"collapseSubTree": "サブツリーを格納",
"collapseWholeTree": "すべてのノートツリーを折りたたむ",
"collapseSubTree": "サブツリーを折りたたむ",
"tabShortcuts": "タブショートカット",
"newTabNoteLink": "ノートのリンクをクリックすると、新しいタブで開く",
"newTabWithActivationNoteLink": "ノートのリンクをクリックすると、新しいタブで開き、アクティブにします",
@@ -515,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}}'",

View File

@@ -1 +1,6 @@
{}
{
"about": {
"title": "Acerca de \"Trillium Notes\"",
"app_version": "Versão da aplicação:"
}
}

View File

@@ -1,5 +1,4 @@
import { ALLOWED_PROTOCOLS } from "../../../services/link.js";
import { MIME_TYPE_AUTO } from "@triliumnext/commons";
import { ALLOWED_PROTOCOLS, 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

@@ -0,0 +1,13 @@
// Ensure sharePath always starts with a single slash and does not end with (one or multiple) trailing slashes
export function normalizeSharePathInput(sharePathInput: string) {
const REGEXP_STARTING_SLASH = /^\/+/g;
const REGEXP_TRAILING_SLASH = /\b\/+$/g;
const normalizedSharePath = (!sharePathInput.startsWith("/")
? `/${sharePathInput}`
: sharePathInput)
.replaceAll(REGEXP_TRAILING_SLASH, "")
.replaceAll(REGEXP_STARTING_SLASH, "/");
return normalizedSharePath;
}

View File

@@ -0,0 +1,58 @@
import { describe, it, expect } from "vitest";
import { normalizeSharePathInput } from "./share_path_utils.js";
type TestCase<T extends (...args: any) => any> = [
desc: string,
fnParams: Parameters<T>,
expected: ReturnType<T>
];
describe("ShareSettingsOptions", () => {
describe("#normalizeSharePathInput", () => {
const testCases: TestCase<typeof normalizeSharePathInput>[] = [
[
"should handle multiple trailing '/' and remove them completely",
["/trailingtest////"],
"/trailingtest"
],
[
"should handle multiple starting '/' and replace them by a single '/'",
["////startingtest"],
"/startingtest"
],
[
"should handle multiple starting & trailing '/' and replace them by a single '/'",
["////startingAndTrailingTest///"],
"/startingAndTrailingTest"
],
[
"should not remove any '/' other than at the end or start of the input",
["/test/with/subpath"],
"/test/with/subpath"
],
[
"should prepend the string with a '/' if it does not start with one",
["testpath"],
"/testpath"
],
[
"should not change anything, if the string is a single '/'",
["/"],
"/"
],
];
testCases.forEach((testCase) => {
const [desc, fnParams, expected] = testCase;
it(desc, () => {
const actual = normalizeSharePathInput(...fnParams);
expect(actual).toStrictEqual(expected);
});
});
})
})

View File

@@ -90,6 +90,10 @@ 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.98.1",
"appVersion": "0.99.1",
"files": [
{
"isClone": false,
@@ -60,6 +60,13 @@
"value": "dayGridMonth",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "dateTemplate",
"value": "bRQvb9VCkc3t",
"isInheritable": false,
"position": 50
}
],
"dataFileName": "Journal.dat",
@@ -75,7 +82,7 @@
"title": "Trilium Demo",
"notePosition": 20,
"prefix": null,
"isExpanded": true,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
@@ -6033,6 +6040,68 @@
]
}
]
},
{
"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,6 +637,12 @@
</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

@@ -0,0 +1,24 @@
<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,10 +23,18 @@
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,6 +18,10 @@
# This script opens 4 terminal windows.
@@ -26,18 +30,38 @@
i="0"
while [ $i -lt 4 ]
do
xterm &amp;
@@ -46,10 +70,22 @@ 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.1.0",
"openai": "6.2.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",

Binary file not shown.

View File

@@ -116,6 +116,13 @@ class="admonition tip">
<td>JavaScript note which will be injected into the share page. JS note must
be in the shared sub-tree as well. Consider using <code>share_hidden_from_tree</code>.</td>
</tr>
<tr>
<td><code>shareHtml</code>
</td>
<td>HTML note which will be injected into the share page at locations specified
by the <code>shareHtmlLocation</code> label. HTML note must be in the shared
sub-tree as well. Consider using <code>share_hidden_from_tree</code>.</td>
</tr>
<tr>
<td><code>shareTemplate</code>
</td>

View File

@@ -6,8 +6,7 @@ class="image">
<img style="aspect-ratio:1144/660;" src="Sharing_image.png" width="1144"
height="660">
</figure>
<h2>Features, interaction and limitations</h2>
<h2>Features, interaction and limitations</h2>
<ul>
<li>Searching by note title.</li>
<li>Automatic dark/light mode based on the user's browser settings.</li>
@@ -189,11 +188,9 @@ class="image">
<img src="Sharing_share-single-note.png" alt="Share Note">
</p>
</li>
<li>
<p><strong>Access the Shared Note</strong>: The link provided will open the
note in your browser. If your server is not configured with a public IP,
the URL will refer to <code>localhost (127.0.0.1)</code>.</p>
</li>
<li><strong>Access the Shared Note</strong>: The link provided will open the
note in your browser. If your server is not configured with a public IP,
the URL will refer to <code>localhost (127.0.0.1)</code>.</li>
</ol>
<h2>Sharing a note subtree</h2>
<p>When you share a note, you actually share the entire subtree of notes
@@ -234,6 +231,34 @@ class="image">
This allows you to access note attributes or traverse the note tree using
the <code>fetchNote()</code> API, which retrieves note data based on its
ID.</p>
<h3>Adding custom HTML</h3>
<p>You can inject custom HTML snippets into specific locations of the shared
page using the <code>~shareHtml</code> relation. The HTML note should contain
the raw HTML content you want to inject, and you can control where it appears
by adding the <code>#shareHtmlLocation</code> label to the HTML snippet note
itself.</p>
<p>The <code>#shareHtmlLocation</code> label accepts values in the format <code>location:position</code>:</p>
<ul>
<li><strong>Locations</strong>: <code>head</code>, <code>body</code>, <code>content</code>
</li>
<li><strong>Positions</strong>: <code>start</code>, <code>end</code>
</li>
</ul>
<p>For example:</p>
<ul>
<li><code>#shareHtmlLocation=head:start</code> - Injects HTML at the beginning
of the <code>&lt;head&gt;</code> section</li>
<li><code>#shareHtmlLocation=head:end</code> - Injects HTML at the end of the <code>&lt;head&gt;</code> section
(default)</li>
<li><code>#shareHtmlLocation=body:start</code> - Injects HTML at the beginning
of the <code>&lt;body&gt;</code> section</li>
<li><code>#shareHtmlLocation=content:start</code> - Injects HTML at the beginning
of the content area</li>
<li><code>#shareHtmlLocation=content:end</code> - Injects HTML at the end of
the content area</li>
</ul>
<p>If no location is specified, the HTML will be injected at <code>content:end</code> by
default.</p>
<p>Example:</p><pre><code class="language-application-javascript-env-backend">const currentNote = await fetchNote();
const parentNote = await fetchNote(currentNote.parentNoteIds[0]);
@@ -344,6 +369,14 @@ for (const attr of parentNote.attributes) {
</td>
<td>Note with this label will list all roots of shared notes.</td>
</tr>
<tr>
<td><code>shareHtmlLocation</code>
</td>
<td>defines where custom HTML injected via <code>~shareHtml</code> relation
should be placed. Applied to the HTML snippet note itself. Format: <code>location:position</code> where
location is <code>head</code>, <code>body</code>, or <code>content</code> and
position is <code>start</code> or <code>end</code>. Defaults to <code>content:end</code>.</td>
</tr>
</tbody>
</table>

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 próxima nota no histórico",
"forward-in-note-history": "Navegar para a nota seguinte 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 completa de notas",
"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 de notas completa",
"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 subnota da nota atual",
"create-note-into-inbox": "Crie uma nota na caixa de entrada (se definida) ou na nota do dia",
"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",
"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": "Pular da árvore para os pormenores da nota e editar o título",
"edit-note-title": "Saltar 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á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",
"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",
"open-new-window": "Abre nova janela vazia",
"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",
"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",
"dialogs": "Diálogos",
"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",
"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",
"text-note-operations": "Operações de nota de texto",
"add-link-to-text": "Abrir diálogo e adicionar ligação ao texto",
"add-link-to-text": "Abrir diálogo para 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

@@ -16,7 +16,8 @@ vi.mock("../../services/ws.js", () => ({
default: {
sendMessageToAllClients: vi.fn(),
sendTransactionEntityChangesToAllClients: vi.fn(),
setLastSyncedPush: vi.fn()
setLastSyncedPush: vi.fn(),
syncFailed() {}
}
}));
@@ -81,7 +82,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;
@@ -91,14 +92,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");
}
@@ -154,7 +155,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),
@@ -171,18 +172,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({
@@ -202,7 +203,7 @@ describe("LLM API Tests", () => {
title: "Test Update Chat"
})
.expect(200);
createdChatId = createResponse.body.id;
}
@@ -224,7 +225,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);
});
});
@@ -240,7 +241,7 @@ describe("LLM API Tests", () => {
title: "Message Test Chat"
})
.expect(200);
testChatId = createResponse.body.id;
});
@@ -260,10 +261,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({
@@ -310,10 +311,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({});
@@ -321,7 +322,7 @@ describe("LLM API Tests", () => {
model: 'test-model',
provider: 'test-provider'
});
// Create a fresh chat for each test
const mockChat = {
id: 'streaming-test-chat',
@@ -331,15 +332,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;
});
@@ -358,7 +359,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,
@@ -372,17 +373,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',
@@ -390,7 +391,7 @@ describe("LLM API Tests", () => {
content: 'Hello',
done: false
});
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -402,7 +403,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,
@@ -419,7 +420,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,
@@ -436,7 +437,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,
@@ -467,7 +468,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, {});
@@ -475,7 +476,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,
@@ -493,10 +494,10 @@ 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
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -517,7 +518,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,
@@ -525,10 +526,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',
@@ -536,7 +537,7 @@ describe("LLM API Tests", () => {
thinking: 'Analyzing the question...',
done: false
});
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -564,7 +565,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,
@@ -572,10 +573,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',
@@ -597,7 +598,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "This will fail",
useAdvancedContext: false,
@@ -605,10 +606,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',
@@ -625,7 +626,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Hello AI",
useAdvancedContext: false,
@@ -633,10 +634,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',
@@ -655,7 +656,7 @@ describe("LLM API Tests", () => {
await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Save this response",
useAdvancedContext: false,
@@ -680,10 +681,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,
@@ -692,7 +693,7 @@ describe("LLM API Tests", () => {
);
const responses = await Promise.all(promises);
// All should succeed
responses.forEach(response => {
expect(response.status).toBe(200);
@@ -716,7 +717,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Generate large response",
useAdvancedContext: false,
@@ -724,10 +725,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
@@ -741,7 +742,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);
@@ -750,7 +751,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
});
@@ -762,7 +763,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
@@ -786,4 +787,4 @@ describe("LLM API Tests", () => {
}
}
});
});
});

View File

@@ -97,6 +97,8 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
"allowedHtmlTags",
"redirectBareDomain",
"showLoginInShareTheme",
"shareSubtree",
"sharePath",
"splitEditorOrientation",
"seenCallToActions",

View File

@@ -80,6 +80,7 @@ const GET = "get",
DEL = "delete";
function register(app: express.Application) {
route(GET, "/", [auth.checkAuth, csrfMiddleware], indexRoute.index);
route(GET, "/login", [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage);
route(GET, "/set-password", [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage);

View File

@@ -37,9 +37,26 @@ function checkAuth(req: Request, res: Response, next: NextFunction) {
// Check if any note has the #shareRoot label
const shareRootNotes = attributes.getNotesWithLabel("shareRoot");
if (shareRootNotes.length === 0) {
// should this be a translation string?
res.status(404).json({ message: "Share root not found. Please set up a note with #shareRoot label first." });
return;
}
// Get the configured share path
const sharePath = options.getOption("sharePath") || '/share';
// Check if we're already at the share path to prevent redirect loops
if (req.path === sharePath || req.path.startsWith(`${sharePath}/`)) {
log.info(`checkAuth: Already at share path, skipping redirect. Path: ${req.path}, SharePath: ${sharePath}`);
next();
return;
}
// Redirect to the share path
log.info(`checkAuth: Redirecting to share path. From: ${req.path}, To: ${sharePath}`);
res.redirect(`${sharePath}/`);
} else {
res.redirect("login");
}
res.redirect(hasRedirectBareDomain ? "share" : "login");
} else if (currentTotpStatus !== lastAuthState.totpEnabled || currentSsoStatus !== lastAuthState.ssoEnabled) {
@@ -81,15 +98,6 @@ function checkApiAuthOrElectron(req: Request, res: Response, next: NextFunction)
}
}
function checkApiAuth(req: Request, res: Response, next: NextFunction) {
if (!req.session.loggedIn && !noAuthentication) {
console.warn(`Missing session with ID '${req.sessionID}'.`);
reject(req, res, "Logged in session not found");
} else {
next();
}
}
function checkAppInitialized(req: Request, res: Response, next: NextFunction) {
if (!sqlInit.isDbInitialized()) {
res.redirect("setup");

View File

@@ -66,6 +66,7 @@ export default [
{ type: "label", name: "shareDisallowRobotIndexing" },
{ type: "label", name: "shareCredentials" },
{ type: "label", name: "shareIndex" },
{ type: "label", name: "shareHtmlLocation" },
{ type: "label", name: "displayRelations" },
{ type: "label", name: "hideRelations" },
{ type: "label", name: "titleTemplate", isDangerous: true },
@@ -105,6 +106,7 @@ export default [
{ type: "relation", name: "renderNote", isDangerous: true },
{ type: "relation", name: "shareCss" },
{ type: "relation", name: "shareJs" },
{ type: "relation", name: "shareHtml" },
{ type: "relation", name: "shareTemplate" },
{ type: "relation", name: "shareFavicon" }
];

View File

@@ -9,13 +9,13 @@ import { changeLanguage } from "./i18n.js";
import { deferred } from "./utils.js";
describe("Hidden Subtree", () => {
describe("Launcher movement persistence", () => {
beforeAll(async () => {
sql_init.initializeDb();
await sql_init.dbReady;
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
});
beforeAll(async () => {
sql_init.initializeDb();
await sql_init.dbReady;
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
});
describe("Launcher movement persistence", () => {
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,4 +119,14 @@ 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,6 +441,15 @@ 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,17 +1,7 @@
import sanitizeHtml from "sanitize-html";
import { sanitizeUrl } from "@braintree/sanitize-url";
import optionService from "./options.js";
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'
];
import { ALLOWED_PROTOCOLS, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
// intended mainly as protection against XSS via import
// secondarily, it (partly) protects against "CSS takeover"

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { StreamProcessor, createStreamHandler, processProviderStream, extractStreamStats, performProviderHealthCheck } from './stream_handler.js';
import type { StreamProcessingOptions, StreamChunk, ProviderStreamOptions } from './stream_handler.js';
import type { StreamProcessingOptions, StreamChunk } 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

@@ -196,8 +196,10 @@ const defaultOptions: DefaultOption[] = [
},
// Share settings
{ name: "sharePath", value: "/share", isSynced: true },
{ name: "redirectBareDomain", value: "false", isSynced: true },
{ name: "showLoginInShareTheme", value: "false", isSynced: true },
{ name: "shareSubtree", value: "false", isSynced: true },
// AI Options
{ name: "aiEnabled", value: "false", isSynced: true },

View File

@@ -32,7 +32,7 @@ export function getContent(note: SNote) {
};
if (note.type === "text") {
renderText(result, note);
renderText(result, note, relativePath);
} else if (note.type === "code") {
renderCode(result);
} else if (note.type === "mermaid") {
@@ -106,10 +106,10 @@ function renderText(result: Result, note: SNote) {
if (result.content.includes(`<span class="math-tex">`)) {
result.header += `
<script src="../${assetPath}/node_modules/katex/dist/katex.min.js"></script>
<link rel="stylesheet" href="../${assetPath}/node_modules/katex/dist/katex.min.css">
<script src="../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
<script src="../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
<script src="${relativePath}${assetPath}/node_modules/katex/dist/katex.min.js"></script>
<link rel="stylesheet" href="${relativePath}${assetPath}/node_modules/katex/dist/katex.min.css">
<script src="${relativePath}${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
<script src="${relativePath}${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.getElementById('content'));

View File

@@ -9,6 +9,7 @@ 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) => {
@@ -43,3 +44,39 @@ describe("Share API test", () => {
});
});
describe("Share Routes - Asset Path Calculation", () => {
it("should calculate correct relative path depth for different share paths", () => {
// Helper function to simulate the path depth calculation
const calculateRelativePath = (sharePath: string) => {
const pathDepth = sharePath.split('/').filter(segment => segment.length > 0).length;
return '../'.repeat(pathDepth);
};
// Test single level path
expect(calculateRelativePath("/share")).toBe("../");
// Test double level path
expect(calculateRelativePath("/sharePath/test")).toBe("../../");
// Test triple level path
expect(calculateRelativePath("/my/custom/share")).toBe("../../../");
// Test root path
expect(calculateRelativePath("/")).toBe("");
// Test path with trailing slash
expect(calculateRelativePath("/share/")).toBe("../");
});
it("should handle normalized share paths correctly", () => {
const calculateRelativePath = (sharePath: string) => {
const pathDepth = sharePath.split('/').filter(segment => segment.length > 0).length;
return '../'.repeat(pathDepth);
};
// Test the examples from the original TODO comment
expect(calculateRelativePath("/sharePath")).toBe("../");
expect(calculateRelativePath("/sharePath/test")).toBe("../../");
});
});

View File

@@ -1,6 +1,6 @@
import safeCompare from "safe-compare";
import type { Request, Response, Router } from "express";
import type { Request, Response, Router, NextFunction } from "express";
import shaca from "./shaca/shaca.js";
import shacaLoader from "./shaca/shaca_loader.js";
@@ -139,17 +139,21 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
}
function register(router: Router) {
function renderNote(note: SNote, req: Request, res: Response) {
function renderNote(note: SNote, req: Request, res: Response) {
// Calculate the correct relative path depth based on the current request path
// We need to go up one level for each path segment in the request URL
const pathSegments = req.path.split('/').filter(segment => segment.length > 0);
const relativePath = '../'.repeat(pathSegments.length);
if (!note) {
console.log("Unable to find note ", note);
res.status(404);
renderDefault(res, "404");
renderDefault(res, "404", { relativePath, t });
return;
}
if (!checkNoteAccess(note.noteId, req, res)) {
requestCredentials(res);
return;
}
@@ -161,18 +165,20 @@ function register(router: Router) {
return;
}
const { header, content, isEmpty } = contentRenderer.getContent(note);
const { header, content, isEmpty } = contentRenderer.getContent(note, relativePath);
const subRoot = getSharedSubTreeRoot(note);
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
const opts = {
note,
header,
content,
isEmpty,
subRoot,
assetPath: isDev ? assetPath : `../${assetPath}`,
assetPath: isDev ? assetPath : `${relativePath}${assetPath}`,
assetUrlFragment,
appPath: isDev ? appPath : `../${appPath}`,
appPath: isDev ? appPath : `${relativePath}${appPath}`,
relativePath,
showLoginInShareTheme,
t,
isDev
@@ -219,184 +225,165 @@ function register(router: Router) {
}
}
router.get("/share/", (req, res) => {
if (req.path.substr(-1) !== "/") {
res.redirect("../share/");
return;
// Dynamic dispatch middleware
router.use((req: Request, res: Response, next: NextFunction) => {
const sharePath = options.getOptionOrNull("sharePath") || "/share";
// Only handle requests starting with sharePath
if (req.path === sharePath || req.path.startsWith(sharePath + "/")) {
// Remove sharePath prefix to get the remaining path
const subPath = req.path.slice(sharePath.length);
// Handle root path
if (subPath === "" || subPath === "/") {
shacaLoader.ensureLoad();
if (!shaca.shareRootNote) {
res.status(404).json({ message: "Share root not found" });
return;
}
renderNote(shaca.shareRootNote, req, res);
return;
}
// Handle /:shareId
const shareIdMatch = subPath.match(/^\/([^/]+)$/);
if (shareIdMatch) {
shacaLoader.ensureLoad();
const shareId = shareIdMatch[1];
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
renderNote(note, req, res);
return;
}
// Handle /api/notes/:noteId
const apiNoteMatch = subPath.match(/^\/api\/notes\/([^/]+)$/);
if (apiNoteMatch) {
shacaLoader.ensureLoad();
const noteId = apiNoteMatch[1];
let note: SNote | boolean;
if (!(note = checkNoteAccess(noteId, req, res))) return;
addNoIndexHeader(note, res);
res.json(note.getPojo());
return;
}
// Handle /api/notes/:noteId/download
const apiNoteDownloadMatch = subPath.match(/^\/api\/notes\/([^/]+)\/download$/);
if (apiNoteDownloadMatch) {
shacaLoader.ensureLoad();
const noteId = apiNoteDownloadMatch[1];
let note: SNote | boolean;
if (!(note = checkNoteAccess(noteId, req, res))) return;
addNoIndexHeader(note, res);
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Content-Type", note.mime);
res.send(note.getContent());
return;
}
// Handle /api/images/:noteId/:filename
const apiImageMatch = subPath.match(/^\/api\/images\/([^/]+)\/([^/]+)$/);
if (apiImageMatch) {
shacaLoader.ensureLoad();
const noteId = apiImageMatch[1];
let image: SNote | boolean;
if (!(image = checkNoteAccess(noteId, req, res))) {
return;
}
if (image.type === "image") {
// normal image
res.set("Content-Type", image.mime);
addNoIndexHeader(image, res);
res.send(image.getContent());
} else if (image.type === "canvas") {
renderImageAttachment(image, res, "canvas-export.svg");
} else if (image.type === "mermaid") {
renderImageAttachment(image, res, "mermaid-export.svg");
} else if (image.type === "mindMap") {
renderImageAttachment(image, res, "mindmap-export.svg");
} else {
res.status(400).json({ message: "Requested note is not a shareable image" });
}
return;
}
// Handle /api/attachments/:attachmentId/image/:filename
const apiAttachmentImageMatch = subPath.match(/^\/api\/attachments\/([^/]+)\/image\/([^/]+)$/);
if (apiAttachmentImageMatch) {
shacaLoader.ensureLoad();
const attachmentId = apiAttachmentImageMatch[1];
let attachment: SAttachment | boolean;
if (!(attachment = checkAttachmentAccess(attachmentId, req, res))) {
return;
}
if (attachment.role === "image") {
res.set("Content-Type", attachment.mime);
addNoIndexHeader(attachment.note, res);
res.send(attachment.getContent());
} else {
res.status(400).json({ message: "Requested attachment is not a shareable image" });
}
return;
}
// Handle /api/attachments/:attachmentId/download
const apiAttachmentDownloadMatch = subPath.match(/^\/api\/attachments\/([^/]+)\/download$/);
if (apiAttachmentDownloadMatch) {
shacaLoader.ensureLoad();
const attachmentId = apiAttachmentDownloadMatch[1];
let attachment: SAttachment | boolean;
if (!(attachment = checkAttachmentAccess(attachmentId, req, res))) {
return;
}
addNoIndexHeader(attachment.note, res);
const filename = utils.formatDownloadTitle(attachment.title, null, attachment.mime);
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Content-Type", attachment.mime);
res.send(attachment.getContent());
return;
}
// Handle /api/notes/:noteId/view
const apiNoteViewMatch = subPath.match(/^\/api\/notes\/([^/]+)\/view$/);
if (apiNoteViewMatch) {
shacaLoader.ensureLoad();
const noteId = apiNoteViewMatch[1];
let note: SNote | boolean;
if (!(note = checkNoteAccess(noteId, req, res))) {
return;
}
addNoIndexHeader(note, res);
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Content-Type", note.mime);
res.send(note.getContent());
return;
}
// Handle /api/notes 搜索
const apiNotesSearchMatch = subPath.match(/^\/api\/notes$/);
if (apiNotesSearchMatch) {
shacaLoader.ensureLoad();
const ancestorNoteId = req.query.ancestorNoteId ?? "_share";
if (typeof ancestorNoteId !== "string") {
res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." });
return;
}
// This will automatically return if no ancestorNoteId is provided and there is no shareIndex
if (!checkNoteAccess(ancestorNoteId, req, res)) {
return;
}
const { search } = req.query;
if (typeof search !== "string" || !search?.trim()) {
res.status(400).json({ message: "'search' parameter is mandatory." });
return;
}
const searchContext = new SearchContext({ ancestorNoteId: ancestorNoteId });
const searchResults = searchService.findResultsWithQuery(search, searchContext);
const filteredResults = searchResults.map((sr) => {
const fullNote = shaca.notes[sr.noteId];
const startIndex = sr.notePathArray.indexOf(ancestorNoteId);
const localPathArray = sr.notePathArray.slice(startIndex + 1).filter((id) => shaca.notes[id]);
const pathTitle = localPathArray.map((id) => shaca.notes[id].title).join(" / ");
return { id: fullNote.shareId, title: fullNote.title, score: sr.score, path: pathTitle };
});
res.json({ results: filteredResults });
return;
}
}
shacaLoader.ensureLoad();
if (!shaca.shareRootNote) {
res.status(404).json({ message: "Share root note not found" });
return;
}
renderNote(shaca.shareRootNote, req, res);
});
router.get("/share/:shareId", (req, res) => {
shacaLoader.ensureLoad();
const { shareId } = req.params;
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
renderNote(note, req, res);
});
router.get("/share/api/notes/:noteId", (req, res) => {
shacaLoader.ensureLoad();
let note: SNote | boolean;
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
return;
}
addNoIndexHeader(note, res);
res.json(note.getPojo());
});
router.get("/share/api/notes/:noteId/download", (req, res) => {
shacaLoader.ensureLoad();
let note: SNote | boolean;
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
return;
}
addNoIndexHeader(note, res);
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Content-Type", note.mime);
res.send(note.getContent());
});
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
router.get("/share/api/images/:noteId/:filename", (req, res) => {
shacaLoader.ensureLoad();
let image: SNote | boolean;
if (!(image = checkNoteAccess(req.params.noteId, req, res))) {
return;
}
if (image.type === "image") {
// normal image
res.set("Content-Type", image.mime);
addNoIndexHeader(image, res);
res.send(image.getContent());
} else if (image.type === "canvas") {
renderImageAttachment(image, res, "canvas-export.svg");
} else if (image.type === "mermaid") {
renderImageAttachment(image, res, "mermaid-export.svg");
} else if (image.type === "mindMap") {
renderImageAttachment(image, res, "mindmap-export.svg");
} else {
res.status(400).json({ message: "Requested note is not a shareable image" });
}
});
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
router.get("/share/api/attachments/:attachmentId/image/:filename", (req, res) => {
shacaLoader.ensureLoad();
let attachment: SAttachment | boolean;
if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) {
return;
}
if (attachment.role === "image") {
res.set("Content-Type", attachment.mime);
addNoIndexHeader(attachment.note, res);
res.send(attachment.getContent());
} else {
res.status(400).json({ message: "Requested attachment is not a shareable image" });
}
});
router.get("/share/api/attachments/:attachmentId/download", (req, res) => {
shacaLoader.ensureLoad();
let attachment: SAttachment | boolean;
if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) {
return;
}
addNoIndexHeader(attachment.note, res);
const filename = utils.formatDownloadTitle(attachment.title, null, attachment.mime);
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Content-Type", attachment.mime);
res.send(attachment.getContent());
});
// used for PDF viewing
router.get("/share/api/notes/:noteId/view", (req, res) => {
shacaLoader.ensureLoad();
let note: SNote | boolean;
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
return;
}
addNoIndexHeader(note, res);
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Content-Type", note.mime);
res.send(note.getContent());
});
// Used for searching, require noteId so we know the subTreeRoot
router.get("/share/api/notes", (req, res) => {
shacaLoader.ensureLoad();
const ancestorNoteId = req.query.ancestorNoteId ?? "_share";
if (typeof ancestorNoteId !== "string") {
res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." });
return;
}
// This will automatically return if no ancestorNoteId is provided and there is no shareIndex
if (!checkNoteAccess(ancestorNoteId, req, res)) {
return;
}
const { search } = req.query;
if (typeof search !== "string" || !search?.trim()) {
res.status(400).json({ message: "'search' parameter is mandatory." });
return;
}
const searchContext = new SearchContext({ ancestorNoteId: ancestorNoteId });
const searchResults = searchService.findResultsWithQuery(search, searchContext);
const filteredResults = searchResults.map((sr) => {
const fullNote = shaca.notes[sr.noteId];
const startIndex = sr.notePathArray.indexOf(ancestorNoteId);
const localPathArray = sr.notePathArray.slice(startIndex + 1).filter((id) => shaca.notes[id]);
const pathTitle = localPathArray.map((id) => shaca.notes[id].title).join(" / ");
return { id: fullNote.shareId, title: fullNote.title, score: sr.score, path: pathTitle };
});
res.json({ results: filteredResults });
next();
});
}

View File

@@ -1,6 +1,7 @@
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; };
@@ -9,6 +10,7 @@ interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
id?: string | undefined;
title?: string;
content?: string;
children?: NoteDefinition[];
}
/**
@@ -51,6 +53,18 @@ 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: "threads"
pool: "vmForks"
},
}));

2
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

File diff suppressed because it is too large Load Diff

View File

@@ -50,5 +50,6 @@ These relations are supported and used internally by Trilium.
| `widget_relation` | target of this relation will be executed and rendered as a widget in the sidebar |
| `shareCss` | CSS note which will be injected into the share page. CSS note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree` and `share_omit_default_css` as well. |
| `shareJs` | JavaScript note which will be injected into the share page. JS note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree`. |
| `shareHtml` | HTML note which will be injected into the share page at locations specified by the `shareHtmlLocation` label. HTML note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree`. |
| `shareTemplate` | Embedded JavaScript note that will be used as the template for displaying the shared note. Falls back to the default template. Consider using `share_hidden_from_tree`. |
| `shareFavicon` | Favicon note to be set in the shared page. Typically you want to set it to share root and make it inheritable. Favicon note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree`. |

View File

@@ -67,6 +67,25 @@ The default design should be a good starting point, but you can customize it usi
You can inject custom JavaScript into the shared note using the `~shareJs` relation. This allows you to access note attributes or traverse the note tree using the `fetchNote()` API, which retrieves note data based on its ID.
### Adding custom HTML
You can inject custom HTML snippets into specific locations of the shared page using the `~shareHtml` relation. The HTML note should contain the raw HTML content you want to inject, and you can control where it appears by adding the `#shareHtmlLocation` label to the HTML snippet note itself.
The `#shareHtmlLocation` label accepts values in the format `location:position`:
* **Locations**: `head`, `body`, `content`
* **Positions**: `start`, `end`
For example:
* `#shareHtmlLocation=head:start` - Injects HTML at the beginning of the `<head>` section
* `#shareHtmlLocation=head:end` - Injects HTML at the end of the `<head>` section (default)
* `#shareHtmlLocation=body:start` - Injects HTML at the beginning of the `<body>` section
* `#shareHtmlLocation=content:start` - Injects HTML at the beginning of the content area
* `#shareHtmlLocation=content:end` - Injects HTML at the end of the content area
If no location is specified, the HTML will be injected at `content:end` by default.
Example:
```javascript
@@ -104,9 +123,19 @@ When accessing a share, the sub-notes will be displayed in a tree on the left. B
To do so, create a shared text note and apply the `shareIndex` label. When viewed, the list of shared roots will be displayed at the bottom of the note.
### Redirect Bare Domain to Share Page
This option can be enabled under `Option → Other → Share Settings`. When activated, anonymous users accessing the bare domain will be redirected to the Share page, preventing them from seeing the login option and thereby improving security.
To ensure accessibility for legitimate users, you can also enable a login link on the Share page, allowing yourself to access the login screen if you're redirected there.
### Setting a Custom Share Path
This option can be enabled under `Option → Other → Share Settings`. It allows you to customize the share URL prefix before the `noteId`. Nested paths are supported.
If you're using a proxy service, make sure to update its configuration accordingly to reflect the new path structure.
## Attribute reference
<table><thead><tr><th>Attribute</th><th>Description</th></tr></thead><tbody><tr><td><code>shareHiddenFromTree</code></td><td>this note is hidden from left navigation tree, but still accessible with its URL</td></tr><tr><td><code>shareExternalLink</code></td><td>note will act as a link to an external website in the share tree</td></tr><tr><td><code>shareAlias</code></td><td>define an alias using which the note will be available under <code>https://your_trilium_host/share/[your_alias]</code></td></tr><tr><td><code>shareOmitDefaultCss</code></td><td>default share page CSS will be omitted. Use when you make extensive styling changes.</td></tr><tr><td><code>shareRoot</code></td><td>marks note which is served on /share root.</td></tr><tr><td><code>shareDescription</code></td><td>define text to be added to the HTML meta tag for description</td></tr><tr><td><code>shareRaw</code></td><td>Note will be served in its raw format, without HTML wrapper. See also&nbsp;<a class="reference-link" href="Sharing/Serving%20directly%20the%20content%20o.md">Serving directly the content of a note</a>&nbsp;for an alternative method without setting an attribute.</td></tr><tr><td><code>shareDisallowRobotIndexing</code></td><td><p>Indicates to web crawlers that the page should not be indexed of this note by:</p><ul><li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li><li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li></ul></td></tr><tr><td><code>shareCredentials</code></td><td>require credentials to access this shared note. Value is expected to be in format <code>username:password</code>. Don't forget to make this inheritable to apply to child-notes/images.</td></tr><tr><td><code>shareIndex</code></td><td>Note with this label will list all roots of shared notes.</td></tr></tbody></table>
<table><thead><tr><th>Attribute</th><th>Description</th></tr></thead><tbody><tr><td><code>shareHiddenFromTree</code></td><td>this note is hidden from left navigation tree, but still accessible with its URL</td></tr><tr><td><code>shareExternalLink</code></td><td>note will act as a link to an external website in the share tree</td></tr><tr><td><code>shareAlias</code></td><td>define an alias using which the note will be available under <code>https://your_trilium_host/share/[your_alias]</code></td></tr><tr><td><code>shareOmitDefaultCss</code></td><td>default share page CSS will be omitted. Use when you make extensive styling changes.</td></tr><tr><td><code>shareRoot</code></td><td>marks note which is served on /share root.</td></tr><tr><td><code>shareDescription</code></td><td>define text to be added to the HTML meta tag for description</td></tr><tr><td><code>shareRaw</code></td><td>Note will be served in its raw format, without HTML wrapper. See also&nbsp;<a class="reference-link" href="Sharing/Serving%20directly%20the%20content%20o.md">Serving directly the content of a note</a>&nbsp;for an alternative method without setting an attribute.</td></tr><tr><td><code>shareDisallowRobotIndexing</code></td><td><p>Indicates to web crawlers that the page should not be indexed of this note by:</p><ul><li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li><li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li></ul></td></tr><tr><td><code>shareCredentials</code></td><td>require credentials to access this shared note. Value is expected to be in format <code>username:password</code>. Don't forget to make this inheritable to apply to child-notes/images.</td></tr><tr><td><code>shareIndex</code></td><td>Note with this label will list all roots of shared notes.</td></tr><tr><td><code>shareHtmlLocation</code></td><td>defines where custom HTML injected via <code>~shareHtml</code> relation should be placed. Applied to the HTML snippet note itself. Format: <code>location:position</code> where location is <code>head</code>, <code>body</code>, or <code>content</code> and position is <code>start</code> or <code>end</code>. Defaults to <code>content:end</code>.</td></tr></tbody></table>
## Credits

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 || true
pnpm postinstall
'';
buildTask = "desktop:build";
mainProgram = "trilium";

View File

@@ -37,7 +37,7 @@
"private": true,
"devDependencies": {
"@electron/rebuild": "4.0.1",
"@playwright/test": "1.55.1",
"@playwright/test": "1.56.0",
"@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.45.0",
"typescript-eslint": "8.46.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.0",
"packageManager": "pnpm@10.18.1",
"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.0.7",
"@types/express-serve-static-core": "5.1.0",
"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.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.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.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.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.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.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.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -30,7 +30,12 @@ export default class MathCommand extends Command {
mathtex = writer.createElement(
display ? 'mathtex-display' : 'mathtex-inline',
{ equation, type, display }
{
...Object.fromEntries(selection.getAttributes()),
equation,
type,
display
}
);
} else {
const selection = this.editor.model.document.selection;
@@ -40,7 +45,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.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/eslint-plugin": "~8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@vitest/browser": "3.2.4",
"@vitest/coverage-istanbul": "3.2.4",
"ckeditor5": "47.0.0",

View File

@@ -49,4 +49,9 @@ 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

@@ -135,6 +135,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
// Share settings
redirectBareDomain: boolean;
showLoginInShareTheme: boolean;
shareSubtree: boolean;
sharePath: string;
// AI/LLM integration options
aiEnabled: boolean;

View File

@@ -1,98 +1,22 @@
// 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",
"en-media", // for ENEX import
"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",
// 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

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

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="shortcut icon" href="<%= relativePath %>favicon.ico">
<title><%= t("share_404.title") %></title>
</head>
<body>

View File

@@ -1,7 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% const hasTree = subRoot.note.hasVisibleChildren(); %>
<%
const hasTree = subRoot.note.hasVisibleChildren();
// Collect HTML snippets by location
const htmlSnippetsByLocation = {};
for (const htmlRelation of note.getRelations("shareHtml")) {
const htmlNote = htmlRelation.targetNote;
if (htmlNote) {
let location = htmlNote.getLabelValue("shareHtmlLocation") || "content:end";
// Default to :end if no position specified
if (!location.includes(":")) {
location = location + ":end";
}
if (!htmlSnippetsByLocation[location]) {
htmlSnippetsByLocation[location] = [];
}
htmlSnippetsByLocation[location].push(htmlNote.getContent());
}
}
const renderSnippets = (location) => {
const snippets = htmlSnippetsByLocation[location];
return snippets ? snippets.join("\n") : "";
};
%>
<%- renderSnippets("head:start") %>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -53,10 +77,11 @@
<meta name="twitter:image" content="<%= openGraphImage %>">
<!-- Meta Tags Generated via https://opengraph.dev -->
<meta name="theme-color" content="<%= openGraphColor %>">
<%- renderSnippets("head:end") %>
</head>
<%
const customLogoId = subRoot.note.getRelation("shareLogo")?.value;
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `../${assetUrlFragment}/images/icon-color.svg`;
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `${relativePath}${assetUrlFragment}/images/icon-color.svg`;
const logoWidth = subRoot.note.getLabelValue("shareLogoWidth") ?? 53;
const logoHeight = subRoot.note.getLabelValue("shareLogoHeight") ?? 40;
const mobileLogoHeight = logoHeight && logoWidth ? 32 / (logoWidth / logoHeight) : "";
@@ -72,6 +97,7 @@ content = content.replaceAll(headingRe, (...match) => {
});
%>
<body data-note-id="<%= note.noteId %>" class="type-<%= note.type %><%= themeClass %>" data-ancestor-note-id="<%= subRoot.note.noteId %>">
<%- renderSnippets("body:start") %>
<div id="mobile-header">
<a href="<%= shareRootLink %>">
<img src="<%= logoUrl %>" width="32" height="<%= mobileLogoHeight %>" alt="Logo" />
@@ -121,8 +147,8 @@ content = content.replaceAll(headingRe, (...match) => {
</div>
<div id="right-pane">
<div id="main">
<div id="content" class="type-<%= note.type %><% if (note.type === "text") { %> ck-content<% } %><% if (isEmpty) { %> no-content<% } %>">
<%- renderSnippets("content:start") %>
<h1 id="title"><%= note.title %></h1>
<% if (isEmpty && (!note.hasVisibleChildren() && note.type !== "book")) { %>
<p>This note has no content.</p>
@@ -132,6 +158,7 @@ content = content.replaceAll(headingRe, (...match) => {
%>
<%- content %>
<% } %>
<%- renderSnippets("content:end") %>
</div>
<% if (note.hasVisibleChildren() || note.type === "book") { %>
@@ -164,7 +191,7 @@ content = content.replaceAll(headingRe, (...match) => {
</div>
<% } %>
<% if (hasTree) { %>
<% if (hasTree) { %>
<%- include("prev_next", { note: note, subRoot: subRoot }) %>
<% } %>
</footer>
@@ -205,5 +232,6 @@ content = content.replaceAll(headingRe, (...match) => {
<% } %>
</div>
</div>
<%- renderSnippets("body:end") %>
</body>
</html>

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.0.7
'@types/express-serve-static-core': 5.1.0
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.55.1
version: 1.55.1
specifier: 1.56.0
version: 1.56.0
'@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.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.1.0
version: 6.1.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.24.4)
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)
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.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)
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)
'@typescript-eslint/parser':
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.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))
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))
'@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.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)
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)
'@typescript-eslint/parser':
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.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))
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))
'@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.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)
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)
'@typescript-eslint/parser':
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.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))
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))
'@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.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)
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)
'@typescript-eslint/parser':
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.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))
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))
'@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.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)
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)
'@typescript-eslint/parser':
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.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))
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))
'@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.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)
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)
'@typescript-eslint/parser':
specifier: 8.45.0
version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
specifier: 8.46.0
version: 8.46.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.55.1':
resolution: {integrity: sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==}
'@playwright/test@1.56.0':
resolution: {integrity: sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==}
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.0.7':
resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==}
'@types/express-serve-static-core@5.1.0':
resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==}
'@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.45.0':
resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==}
'@typescript-eslint/eslint-plugin@8.46.0':
resolution: {integrity: sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.45.0
'@typescript-eslint/parser': ^8.46.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.45.0':
resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==}
'@typescript-eslint/parser@8.46.0':
resolution: {integrity: sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==}
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.45.0':
resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==}
'@typescript-eslint/project-service@8.46.0':
resolution: {integrity: sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==}
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.45.0':
resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==}
'@typescript-eslint/scope-manager@8.46.0':
resolution: {integrity: sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.40.0':
@@ -5059,6 +5059,12 @@ 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}
@@ -5066,8 +5072,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/type-utils@8.45.0':
resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==}
'@typescript-eslint/type-utils@8.46.0':
resolution: {integrity: sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -5085,6 +5091,10 @@ 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}
@@ -5097,8 +5107,8 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/typescript-estree@8.45.0':
resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==}
'@typescript-eslint/typescript-estree@8.46.0':
resolution: {integrity: sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
@@ -5117,8 +5127,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.45.0':
resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==}
'@typescript-eslint/utils@8.46.0':
resolution: {integrity: sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -5132,8 +5142,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.45.0':
resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==}
'@typescript-eslint/visitor-keys@8.46.0':
resolution: {integrity: sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
@@ -10096,8 +10106,8 @@ packages:
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
engines: {node: '>=18'}
openai@6.1.0:
resolution: {integrity: sha512-5sqb1wK67HoVgGlsPwcH2bUbkg66nnoIYKoyV9zi5pZPqh7EWlmSrSDjAh4O5jaIg/0rIlcDKBtWvZBuacmGZg==}
openai@6.2.0:
resolution: {integrity: sha512-qqjzHls7F5xkXNGy9P1Ei1rorI5LWupUUFWP66zPU8FlZbiITX8SFcHMKNZg/NATJ0LpIZcMUFxSwQmdeQPwSw==}
hasBin: true
peerDependencies:
ws: ^8.18.0
@@ -10428,13 +10438,13 @@ packages:
pkg-types@2.1.0:
resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==}
playwright-core@1.55.1:
resolution: {integrity: sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==}
playwright-core@1.56.0:
resolution: {integrity: sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==}
engines: {node: '>=18'}
hasBin: true
playwright@1.55.1:
resolution: {integrity: sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==}
playwright@1.56.0:
resolution: {integrity: sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==}
engines: {node: '>=18'}
hasBin: true
@@ -13012,8 +13022,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
typescript-eslint@8.45.0:
resolution: {integrity: sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==}
typescript-eslint@8.46.0:
resolution: {integrity: sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -14714,6 +14724,8 @@ 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:
@@ -14905,6 +14917,8 @@ 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:
@@ -14927,6 +14941,8 @@ 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:
@@ -15098,6 +15114,8 @@ 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': {}
@@ -15484,8 +15502,6 @@ 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:
@@ -17551,9 +17567,9 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@playwright/test@1.55.1':
'@playwright/test@1.56.0':
dependencies:
playwright: 1.55.1
playwright: 1.56.0
'@polka/url@1.0.0-next.29': {}
@@ -18777,7 +18793,7 @@ snapshots:
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 5.0.7
'@types/express-serve-static-core': 5.1.0
'@types/node': 22.18.8
'@types/connect@3.4.38':
@@ -18948,7 +18964,7 @@ snapshots:
dependencies:
'@types/express': 5.0.3
'@types/express-serve-static-core@5.0.7':
'@types/express-serve-static-core@5.1.0':
dependencies:
'@types/node': 22.18.8
'@types/qs': 6.14.0
@@ -18962,14 +18978,14 @@ snapshots:
'@types/express@4.17.23':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 5.0.7
'@types/express-serve-static-core': 5.1.0
'@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.0.7
'@types/express-serve-static-core': 5.1.0
'@types/serve-static': 1.15.9
'@types/fs-extra@11.0.4':
@@ -19270,14 +19286,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@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/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)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@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
'@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
eslint: 9.37.0(jiti@2.6.1)
graphemer: 1.4.0
ignore: 7.0.5
@@ -19299,12 +19315,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.45.0(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)':
dependencies:
'@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
'@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
debug: 4.4.3(supports-color@6.0.0)
eslint: 9.37.0(jiti@2.6.1)
typescript: 5.9.3
@@ -19329,10 +19345,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.45.0(typescript@5.9.3)':
'@typescript-eslint/project-service@8.46.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3)
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/tsconfig-utils': 8.46.0(typescript@5.9.3)
'@typescript-eslint/types': 8.46.0
debug: 4.4.3(supports-color@6.0.0)
typescript: 5.9.3
transitivePeerDependencies:
@@ -19348,10 +19364,10 @@ snapshots:
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/visitor-keys': 8.44.1
'@typescript-eslint/scope-manager@8.45.0':
'@typescript-eslint/scope-manager@8.46.0':
dependencies:
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/visitor-keys': 8.45.0
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/visitor-keys': 8.46.0
'@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.3)':
dependencies:
@@ -19365,6 +19381,10 @@ 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
@@ -19377,11 +19397,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/type-utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@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)
'@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)
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)
@@ -19395,6 +19415,8 @@ 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)
@@ -19427,12 +19449,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)':
'@typescript-eslint/typescript-estree@8.46.0(typescript@5.9.3)':
dependencies:
'@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
'@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
debug: 4.4.3(supports-color@6.0.0)
fast-glob: 3.3.3
is-glob: 4.0.3
@@ -19465,12 +19487,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.45.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)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1))
'@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/scope-manager': 8.46.0
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3)
eslint: 9.37.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
@@ -19486,9 +19508,9 @@ snapshots:
'@typescript-eslint/types': 8.44.1
eslint-visitor-keys: 4.2.1
'@typescript-eslint/visitor-keys@8.45.0':
'@typescript-eslint/visitor-keys@8.46.0':
dependencies:
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/types': 8.46.0
eslint-visitor-keys: 4.2.1
'@ungap/structured-clone@1.3.0': {}
@@ -19524,7 +19546,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.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/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))':
dependencies:
'@testing-library/dom': 10.4.0
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0)
@@ -19536,7 +19558,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.55.1
playwright: 1.56.0
webdriverio: 9.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
transitivePeerDependencies:
- bufferutil
@@ -19577,7 +19599,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.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/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))
transitivePeerDependencies:
- supports-color
@@ -25917,7 +25939,7 @@ snapshots:
is-inside-container: 1.0.0
wsl-utils: 0.1.0
openai@6.1.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.24.4):
openai@6.2.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
@@ -26251,11 +26273,11 @@ snapshots:
exsolve: 1.0.5
pathe: 2.0.3
playwright-core@1.55.1: {}
playwright-core@1.56.0: {}
playwright@1.55.1:
playwright@1.56.0:
dependencies:
playwright-core: 1.55.1
playwright-core: 1.56.0
optionalDependencies:
fsevents: 2.3.2
@@ -29434,12 +29456,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
typescript-eslint@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3):
typescript-eslint@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@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)
'@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)
eslint: 9.37.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
@@ -29806,7 +29828,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.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/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/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)
@@ -29972,7 +29994,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.0.7
'@types/express-serve-static-core': 5.1.0
'@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 });
}
function rebuildNativeDependencies(projectRoot: string) {
async function rebuildNativeDependencies(projectRoot: string) {
const electronVersion = determineElectronVersion(projectRoot);
if (!electronVersion) {
@@ -35,7 +35,7 @@ function rebuildNativeDependencies(projectRoot: string) {
console.log(`Rebuilding ${projectRoot} with ${electronVersion} for ${targetArch}...`);
const resolvedPath = resolve(projectRoot);
rebuild({
await 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);
rebuildNativeDependencies(projectRoot);
await rebuildNativeDependencies(projectRoot);
}