Compare commits

..

2 Commits

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

View File

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

View File

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

View File

@@ -1743,17 +1743,9 @@
"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_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": "共享整个子树,而不是仅共享笔记"
"share_root_not_shared": "笔记 '{{noteTitle}}' 具有 #shareRoot 标签,但未共享"
},
"time_selector": {
"invalid_input": "输入的时间值不是有效数字。",

View File

@@ -1907,17 +1907,9 @@
"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_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"
"share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not shared"
},
"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,6 +1 @@
{
"about": {
"title": "Acerca de \"Trillium Notes\"",
"app_version": "Versão da aplicação:"
}
}
{}

View File

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

View File

@@ -1,13 +0,0 @@
// 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

@@ -1,58 +0,0 @@
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,10 +90,6 @@ const config: ForgeConfig = {
base: "org.electronjs.Electron2.BaseApp",
baseVersion: "24.08",
baseFlatpakref: "https://flathub.org/repo/flathub.flatpakrepo",
finishArgs: [
"--socket=fallback-x11",
"--socket=wayland"
],
modules: [
{
name: "zypak",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@@ -6,7 +6,8 @@ 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>
@@ -188,9 +189,11 @@ class="image">
<img src="Sharing_share-single-note.png" alt="Share Note">
</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>
<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>
</ol>
<h2>Sharing a note subtree</h2>
<p>When you share a note, you actually share the entire subtree of notes

View File

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

View File

@@ -16,8 +16,7 @@ vi.mock("../../services/ws.js", () => ({
default: {
sendMessageToAllClients: vi.fn(),
sendTransactionEntityChangesToAllClients: vi.fn(),
setLastSyncedPush: vi.fn(),
syncFailed() {}
setLastSyncedPush: vi.fn()
}
}));
@@ -82,7 +81,7 @@ async function loginWithSession(app: Application) {
.post("/login")
.send({ password: "demo1234" })
.expect(302);
const setCookieHeader = response.headers["set-cookie"][0];
expect(setCookieHeader).toBeTruthy();
return setCookieHeader;
@@ -92,14 +91,14 @@ async function loginWithSession(app: Application) {
async function getCsrfToken(app: Application, sessionCookie: string) {
const response = await supertest(app)
.get("/")
.expect(200);
const csrfTokenMatch = response.text.match(/csrfToken: '([^']+)'/);
if (csrfTokenMatch) {
return csrfTokenMatch[1];
}
throw new Error("CSRF token not found in response");
}
@@ -155,7 +154,7 @@ describe("LLM API Tests", () => {
expect(response.body).toHaveProperty('sessions');
expect(Array.isArray(response.body.sessions)).toBe(true);
if (response.body.sessions.length > 0) {
expect(response.body.sessions[0]).toMatchObject({
id: expect.any(String),
@@ -172,18 +171,18 @@ describe("LLM API Tests", () => {
// Create a chat first if we don't have one
const createResponse = await supertest(app)
.post("/api/llm/chat")
.send({
title: "Test Retrieval Chat"
})
.expect(200);
createdChatId = createResponse.body.id;
}
const response = await supertest(app)
.get(`/api/llm/chat/${createdChatId}`)
.expect(200);
expect(response.body).toMatchObject({
@@ -203,7 +202,7 @@ describe("LLM API Tests", () => {
title: "Test Update Chat"
})
.expect(200);
createdChatId = createResponse.body.id;
}
@@ -225,7 +224,7 @@ describe("LLM API Tests", () => {
it("should return 404 for non-existent chat session", async () => {
await supertest(app)
.get("/api/llm/chat/nonexistent-chat-id")
.expect(404);
});
});
@@ -241,7 +240,7 @@ describe("LLM API Tests", () => {
title: "Message Test Chat"
})
.expect(200);
testChatId = createResponse.body.id;
});
@@ -261,10 +260,10 @@ describe("LLM API Tests", () => {
// The response depends on whether AI is actually configured
// We should get either a successful response or an error about AI not being configured
expect([200, 400, 500]).toContain(response.status);
// All responses should have some body
expect(response.body).toBeDefined();
// Either success with response or error
if (response.body.response) {
expect(response.body).toMatchObject({
@@ -311,10 +310,10 @@ describe("LLM API Tests", () => {
beforeEach(async () => {
// Reset all mocks
vi.clearAllMocks();
// Import options service to access mock
const options = (await import("../../services/options.js")).default;
// Setup default mock behaviors
(options.getOptionBool as any).mockReturnValue(true); // AI enabled
mockAiServiceManager.getOrCreateAnyService.mockResolvedValue({});
@@ -322,7 +321,7 @@ describe("LLM API Tests", () => {
model: 'test-model',
provider: 'test-provider'
});
// Create a fresh chat for each test
const mockChat = {
id: 'streaming-test-chat',
@@ -332,15 +331,15 @@ describe("LLM API Tests", () => {
};
mockChatStorage.createChat.mockResolvedValue(mockChat);
mockChatStorage.getChat.mockResolvedValue(mockChat);
const createResponse = await supertest(app)
.post("/api/llm/chat")
.send({
title: "Streaming Test Chat"
})
.expect(200);
testChatId = createResponse.body.id;
});
@@ -359,7 +358,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Tell me a short story",
useAdvancedContext: false,
@@ -373,17 +372,17 @@ describe("LLM API Tests", () => {
success: true,
message: "Streaming initiated successfully"
});
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify WebSocket messages were sent
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
thinking: undefined
});
// Verify streaming chunks were sent
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -391,7 +390,7 @@ describe("LLM API Tests", () => {
content: 'Hello',
done: false
});
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -403,7 +402,7 @@ describe("LLM API Tests", () => {
it("should handle empty content for streaming", async () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "",
useAdvancedContext: false,
@@ -420,7 +419,7 @@ describe("LLM API Tests", () => {
it("should handle whitespace-only content for streaming", async () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: " \n\t ",
useAdvancedContext: false,
@@ -437,7 +436,7 @@ describe("LLM API Tests", () => {
it("should handle invalid chat ID for streaming", async () => {
const response = await supertest(app)
.post("/api/llm/chat/invalid-chat-id/messages/stream")
.send({
content: "Hello",
useAdvancedContext: false,
@@ -468,7 +467,7 @@ describe("LLM API Tests", () => {
// Verify mention content is included
expect(input.query).toContain('Tell me about this note');
expect(input.query).toContain('Root note content for testing');
const callback = input.streamCallback;
await callback('The root note contains', false, {});
await callback(' important information.', true, {});
@@ -476,7 +475,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Tell me about this note",
useAdvancedContext: true,
@@ -494,10 +493,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',
@@ -518,7 +517,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "What is the meaning of life?",
useAdvancedContext: false,
@@ -526,10 +525,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify thinking messages
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -537,7 +536,7 @@ describe("LLM API Tests", () => {
thinking: 'Analyzing the question...',
done: false
});
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
chatNoteId: testChatId,
@@ -565,7 +564,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "What is 2 + 2?",
useAdvancedContext: false,
@@ -573,10 +572,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify tool execution message
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -598,7 +597,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "This will fail",
useAdvancedContext: false,
@@ -606,10 +605,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200); // Still returns 200
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify error message was sent via WebSocket
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -626,7 +625,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Hello AI",
useAdvancedContext: false,
@@ -634,10 +633,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify error message about AI being disabled
expect(ws.sendMessageToAllClients).toHaveBeenCalledWith({
type: 'llm-stream',
@@ -656,7 +655,7 @@ describe("LLM API Tests", () => {
await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Save this response",
useAdvancedContext: false,
@@ -681,10 +680,10 @@ describe("LLM API Tests", () => {
});
// Send multiple requests rapidly
const promises = Array.from({ length: 3 }, (_, i) =>
const promises = Array.from({ length: 3 }, (_, i) =>
supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: `Request ${i + 1}`,
useAdvancedContext: false,
@@ -693,7 +692,7 @@ describe("LLM API Tests", () => {
);
const responses = await Promise.all(promises);
// All should succeed
responses.forEach(response => {
expect(response.status).toBe(200);
@@ -717,7 +716,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post(`/api/llm/chat/${testChatId}/messages/stream`)
.send({
content: "Generate large response",
useAdvancedContext: false,
@@ -725,10 +724,10 @@ describe("LLM API Tests", () => {
});
expect(response.status).toBe(200);
// Import ws service to access mock
const ws = (await import("../../services/ws.js")).default;
// Verify multiple chunks were sent
const streamCalls = (ws.sendMessageToAllClients as any).mock.calls.filter(
call => call[0].type === 'llm-stream' && call[0].content
@@ -742,7 +741,7 @@ describe("LLM API Tests", () => {
const response = await supertest(app)
.post("/api/llm/chat")
.set('Content-Type', 'application/json')
.send('{ invalid json }');
expect([400, 500]).toContain(response.status);
@@ -751,7 +750,7 @@ describe("LLM API Tests", () => {
it("should handle missing required fields", async () => {
const response = await supertest(app)
.post("/api/llm/chat")
.send({
// Missing required fields
});
@@ -763,7 +762,7 @@ describe("LLM API Tests", () => {
it("should handle invalid parameter types", async () => {
const response = await supertest(app)
.post("/api/llm/chat")
.send({
title: "Test Chat",
temperature: "invalid", // Should be number
@@ -787,4 +786,4 @@ describe("LLM API Tests", () => {
}
}
});
});
});

View File

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

View File

@@ -80,7 +80,6 @@ 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,26 +37,9 @@ 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) {
@@ -98,6 +81,15 @@ 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

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

View File

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

View File

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

View File

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

View File

@@ -196,10 +196,8 @@ 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, relativePath);
renderText(result, note);
} 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="${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 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>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.getElementById('content'));

View File

@@ -9,7 +9,6 @@ describe("Share API test", () => {
let cannotSetHeadersCount = 0;
beforeAll(async () => {
vi.useFakeTimers();
const buildApp = (await import("../app.js")).default;
app = await buildApp();
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
@@ -44,39 +43,3 @@ 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, NextFunction } from "express";
import type { Request, Response, Router } from "express";
import shaca from "./shaca/shaca.js";
import shacaLoader from "./shaca/shaca_loader.js";
@@ -139,21 +139,17 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
}
function register(router: Router) {
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);
function renderNote(note: SNote, req: Request, res: Response) {
if (!note) {
console.log("Unable to find note ", note);
res.status(404);
renderDefault(res, "404", { relativePath, t });
renderDefault(res, "404");
return;
}
if (!checkNoteAccess(note.noteId, req, res)) {
requestCredentials(res);
return;
}
@@ -165,20 +161,18 @@ function register(router: Router) {
return;
}
const { header, content, isEmpty } = contentRenderer.getContent(note, relativePath);
const { header, content, isEmpty } = contentRenderer.getContent(note);
const subRoot = getSharedSubTreeRoot(note);
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
const opts = {
note,
header,
content,
isEmpty,
subRoot,
assetPath: isDev ? assetPath : `${relativePath}${assetPath}`,
assetPath: isDev ? assetPath : `../${assetPath}`,
assetUrlFragment,
appPath: isDev ? appPath : `${relativePath}${appPath}`,
relativePath,
appPath: isDev ? appPath : `../${appPath}`,
showLoginInShareTheme,
t,
isDev
@@ -225,165 +219,184 @@ function register(router: Router) {
}
}
// 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;
}
router.get("/share/", (req, res) => {
if (req.path.substr(-1) !== "/") {
res.redirect("../share/");
return;
}
next();
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 });
});
}

View File

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

View File

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

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

View File

@@ -123,16 +123,6 @@ 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><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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,7 +81,7 @@
</head>
<%
const customLogoId = subRoot.note.getRelation("shareLogo")?.value;
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `${relativePath}${assetUrlFragment}/images/icon-color.svg`;
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `../${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) : "";

260
pnpm-lock.yaml generated
View File

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

View File

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