Compare commits

..

88 Commits

Author SHA1 Message Date
Elian Doran
e6d728715f feat(geomap): support hiding labels 2026-02-12 19:47:24 +02:00
Elian Doran
54a52f0589 feat(geomap): support for custom tile URLs 2026-02-12 19:32:22 +02:00
Elian Doran
badfa23f86 refactor(geomap): delegate layer data handling to index 2026-02-12 19:12:56 +02:00
Elian Doran
b83eee9bdc Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-11 20:21:55 +02:00
Elian Doran
e41b2e8d31 fix(options/shortcuts): filter text box forces lower case 2026-02-11 20:21:52 +02:00
Elian Doran
d93cec2bfd feat(options/shortcuts): add no results 2026-02-11 20:19:53 +02:00
Elian Doran
9eb87a39cd chore(options/shortcuts): refactor filtering 2026-02-11 20:15:39 +02:00
Elian Doran
07818ec1df fix(options): unnecessary full-height 2026-02-11 20:11:46 +02:00
Elian Doran
d4052dbe37 feat(options/shortcuts): equal height 2026-02-11 20:04:09 +02:00
Elian Doran
656f5e0a7f feat(options/shortcuts): make header and footer sticky (closes #8675) 2026-02-11 20:02:37 +02:00
Elian Doran
0cc5e4dac3 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.3.3 (#8609) 2026-02-11 07:51:46 +02:00
Elian Doran
8bbfff3cb2 fix(deps): update dependency i18next to v25.8.4 (#8611) 2026-02-11 07:51:31 +02:00
Elian Doran
33fae88cad chore(deps): update dependency stylelint to v17.2.0 (#8612) 2026-02-11 07:51:15 +02:00
Elian Doran
4feb23e9ca fix(deps): update dependency @preact/signals to v2.7.1 (#8619) 2026-02-11 07:50:04 +02:00
Elian Doran
c2b6b7ba72 chore(deps): update dependency esbuild to v0.27.3 (#8631) 2026-02-11 07:48:22 +02:00
Elian Doran
fb76aee258 chore(deps): update dependency @anthropic-ai/sdk to v0.74.0 (#8633) 2026-02-11 07:47:46 +02:00
Elian Doran
320b1829cc chore(deps): update dependency openai to v6.21.0 (#8634) 2026-02-11 07:47:16 +02:00
Elian Doran
601f0255a4 chore(deps): update dependency @playwright/test to v1.58.2 (#8642) 2026-02-11 07:46:46 +02:00
Elian Doran
b6cc2b227a chore(deps): update dependency wxt to v0.20.14 (#8643) 2026-02-11 07:46:36 +02:00
Elian Doran
1ae3be2fda fix(deps): update codemirror (#8644) 2026-02-11 07:44:55 +02:00
Elian Doran
5b4d35ea86 chore(deps): update dependency @redocly/cli to v2.17.0 (#8645) 2026-02-11 07:44:38 +02:00
Elian Doran
00735e6c8e chore(deps): update dependency axios to v1.13.5 (#8661) 2026-02-11 07:44:27 +02:00
Elian Doran
66a42a38c9 chore(deps): update dependency @smithy/middleware-retry to v4.4.31 (#8669) 2026-02-11 07:43:54 +02:00
Elian Doran
05af1fba80 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.11 (#8670) 2026-02-11 07:43:45 +02:00
Elian Doran
3b2dd0f5e9 chore(deps): update pnpm to v10.29.2 (#8671) 2026-02-11 07:43:33 +02:00
Elian Doran
1493a66a36 chore(deps): update dependency webdriverio to v9.24.0 (#8672) 2026-02-11 07:43:15 +02:00
Elian Doran
b4bf103fd8 chore(deps): update typescript-eslint monorepo to v8.55.0 (#8673) 2026-02-11 07:43:00 +02:00
renovate[bot]
94286becfd chore(deps): update typescript-eslint monorepo to v8.55.0 2026-02-11 01:04:05 +00:00
renovate[bot]
a68aade58c chore(deps): update dependency webdriverio to v9.24.0 2026-02-11 01:03:25 +00:00
renovate[bot]
014201edf4 chore(deps): update pnpm to v10.29.2 2026-02-11 01:02:09 +00:00
renovate[bot]
8ae6297148 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.11 2026-02-11 01:01:56 +00:00
renovate[bot]
5e0300aa8e chore(deps): update dependency @smithy/middleware-retry to v4.4.31 2026-02-11 01:01:18 +00:00
renovate[bot]
80a7e18413 chore(deps): update dependency openai to v6.21.0 2026-02-10 21:08:56 +00:00
Elian Doran
43be0a1a3f chore(desktop): disable dev tools for the print window 2026-02-10 17:29:00 +02:00
Elian Doran
5eb32744c3 feat(desktop): display print errors 2026-02-10 17:26:58 +02:00
Elian Doran
89d39f5f2b feat(desktop): add logs when printing 2026-02-10 17:12:28 +02:00
Elian Doran
1f4900dd1e fix(desktop): print failing on upgrade due to cache issues 2026-02-10 16:30:16 +02:00
renovate[bot]
1c561c1483 chore(deps): update dependency stylelint to v17.2.0 2026-02-10 11:44:18 +00:00
Adorian Doran
6baaf60b67 style/web view setup: update icon 2026-02-10 02:53:46 +02:00
Adorian Doran
dde73f6c2b style/attachments: tweak 2026-02-10 02:46:10 +02:00
Adorian Doran
f445a49b34 client/notes/web view: create a web view setup form 2026-02-10 02:45:43 +02:00
Adorian Doran
29016d1cf5 style/refactor: promote centered form as global style 2026-02-10 01:26:07 +02:00
Adorian Doran
c06435046b style/alert bars: use a global style, tweak spacing 2026-02-09 23:39:22 +02:00
Adorian Doran
134422802f style/protected session password request: improve layout and appearance 2026-02-09 23:14:32 +02:00
Adorian Doran
5e00d6a305 style/collapsible: use low profile style for the expand/collapse button 2026-02-09 20:34:58 +02:00
Adorian Doran
b5f0137d8e Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-09 20:33:32 +02:00
Adorian Doran
081d041cbc style/buttons: define a style for low profile buttons 2026-02-09 20:33:24 +02:00
Elian Doran
95e733a67c Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-09 20:22:43 +02:00
Elian Doran
a664057312 fix(website): missing RPM GPG key (closes #8618) 2026-02-09 20:22:41 +02:00
Elian Doran
5b1a2d93bf fix(tree): child note badge overlapping text (closes #8567) 2026-02-09 20:20:20 +02:00
Adorian Doran
0323f95828 style/status bar: do not wrap the action buttons text 2026-02-09 19:57:13 +02:00
Elian Doran
375838449f fix(geomap): z-index issues with toolbar and buttons (closes #8649) 2026-02-09 19:55:57 +02:00
Adorian Doran
4562de8c2c close #2137 2026-02-09 19:19:41 +02:00
Adorian Doran
68d21669e7 style/similar notes: fix font size variation according to similarity 2026-02-09 19:12:47 +02:00
renovate[bot]
625e0cf159 chore(deps): update dependency @redocly/cli to v2.17.0 2026-02-09 13:58:05 +00:00
renovate[bot]
551ef00c61 fix(deps): update dependency @preact/signals to v2.7.1 2026-02-09 10:46:41 +00:00
renovate[bot]
10518f6364 chore(deps): update dependency axios to v1.13.5 2026-02-09 00:39:09 +00:00
Elian Doran
1eafda36a9 Translations update from Hosted Weblate (#8652) 2026-02-08 21:02:38 +02:00
TS
871ecf0158 Translated using Weblate (Polish)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pl/
2026-02-08 18:57:05 +00:00
TS
429000bdcb Translated using Weblate (Polish)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pl/
2026-02-08 18:57:04 +00:00
TS
607940ed60 Translated using Weblate (Polish)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pl/
2026-02-08 18:57:03 +00:00
noobhjy
46f61a4311 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/zh_Hans/
2026-02-08 18:57:02 +00:00
Marcel
3721df0502 Translated using Weblate (German)
Currently translated at 100.0% (1772 of 1772 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-02-08 18:57:02 +00:00
Marcel
11add681ec Translated using Weblate (German)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/de/
2026-02-08 18:57:01 +00:00
TS
6e36eea6c8 Translated using Weblate (Polish)
Currently translated at 100.0% (1772 of 1772 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2026-02-08 18:57:00 +00:00
Aindriú Mac Giolla Eoin
6a82e7a24c Translated using Weblate (Irish)
Currently translated at 100.0% (1772 of 1772 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-08 18:56:59 +00:00
Ulices
5f625fa9f3 Translated using Weblate (Spanish)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/es/
2026-02-08 18:56:58 +00:00
Aindriú Mac Giolla Eoin
a95527674f Translated using Weblate (Irish)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ga/
2026-02-08 18:56:58 +00:00
green
f6149c67dc Translated using Weblate (Japanese)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ja/
2026-02-08 18:56:57 +00:00
Ulices
00c2a07e33 Translated using Weblate (Spanish)
Currently translated at 100.0% (1772 of 1772 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-02-08 18:56:56 +00:00
Elian Doran
bc49b22c32 docs(user): add note about icon packs 2026-02-08 20:55:48 +02:00
Elian Doran
fe1b1c8bc3 feat(website/resources): add note about icon packs 2026-02-08 20:52:13 +02:00
Elian Doran
a2825a06d6 fix(website): top level subpages not rendered 2026-02-08 20:43:48 +02:00
renovate[bot]
3c33b5b169 fix(deps): update codemirror 2026-02-08 13:39:10 +00:00
Elian Doran
934a867c83 chore(deps): update dependency electron to v40.2.1 (#8646) 2026-02-08 10:42:50 +02:00
Elian Doran
a36337fba8 chore(deps): update pnpm to v10.29.1 (#8655) 2026-02-08 10:42:17 +02:00
Elian Doran
b3bd53bdd0 fix(deps): update dependency mind-elixir to v5.8.0 (#8656) 2026-02-08 10:41:23 +02:00
Elian Doran
55518d4a8e fix(deps): update dependency eslint-linter-browserify to v10 (#8657) 2026-02-08 10:39:34 +02:00
renovate[bot]
2949c330d7 fix(deps): update dependency eslint-linter-browserify to v10 2026-02-08 00:28:40 +00:00
renovate[bot]
7ec056dbe0 fix(deps): update dependency mind-elixir to v5.8.0 2026-02-08 00:28:02 +00:00
renovate[bot]
983b60a8b9 chore(deps): update pnpm to v10.29.1 2026-02-08 00:27:27 +00:00
renovate[bot]
000c31b66c chore(deps): update dependency @anthropic-ai/sdk to v0.74.0 2026-02-07 20:46:58 +00:00
renovate[bot]
fd7780abb0 chore(deps): update dependency esbuild to v0.27.3 2026-02-07 11:53:22 +00:00
renovate[bot]
328740909b chore(deps): update dependency electron to v40.2.1 2026-02-07 00:35:41 +00:00
renovate[bot]
c8a981e8d6 chore(deps): update dependency wxt to v0.20.14 2026-02-07 00:33:00 +00:00
renovate[bot]
faac45784c chore(deps): update dependency @playwright/test to v1.58.2 2026-02-07 00:32:04 +00:00
renovate[bot]
f6b454cb9a fix(deps): update dependency i18next to v25.8.4 2026-02-05 13:44:21 +00:00
renovate[bot]
563463782c Update dependency @ckeditor/ckeditor5-dev-build-tools to v54.3.3 2026-02-04 01:51:41 +00:00
84 changed files with 1999 additions and 2022 deletions

View File

@@ -9,9 +9,9 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.28.2",
"packageManager": "pnpm@10.29.2",
"devDependencies": {
"@redocly/cli": "2.15.1",
"@redocly/cli": "2.17.0",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"react": "19.2.4",

View File

@@ -27,7 +27,7 @@
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.6.2",
"@preact/signals": "2.7.1",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
@@ -44,7 +44,7 @@
"draggabilly": "3.0.0",
"force-graph": "1.51.1",
"globals": "17.3.0",
"i18next": "25.8.0",
"i18next": "25.8.4",
"i18next-http-backend": "3.0.2",
"jquery": "4.0.0",
"jquery.fancytree": "2.38.5",
@@ -56,7 +56,7 @@
"mark.js": "8.11.1",
"marked": "17.0.1",
"mermaid": "11.12.2",
"mind-elixir": "5.7.1",
"mind-elixir": "5.8.0",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.3",

View File

@@ -13,13 +13,12 @@ import type LoadResults from "../services/load_results.js";
import type { CreateNoteOpts } from "../services/note_create.js";
import options from "../services/options.js";
import toast from "../services/toast.js";
import utils, { dynamicRequire, hasTouchBar } from "../services/utils.js";
import utils, { hasTouchBar } from "../services/utils.js";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import type RootContainer from "../widgets/containers/root_container.js";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import { ImportPreviewData } from "../widgets/dialogs/import_preview.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import type { InfoProps } from "../widgets/dialogs/info.jsx";
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
@@ -131,7 +130,6 @@ export type CommandMappings = {
showConfirmDialog: ConfirmWithMessageOptions;
showRecentChanges: CommandData & { ancestorNoteId: string };
showImportDialog: CommandData & { noteId: string };
showImportPreviewDialog: CommandData & ImportPreviewData;
openNewNoteSplit: NoteCommandData;
openInWindow: NoteCommandData;
openInPopup: CommandData & { noteIdOrPath: string; };
@@ -641,10 +639,6 @@ export class AppContext extends Component {
this.child(rootWidget as Component);
this.triggerEvent("initialRenderComplete", {});
if (utils.isElectron()) {
const { ipcRenderer } = dynamicRequire('electron');
ipcRenderer.send("initial-render-complete");
}
}
triggerEvent<K extends EventNames>(name: K, data: EventData<K>) {

View File

@@ -1,29 +1,29 @@
import type RootContainer from "../widgets/containers/root_container.js";
import AboutDialog from "../widgets/dialogs/about.js";
import AddLinkDialog from "../widgets/dialogs/add_link.js";
import BranchPrefixDialog from "../widgets/dialogs/branch_prefix.js";
import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js";
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
import CloneToDialog from "../widgets/dialogs/clone_to.js";
import ConfirmDialog from "../widgets/dialogs/confirm.js";
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
import ExportDialog from "../widgets/dialogs/export.js";
import HelpDialog from "../widgets/dialogs/help.js";
import ImportDialog from "../widgets/dialogs/import.js";
import ImportPreviewDialog from "../widgets/dialogs/import_preview.jsx";
import IncludeNoteDialog from "../widgets/dialogs/include_note.js";
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
import InfoDialog from "../widgets/dialogs/info.js";
import JumpToNoteDialog from "../widgets/dialogs/jump_to_note.js";
import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js";
import MoveToDialog from "../widgets/dialogs/move_to.js";
import NoteTypeChooserDialog from "../widgets/dialogs/note_type_chooser.js";
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
import PromptDialog from "../widgets/dialogs/prompt.js";
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
import RecentChangesDialog from "../widgets/dialogs/recent_changes.js";
import RevisionsDialog from "../widgets/dialogs/revisions.js";
import PromptDialog from "../widgets/dialogs/prompt.js";
import AddLinkDialog from "../widgets/dialogs/add_link.js";
import IncludeNoteDialog from "../widgets/dialogs/include_note.js";
import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js";
import BranchPrefixDialog from "../widgets/dialogs/branch_prefix.js";
import SortChildNotesDialog from "../widgets/dialogs/sort_child_notes.js";
import NoteTypeChooserDialog from "../widgets/dialogs/note_type_chooser.js";
import MoveToDialog from "../widgets/dialogs/move_to.js";
import CloneToDialog from "../widgets/dialogs/clone_to.js";
import ImportDialog from "../widgets/dialogs/import.js";
import ExportDialog from "../widgets/dialogs/export.js";
import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js";
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
import ConfirmDialog from "../widgets/dialogs/confirm.js";
import RevisionsDialog from "../widgets/dialogs/revisions.js";
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
import InfoDialog from "../widgets/dialogs/info.js";
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
import ToastContainer from "../widgets/Toast.jsx";
export function applyModals(rootContainer: RootContainer) {
@@ -52,6 +52,5 @@ export function applyModals(rootContainer: RootContainer) {
.child(<IncorrectCpuArchDialog />)
.child(<PopupEditorDialog />)
.child(<CallToActionDialog />)
.child(<ToastContainer />)
.child(<ImportPreviewDialog />);
.child(<ToastContainer />);
}

View File

@@ -18,6 +18,10 @@ export type PrintReport = {
} | {
type: "collection";
ignoredNoteIds: string[];
} | {
type: "error";
message: string;
stack?: string;
};
async function main() {

View File

@@ -1,13 +1,12 @@
import { ImportPreviewResponse, WebSocketMessage } from "@triliumnext/commons";
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
import server from "./server.js";
import ws from "./ws.js";
import utils from "./utils.js";
import appContext from "../components/app_context.js";
import { t } from "./i18n.js";
import server from "./server.js";
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
import utils from "./utils.js";
import ws from "./ws.js";
import { WebSocketMessage } from "@triliumnext/commons";
type BooleanLike = "true" | "false";
type BooleanLike = boolean | "true" | "false";
export interface UploadFilesOptions {
safeImport?: BooleanLike;
@@ -49,7 +48,7 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
dataType: "json",
type: "POST",
timeout: 60 * 60 * 1000,
error (xhr) {
error: function (xhr) {
toastService.showError(t("import.failed", { message: xhr.responseText }));
},
contentType: false, // NEEDED, DON'T REMOVE THIS
@@ -58,64 +57,6 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
}
}
export async function uploadFilesWithPreview(files: string[] | File[]) {
if (files.length === 0) {
return;
}
const taskId = utils.randomString(10);
const results: ImportPreviewResponse[] = [];
for (const file of files) {
const formData = new FormData();
formData.append("upload", file);
formData.append("taskId", taskId);
results.push(await $.ajax({
url: `${window.glob.baseApiUrl}notes/preview-import`,
headers: await server.getHeaders(),
data: formData,
dataType: "json",
type: "POST",
timeout: 60 * 60 * 1000,
error (xhr) {
toastService.showError(t("import.failed", { message: xhr.responseText }));
},
contentType: false, // NEEDED, DON'T REMOVE THIS
processData: false // NEEDED, DON'T REMOVE THIS
}));
}
return results;
}
export async function executeUploadWithPreview(parentNoteId: string, files: ImportPreviewResponse[], options: UploadFilesOptions) {
if (files.length === 0) {
return;
}
const taskId = utils.randomString(10);
let counter = 0;
for (const file of files) {
counter++;
server.post(
`notes/${parentNoteId}/execute-import`,
{
...options,
id: file.id,
taskId,
last: counter === files.length ? "true" : "false"
}
);
}
}
export function cancelUploadWithPreview(files: ImportPreviewResponse[]) {
for (const file of files) {
server.remove(`notes/preview-import/${file.id}`);
}
}
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
return {
id,

View File

@@ -913,10 +913,6 @@ export function handleRightToLeftPlacement<T extends string>(placement: T) {
return placement;
}
export function boolToString(value: boolean) {
return value ? "true" : "false";
}
export default {
reloadFrontendApp,
restartDesktopApp,

View File

@@ -800,3 +800,18 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
background: var(--hover-item-background-color);
color: var(--hover-item-text-color);
}
/*
* Alert bars
*/
div.alert {
margin-bottom: 8px;
background: var(--alert-bar-background) !important;
border-radius: 8px;
font-size: .85em;
}
div.alert p + p {
margin-block: 1em 0;
}

View File

@@ -84,6 +84,22 @@ button.btn.btn-success kbd {
letter-spacing: 0.5pt;
}
/*
* Low profile buttons
*/
button.tn-low-profile {
appearance: none;
background: transparent;
border: 0;
border-radius: 8px;
color: inherit;
}
button.tn-low-profile:hover {
background-color: var(--icon-button-hover-background);
}
/*
* Icon buttons
*/
@@ -794,3 +810,35 @@ input[type="range"] {
scrollbar-width: unset;
}
}
/*
* Centered forms
*/
.tn-centered-form {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20vh;
}
.tn-centered-form .form-group {
text-align: center;
color: var(--muted-text-color);
}
.tn-centered-form .form-icon {
font-size: 140px;
color: var(--main-border-color);
}
.tn-centered-form .protected-session-password {
margin-inline: auto;
max-width: 350px;
text-align: center;
}
.tn-centered-form input,
.tn-centered-form button {
margin-top: 12px;
}

View File

@@ -265,13 +265,6 @@ body.desktop .options-section:not(.tn-no-card) {
margin-bottom: 6px;
}
.options-section .alert {
margin-bottom: 8px;
background: var(--alert-bar-background) !important;
border-radius: 8px;
font-size: .85em;
}
nav.options-section-tabs {
min-width: var(--options-card-min-width);
max-width: var(--options-card-max-width);

View File

@@ -1,6 +1,6 @@
{
"about": {
"title": "Über Trilium Notizen",
"title": "Über Trilium Notes",
"homepage": "Startseite:",
"app_version": "App-Version:",
"db_version": "DB-Version:",
@@ -662,7 +662,8 @@
"show-cheatsheet": "Cheatsheet anzeigen",
"toggle-zen-mode": "Zen Modus",
"new-version-available": "Neues Update verfügbar",
"download-update": "Version {{latestVersion}} herunterladen"
"download-update": "Version {{latestVersion}} herunterladen",
"search_notes": "Notizen durchsuchen"
},
"sync_status": {
"unknown": "<p>Der Synchronisations-Status wird bekannt, sobald der nächste Synchronisierungsversuch gestartet wird.</p><p>Klicke, um eine Synchronisierung jetzt auszulösen.</p>",
@@ -758,7 +759,8 @@
"error_cannot_get_branch_id": "BranchId für notePath „{{notePath}}“ kann nicht abgerufen werden",
"error_unrecognized_command": "Unbekannter Befehl {{command}}",
"note_revisions": "Notiz Revisionen",
"backlinks": "Rücklinks"
"backlinks": "Rücklinks",
"content_language_switcher": "Inhaltssprache: {{language}}"
},
"note_icon": {
"change_note_icon": "Notiz-Icon ändern",
@@ -910,7 +912,8 @@
"unknown_search_option": "Unbekannte Suchoption {{searchOptionName}}",
"search_note_saved": "Suchnotiz wurde in {{-notePathTitle}} gespeichert",
"actions_executed": "Aktionen wurden ausgeführt.",
"view_options": "Optionen anzeigen:"
"view_options": "Optionen anzeigen:",
"option": "Option"
},
"similar_notes": {
"title": "Ähnliche Notizen",
@@ -2277,5 +2280,8 @@
"title_one": "{{count}} Tab",
"title_other": "{{count}} Tabs",
"more_options": "Weitere Optionen"
},
"bookmark_buttons": {
"bookmarks": "Lesezeichen"
}
}

View File

@@ -1070,10 +1070,12 @@
"note_detail_render_help_1": "This help note is shown because this note of type Render HTML doesn't have required relation to function properly.",
"note_detail_render_help_2": "Render HTML note type is used for <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. In short, you have a HTML code note (optionally with some JavaScript) and this note will render it. To make it work, you need to define a <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relation</a> called \"renderNote\" pointing to the HTML note to render."
},
"web_view": {
"web_view": "Web View",
"embed_websites": "Note of type Web View allows you to embed websites into Trilium.",
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
"web_view_setup": {
"title": "Create a live view of a webpage directly into Trilium",
"url_placeholder": "Enter or paste the website address, for example https://triliumnotes.org",
"create_button": "Create Web View",
"invalid_url_title": "Invalid address",
"invalid_url_message": "Insert a valid web address, for example https://triliumnotes.org."
},
"backend_log": {
"refresh": "Refresh"
@@ -1589,7 +1591,8 @@
"description": "Description",
"reload_app": "Reload app to apply changes",
"set_all_to_default": "Set all shortcuts to the default",
"confirm_reset": "Do you really want to reset all keyboard shortcuts to the default?"
"confirm_reset": "Do you really want to reset all keyboard shortcuts to the default?",
"no_results": "No shortcuts found matching '{{filter}}'"
},
"spellcheck": {
"title": "Spell Check",
@@ -1795,6 +1798,8 @@
"printing": "Printing in progress...",
"printing_pdf": "Exporting to PDF in progress...",
"print_report_title": "Print report",
"print_report_error_title": "Failed to print",
"print_report_stack_trace": "Stack trace",
"print_report_collection_content_one": "{{count}} note in the collection could not be printed because they are not supported or they are protected.",
"print_report_collection_content_other": "{{count}} notes in the collection could not be printed because they are not supported or they are protected.",
"print_report_collection_details_button": "See details",
@@ -2099,7 +2104,8 @@
"raster": "Raster",
"vector_light": "Vector (Light)",
"vector_dark": "Vector (Dark)",
"show-scale": "Show scale"
"show-scale": "Show scale",
"show-labels": "Show marker names"
},
"table_context_menu": {
"delete_row": "Delete row"
@@ -2283,37 +2289,5 @@
},
"bookmark_buttons": {
"bookmarks": "Bookmarks"
},
"import_preview": {
"intro_safe_one": "You are about to import an archive with no unsafe content.",
"intro_safe_other": "You are about to import {{count}} archives with no unsafe content.",
"intro_unsafe_one": "You are about to import an archive with active content.",
"intro_unsafe_other": "You are about to import {{count}} archives, some of which have active content.",
"title": "Import preview",
"notes_count_one": "{{count}} note",
"notes_count_other": "{{count}} notes",
"attributes_count_one": "{{count}} attribute",
"attributes_count_other": "{{count}} attributes",
"attachments_count_one": "{{count}} attachment",
"attachments_count_other": "{{count}} attachments",
"cancel": "Cancel",
"import": "Import",
"import_with_timeout": "Import ({{timeout}})",
"import_safely": "Import safely (recommended)",
"import_safely_description": "Scripts, widgets and icon packs will be disabled.",
"import_trust": "Trust and enable active content",
"import_trust_description": "Only do this if you trust the source.",
"parent_note": "Parent note",
"badge_client_side_scripting_title": "Client-side scripting",
"badge_client_side_scripting_tooltip": "Can modify the application's interface and send requests to the server. Malicious scripts can read or change your notes.",
"badge_server_side_scripting_title": "Server-side scripting",
"badge_server_side_scripting_tooltip": "Runs on the server with access to your database and local files. Malicious scripts could read, modify, or delete your data.",
"badge_code_execution_title": "Code execution",
"badge_code_execution_description": "Allows running arbitrary programs on your system or server. This can fully compromise your data and environment.",
"badge_icon_pack_title": "Icon pack",
"badge_icon_pack_description": "Provides custom icons. Usually safe, but malformed or very large icon packs may cause performance or stability issues.",
"badge_web_view_title": "Web view",
"badge_web_view_description": "Displays external web pages inside Trilium. These pages may track activity or receive information about your notes."
}
}

View File

@@ -662,7 +662,8 @@
"show-cheatsheet": "Mostrar hoja de trucos",
"toggle-zen-mode": "Modo Zen",
"new-version-available": "Nueva actualización disponible",
"download-update": "Obtener versión {{latestVersion}}"
"download-update": "Obtener versión {{latestVersion}}",
"search_notes": "Buscar notas"
},
"zen_mode": {
"button_exit": "Salir del modo Zen"
@@ -762,7 +763,8 @@
"error_cannot_get_branch_id": "No se puede obtener el branchID del notePath '{{notePath}}'",
"error_unrecognized_command": "Comando no reconocido {{command}}",
"note_revisions": "Revisiones de notas",
"backlinks": "Vínculos de retroceso"
"backlinks": "Vínculos de retroceso",
"content_language_switcher": "Idioma de contenido: {{language}}"
},
"note_icon": {
"change_note_icon": "Cambiar icono de nota",
@@ -915,7 +917,8 @@
"unknown_search_option": "Opción de búsqueda desconocida {{searchOptionName}}",
"search_note_saved": "La nota de búsqueda se ha guardado en {{- notePathTitle}}",
"actions_executed": "Las acciones han sido ejecutadas.",
"view_options": "Ver opciones:"
"view_options": "Ver opciones:",
"option": "opción"
},
"similar_notes": {
"title": "Notas similares",
@@ -2292,5 +2295,8 @@
"title_many": "{{count}} pestañas",
"title_other": "{{count}} pestañas",
"more_options": "Más opciones"
},
"bookmark_buttons": {
"bookmarks": "Marcadores"
}
}

View File

@@ -27,7 +27,8 @@
"show-cheatsheet": "Taispeáin Bileog leideanna",
"toggle-zen-mode": "Mód Zen",
"new-version-available": "Nuashonrú Nua ar Fáil",
"download-update": "Faigh Leagan {{latestVersion}}"
"download-update": "Faigh Leagan {{latestVersion}}",
"search_notes": "Cuardaigh nótaí"
},
"about": {
"title": "Maidir le Trilium Notes",
@@ -764,7 +765,8 @@
"note_revisions": "Athbhreithnithe nóta",
"error_cannot_get_branch_id": "Ní féidir aitheantas brainse a fháil do NotePad '{{notePath}}'",
"error_unrecognized_command": "Ordú gan aitheantas {{command}}",
"backlinks": "Naisc ar ais"
"backlinks": "Naisc ar ais",
"content_language_switcher": "Teanga an ábhair: {{language}}"
},
"note_icon": {
"change_note_icon": "Deilbhín nóta athraithe",
@@ -919,7 +921,8 @@
"unknown_search_option": "Rogha cuardaigh anaithnid {{searchOptionName}}",
"search_note_saved": "Tá an nóta cuardaigh sábháilte i {{- notePathTitle}}",
"actions_executed": "Tá gníomhartha curtha i gcrích.",
"view_options": "Roghanna féachana:"
"view_options": "Roghanna féachana:",
"option": "rogha"
},
"similar_notes": {
"title": "Nótaí Comhchosúla",
@@ -2322,5 +2325,8 @@
"title_many": "{{count}} cluaisíní",
"title_other": "{{count}} cluaisíní",
"more_options": "Tuilleadh roghanna"
},
"bookmark_buttons": {
"bookmarks": "Leabharmharcanna"
}
}

View File

@@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "Nie udało się załadować niestandardowego skryptu",
"message": "Skrypt z notatki o ID \"{{id}}\", zatytułowany \"{{title}}\", nie mógł zostać wykonany z powodu:\n\n{{message}}"
"message": "Skrypt nie mógł zostać wykonany z powodu:\n\n{{message}}"
},
"widget-list-error": {
"title": "Nie udało się pobrać listy widżetów z serwera"
@@ -29,8 +29,9 @@
"widget-render-error": {
"title": "Nie udało się wyrenderować niestandardowego widżetu React"
},
"widget-missing-parent": "Niestandardowy widżet nie ma zdefiniowanej obowiązkowej właściwości „{{property}}”.",
"open-script-note": "Otwórz notatkę ze skryptem"
"widget-missing-parent": "Niestandardowy widżet nie ma zdefiniowanej obowiązkowej właściwości „{{property}}”.\nJeśli skrypt ma działać bez interfejsu użytkownika (UI) wyłącz go: '#run=frontendStartup'.",
"open-script-note": "Otwórz notatkę ze skryptem",
"scripting-error": "Błąd skryptu użytkownika: {{title}}"
},
"add_link": {
"add_link": "Dodaj link",
@@ -191,7 +192,8 @@
"expand_tooltip": "Rozwija bezpośrednie elementy podrzędne tej kolekcji (o jeden poziom). Aby uzyskać więcej opcji, naciśnij strzałkę po prawej.",
"expand_first_level": "Rozwiń bezpośrednie elementy podrzędne",
"expand_nth_level": "Rozwiń {{depth}} poziomów",
"expand_all_levels": "Rozwiń wszystkie poziomy"
"expand_all_levels": "Rozwiń wszystkie poziomy",
"hide_child_notes": "Ukryj notatki podrzędne w derzwie"
},
"board_view": {
"move-to": "Przenieś do",
@@ -240,7 +242,7 @@
"background_effects_title": "Efekty tła są teraz stabilne",
"dismiss": "Odrzuć",
"background_effects_button": "Włącz efekty tła",
"background_effects_message": "Na urządzeniach z systemem Windows efekty tła są teraz w pełni stabilne. Efekty tła dodają odrobinę koloru do interfejsu użytkownika poprzez rozmycie tła za nim. Ta technika jest również stosowana w innych aplikacjach, takich jak Eksplorator Windows.",
"background_effects_message": "Na urządzeniach z systemem Windows i macOS efekty tła są stabilne. Efekty tła dodają odrobinę koloru do interfejsu użytkownika poprzez rozmycie tła za nim.",
"new_layout_title": "Nowy układ",
"new_layout_message": "Wprowadziliśmy zmodernizowany układ interfejsu dla Trilium. Wstążka została usunięta i płynnie zintegrowana z głównym interfejsem, a jej kluczowe funkcje przejęły nowy pasek stanu i rozwijane sekcje (takie jak promowane atrybuty).\n\nNowy układ jest domyślnie włączony i można go tymczasowo wyłączyć w Ustawienia → Wygląd.",
"new_layout_button": "Szczegóły"
@@ -520,7 +522,8 @@
"action": "akcja",
"search_button": "Szukaj",
"search_execute": "Szukaj i wykonaj akcje",
"view_options": "Ustawienia widoku:"
"view_options": "Ustawienia widoku:",
"option": "opcja"
},
"similar_notes": {
"title": "Podobne notatki",
@@ -602,8 +605,8 @@
"desktop-application": "Aplikacja desktopowa",
"native-title-bar": "Natywny pasek tytułu",
"native-title-bar-description": "Dla Windows i macOS, wyłączenie natywnego paska tytułu sprawia, że aplikacja wygląda bardziej kompaktowo. Na Linuxie, włączenie natywnego paska tytułu lepiej integruje się z resztą systemu.",
"background-effects": "Włącz efekty tła (tylko Windows 11)",
"background-effects-description": "Efekt Mica dodaje rozmyte, stylowe tło do okien aplikacji, tworząc głębię i nowoczesny wygląd. \"Natywny pasek tytułu\" musi być wyłączony.",
"background-effects": "Włącz efekty tła",
"background-effects-description": "Dodaje rozmyte, stylowe tło do okien aplikacji, tworząc głębię i nowoczesny wygląd. \"Natywny pasek tytułu\" musi być wyłączony.",
"restart-app-button": "Zrestartuj aplikację, aby zobaczyć zmiany",
"zoom-factor": "Współczynnik powiększenia"
},
@@ -1182,7 +1185,8 @@
"show-cheatsheet": "Pokaż ściągawkę",
"toggle-zen-mode": "Tryb Zen",
"new-version-available": "Dostępna nowa aktualizacja",
"download-update": "Pobierz wersję {{latestVersion}}"
"download-update": "Pobierz wersję {{latestVersion}}",
"search_notes": "Przeszukaj notatki"
},
"zen_mode": {
"button_exit": "Wyjdź z trybu Zen"
@@ -1265,7 +1269,7 @@
"button_title": "Eksportuj diagram jako SVG"
},
"relation_map_buttons": {
"create_child_note_title": "Utwórz nową notatkę podrzędną i dodaj ją do tej mapy relacji",
"create_child_note_title": "Utwórz notatkę podrzędną i dodaj ją do mapy",
"reset_pan_zoom_title": "Zresetuj przesunięcie i powiększenie do początkowych współrzędnych i powiększenia",
"zoom_in_title": "Powiększ",
"zoom_out_title": "Pomniejsz"
@@ -1281,12 +1285,23 @@
"delete_this_note": "Usuń tę notatkę",
"note_revisions": "Wersje notatki",
"error_cannot_get_branch_id": "Nie można pobrać branchId dla ścieżki notatki '{{notePath}}'",
"error_unrecognized_command": "Nierozpoznane polecenie {{command}}"
"error_unrecognized_command": "Nierozpoznane polecenie {{command}}",
"backlinks": "Linki zwrotne",
"content_language_switcher": "Język treści: {{language}}"
},
"note_icon": {
"change_note_icon": "Zmień ikonę notatki",
"search": "Szukaj:",
"reset-default": "Przywróć domyślną ikonę"
"reset-default": "Przywróć domyślną ikonę",
"search_placeholder_one": "Znaleziono {{number}} ikonę w {{count}} pakietach",
"search_placeholder_few": "Znaleziono {{number}} ikon w {{count}} pakietach",
"search_placeholder_many": "Znaleziono {{number}} ikon w {{count}} pakietach",
"search_placeholder_filtered": "Wyszukaj {{number}} ikon w {{name}}",
"filter": "Filtr",
"filter-none": "Wszystkie ikony",
"filter-default": "Domyślne ikony",
"icon_tooltip": "{{name}}\npakiet ikon: {{iconPack}}",
"no_results": "Nie znaleziono ikon."
},
"basic_properties": {
"note_type": "Typ notatki",
@@ -1826,7 +1841,7 @@
"will_be_deleted_in": "Ten załącznik zostanie automatycznie usunięty za {{time}}",
"will_be_deleted_soon": "Ten załącznik zostanie wkrótce automatycznie usunięty",
"deletion_reason": ", ponieważ załącznik nie jest podlinkowany w treści notatki. Aby zapobiec usunięciu, dodaj link do załącznika z powrotem do treści lub przekonwertuj załącznik na notatkę.",
"role_and_size": "Rola: {{role}}, Rozmiar: {{size}}",
"role_and_size": "Rola: {{role}}, Rozmiar: {{size}}, MIME: {{- mimeType}}",
"link_copied": "Link do załącznika skopiowany do schowka.",
"unrecognized_role": "Nierozpoznana rola załącznika '{{role}}'."
},
@@ -1880,7 +1895,10 @@
"apply-bulk-actions": "Zastosuj akcje masowe",
"converted-to-attachments": "{{count}} notatek zostało przekonwertowanych na załączniki.",
"convert-to-attachment-confirm": "Czy na pewno chcesz przekonwertować wybrane notatki na załączniki ich notatek nadrzędnych? Ta operacja dotyczy tylko notatek Obrazów, inne notatki zostaną pominięte.",
"open-in-popup": "Szybka edycja"
"open-in-popup": "Szybka edycja",
"open-in-a-new-window": "Otwórz w nowym oknie",
"hide-subtree": "Ukryj gałąź",
"show-subtree": "Rozwiń gałąź"
},
"shared_info": {
"shared_publicly": "Ta notatka jest udostępniona publicznie pod adresem {{- link}}.",
@@ -1971,7 +1989,17 @@
"create-child-note": "Utwórz notatkę podrzędną",
"unhoist": "Cofnij zawężenie",
"toggle-sidebar": "Przełącz pasek boczny",
"dropping-not-allowed": "Upuszczanie notatek w tej lokalizacji jest niedozwolone."
"dropping-not-allowed": "Upuszczanie notatek w tej lokalizacji jest niedozwolone.",
"clone-indicator-tooltip": "Ta notatka ma {{- count}} notatek nadrzędnych: {{- parents}}",
"clone-indicator-tooltip-single": "Ta notatka jest sklonowana (1 dodatkowa notatka nadrzędna: {{- parent}})",
"shared-indicator-tooltip": "Ta notatka jest udostępniona publicznie",
"shared-indicator-tooltip-with-url": "Ta notatka jest udostępniana publicznie jako: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} notatka podrzędna ukryta w drzewie",
"subtree-hidden-tooltip_few": "{{count}} notatek podrzędnych ukrytych w drzewie",
"subtree-hidden-tooltip_many": "{{count}} notatek podrzędnych ukrytych w drzewie",
"subtree-hidden-moved-title": "Dodano do {{title}}",
"subtree-hidden-moved-description-collection": "Ta kolekcja ukrywa swoje notatki podrzędne w drzewie.",
"subtree-hidden-moved-description-other": "Notatki podrzędne są ukryte w drzewie tej notatki."
},
"title_bar_buttons": {
"window-on-top": "Utrzymuj okno na wierzchu"
@@ -1979,7 +2007,13 @@
"note_detail": {
"could_not_find_typewidget": "Nie można znaleźć widżetu typu dla typu '{{type}}'",
"printing": "Drukowanie w toku...",
"printing_pdf": "Eksportowanie do PDF w toku..."
"printing_pdf": "Eksportowanie do PDF w toku...",
"print_report_title": "Wydrukuj raport",
"print_report_collection_content_one": "Nie można wydrukować {{count}} notatki w kolekcji, ponieważ nie jest ona obsługiwana lub jest chroniona.",
"print_report_collection_content_few": "Nie można wydrukować {{count}} notatek w kolekcji, ponieważ nie są one obsługiwane lub są chronione.",
"print_report_collection_content_many": "Nie można wydrukować {{count}} notatek w kolekcji, ponieważ nie są one obsługiwane lub są chronione.",
"print_report_collection_details_button": "Zobacz szczegóły",
"print_report_collection_details_ignored_notes": "Zignorowane notatki"
},
"note_title": {
"placeholder": "wpisz tytuł notatki tutaj...",
@@ -1989,7 +2023,8 @@
"note_type_switcher_others": "Inny typ notatki",
"note_type_switcher_templates": "Szablon",
"note_type_switcher_collection": "Kolekcja",
"edited_notes": "Edytowane notatki"
"edited_notes": "Notatki edytowane dzisiaj",
"promoted_attributes": "Sugerowane atrybuty"
},
"search_result": {
"no_notes_found": "Nie znaleziono notatek dla podanych parametrów wyszukiwania.",
@@ -1999,7 +2034,11 @@
"configure_launchbar": "Konfiguruj pasek szybkiego dostępu"
},
"sql_result": {
"no_rows": "Dla tego zapytania nie zwrócono żadnych wierszy"
"no_rows": "Dla tego zapytania nie zwrócono żadnych wierszy",
"not_executed": "Zapytanie nie zostało jeszcze wykonane.",
"failed": "Wykonanie zapytania SQL nie powiodło się",
"statement_result": "Wynik wyrażenia",
"execute_now": "Wykonaj teraz"
},
"sql_table_schemas": {
"tables": "Tabele"
@@ -2116,7 +2155,8 @@
"geo-map": {
"create-child-note-title": "Utwórz nową notatkę podrzędną i dodaj ją do mapy",
"create-child-note-instruction": "Kliknij na mapie, aby utworzyć nową notatkę w tej lokalizacji lub naciśnij Escape, aby anulować.",
"unable-to-load-map": "Nie można załadować mapy."
"unable-to-load-map": "Nie można załadować mapy.",
"create-child-note-text": "Dodaj zaznaczenie"
},
"geo-map-context": {
"open-location": "Otwórz lokalizację",
@@ -2183,7 +2223,14 @@
"execute_sql_description": "Ta notatka jest notatką SQL. Kliknij, aby wykonać zapytanie SQL.",
"shared_copy_to_clipboard": "Kopiuj link do schowka",
"shared_open_in_browser": "Otwórz link w przeglądarce",
"shared_unshare": "Usuń udostępnienie"
"shared_unshare": "Usuń udostępnienie",
"save_status_saved": "Zapisane",
"save_status_saving": "Zapisywanie...",
"save_status_unsaved": "Niezapisane",
"save_status_error": "Zapis nie powiódł się",
"save_status_saving_tooltip": "Zmiany zostały zapisane.",
"save_status_unsaved_tooltip": "Są niezapisane zmiany. Zostaną one zapisane automatycznie za chwilę.",
"save_status_error_tooltip": "Wystąpił błąd podczas zapisywania notatki. Spróbuj skopiować treść notatki w inne miejsce i ponownie załadować aplikację."
},
"status_bar": {
"language_title": "Zmień język treści",
@@ -2226,5 +2273,30 @@
"empty_button": "Ukryj panel",
"toggle": "Pokaż/ukryj prawy panel",
"custom_widget_go_to_source": "Przejdź do kodu źródłowego"
},
"pdf": {
"attachments_one": "{{count}} załącznik",
"attachments_few": "{{count}} załączniki",
"attachments_many": "{{count}} załączników",
"layers_one": "{{count}} warstwa",
"layers_few": "{{count}} warstw",
"layers_many": "{{count}} warstw",
"pages_one": "{{count}} strona",
"pages_few": "{{count}} stron",
"pages_many": "{{count}} stron",
"pages_alt": "Strona {{pageNumber}}",
"pages_loading": "Wczytuję..."
},
"platform_indicator": {
"available_on": "Dostępne na {{platform}}"
},
"mobile_tab_switcher": {
"title_one": "{{count}} zakładka",
"title_few": "{{count}} zakładki",
"title_many": "{{count}} zakładek",
"more_options": "Więcej opcji"
},
"bookmark_buttons": {
"bookmarks": "Zakładki"
}
}

View File

@@ -370,7 +370,33 @@ function showToast(type: "printing" | "exporting_pdf", progress: number = 0) {
}
function handlePrintReport(printReport?: PrintReport) {
if (printReport?.type === "collection" && printReport.ignoredNoteIds.length > 0) {
if (!printReport) return;
if (printReport.type === "error") {
toast.showPersistent({
id: "print-error",
icon: "bx bx-error-circle",
title: t("note_detail.print_report_error_title"),
message: printReport.message,
buttons: printReport.stack ? [
{
text: t("note_detail.print_report_collection_details_button"),
onClick(api) {
api.dismissToast();
dialog.info(<>
<p>{printReport.message}</p>
<details>
<summary>{t("note_detail.print_report_stack_trace")}</summary>
<pre style="font-size: 0.85em; overflow-x: auto;">{printReport.stack}</pre>
</details>
</>, {
title: t("note_detail.print_report_error_title")
});
}
}
] : undefined
});
} else if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
toast.showPersistent({
id: "print-report",
icon: "bx bx-collection",

View File

@@ -7,7 +7,7 @@
> .collection-properties {
position: relative;
z-index: 2000;
z-index: 998;
}
}
@@ -22,7 +22,7 @@
.leaflet-top,
.leaflet-bottom {
z-index: 997;
z-index: 997 !important;
}
.geo-view.placing-note .geo-map-container {

View File

@@ -22,7 +22,7 @@ import { ViewModeProps } from "../interface";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import Map from "./map";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, MapLayer } from "./map_layer";
import Marker, { GpxTrack } from "./marker";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
@@ -45,10 +45,11 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
const [ state, setState ] = useState(State.Normal);
const [ coordinates, setCoordinates ] = useState(viewConfig?.view?.center);
const [ zoom, setZoom ] = useState(viewConfig?.view?.zoom);
const [ layerName ] = useNoteLabel(note, "map:style");
const [ hasScale ] = useNoteLabelBoolean(note, "map:scale");
const [ hideLabels ] = useNoteLabelBoolean(note, "map:hideLabels");
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const [ notes, setNotes ] = useState<FNote[]>([]);
const layerData = useLayerData(note);
const spacedUpdate = useSpacedUpdate(() => {
if (viewConfig) {
saveConfig(viewConfig);
@@ -152,7 +153,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
apiRef={apiRef} containerRef={containerRef}
coordinates={coordinates}
zoom={zoom}
layerName={layerName ?? DEFAULT_MAP_LAYER_NAME}
layerData={layerData}
viewportChanged={(coordinates, zoom) => {
if (!viewConfig) viewConfig = {};
viewConfig.view = { center: coordinates, zoom };
@@ -162,13 +163,35 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
onContextMenu={onContextMenu}
scale={hasScale}
>
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} />)}
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} hideLabels={hideLabels} />)}
</Map>}
<GeoMapTouchBar state={state} map={apiRef.current} />
</div>
);
}
function useLayerData(note: FNote) {
const [ layerName ] = useNoteLabel(note, "map:style");
// Memo is needed because it would generate unnecessary reloads due to layer change.
const layerData = useMemo(() => {
// Custom layers.
if (layerName?.startsWith("http")) {
return {
name: "Custom",
type: "raster",
url: layerName,
attribution: ""
} satisfies MapLayer;
}
// Built-in layers.
const layerData = MAP_LAYERS[layerName ?? ""] ?? MAP_LAYERS[DEFAULT_MAP_LAYER_NAME];
return layerData;
}, [ layerName ]);
return layerData;
}
function ToggleReadOnlyButton({ note }: { note: FNote }) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
@@ -179,22 +202,26 @@ function ToggleReadOnlyButton({ note }: { note: FNote }) {
/>;
}
function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) {
function NoteWrapper({ note, isReadOnly, hideLabels }: {
note: FNote,
isReadOnly: boolean,
hideLabels: boolean
}) {
const mime = useNoteProperty(note, "mime");
const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE);
if (mime === "application/gpx+xml") {
return <NoteGpxTrack note={note} />;
return <NoteGpxTrack note={note} hideLabels={hideLabels} />;
}
if (location) {
const latLng = location?.split(",", 2).map((el) => parseFloat(el)) as [ number, number ] | undefined;
if (!latLng) return;
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} />;
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} hideLabels={hideLabels} />;
}
}
function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean, latLng: [number, number] }) {
function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, editable: boolean, latLng: [number, number], hideLabels: boolean }) {
// React to changes
const [ color ] = useNoteLabel(note, "color");
const [ iconClass ] = useNoteLabel(note, "iconClass");
@@ -202,8 +229,9 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
const title = useNoteProperty(note, "title");
const icon = useMemo(() => {
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, title, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived]);
const titleOrNone = hideLabels ? undefined : title;
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived, hideLabels ]);
const onClick = useCallback(() => {
appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId });
@@ -235,7 +263,7 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
/>;
}
function NoteGpxTrack({ note }: { note: FNote }) {
function NoteGpxTrack({ note, hideLabels }: { note: FNote, hideLabels?: boolean }) {
const [ xmlString, setXmlString ] = useState<string>();
const blob = useNoteBlob(note);
@@ -256,7 +284,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
const options = useMemo<GPXOptions>(() => ({
markers: {
startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title),
startIcon: buildIcon(note.getIcon(), note.getColorClass(), hideLabels ? undefined : note.title),
endIcon: buildIcon("bxs-flag-checkered"),
wptIcons: {
"": buildIcon("bx bx-pin")
@@ -265,7 +293,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
polyline_options: {
color: note.getLabelValue("color") ?? "blue"
}
}), [ color, iconClass ]);
}), [ color, iconClass, hideLabels ]);
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />;
}

View File

@@ -1,7 +1,7 @@
import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
import L, { control, LatLng, Layer, LeafletMouseEvent } from "leaflet";
import "leaflet/dist/leaflet.css";
import { MAP_LAYERS } from "./map_layer";
import { MAP_LAYERS, type MapLayer } from "./map_layer";
import { ComponentChildren, createContext, RefObject } from "preact";
import { useElementSize, useSyncedRef } from "../../react/hooks";
@@ -12,7 +12,7 @@ interface MapProps {
containerRef?: RefObject<HTMLDivElement>;
coordinates: LatLng | [number, number];
zoom: number;
layerName: string;
layerData: MapLayer;
viewportChanged: (coordinates: LatLng, zoom: number) => void;
children: ComponentChildren;
onClick?: (e: LeafletMouseEvent) => void;
@@ -21,7 +21,7 @@ interface MapProps {
scale: boolean;
}
export default function Map({ coordinates, zoom, layerName, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
export default function Map({ coordinates, zoom, layerData, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
const mapRef = useRef<L.Map>(null);
const containerRef = useSyncedRef<HTMLDivElement>(_containerRef);
@@ -49,8 +49,6 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
const [ layer, setLayer ] = useState<Layer>();
useEffect(() => {
async function load() {
const layerData = MAP_LAYERS[layerName];
if (layerData.type === "vector") {
const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
await import("@maplibre/maplibre-gl-leaflet");
@@ -68,7 +66,7 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
}
load();
}, [ layerName ]);
}, [ layerData ]);
// Attach layer to the map.
useEffect(() => {
@@ -139,7 +137,7 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
return (
<div
ref={containerRef}
className={`geo-map-container ${MAP_LAYERS[layerName].isDarkTheme ? "dark" : ""}`}
className={`geo-map-container ${layerData.isDarkTheme ? "dark" : ""}`}
>
<ParentMap.Provider value={mapRef.current}>
{children}

View File

@@ -1,20 +1,17 @@
export interface MapLayer {
name: string;
isDarkTheme?: boolean;
}
interface VectorLayer extends MapLayer {
export type MapLayer = ({
type: "vector";
style: string | (() => Promise<{}>)
}
interface RasterLayer extends MapLayer {
} | {
type: "raster";
url: string;
attribution: string;
}
}) & {
// Common properties
name: string;
isDarkTheme?: boolean;
};
export const MAP_LAYERS: Record<string, VectorLayer | RasterLayer> = {
export const MAP_LAYERS: Record<string, MapLayer> = {
"openstreetmap": {
name: "OpenStreetMap",
type: "raster",

View File

@@ -1,12 +1,11 @@
import { useEffect, useState } from "preact/hooks";
import appContext, { CommandNames } from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import keyboard_actions from "../../services/keyboard_actions.js";
import { Card } from "../react/Card.jsx";
import { useTriliumEvent } from "../react/hooks.jsx";
import Modal from "../react/Modal.jsx";
import { t } from "../../services/i18n.js";
import { ComponentChildren } from "preact";
import appContext, { CommandNames } from "../../components/app_context.js";
import RawHtml from "../react/RawHtml.jsx";
import { useEffect, useState } from "preact/hooks";
import keyboard_actions from "../../services/keyboard_actions.js";
import { useTriliumEvent } from "../react/hooks.jsx";
export default function HelpDialog() {
const [ shown, setShown ] = useState(false);
@@ -111,7 +110,7 @@ export default function HelpDialog() {
function KeyboardShortcut({ commands, description }: { commands: CommandNames | CommandNames[], description: string }) {
const [ shortcuts, setShortcuts ] = useState<string[]>([]);
useEffect(() => {
(async () => {
const shortcuts: string[] = [];
@@ -149,6 +148,20 @@ function FixedKeyboardShortcut({ keys, description }: { keys?: string[], descrip
);
}
function Card({ title, children }: { title: string, children: ComponentChildren }) {
return (
<div className="card">
<div className="card-body">
<h5 className="card-title">{title}</h5>
<p className="card-text">
{children}
</p>
</div>
</div>
)
}
function editShortcuts() {
appContext.tabManager.openContextWithNote("_optionsShortcuts", { activate: true });
}
}

View File

@@ -1,16 +1,14 @@
import { useState } from "preact/hooks";
import { t } from "../../services/i18n";
import importService, { UploadFilesOptions } from "../../services/import";
import tree from "../../services/tree";
import { boolToString } from "../../services/utils";
import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox";
import FormFileUpload from "../react/FormFileUpload";
import FormGroup, { FormMultiGroup } from "../react/FormGroup";
import { useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
import Modal from "../react/Modal";
import RawHtml from "../react/RawHtml";
import importService, { UploadFilesOptions } from "../../services/import";
import { useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
export default function ImportDialog() {
const [ compressImages ] = useTriliumOptionBool("compressImages");
@@ -100,4 +98,6 @@ export default function ImportDialog() {
);
}
function boolToString(value: boolean) {
return value ? "true" : "false";
}

View File

@@ -1,47 +0,0 @@
.modal.import-preview-dialog {
.stats {
color: var(--muted-text-color);
font-size: 0.9em;
span:after {
content: " • "
}
span:last-of-type:after {
content: unset;
}
}
.critical { --color: #ff7979; }
.warning { --color: #e2b11d; }
.safe { --color: var(--admonition-tip-accent-color); }
.card {
margin-bottom: 1em;
position: relative;
overflow: hidden;
padding-inline: 0.75em;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 4px;
background: var(--color);
}
}
.dangerous-categories {
display: flex;
gap: 0.5em;
margin-top: 0.5em;
--badge-radius: 1000px;
.ext-badge {
color: black;
}
}
}

View File

@@ -1,240 +0,0 @@
import "./import_preview.css";
import { DangerousAttributeCategory, ImportPreviewResponse } from "@triliumnext/commons";
import { useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import { cancelUploadWithPreview, executeUploadWithPreview } from "../../services/import";
import { boolToString, dynamicRequire, isElectron } from "../../services/utils";
import { Badge } from "../react/Badge";
import Button from "../react/Button";
import { Card } from "../react/Card";
import FormGroup from "../react/FormGroup";
import FormRadioGroup from "../react/FormRadioGroup";
import { useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
import Modal from "../react/Modal";
import NoteAutocomplete from "../react/NoteAutocomplete";
export interface ImportPreviewData {
parentNoteId?: string;
previews: ImportPreviewResponse[];
}
type DangerousCategory = "critical" | "warning";
const DANGEROUS_CATEGORIES_MAPPINGS: Record<DangerousAttributeCategory, {
title: string;
description: string;
icon: string;
category: DangerousCategory;
}> = {
clientSideScripting: {
icon: "bx bx-window-alt",
title: t("import_preview.badge_client_side_scripting_title"),
description: t("import_preview.badge_client_side_scripting_tooltip"),
category: "critical"
},
serverSideScripting: {
icon: "bx bx-server",
title: t("import_preview.badge_server_side_scripting_title"),
description: t("import_preview.badge_server_side_scripting_tooltip"),
category: "critical"
},
codeExecution: {
icon: "bx bx-terminal",
title: t("import_preview.badge_code_execution_title"),
description: t("import_preview.badge_code_execution_description"),
category: "critical"
},
iconPack: {
icon: "bx bx-package",
title: t("import_preview.badge_icon_pack_title"),
description: t("import_preview.badge_icon_pack_description"),
category: "warning"
},
webview: {
icon: "bx bx-globe",
title: t("import_preview.badge_web_view_title"),
description: t("import_preview.badge_web_view_description"),
category: "warning"
}
};
const SEVERITY_ORDER: Record<DangerousCategory, number> = {
critical: 0,
warning: 1
};
const IMPORT_BUTTON_TIMEOUT = 3;
export default function ImportPreviewDialog() {
const [ data, setData ] = useState<ImportPreviewData | null>(null);
const [ shown, setShown ] = useState(false);
const [ importMethod, setImportMethod ] = useState<string>("safe");
const isDangerousImport = data?.previews.some(preview => preview.isDangerous);
const [ importButtonTimeout, setImportButtonTimeout ] = useState(0);
const [ compressImages ] = useTriliumOptionBool("compressImages");
const hasSubmittedRef = useRef(false);
useEffect(() => {
// If safe → reset and do nothing
if (!isDangerousImport) {
setImportButtonTimeout(0);
return;
}
// Start countdown
setImportButtonTimeout(IMPORT_BUTTON_TIMEOUT);
const interval = setInterval(() => {
setImportButtonTimeout(prev => {
if (prev <= 1) {
clearInterval(interval);
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
}, [isDangerousImport]);
useTriliumEvent("showImportPreviewDialog", (data) => {
setData(data);
setShown(true);
setImportButtonTimeout(IMPORT_BUTTON_TIMEOUT);
hasSubmittedRef.current = false;
});
useEffect(() => {
if (!isElectron()) return;
const { ipcRenderer } = dynamicRequire("electron");
const onShow = (_event: any, data: ImportPreviewData) => {
setData(data);
setShown(true);
setImportButtonTimeout(IMPORT_BUTTON_TIMEOUT);
hasSubmittedRef.current = false;
};
ipcRenderer.on("show-import-preview-dialog", onShow);
return () => {
ipcRenderer.removeListener("show-import-preview-dialog", onShow);
}
}, []);
return (
<Modal
className="import-preview-dialog"
size="lg"
title={t("import_preview.title")}
footer={<>
<Button text={t("import_preview.cancel")} onClick={() => setShown(false)}/>
<Button
text={importButtonTimeout
? t("import_preview.import_with_timeout", { timeout: importButtonTimeout })
: t("import_preview.import")}
disabled={importButtonTimeout > 0}
primary
/>
</>}
show={shown}
onSubmit={() => {
if (!data) return;
hasSubmittedRef.current = true;
executeUploadWithPreview(data.parentNoteId, data.previews, {
shrinkImages: boolToString(compressImages),
safeImport: boolToString(importMethod === "safe")
});
setShown(false);
}}
onHidden={() => {
setShown(false);
setData(null);
setImportButtonTimeout(3);
if (!hasSubmittedRef.current) {
hasSubmittedRef.current = true;
if (data?.previews) {
cancelUploadWithPreview(data.previews);
}
}
}}
>
<p>{isDangerousImport
? t("import_preview.intro_unsafe", { count: data?.previews.length })
: t("import_preview.intro_safe", { count: data?.previews.length })}</p>
{data?.previews.map(preview => <SinglePreview key={preview.id} preview={preview} />)}
<div className="import-options">
<FormGroup name="parent-note" label={t("import_preview.parent_note")}>
<NoteAutocomplete
noteId={data?.parentNoteId}
noteIdChanged={noteId => {
if (!data) return;
setData({
...data,
parentNoteId: noteId
});
}}
/>
</FormGroup>
<FormRadioGroup
name="import-method"
currentValue={importMethod} onChange={setImportMethod}
values={[
{ value: "safe", label: t("import_preview.import_safely"), inlineDescription: t("import_preview.import_safely_description") },
{ value: "unsafe", label: t("import_preview.import_trust"), inlineDescription: t("import_preview.import_trust_description") }
]}
/>
</div>
</Modal>
);
}
function SinglePreview({ preview }: { preview: ImportPreviewResponse }) {
const categories = sortDangerousAttributeCategoryBySeverity(preview.dangerousAttributeCategories);
return (
<Card
title={preview.fileName}
className={DANGEROUS_CATEGORIES_MAPPINGS[categories[0]]?.category ?? "safe"}
>
<div className="stats">
<span>{t("import_preview.notes_count", { count: preview.numNotes })}</span>
<span>{t("import_preview.attributes_count", { count: preview.numAttributes })}</span>
<span>{t("import_preview.attachments_count", { count: preview.numAttachments })}</span>
</div>
<div className="dangerous-categories">
{categories.length > 0
? categories.map(dangerousCategory => {
const mapping = DANGEROUS_CATEGORIES_MAPPINGS[dangerousCategory];
return (
<Badge
key={dangerousCategory}
className={mapping.category}
icon={mapping.icon}
text={mapping.title}
tooltip={mapping.description}
/>
);
})
: (
<Badge
className="safe"
icon="bx bx-check"
text="Safe"
tooltip="This archive has no active content such as scripts or widgets that could affect your knowledge base or access sensitive data."
/>
)}
</div>
</Card>
);
}
function sortDangerousAttributeCategoryBySeverity(categories: string[]) {
return categories.toSorted((a, b) => {
const aLevel = DANGEROUS_CATEGORIES_MAPPINGS[a].category;
const bLevel = DANGEROUS_CATEGORIES_MAPPINGS[b].category;
return SEVERITY_ORDER[aLevel] - SEVERITY_ORDER[bLevel];
});
}

View File

@@ -37,6 +37,10 @@
&:hover {
background: var(--input-background-color);
}
.text {
white-space: nowrap;
}
}
.status-bar-dropdown-button {

View File

@@ -226,8 +226,8 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check
<FormListToggleableItem
icon={property.icon}
title={property.label}
currentValue={value}
onChange={setValue}
currentValue={ property.reverseValue ? !value : value }
onChange={newValue => setValue(property.reverseValue ? !newValue : newValue)}
/>
);
}

View File

@@ -9,7 +9,6 @@
border-radius: 0.5em;
font-size: 0.7rem;
font-weight: normal;
float: right;
vertical-align: middle;
}

View File

@@ -523,59 +523,46 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const dataTransfer = data.dataTransfer;
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
const files: File[] = [...dataTransfer.files]; // chrome has issue that dataTransfer.files empties after async operation
const files = [...dataTransfer.files]; // chrome has issue that dataTransfer.files empties after async operation
const importService = await import("../services/import.js");
const isTriliumImport = files.every(f => f.name.endsWith(".trilium"));
const parentNoteId = node.data.noteId;
if (!isTriliumImport) {
importService.uploadFiles("notes", parentNoteId, files, {
safeImport: true,
shrinkImages: true,
textImportedAsText: true,
codeImportedAsCode: true,
explodeArchives: true,
replaceUnderscoresWithSpaces: true
});
} else {
importService.uploadFilesWithPreview(files).then((previews) => {
if (!previews) return;
this.triggerCommand("showImportPreviewDialog", {
parentNoteId,
previews
});
});
importService.uploadFiles("notes", node.data.noteId, files, {
safeImport: true,
shrinkImages: true,
textImportedAsText: true,
codeImportedAsCode: true,
explodeArchives: true,
replaceUnderscoresWithSpaces: true
});
} else {
const jsonStr = dataTransfer.getData("text");
let notes: BranchRow[];
try {
notes = JSON.parse(jsonStr);
} catch (e) {
logError(`Cannot parse JSON '${jsonStr}' into notes for drop`);
return;
}
// This function MUST be defined to enable dropping of items on the tree.
// data.hitMode is 'before', 'after', or 'over'.
const selectedBranchIds = notes
.map((note) => note.branchId)
.filter((branchId) => branchId) as string[];
if (data.hitMode === "before") {
branchService.moveBeforeBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "after") {
branchService.moveAfterBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "over") {
branchService.moveToParentNote(selectedBranchIds, node.data.branchId, this.componentId);
} else {
throw new Error(`Unknown hitMode '${data.hitMode}'`);
}
}
const jsonStr = dataTransfer.getData("text");
let notes: BranchRow[];
try {
notes = JSON.parse(jsonStr);
} catch (e) {
logError(`Cannot parse JSON '${jsonStr}' into notes for drop`);
return;
}
// This function MUST be defined to enable dropping of items on the tree.
// data.hitMode is 'before', 'after', or 'over'.
const selectedBranchIds = notes
.map((note) => note.branchId)
.filter((branchId) => branchId) as string[];
if (data.hitMode === "before") {
branchService.moveBeforeBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "after") {
branchService.moveAfterBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "over") {
branchService.moveToParentNote(selectedBranchIds, node.data.branchId, this.componentId);
} else {
throw new Error(`Unknown hitMode '${data.hitMode}'`);
}
}
},
lazyLoad: (event, data) => {
@@ -2005,7 +1992,7 @@ function buildEnhanceTitle() {
if (isSubtreeHidden && count > 0) {
const $badge = $(`<span class="note-indicator-icon subtree-hidden-badge">${count}</span>`);
$badge.attr("title", t("note_tree.subtree-hidden-tooltip", { count }));
$span.find(".fancytree-title").append($badge);
$span.append($badge);
}
};
}

View File

@@ -43,7 +43,8 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
},
protectedSession: {
view: () => import("./type_widgets/ProtectedSession"),
className: "protected-session-password-component"
className: "protected-session-password-component",
isFullHeight: true
},
book: {
view: () => import("./type_widgets/Book"),

View File

@@ -1,20 +0,0 @@
import clsx from "clsx";
import type { ComponentChildren } from "preact";
export function Card({ title, className, children }: {
title: string;
children: ComponentChildren;
className?: string;
}) {
return (
<div className={clsx("card", className)}>
<div className="card-body">
<h5 className="card-title">{title}</h5>
<p className="card-text">
{children}
</p>
</div>
</div>
);
}

View File

@@ -3,10 +3,7 @@
line-height: 1em;
display: flex;
align-items: center;
appearance: none;
background: transparent;
border: 0;
color: inherit;
padding-inline-end: 12px;
.arrow {
font-size: 1.3em;

View File

@@ -57,7 +57,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
"with-transition": transitionEnabled
})}>
<button
className="collapsible-title"
className="collapsible-title tn-low-profile"
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
aria-controls={contentId}

View File

@@ -0,0 +1,4 @@
.similar-notes-widget > .similar-notes-wrapper {
/* The font size of the links with the highest similarity score */
font-size: 17px;
}

View File

@@ -1,3 +1,5 @@
import "./SimilarNotesTab.css";
import { SimilarNoteResponse } from "@triliumnext/commons";
import { useEffect, useState } from "preact/hooks";
@@ -33,7 +35,7 @@ export default function SimilarNotesTab({ note }: Pick<TabContext, "note">) {
notePath={notePath}
noTnLink
style={{
"font-size": 20 * (1 - 1 / (1 + score))
"font-size": (1 - 1 / (1 + score)) + "em"
}}
/>
))}

View File

@@ -20,6 +20,8 @@ export interface CheckBoxProperty {
label: string;
bindToLabel: FilterLabelsByType<boolean>;
icon?: string;
/** When true, the checkbox will be checked when the label value is false. Useful when the label represents a "hide" action, without exposing double negatives to the user. */
reverseValue?: boolean;
}
export interface ButtonProperty {
@@ -156,6 +158,13 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
icon: "bx bx-ruler",
type: "checkbox",
bindToLabel: "map:scale"
},
{
label: t("book_properties_config.show-labels"),
icon: "bx bx-label",
type: "checkbox",
bindToLabel: "map:hideLabels",
reverseValue: true
}
]
},

View File

@@ -6,7 +6,7 @@
.attachment-list .links-wrapper {
font-size: larger;
margin-bottom: 15px;
margin-block: 12px;
display: flex;
justify-content: space-between;
align-items: baseline;

View File

@@ -1,16 +1,7 @@
.type-contentWidget .note-detail {
height: 100%;
}
.note-detail-content-widget {
height: 100%;
}
.note-detail-content-widget-content {
padding: 15px;
height: 100%;
}
.note-detail.full-height .note-detail-content-widget-content {
padding: 0;
}
}

View File

@@ -1,9 +1,6 @@
.protected-session-password-component {
width: 300px;
margin: 30px auto auto;
}
.protected-session-password-component input,
.protected-session-password-component button {
margin-top: 12px;
display: flex;
margin-inline: 40px;
flex-direction: column;
justify-content: center;
}

View File

@@ -20,7 +20,9 @@ export default function ProtectedSession() {
}, [ passwordRef ]);
return (
<form class="protected-session-password-form" onSubmit={submitCallback}>
<form class="protected-session-password-form tn-centered-form" onSubmit={submitCallback}>
<span class="form-icon bx bx-key" />
<FormGroup name="protected-session-password-in-detail" label={t("protected_session.enter_password_instruction")}>
<FormTextBox
type="password"
@@ -37,4 +39,4 @@ export default function ProtectedSession() {
/>
</form>
)
}
}

View File

@@ -16,3 +16,20 @@
width: 100%;
height: 100%;
}
.web-view-setup-form {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding-inline: 40px;
.form-icon {
margin-bottom: 12px;
}
.form-group {
width: 100%;
max-width: 600px;
}
}

View File

@@ -1,9 +1,13 @@
import { useCallback, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import { t } from "../../services/i18n";
import utils from "../../services/utils";
import Alert from "../react/Alert";
import { useNoteLabel } from "../react/hooks";
import { TypeWidgetProps } from "./type_widget";
import "./WebView.css";
import FormGroup from "../react/FormGroup";
import toast from "../../services/toast";
import Button from "../react/Button";
const isElectron = utils.isElectron();
@@ -12,7 +16,7 @@ export default function WebView({ note }: TypeWidgetProps) {
return (webViewSrc
? <WebViewContent src={webViewSrc} />
: <WebViewHelp />
: <SetupWebView note={note} />
);
}
@@ -24,12 +28,41 @@ function WebViewContent({ src }: { src: string }) {
}
}
function WebViewHelp() {
return (
<Alert className="note-detail-web-view-help" type="warning">
<h4>{t("web_view.web_view")}</h4>
<p>{t("web_view.embed_websites")}</p>
<p>{t("web_view.create_label")}</p>
</Alert>
)
function SetupWebView({note}: {note: FNote}) {
const [srcLabel, setSrcLabel] = useNoteLabel(note, "webViewSrc");
const [src, setSrc] = useState("");
const submit = useCallback((url: string) => {
try {
// Validate URL
new URL(url);
} catch (ex) {
toast.showErrorTitleAndMessage(t("web_view_setup.invalid_url_title"),
t("web_view_setup.invalid_url_message"));
return;
}
setSrcLabel(url);
}, [note]);
return <div class="web-view-setup-form">
<form class="tn-centered-form" onSubmit={() => submit(src)}>
<span className="bx bx-globe-alt form-icon" />
<FormGroup name="web-view-src-detail" label={t("web_view_setup.title")}>
<input className="form-control"
type="text"
value={src}
placeholder={t("web_view_setup.url_placeholder")}
onChange={(e) => {setSrc((e.target as HTMLInputElement)?.value)}}
/>
</FormGroup>
<Button
text={t("web_view_setup.create_button")}
primary
keyboardShortcut="Enter"
/>
</form>
</div>
}

View File

@@ -0,0 +1,40 @@
.shortcuts-options-section {
> header {
position: sticky;
top: 0;
left: 0;
right: 0;
background: var(--main-background-color);
padding-block: 0.5em;
border-bottom: 1px solid var(--main-border-color);
}
> footer {
position: sticky;
bottom: 0;
left: 0;
right: 0;
background: var(--main-background-color);
padding-block: 0.5em;
border-top: 1px solid var(--main-border-color);
display: flex;
justify-content: space-between;
}
table {
width: 100%;
th {
width: 25%;
}
.separator {
background-color: var(--accented-background-color);
font-weight: bold;
&:first-of-type {
padding-top: 1em;
}
}
}
}

View File

@@ -2,7 +2,6 @@ import { ActionKeyboardShortcut, KeyboardShortcut, OptionNames } from "@triliumn
import { t } from "../../../services/i18n";
import { arrayEqual, reloadFrontendApp } from "../../../services/utils";
import Button from "../../react/Button";
import FormGroup from "../../react/FormGroup";
import FormText from "../../react/FormText";
import FormTextBox from "../../react/FormTextBox";
import RawHtml from "../../react/RawHtml";
@@ -12,6 +11,8 @@ import server from "../../../services/server";
import options from "../../../services/options";
import dialog from "../../../services/dialog";
import { useTriliumEvent } from "../../react/hooks";
import "./shortcuts.css";
import NoItems from "../../react/NoItems";
export default function ShortcutSettings() {
const [ keyboardShortcuts, setKeyboardShortcuts ] = useState<KeyboardShortcut[]>([]);
@@ -70,29 +71,29 @@ export default function ShortcutSettings() {
options.saveMany(optionsToSet);
}, [ keyboardShortcuts ]);
const filterLowerCase = filter?.toLowerCase() ?? "";
const filteredKeyboardShortcuts = filter ? keyboardShortcuts.filter((action) => filterKeyboardAction(action, filterLowerCase)) : keyboardShortcuts;
return (
<OptionsSection
className="shortcuts-options-section"
style={{ display: "flex", flexDirection: "column", height: "100%" }}
noCard
>
<FormText>
{t("shortcuts.multiple_shortcuts")}
{t("shortcuts.multiple_shortcuts")}{" "}
<RawHtml html={t("shortcuts.electron_documentation")} />
</FormText>
<FormGroup name="keyboard-shortcut-filter">
<header>
<FormTextBox
placeholder={t("shortcuts.type_text_to_filter")}
currentValue={filter} onChange={(value) => setFilter(value.toLowerCase())}
currentValue={filter} onChange={(value) => setFilter(value)}
/>
</FormGroup>
</header>
<div style={{overflow: "auto", flexGrow: 1, flexShrink: 1}}>
<KeyboardShortcutTable keyboardShortcuts={keyboardShortcuts} filter={filter} />
</div>
<KeyboardShortcutTable filteredKeyboardActions={filteredKeyboardShortcuts} filter={filter} />
<div style={{ display: "flex", justifyContent: "space-between", margin: "15px 15px 0 15px"}}>
<footer>
<Button
text={t("shortcuts.reload_app")}
onClick={reloadFrontendApp}
@@ -102,12 +103,17 @@ export default function ShortcutSettings() {
text={t("shortcuts.set_all_to_default")}
onClick={resetShortcuts}
/>
</div>
</footer>
</OptionsSection>
)
}
function filterKeyboardAction(action: ActionKeyboardShortcut, filter: string) {
function filterKeyboardAction(action: KeyboardShortcut, filter: string) {
// Hide separators when filtering is active.
if ("separator" in action) {
return !filter;
}
return action.actionName.toLowerCase().includes(filter) ||
(action.friendlyName && action.friendlyName.toLowerCase().includes(filter)) ||
(action.defaultShortcuts ?? []).some((shortcut) => shortcut.toLowerCase().includes(filter)) ||
@@ -115,7 +121,7 @@ function filterKeyboardAction(action: ActionKeyboardShortcut, filter: string) {
(action.description && action.description.toLowerCase().includes(filter));
}
function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string, keyboardShortcuts: KeyboardShortcut[] }) {
function KeyboardShortcutTable({ filteredKeyboardActions, filter }: { filteredKeyboardActions: KeyboardShortcut[], filter: string | undefined }) {
return (
<table class="keyboard-shortcut-table" cellPadding="10">
<thead>
@@ -127,16 +133,17 @@ function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string,
</tr>
</thead>
<tbody>
{keyboardShortcuts.map(action => (
{filteredKeyboardActions.length > 0
? filteredKeyboardActions.map(action => (
<tr>
{"separator" in action ? ( !filter &&
{"separator" in action ?
<td class="separator" colspan={4} style={{
backgroundColor: "var(--accented-background-color)",
fontWeight: "bold"
}}>
{action.separator}
</td>
) : ( (!filter || filterKeyboardAction(action, filter)) &&
: (
<>
<td>{action.friendlyName}</td>
<td>
@@ -147,7 +154,17 @@ function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string,
</>
)}
</tr>
))}
))
: (
<tr>
<td colspan={4} class="text-center">
<NoItems
icon="bx bx-filter-alt"
text={t("shortcuts.no_results", { filter })}
/>
</td>
</tr>
)}
</tbody>
</table>
);

View File

@@ -95,7 +95,7 @@ export default defineConfig(() => ({
output: {
entryFileNames: (chunk) => {
// We enforce a hash in the main index file to avoid caching issues, this only works because we have the HTML entry point.
if (chunk.name === "index") {
if (chunk.name === "index" || chunk.name === "print") {
return "src/[name]-[hash].js";
}

View File

@@ -35,7 +35,7 @@
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"copy-webpack-plugin": "13.0.1",
"electron": "40.1.0",
"electron": "40.2.1",
"@electron-forge/cli": "7.11.1",
"@electron-forge/maker-deb": "7.11.1",
"@electron-forge/maker-dmg": "7.11.1",

View File

@@ -12,8 +12,6 @@ import { PRODUCT_NAME } from "./app-info";
import port from "@triliumnext/server/src/services/port.js";
import { join } from "path";
import { deferred, LOCALES } from "../../../packages/commons/src";
import previewZipForImport from "@triliumnext/server/src/services/import/zip_preview";
import { ipcMain } from "electron/main";
async function main() {
const userDataPath = getUserData();
@@ -117,8 +115,7 @@ async function onReady() {
if (sqlInit.isDbInitialized()) {
await sqlInit.dbReady;
const mainWindow = await windowService.createMainWindow(app);
handleImportArguments(process.argv, mainWindow);
await windowService.createMainWindow(app);
if (process.platform === "darwin") {
app.on("activate", async () => {
@@ -136,20 +133,6 @@ async function onReady() {
await windowService.registerGlobalShortcuts();
}
async function handleImportArguments(args: string[], window: BrowserWindow) {
const filesToImport = args.filter(arg => !arg.startsWith("-") && arg.endsWith(".trilium"));
if (filesToImport.length === 0) return;
const previews = await Promise.all(filesToImport.map(file => previewZipForImport(file)));
console.log("Sendimg import preview data to window", window);
ipcMain.on("initial-render-complete", () => {
console.log("READYYY!!!");
window.webContents.send("show-import-preview-dialog", {
previews
});
});
}
function getElectronLocale() {
const uiLocale = options.getOptionOrNull("locale");
const formattingLocale = options.getOptionOrNull("formattingLocale");

View File

@@ -12,7 +12,7 @@
"@triliumnext/desktop": "workspace:*",
"@types/fs-extra": "11.0.4",
"copy-webpack-plugin": "13.0.1",
"electron": "40.1.0",
"electron": "40.2.1",
"fs-extra": "11.3.3"
},
"scripts": {

View File

@@ -35,7 +35,7 @@
"sucrase": "3.35.1"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.72.1",
"@anthropic-ai/sdk": "0.74.0",
"@braintree/sanitize-url": "7.1.2",
"@electron/remote": "2.1.3",
"@triliumnext/commons": "workspace:*",
@@ -70,7 +70,7 @@
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.13.4",
"axios": "1.13.5",
"bindings": "1.5.0",
"bootstrap": "5.3.8",
"chardet": "2.1.1",
@@ -83,7 +83,7 @@
"debounce": "3.0.0",
"debug": "4.4.3",
"ejs": "4.0.1",
"electron": "40.1.0",
"electron": "40.2.1",
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
@@ -99,7 +99,7 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"i18next": "25.8.0",
"i18next": "25.8.4",
"i18next-fs-backend": "2.6.1",
"image-type": "6.0.0",
"ini": "6.0.0",
@@ -112,7 +112,7 @@
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.3",
"openai": "6.17.0",
"openai": "6.21.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",

View File

@@ -1,3 +1,6 @@
<aside class="admonition important">
<p>This feature is still in preview and is available only in the <a href="https://docs.triliumnotes.org/user-guide/advanced-usage/nightly-release">nightly release</a>.</p>
</aside>
<figure class="image image-style-align-right image_resized" style="width:45.14%;">
<img style="aspect-ratio:854/649;" src="Icon Packs_image.png"
width="854" height="649">

View File

@@ -317,7 +317,8 @@
"other": "Inne",
"visible-launchers-title": "Widoczne launchery",
"user-guide": "Podręcznik użytkownika",
"inbox-title": "Skrzynka odbiorcza"
"inbox-title": "Skrzynka odbiorcza",
"tab-switcher-title": "Przełącznik kart"
},
"notes": {
"new-note": "Nowa notatka",

View File

@@ -1,22 +1,18 @@
"use strict";
import { ImportPreviewResponse } from "@triliumnext/commons";
import type { Request } from "express";
import enexImportService from "../../services/import/enex.js";
import opmlImportService from "../../services/import/opml.js";
import zipImportService from "../../services/import/zip.js";
import singleImportService from "../../services/import/single.js";
import cls from "../../services/cls.js";
import path from "path";
import becca from "../../becca/becca.js";
import beccaLoader from "../../becca/becca_loader.js";
import type BNote from "../../becca/entities/bnote.js";
import ValidationError from "../../errors/validation_error.js";
import cls from "../../services/cls.js";
import enexImportService from "../../services/import/enex.js";
import importStore from "../../services/import/import_store.js";
import opmlImportService from "../../services/import/opml.js";
import singleImportService from "../../services/import/single.js";
import zipImportService from "../../services/import/zip.js";
import previewZipForImport from "../../services/import/zip_preview.js";
import log from "../../services/log.js";
import TaskContext from "../../services/task_context.js";
import ValidationError from "../../errors/validation_error.js";
import type { Request } from "express";
import type BNote from "../../becca/entities/bnote.js";
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
async function importNotesToBranch(req: Request) {
@@ -83,10 +79,6 @@ async function importNotesToBranch(req: Request) {
return [500, message];
}
onImportDone(note, last, taskContext, parentNoteId);
}
function onImportDone(note: BNote | null, last: "true" | "false", taskContext: TaskContext<"importNotes">, parentNoteId: string) {
if (!note) {
return [500, "No note was generated as a result of the import."];
}
@@ -96,7 +88,7 @@ function onImportDone(note: BNote | null, last: "true" | "false", taskContext: T
setTimeout(
() =>
taskContext.taskSucceeded({
parentNoteId,
parentNoteId: parentNoteId,
importedNoteId: note?.noteId
}),
1000
@@ -146,83 +138,14 @@ function importAttachmentsToNote(req: Request) {
setTimeout(
() =>
taskContext.taskSucceeded({
parentNoteId
parentNoteId: parentNoteId
}),
1000
);
}
}
async function importPreview(req: Request) {
const file = req.file;
if (!file) {
throw new ValidationError("No file has been uploaded");
}
if (!file.originalname.endsWith(".trilium")) {
throw new ValidationError("Preview supports only .trilium files.");
}
try {
const previewInfo = await previewZipForImport(file.path);
const id = file.filename;
importStore.set(id, {
path: file.path
});
return {
...previewInfo,
fileName: file.originalname,
id
} satisfies ImportPreviewResponse;
} catch (e) {
console.warn(e);
throw new ValidationError("Error while generating the preview.");
}
}
async function importExecute(req: Request) {
const { id } = req.body;
const importRecord = importStore.get(id);
if (!importRecord) throw new ValidationError("Unable to find a record of the upload, maybe it expired or the ID is missing or incorrect.");
try {
const { taskId, last } = req.body;
const options = {
safeImport: req.body.safeImport !== "false",
shrinkImages: req.body.shrinkImages !== "false",
textImportedAsText: req.body.textImportedAsText !== "false",
codeImportedAsCode: req.body.codeImportedAsCode !== "false",
explodeArchives: req.body.explodeArchives !== "false",
replaceUnderscoresWithSpaces: req.body.replaceUnderscoresWithSpaces !== "false"
};
const taskContext = TaskContext.getInstance(taskId, "importNotes", options);
const { parentNoteId } = req.params;
const parentNote = becca.getNoteOrThrow(parentNoteId);
const note = await zipImportService.importZip(taskContext, importRecord.path, parentNote);
onImportDone(note, last, taskContext, parentNoteId);
} finally {
importStore.remove(id);
}
return importRecord;
}
async function importCancel(req: Request) {
const { id } = req.params;
if (!id) throw new ValidationError("Missing ID to cancel.");
importStore.remove(id);
}
export default {
importNotesToBranch,
importAttachmentsToNote,
importPreview,
importExecute,
importCancel
importAttachmentsToNote
};

View File

@@ -1,20 +1,15 @@
import express, { type RequestHandler } from "express";
import { mkdirSync } from "fs";
import multer from "multer";
import { join } from "path";
import log from "../services/log.js";
import cls from "../services/cls.js";
import sql from "../services/sql.js";
import entityChangesService from "../services/entity_changes.js";
import AbstractBeccaEntity from "../becca/entities/abstract_becca_entity.js";
import NotFoundError from "../errors/not_found_error.js";
import ValidationError from "../errors/validation_error.js";
import auth from "../services/auth.js";
import cls from "../services/cls.js";
import dataDirs from "../services/data_dir.js";
import entityChangesService from "../services/entity_changes.js";
import import_store from "../services/import/import_store.js";
import log from "../services/log.js";
import sql from "../services/sql.js";
import { randomString, safeExtractMessageAndStackFromError } from "../services/utils.js";
import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js";
import { safeExtractMessageAndStackFromError } from "../services/utils.js";
const MAX_ALLOWED_FILE_SIZE_MB = 250;
export const router = express.Router();
@@ -72,9 +67,9 @@ export function apiResultHandler(req: express.Request, res: express.Response, re
return send(res, statusCode, response);
} else if (result === undefined) {
return send(res, 204, "");
} else {
return send(res, 200, result);
}
return send(res, 200, result);
}
function send(res: express.Response, statusCode: number, response: unknown) {
@@ -86,14 +81,14 @@ function send(res: express.Response, statusCode: number, response: unknown) {
res.status(statusCode).send(response);
return response.length;
} else {
const json = JSON.stringify(response);
res.setHeader("Content-Type", "application/json");
res.status(statusCode).send(json);
return json.length;
}
const json = JSON.stringify(response);
res.setHeader("Content-Type", "application/json");
res.status(statusCode).send(json);
return json.length;
}
export function apiRoute(method: HttpMethod, path: string, routeHandler: SyncRouteRequestHandler) {
@@ -195,49 +190,10 @@ export function createUploadMiddleware(): RequestHandler {
return multer(multerOptions).single("upload");
}
export function createImportUploadMiddleware(): RequestHandler {
const uploadDir = import_store.uploadDir;
const multerOptions: multer.Options = {
storage: multer.diskStorage({
destination(req, file, cb) {
cb(null, uploadDir);
},
filename(req, file, cb) {
cb(null, `${randomString(13)}.trilium`);
}
}),
fileFilter: (_req: express.Request, file, cb) => {
// UTF-8 file names are not well decoded by multer/busboy, so we handle the conversion on our side.
// See https://github.com/expressjs/multer/pull/1102.
file.originalname = Buffer.from(file.originalname, "latin1").toString("utf-8");
cb(null, true);
}
};
if (!process.env.TRILIUM_NO_UPLOAD_LIMIT) {
multerOptions.limits = {
fileSize: MAX_ALLOWED_FILE_SIZE_MB * 1024 * 1024
};
}
return multer(multerOptions).single("upload");
}
const uploadMiddleware = createUploadMiddleware();
const importUploadMiddleware = createImportUploadMiddleware();
export const uploadMiddlewareWithErrorHandling = function (req: express.Request, res: express.Response, next: express.NextFunction) {
uploadMiddleware(req, res, (err) => {
if (err?.code === "LIMIT_FILE_SIZE") {
res.setHeader("Content-Type", "text/plain").status(400).send(`Cannot upload file because it excceeded max allowed file size of ${MAX_ALLOWED_FILE_SIZE_MB} MiB`);
} else {
next();
}
});
};
export const importMiddlewareWithErrorHandling = function (req: express.Request, res: express.Response, next: express.NextFunction) {
importUploadMiddleware(req, res, (err) => {
uploadMiddleware(req, res, function (err) {
if (err?.code === "LIMIT_FILE_SIZE") {
res.setHeader("Content-Type", "text/plain").status(400).send(`Cannot upload file because it excceeded max allowed file size of ${MAX_ALLOWED_FILE_SIZE_MB} MiB`);
} else {

View File

@@ -10,9 +10,9 @@ import etapiBackupRoute from "../etapi/backup.js";
import etapiBranchRoutes from "../etapi/branches.js";
import etapiMetricsRoute from "../etapi/metrics.js";
import etapiNoteRoutes from "../etapi/notes.js";
import etapiRevisionsRoutes from "../etapi/revisions.js";
import etapiSpecRoute from "../etapi/spec.js";
import etapiSpecialNoteRoutes from "../etapi/special_notes.js";
import etapiRevisionsRoutes from "../etapi/revisions.js";
import auth from "../services/auth.js";
import openID from '../services/open_id.js';
import { isElectron } from "../services/utils.js";
@@ -66,7 +66,7 @@ import treeApiRoute from "./api/tree.js";
import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js";
import * as indexRoute from "./index.js";
import loginRoute from "./login.js";
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, importMiddlewareWithErrorHandling, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
// page routes
import setupRoute from "./setup.js";
@@ -195,11 +195,7 @@ function register(app: express.Application) {
route(GET, "/api/branches/:branchId/export/:type/:format/:version/:taskId", [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
asyncRoute(PST, "/api/notes/:parentNoteId/notes-import", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importNotesToBranch, apiResultHandler);
route(PST, "/api/notes/:parentNoteId/attachments-import", [auth.checkApiAuthOrElectron,
uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importAttachmentsToNote, apiResultHandler);
asyncRoute(PST, "/api/notes/:parentNoteId/execute-import", [auth.checkApiAuthOrElectron, csrfMiddleware], importRoute.importExecute, apiResultHandler);
asyncRoute(PST, "/api/notes/preview-import", [auth.checkApiAuthOrElectron, importMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importPreview, apiResultHandler);
asyncRoute(DEL, "/api/notes/preview-import/:id", [auth.checkApiAuthOrElectron, csrfMiddleware], importRoute.importCancel, apiResultHandler);
route(PST, "/api/notes/:parentNoteId/attachments-import", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importAttachmentsToNote, apiResultHandler);
apiRoute(GET, "/api/notes/:noteId/attributes", attributesRoute.getEffectiveNoteAttributes);
apiRoute(PST, "/api/notes/:noteId/attributes", attributesRoute.addNoteAttribute);

View File

@@ -1,19 +1,4 @@
import type { DangerousAttributeCategory } from "@triliumnext/commons";
type AttributeInfo = {
type: "label" | "relation";
name: string;
isDangerous?: false;
} | DangerousAttributeInfo;
export type DangerousAttributeInfo = {
type: "label" | "relation";
name: string;
isDangerous: true;
dangerCategory: DangerousAttributeCategory;
};
const builtinAttributes: AttributeInfo[] = [
export default [
// label names
{ type: "label", name: "inbox" },
{ type: "label", name: "disableVersioning" },
@@ -30,12 +15,12 @@ const builtinAttributes: AttributeInfo[] = [
{ type: "label", name: "cssClass" },
{ type: "label", name: "iconClass" },
{ type: "label", name: "keyboardShortcut" },
{ type: "label", name: "run", isDangerous: true, dangerCategory: "codeExecution" },
{ type: "label", name: "run", isDangerous: true },
{ type: "label", name: "runOnInstance", isDangerous: false },
{ type: "label", name: "runAtHour", isDangerous: false },
{ type: "label", name: "customRequestHandler", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "label", name: "customResourceProvider", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "label", name: "widget", isDangerous: true, dangerCategory: "clientSideScripting" },
{ type: "label", name: "customRequestHandler", isDangerous: true },
{ type: "label", name: "customResourceProvider", isDangerous: true },
{ type: "label", name: "widget", isDangerous: true },
{ type: "label", name: "noteInfoWidgetDisabled" },
{ type: "label", name: "linkMapWidgetDisabled" },
{ type: "label", name: "revisionsWidgetDisabled" },
@@ -84,7 +69,7 @@ const builtinAttributes: AttributeInfo[] = [
{ type: "label", name: "shareHtmlLocation" },
{ type: "label", name: "displayRelations" },
{ type: "label", name: "hideRelations" },
{ type: "label", name: "titleTemplate", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "label", name: "titleTemplate", isDangerous: true },
{ type: "label", name: "template" },
{ type: "label", name: "toc" },
{ type: "label", name: "color" },
@@ -93,9 +78,9 @@ const builtinAttributes: AttributeInfo[] = [
{ type: "label", name: "executeDescription" },
{ type: "label", name: "newNotesOnTop" },
{ type: "label", name: "clipperInbox" },
{ type: "label", name: "webViewSrc", isDangerous: true, dangerCategory: "webview" },
{ type: "label", name: "webViewSrc", isDangerous: true },
{ type: "label", name: "hideHighlightWidget" },
{ type: "label", name: "iconPack", isDangerous: true, dangerCategory: "iconPack" },
{ type: "label", name: "iconPack", isDangerous: true },
{ type: "label", name: "printLandscape" },
{ type: "label", name: "printPageSize" },
@@ -105,26 +90,24 @@ const builtinAttributes: AttributeInfo[] = [
{ type: "relation", name: "imageLink" },
{ type: "relation", name: "relationMapLink" },
{ type: "relation", name: "includeMapLink" },
{ type: "relation", name: "runOnNoteCreation", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnNoteTitleChange", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnNoteChange", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnNoteContentChange", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnNoteDeletion", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnBranchCreation", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnBranchChange", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnBranchDeletion", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnChildNoteCreation", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnAttributeCreation", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnAttributeChange", isDangerous: true, dangerCategory: "serverSideScripting" },
{ type: "relation", name: "runOnNoteCreation", isDangerous: true },
{ type: "relation", name: "runOnNoteTitleChange", isDangerous: true },
{ type: "relation", name: "runOnNoteChange", isDangerous: true },
{ type: "relation", name: "runOnNoteContentChange", isDangerous: true },
{ type: "relation", name: "runOnNoteDeletion", isDangerous: true },
{ type: "relation", name: "runOnBranchCreation", isDangerous: true },
{ type: "relation", name: "runOnBranchChange", isDangerous: true },
{ type: "relation", name: "runOnBranchDeletion", isDangerous: true },
{ type: "relation", name: "runOnChildNoteCreation", isDangerous: true },
{ type: "relation", name: "runOnAttributeCreation", isDangerous: true },
{ type: "relation", name: "runOnAttributeChange", isDangerous: true },
{ type: "relation", name: "template" },
{ type: "relation", name: "inherit" },
{ type: "relation", name: "widget", isDangerous: true, dangerCategory: "clientSideScripting" },
{ type: "relation", name: "renderNote", isDangerous: true, dangerCategory: "clientSideScripting" },
{ type: "relation", name: "widget", isDangerous: true },
{ type: "relation", name: "renderNote", isDangerous: true },
{ type: "relation", name: "shareCss" },
{ type: "relation", name: "shareJs" },
{ type: "relation", name: "shareHtml" },
{ type: "relation", name: "shareTemplate" },
{ type: "relation", name: "shareFavicon" }
];
export default builtinAttributes;

View File

@@ -1,90 +0,0 @@
import { mkdir, readdir, rm, } from "fs/promises";
import { join } from "path";
import dataDirs from "../data_dir";
export interface ImportRecord {
path: string;
created: number;
}
const MAX_AGE = 30 * 60_000;
abstract class ImportStoreBase {
private importStore: Map<string, ImportRecord> = new Map();
get(id: string) {
return this.importStore.get(id);
}
set(id: string, record: Omit<ImportRecord, "created">) {
this.importStore.set(id, {
...record,
created: Date.now()
});
}
async remove(id: string) {
const record = this.get(id);
if (!record) return;
this.onRecordRemoved(record);
this.importStore.delete(id);
}
startCleanupTimer() {
setInterval(async () => {
const now = Date.now();
for (const [id, record] of this.importStore.entries()) {
if (now - record.created > MAX_AGE) {
await this.remove(id);
}
}
}, 5 * 60_000);
}
public abstract onRecordRemoved(record: ImportRecord): Promise<void>;
}
class DiskImportStore extends ImportStoreBase {
constructor(private _uploadDir: string) {
super();
}
get uploadDir() {
return this._uploadDir;
}
async init() {
// Ensure the import directory exists.
await mkdir(this._uploadDir, { recursive: true });
// Delete all files at start-up.
const files = await readdir(this._uploadDir);
await Promise.all(files.map(async file => {
try {
await rm(join(this._uploadDir, file));
} catch (e) {
console.error(`Error while deleting import: ${file}`, e);
}
}));
}
public async onRecordRemoved(record: ImportRecord): Promise<void> {
try {
await rm(record.path);
} catch (e) {
console.error(`Unable to delete file from import store: ${record.path}.`, e);
}
}
}
const store = new DiskImportStore(join(dataDirs.TMP_DIR, "upload"));
store.init();
store.startCleanupTimer();
export default store;

View File

@@ -31,7 +31,7 @@ interface ImportZipOpts {
preserveIds?: boolean;
}
async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath: string | Buffer, importRootNote: BNote, opts?: ImportZipOpts): Promise<BNote> {
async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Buffer, importRootNote: BNote, opts?: ImportZipOpts): Promise<BNote> {
/** maps from original noteId (in ZIP file) to newly generated noteId */
const noteIdMap: Record<string, string> = {};
/** type maps from original attachmentId (in ZIP file) to newly generated attachmentId */
@@ -295,12 +295,12 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
noteId: getNewNoteId(noteMeta.noteId)
};
}
}
// don't check for noteMeta since it's not mandatory for notes
return {
noteId: getNoteId(noteMeta, absUrl)
};
}
function processTextNoteContent(content: string, noteTitle: string, filePath: string, noteMeta?: NoteMeta) {
@@ -313,9 +313,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
if (noteTitle.trim() === text.trim()) {
return ""; // remove whole H1 tag
}
}
return `<h2>${text}</h2>`;
});
if (taskContext.data?.safeImport) {
@@ -348,9 +348,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
return `src="api/attachments/${target.attachmentId}/image/${path.basename(url)}"`;
} else if (target.noteId) {
return `src="api/images/${target.noteId}/${path.basename(url)}"`;
}
}
return match;
});
content = content.replace(/href="([^"]*)"/g, (match, url) => {
@@ -374,9 +374,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
return `href="#root/${target.noteId}?viewMode=attachments&attachmentId=${target.attachmentId}"`;
} else if (target.noteId) {
return `href="#root/${target.noteId}"`;
}
}
return match;
});
if (noteMeta) {
@@ -559,7 +559,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
// we're running two passes in order to obtain critical information first (meta file and root)
const topLevelItems = new Set<string>();
await readZipFile(bufferOrPath, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => {
await readZipFile(fileBuffer, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => {
const filePath = normalizeFilePath(entry.fileName);
// make sure that the meta file is loaded before the rest of the files is processed.
@@ -579,7 +579,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
topLevelPath = (topLevelItems.size > 1 ? "" : topLevelItems.values().next().value ?? "");
await readZipFile(bufferOrPath, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => {
await readZipFile(fileBuffer, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => {
const filePath = normalizeFilePath(entry.fileName);
if (/\/$/.test(entry.fileName)) {
@@ -626,7 +626,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, bufferOrPath:
}
/** @returns path without leading or trailing slash and backslashes converted to forward ones */
export function normalizeFilePath(filePath: string): string {
function normalizeFilePath(filePath: string): string {
filePath = filePath.replace(/\\/g, "/");
if (filePath.startsWith("/")) {
@@ -658,41 +658,22 @@ export function readContent(zipfile: yauzl.ZipFile, entry: yauzl.Entry): Promise
});
}
export function readZipFile(bufferOrPath: Buffer | string, processEntryCallback: (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => Promise<void>) {
export function readZipFile(buffer: Buffer, processEntryCallback: (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => Promise<void>) {
return new Promise<void>((res, rej) => {
const options: yauzl.Options = { lazyEntries: true, validateEntrySizes: false };
const callback = (err, zipfile) => {
if (err) {
rej(err);
return;
}
if (!zipfile) {
rej("Unable to read zip file.");
return;
}
yauzl.fromBuffer(buffer, { lazyEntries: true, validateEntrySizes: false }, (err, zipfile) => {
if (err) rej(err);
if (!zipfile) throw new Error("Unable to read zip file.");
try {
zipfile.readEntry();
} catch (err) {
rej(err);
return;
}
zipfile.readEntry();
zipfile.on("entry", async (entry) => {
try {
await processEntryCallback(zipfile, entry);
} catch (e) {
rej(e);
return;
}
});
zipfile.on("end", res);
};
if (typeof bufferOrPath === "string") {
yauzl.open(bufferOrPath, options, callback);
} else {
yauzl.fromBuffer(bufferOrPath, options, callback);
}
});
});
}
@@ -711,9 +692,9 @@ function resolveNoteType(type: string | undefined): NoteType {
if (type && (ALLOWED_NOTE_TYPES as readonly string[]).includes(type)) {
return type as NoteType;
}
}
return "text";
}
export function removeTriliumTags(content: string) {

View File

@@ -1,73 +0,0 @@
import { describe, expect, it } from "vitest";
import NoteMeta, { NoteMetaFile } from "../meta/note_meta";
import { previewMeta } from "./zip_preview";
describe("Preview meta", () => {
it("identifies dangerous attributes", () => {
const meta = wrapMeta({
title: "First unsafe note",
attributes: [
{
type: "label",
name: "widget",
value: ""
}
],
children: [
{
title: "Sub unsafe note",
attributes: [
{
type: "relation",
name: "runOnBranchCreation",
value: ""
}
]
}
]
}, {
title: "Second unsafe note",
attributes: [
{
type: "label",
name: "customRequestHandler",
value: ""
},
{
type: "label",
name: "safe",
value: ""
}
],
attachments: [
{
attachmentId: "YRAEUXCDKNtn",
title: "icon-color.svg",
role: "image",
mime: "image/svg+xml",
position: 10,
dataFileName: "Trilium Demo_icon-color.svg"
}
]
});
const result = previewMeta(meta);
expect(result.numNotes).toBe(3);
expect(result.numAttributes).toBe(4);
expect(result.numAttachments).toBe(1);
expect(result.isDangerous).toBe(true);
expect(result.dangerousAttributes).toContain("widget");
expect(result.dangerousAttributes).toContain("customRequestHandler");
expect(result.dangerousAttributes).toContain("runOnBranchCreation");
expect(result.dangerousAttributeCategories).toContain("serverSideScripting");
expect(result.dangerousAttributeCategories).toContain("clientSideScripting");
});
});
function wrapMeta(...noteMeta: NoteMeta[]): NoteMetaFile {
return {
formatVersion: 2,
appVersion: "0.101.3-test-260207-031832",
files: noteMeta
};
};

View File

@@ -1,86 +0,0 @@
import { DangerousAttributeCategory, ImportPreviewResponse } from "@triliumnext/commons";
import ValidationError from "../../errors/validation_error";
import { DangerousAttributeInfo } from "../builtin_attributes";
import BUILTIN_ATTRIBUTES from "../builtin_attributes.js";
import NoteMeta, { NoteMetaFile } from "../meta/note_meta";
import { normalizeFilePath, readContent, readZipFile } from "./zip";
export default async function previewZipForImport(bufferOrPath: string | Buffer) {
let metaFile: NoteMetaFile | null = null;
await readZipFile(bufferOrPath, async (zipfile, entry) => {
const filePath = normalizeFilePath(entry.fileName);
if (filePath === "!!!meta.json") {
const content = await readContent(zipfile, entry);
metaFile = JSON.parse(content.toString("utf-8")) as NoteMetaFile;
}
zipfile.readEntry();
});
if (!metaFile) {
throw new ValidationError("Missing meta file.");
}
const previewResults = previewMeta(metaFile);
return previewResults;
}
interface PreviewContext {
dangerousAttributes: Set<string>;
dangerousAttributeCategories: Set<DangerousAttributeCategory>;
numNotes: number;
numAttributes: number;
numAttachments: number;
}
export function previewMeta(meta: NoteMetaFile): Omit<ImportPreviewResponse, "id" | "fileName"> {
const context: PreviewContext = {
dangerousAttributes: new Set<string>(),
dangerousAttributeCategories: new Set<DangerousAttributeCategory>(),
numNotes: 0,
numAttributes: 0,
numAttachments: 0
};
previewMetaInternal(meta.files, context);
return {
isDangerous: context.dangerousAttributes.size > 0,
dangerousAttributes: Array.from(context.dangerousAttributes),
dangerousAttributeCategories: Array.from(context.dangerousAttributeCategories),
numNotes: context.numNotes,
numAttributes: context.numAttributes,
numAttachments: context.numAttachments
};
}
function previewMetaInternal(metaFiles: NoteMeta[], context: PreviewContext) {
for (const metaFile of metaFiles) {
context.numNotes++;
// Look through the attributes for dangerous ones.
if (metaFile.attributes) {
for (const { name, type } of metaFile.attributes) {
context.numAttributes++;
const dangerousAttribute = BUILTIN_ATTRIBUTES.find((attr) =>
attr.type === type &&
attr.name.toLowerCase() === name.trim().toLowerCase() && attr.isDangerous) as DangerousAttributeInfo | undefined;
if (!dangerousAttribute) continue;
context.dangerousAttributes.add(name);
context.dangerousAttributeCategories.add(dangerousAttribute.dangerCategory);
}
}
if (metaFile.attachments) {
context.numAttachments += metaFile.attachments.length;
}
if (metaFile.children) {
previewMetaInternal(metaFile.children, context);
}
}
}

View File

@@ -81,66 +81,82 @@ interface ExportAsPdfOpts {
}
electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => {
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "printing");
browserWindow.webContents.print({}, (success, failureReason) => {
if (!success && failureReason !== "Print job canceled") {
electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason);
}
e.sender.send("print-done", printReport);
browserWindow.destroy();
});
try {
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "printing");
browserWindow.webContents.print({}, (success, failureReason) => {
if (!success && failureReason !== "Print job canceled") {
electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason);
}
e.sender.send("print-done", printReport);
browserWindow.destroy();
});
} catch (err) {
e.sender.send("print-done", {
type: "error",
message: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined
});
}
});
electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pageSize }: ExportAsPdfOpts) => {
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "exporting_pdf");
async function print() {
const filePath = electron.dialog.showSaveDialogSync(browserWindow, {
defaultPath: formatDownloadTitle(title, "file", "application/pdf"),
filters: [
{
name: t("pdf.export_filter"),
extensions: ["pdf"]
}
]
});
if (!filePath) return;
let buffer: Buffer;
try {
buffer = await browserWindow.webContents.printToPDF({
landscape,
pageSize,
generateDocumentOutline: true,
generateTaggedPDF: true,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `<div></div>`,
footerTemplate: `
<div class="pageNumber" style="width: 100%; text-align: center; font-size: 10pt;">
</div>
`
});
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message"));
return;
}
try {
await fs.writeFile(filePath, buffer);
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message"));
return;
}
electron.shell.openPath(filePath);
}
try {
await print();
} finally {
e.sender.send("print-done", printReport);
browserWindow.destroy();
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "exporting_pdf");
async function print() {
const filePath = electron.dialog.showSaveDialogSync(browserWindow, {
defaultPath: formatDownloadTitle(title, "file", "application/pdf"),
filters: [
{
name: t("pdf.export_filter"),
extensions: ["pdf"]
}
]
});
if (!filePath) return;
let buffer: Buffer;
try {
buffer = await browserWindow.webContents.printToPDF({
landscape,
pageSize,
generateDocumentOutline: true,
generateTaggedPDF: true,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `<div></div>`,
footerTemplate: `
<div class="pageNumber" style="width: 100%; text-align: center; font-size: 10pt;">
</div>
`
});
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message"));
return;
}
try {
await fs.writeFile(filePath, buffer);
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message"));
return;
}
electron.shell.openPath(filePath);
}
try {
await print();
} finally {
e.sender.send("print-done", printReport);
browserWindow.destroy();
}
} catch (err) {
e.sender.send("print-done", {
type: "error",
message: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined
});
}
});
@@ -151,6 +167,7 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac
nodeIntegration: true,
contextIsolation: false,
offscreen: true,
devTools: false,
session: e.sender.session
},
});
@@ -158,13 +175,78 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac
const progressCallback = (_e, progress: number) => e.sender.send("print-progress", { progress, action });
ipcMain.on("print-progress", progressCallback);
await browserWindow.loadURL(`http://127.0.0.1:${port}/?print#${notePath}`);
const printReport = await browserWindow.webContents.executeJavaScript(`
new Promise(resolve => {
if (window._noteReady) return resolve(window._noteReady);
window.addEventListener("note-ready", (data) => resolve(data.detail));
});
`);
// Capture ALL console output (including errors) for debugging
browserWindow.webContents.on("console-message", (e, message, line, sourceId) => {
if (e.level === "debug") return;
if (e.level === "error") {
log.error(`[Print Window ${sourceId}:${line}] ${message}`);
return;
}
log.info(`[Print Window ${sourceId}:${line}] ${message}`);
});
try {
await browserWindow.loadURL(`http://127.0.0.1:${port}/?print#${notePath}`);
} catch (err) {
log.error(`Failed to load print window: ${err}`);
ipcMain.off("print-progress", progressCallback);
throw err;
}
// Set up error tracking and logging in the renderer process
await browserWindow.webContents.executeJavaScript(`
(function() {
window._printWindowErrors = [];
window.addEventListener("error", (e) => {
const errorMsg = "Uncaught error: " + e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno;
console.error(errorMsg);
if (e.error?.stack) console.error(e.error.stack);
window._printWindowErrors.push({
type: 'error',
message: errorMsg,
stack: e.error?.stack
});
});
window.addEventListener("unhandledrejection", (e) => {
const errorMsg = "Unhandled rejection: " + String(e.reason);
console.error(errorMsg);
if (e.reason?.stack) console.error(e.reason.stack);
window._printWindowErrors.push({
type: 'rejection',
message: errorMsg,
stack: e.reason?.stack
});
});
})();
`).catch(err => log.error(`Failed to set up error handlers in print window: ${err}`));
let printReport;
try {
printReport = await browserWindow.webContents.executeJavaScript(`
new Promise((resolve, reject) => {
if (window._noteReady) return resolve(window._noteReady);
// Check for errors periodically
const errorChecker = setInterval(() => {
if (window._printWindowErrors && window._printWindowErrors.length > 0) {
clearInterval(errorChecker);
const errors = window._printWindowErrors.map(e => e.message).join('; ');
reject(new Error("Print window errors: " + errors));
}
}, 100);
window.addEventListener("note-ready", (data) => {
clearInterval(errorChecker);
resolve(data.detail);
});
});
`);
} catch (err) {
log.error(`Print window promise failed for ${notePath}: ${err}`);
ipcMain.off("print-progress", progressCallback);
throw err;
}
ipcMain.off("print-progress", progressCallback);
return { browserWindow, printReport };
}
@@ -221,7 +303,6 @@ async function createMainWindow(app: App) {
configureWebContents(mainWindow.webContents, spellcheckEnabled);
trackWindowFocus(mainWindow);
return mainWindow;
}
function getWindowExtraOpts() {

View File

@@ -13,10 +13,10 @@
"postinstall": "wxt prepare"
},
"keywords": [],
"packageManager": "pnpm@10.28.2",
"packageManager": "pnpm@10.29.2",
"devDependencies": {
"@wxt-dev/auto-icons": "1.1.0",
"wxt": "0.20.13"
"wxt": "0.20.14"
},
"dependencies": {
"cash-dom": "8.1.5"

View File

@@ -9,7 +9,7 @@
"preview": "pnpm build && vite preview"
},
"dependencies": {
"i18next": "25.8.0",
"i18next": "25.8.4",
"i18next-http-backend": "3.0.2",
"preact": "10.28.3",
"preact-iso": "2.11.1",

View File

@@ -27,6 +27,10 @@ export default function Resources() {
<Section className="icon-packs fill">
<h2>{t("resources.icon_packs")}</h2>
<div>
<p>Note: This feature is still in preview and is available only in the <a href="https://docs.triliumnotes.org/user-guide/advanced-usage/nightly-release" target="_blank" rel="noopener noreferrer">nightly release</a>.</p>
</div>
<p>
<Trans
i18nKey="resources.icon_packs_intro"

View File

@@ -154,7 +154,8 @@
"header": {
"get-started": "Loslegen",
"documentation": "Dokumentation",
"support-us": "Unterstützt uns"
"support-us": "Unterstützt uns",
"resources": "Ressourcen"
},
"footer": {
"copyright_and_the": " und die ",
@@ -196,5 +197,12 @@
"download_exe": "Installationsdatei herunterladen (.exe)",
"download_zip": "Portable (.zip)",
"download_scoop": "Scoop"
},
"resources": {
"icon_packs": "Symbolpakete",
"download": "Herunterladen",
"website": "Webseite",
"title": "Ressourcen",
"icon_packs_intro": "Erweitere die Auswahl an verfügbaren Symbolen für deine Notizen, indem du ein Symbolpaket verwendest. Für weitere Informationen zu Symbolpaketen, schaue in die <DocumentationLink>offizielle Dokumentation</DocumentationLink>."
}
}

View File

@@ -195,6 +195,14 @@
"header": {
"get-started": "Comencemos",
"documentation": "Documentación",
"support-us": "Apóyanos"
"support-us": "Apóyanos",
"resources": "Recursos"
},
"resources": {
"title": "Recursos",
"icon_packs": "Paquetes de iconos",
"icon_packs_intro": "Ampliar la selección de iconos disponibles para sus notas utilizando un paquete de iconos. Para más información acerca de paquetes de icono, vea la <DocumentationLink>documentación oficial</DocumentationLink>.",
"download": "Descargar",
"website": "Sitio web"
}
}

View File

@@ -112,7 +112,8 @@
"header": {
"get-started": "Tosaigh",
"documentation": "Doiciméadú",
"support-us": "Tacaigh linn"
"support-us": "Tacaigh linn",
"resources": "Acmhainní"
},
"footer": {
"copyright_and_the": " agus an ",
@@ -196,5 +197,12 @@
"description": "Nótaí Trilium atá á n-óstáil ar PikaPods, seirbhís íoctha le haghaidh rochtana agus bainistíochta éasca. Níl baint dhíreach aige le foireann Trilium.",
"download_pikapod": "Socraigh ar PikaPods",
"download_triliumcc": "Nó féach ar trilium.cc"
},
"resources": {
"title": "Acmhainní",
"icon_packs": "Pacáistí deilbhín",
"icon_packs_intro": "Leathnaigh rogha na ndeilbhíní atá ar fáil le do nótaí trí phacáiste deilbhíní a úsáid. Le haghaidh tuilleadh eolais faoi phacáistí deilbhíní, féach ar an <DocumentationLink>dhoiciméadú oifigiúil</DocumentationLink>.",
"download": "Íoslódáil",
"website": "Suíomh Gréasáin"
}
}

View File

@@ -195,6 +195,14 @@
"header": {
"get-started": "はじめる",
"documentation": "ドキュメント",
"support-us": "サポート"
"support-us": "サポート",
"resources": "リソース"
},
"resources": {
"title": "リソース",
"icon_packs": "アイコンパック",
"icon_packs_intro": "アイコンパックを使用すると、ノートで使用できるアイコンの選択肢が広がります。アイコンパックの詳細については、<DocumentationLink>公式ドキュメント</DocumentationLink>をご覧ください。",
"download": "ダウンロード",
"website": "Webサイト"
}
}

View File

@@ -195,6 +195,14 @@
"header": {
"get-started": "Start",
"documentation": "Dokumentacja",
"support-us": "Wesprzyj nas"
"support-us": "Wesprzyj nas",
"resources": "Zasoby"
},
"resources": {
"title": "Zasoby",
"icon_packs": "Paczki ikon",
"icon_packs_intro": "Rozszerz wybór dostępnych ikon dla swoich notatek, korzystając z pakietu ikon. Więcej informacji na temat pakietów ikon znajdziesz w <DocumentationLink> dokumentacji </DocumentationLink>.",
"download": "Pobieranie",
"website": "Strona internetowa"
}
}

View File

@@ -131,7 +131,8 @@
"header": {
"get-started": "开始使用",
"documentation": "文档",
"support-us": "支持我们"
"support-us": "支持我们",
"resources": "资源"
},
"footer": {
"copyright_and_the": " 以及 ",
@@ -196,5 +197,12 @@
"download_tar_x64": "64 位 (.tar.xz)",
"download_tar_arm64": "ARM (.tar.xz)",
"download_nixos": "NixOS 模块"
},
"resources": {
"icon_packs": "图标包",
"icon_packs_intro": "使用图标包可以扩展笔记中可用的图标选择。有关图标包的更多信息,请参阅<DocumentationLink>官方文档</DocumentationLink>。",
"download": "下载",
"website": "网站",
"title": "资源"
}
}

View File

@@ -8,7 +8,12 @@ export default defineConfig({
prerender: {
enabled: true,
renderTarget: '#app',
additionalPrerenderRoutes: ['/404'],
additionalPrerenderRoutes: [
'/404',
'/get-started',
'/resources',
'/support-us'
],
previewMiddlewareEnabled: true,
previewMiddlewareFallback: '/404',
},

21
docs/README-pl.md vendored
View File

@@ -35,7 +35,7 @@ wiedzy.
<img src="./app.png" alt="Trilium Screenshot" width="1000">
## ⏬ Pobierz
## ⏬ Pobieranie
- [Ostatnie wydanie](https://github.com/TriliumNext/Trilium/releases/latest)
stabilna wersja, polecane dla większości użytkowników.
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) -
@@ -48,7 +48,7 @@ wiedzy.
[docs.triliumnotes.org](https://docs.triliumnotes.org/)**
Nasza dokumentacja jest dostępna w wielu formatach:
- **Dokumentacja Online**: Pełna dokumentacja dostępna pod
- **Dokumentacja Online**: Pełna dokumentacja dostępna na
[docs.triliumnotes.org](https://docs.triliumnotes.org/)
- **Pomoc w aplikacji**: Naciśnij `F1` w Trilium, aby uzyskać dostęp do tej
samej dokumentacji bezpośrednio w aplikacji
@@ -64,15 +64,15 @@ Nasza dokumentacja jest dostępna w wielu formatach:
TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading)
- [Podstawowe koncepcje i
funkcjonalność](https://docs.triliumnotes.org/user-guide/concepts/notes)
- [Wzorce Bazy Wiedzy
Osobistej](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge)
- [Wzorce osobistej Bazy
Wiedzy](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge)
## 🎁 Funkcjonalność
* Notatki mogą być zorganizowane w drzewa dowolnej głębokości. Pojedyncza
notatka może być umieszczona w wielu miejscach w drzewie (patrz
[klonowanie](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning))
* Bogaty edytor notatek WYSIWYG, zawierający np. tabele, obrazy i
* Bogato wyposażony edytor notatek WYSIWYG, zawierający np. tabele, obrazy i
[matematykę](https://docs.triliumnotes.org/user-guide/note-types/text) z
[autoformat](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting)
Markdown
@@ -83,7 +83,7 @@ Nasza dokumentacja jest dostępna w wielu formatach:
notatkach](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation),
wyszukiwanie po pełnym tekście i[wyróżnienie
notatki](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting)
* Płynne [aktualizowanie wersji
* Bezproblemowe [aktualizowanie wersji
notatki](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions)
* [Atrybuty](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes)
notatki mogą być użyte dla jej organizacji, wyszukiwania i użycia
@@ -113,7 +113,7 @@ Nasza dokumentacja jest dostępna w wielu formatach:
* [Mapy
geograficzne](https://docs.triliumnotes.org/user-guide/collections/geomap) z
oznaczeniami lokalizacji i trasami GPX
* [Skryptowanie](https://docs.triliumnotes.org/user-guide/scripts) patrz
* [Skrypty](https://docs.triliumnotes.org/user-guide/scripts) patrz
[zaawansowane
przykłady](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases)
* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) do
@@ -209,7 +209,7 @@ interfejsu webowego, niemal identycznego z aplikacją desktopową.
Aktualnie wspierane i testowane są tylko najnowsze wersje Chrome i Firefox.
### Mobile
### Urządzenia mobilne
Aby korzystać z TriliumNext na urządzeniu mobilnym, możesz użyć mobilnej
przeglądarki internetowej, aby uzyskać dostęp do mobilnego interfejsu instalacji
@@ -295,9 +295,8 @@ W razie dodatkowych pytań możesz skorzystać z linków podanych w sekcji
implementację aplikacji.
* [Sarah Hussein](https://github.com/Sarah-Hussein) za zaprojektowanie ikony
aplikacji.
* [nriver](https://github.com/nriver) za prace nad internacjonalizacją.
* [Thomas Frei](https://github.com/thfrei) za pierwotne prace nad Płótnem (
Canvas ).
* [nriver](https://github.com/nriver) za prace nad wersjami językowymi.
* [Thomas Frei](https://github.com/thfrei) za pierwotne prace nad Canvas.
* [antoniotejada](https://github.com/nriver) za pierwotny widżet podświetlania
składni.
* [Dosu](https://dosu.dev/) za stworzenie rozwiązania umożliwiającego

View File

@@ -1,4 +1,7 @@
# Icon Packs
> [!IMPORTANT]
> This feature is still in preview and is available only in the [nightly release](https://docs.triliumnotes.org/user-guide/advanced-usage/nightly-release).
<figure class="image image-style-align-right image_resized" style="width:45.14%;"><img style="aspect-ratio:854/649;" src="Icon Packs_image.png" width="854" height="649"></figure>
By default, Trilium comes with a set of icons called Boxicons v2. Since v0.102.0, custom icon packs allow a wider selection of icons for notes.

View File

@@ -46,7 +46,7 @@
"devDependencies": {
"@electron/rebuild": "4.0.3",
"@fast-csv/parse": "5.0.5",
"@playwright/test": "1.58.1",
"@playwright/test": "1.58.2",
"@triliumnext/server": "workspace:*",
"@types/express": "5.0.6",
"@types/js-yaml": "4.0.9",
@@ -57,7 +57,7 @@
"chalk": "5.6.2",
"cross-env": "10.1.0",
"dpdm": "4.0.1",
"esbuild": "0.27.2",
"esbuild": "0.27.3",
"eslint": "10.0.0",
"eslint-config-preact": "2.0.0",
"eslint-config-prettier": "10.1.8",
@@ -69,11 +69,11 @@
"js-yaml": "4.1.1",
"jsonc-eslint-parser": "2.4.2",
"react-refresh": "0.18.0",
"rollup-plugin-webpack-stats": "2.1.10",
"rollup-plugin-webpack-stats": "2.1.11",
"tslib": "2.8.1",
"tsx": "4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "8.54.0",
"typescript-eslint": "8.55.0",
"upath": "2.0.1",
"vite": "7.3.1",
"vite-plugin-dts": "4.5.4",
@@ -93,7 +93,7 @@
"url": "https://github.com/TriliumNext/Trilium/issues"
},
"homepage": "https://triliumnotes.org",
"packageManager": "pnpm@10.28.2",
"packageManager": "pnpm@10.29.2",
"pnpm": {
"patchedDependencies": {
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",

View File

@@ -21,11 +21,11 @@
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.3.2",
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@vitest/browser": "4.0.18",
"@vitest/coverage-istanbul": "4.0.18",
"ckeditor5": "47.4.0",
@@ -33,13 +33,13 @@
"eslint-config-ckeditor5": ">=9.1.0",
"http-server": "14.1.1",
"lint-staged": "16.2.7",
"stylelint": "17.1.0",
"stylelint": "17.2.0",
"stylelint-config-ckeditor5": ">=9.1.0",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vite-plugin-svgo": "2.0.0",
"vitest": "4.0.18",
"webdriverio": "9.23.3"
"webdriverio": "9.24.0"
},
"peerDependencies": {
"ckeditor5": "47.4.0"

View File

@@ -22,11 +22,11 @@
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.3.2",
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@vitest/browser": "4.0.18",
"@vitest/coverage-istanbul": "4.0.18",
"ckeditor5": "47.4.0",
@@ -34,13 +34,13 @@
"eslint-config-ckeditor5": ">=9.1.0",
"http-server": "14.1.1",
"lint-staged": "16.2.7",
"stylelint": "17.1.0",
"stylelint": "17.2.0",
"stylelint-config-ckeditor5": ">=9.1.0",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vite-plugin-svgo": "2.0.0",
"vitest": "4.0.18",
"webdriverio": "9.23.3"
"webdriverio": "9.24.0"
},
"peerDependencies": {
"ckeditor5": "47.4.0"

View File

@@ -24,11 +24,11 @@
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.3.2",
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@vitest/browser": "4.0.18",
"@vitest/coverage-istanbul": "4.0.18",
"ckeditor5": "47.4.0",
@@ -36,13 +36,13 @@
"eslint-config-ckeditor5": ">=9.1.0",
"http-server": "14.1.1",
"lint-staged": "16.2.7",
"stylelint": "17.1.0",
"stylelint": "17.2.0",
"stylelint-config-ckeditor5": ">=9.1.0",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vite-plugin-svgo": "2.0.0",
"vitest": "4.0.18",
"webdriverio": "9.23.3"
"webdriverio": "9.24.0"
},
"peerDependencies": {
"ckeditor5": "47.4.0"

View File

@@ -24,11 +24,11 @@
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.3.2",
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@vitest/browser": "4.0.18",
"@vitest/coverage-istanbul": "4.0.18",
"ckeditor5": "47.4.0",
@@ -36,13 +36,13 @@
"eslint-config-ckeditor5": ">=9.1.0",
"http-server": "14.1.1",
"lint-staged": "16.2.7",
"stylelint": "17.1.0",
"stylelint": "17.2.0",
"stylelint-config-ckeditor5": ">=9.1.0",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vite-plugin-svgo": "2.0.0",
"vitest": "4.0.18",
"webdriverio": "9.23.3"
"webdriverio": "9.24.0"
},
"peerDependencies": {
"ckeditor5": "47.4.0"

View File

@@ -24,11 +24,11 @@
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "54.3.2",
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "5.0.1",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@vitest/browser": "4.0.18",
"@vitest/coverage-istanbul": "4.0.18",
"ckeditor5": "47.4.0",
@@ -36,13 +36,13 @@
"eslint-config-ckeditor5": ">=9.1.0",
"http-server": "14.1.1",
"lint-staged": "16.2.7",
"stylelint": "17.1.0",
"stylelint": "17.2.0",
"stylelint-config-ckeditor5": ">=9.1.0",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vite-plugin-svgo": "2.0.0",
"vitest": "4.0.18",
"webdriverio": "9.23.3"
"webdriverio": "9.24.0"
},
"peerDependencies": {
"ckeditor5": "47.4.0"

View File

@@ -16,7 +16,7 @@
"ckeditor5-premium-features": "47.4.0"
},
"devDependencies": {
"@smithy/middleware-retry": "4.4.30",
"@smithy/middleware-retry": "4.4.31",
"@types/jquery": "3.5.33"
}
}

View File

@@ -5,7 +5,7 @@
"type": "module",
"main": "./src/index.ts",
"dependencies": {
"@codemirror/commands": "6.10.1",
"@codemirror/commands": "6.10.2",
"@codemirror/lang-css": "6.3.1",
"@codemirror/lang-html": "6.4.11",
"@codemirror/lang-javascript": "6.2.4",
@@ -16,7 +16,7 @@
"@codemirror/lang-xml": "6.1.0",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.6.0",
"@codemirror/view": "6.39.12",
"@codemirror/view": "6.39.13",
"@fsegurai/codemirror-theme-abcdef": "6.2.3",
"@fsegurai/codemirror-theme-abyss": "6.2.3",
"@fsegurai/codemirror-theme-android-studio": "6.2.3",
@@ -50,6 +50,6 @@
"codemirror-lang-elixir": "4.0.0",
"codemirror-lang-hcl": "0.1.0",
"codemirror-lang-mermaid": "0.5.0",
"eslint-linter-browserify": "9.39.2"
"eslint-linter-browserify": "10.0.0"
}
}

View File

@@ -48,6 +48,7 @@ type Labels = {
"calendar:initialDate": string;
"map:style": string;
"map:scale": boolean;
"map:hideLabels": boolean;
"board:groupBy": string;
maxNestingDepth: number;
includeArchived: boolean;

View File

@@ -298,16 +298,3 @@ export interface IconRegistry {
}[]
}[];
}
export type DangerousAttributeCategory = "codeExecution" | "serverSideScripting" | "clientSideScripting" | "iconPack" | "webview";
export interface ImportPreviewResponse {
id: string;
fileName: string;
numNotes: number;
numAttributes: number;
numAttachments: number;
isDangerous: boolean;
dangerousAttributeCategories: string[];
dangerousAttributes: string[];
}

View File

@@ -31,10 +31,10 @@
"devDependencies": {
"@digitak/esrun": "3.2.26",
"@triliumnext/ckeditor5": "workspace:*",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"dotenv": "17.2.3",
"esbuild": "0.27.2",
"esbuild": "0.27.3",
"eslint": "10.0.0",
"highlight.js": "11.11.1",
"typescript": "5.9.3"

1843
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff