Compare commits

...

74 Commits

Author SHA1 Message Date
SiriusXT
787b180378 Merge branch 'main' into feat/extra-window 2026-01-09 16:45:20 +08:00
Elian Doran
b3ccf89094 Translations update from Hosted Weblate (#8315) 2026-01-09 00:06:09 +02:00
Ulices
d31c6b1627 Translated using Weblate (Spanish)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/es/
2026-01-08 23:01:53 +01:00
Yatrik Patel
1481356d1f Translated using Weblate (Hindi)
Currently translated at 36.8% (56 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-08 23:01:52 +01:00
Ulices
a54661fd0a Translated using Weblate (Spanish)
Currently translated at 93.8% (1643 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-08 23:01:49 +01:00
Elian Doran
ae4a3f10ae fix(toc): equations not rendered in new layout 2026-01-08 21:26:04 +02:00
Elian Doran
fe3160e7a1 e2e(server): adapt tests to new layout directly 2026-01-08 20:32:54 +02:00
Elian Doran
66659d4786 e2e(server): flaky test in PDF 2026-01-08 20:11:21 +02:00
Elian Doran
0b25b09040 feat(ci): check version consistency before releasing 2026-01-08 19:49:29 +02:00
Elian Doran
0d41cc2660 Merge remote-tracking branch 'origin/stable' 2026-01-08 19:42:10 +02:00
Elian Doran
f5e8822718 chore(release): prepare for v0.101.3 2026-01-08 19:38:21 +02:00
Elian Doran
bdc220ec12 Merge remote-tracking branch 'origin/stable' 2026-01-08 18:19:16 +02:00
Elian Doran
3eb68e5271 Stable fixes (#8310) 2026-01-08 18:16:55 +02:00
Elian Doran
521952ebcc test(client): remove debug statements 2026-01-08 18:10:00 +02:00
Elian Doran
034091a696 docs(release): prepare for v0.101.2 2026-01-08 18:08:34 +02:00
Elian Doran
ae881101d8 fix(note_list): archived notes displayed in empty grid card (closes #8184) 2026-01-08 17:23:40 +02:00
Elian Doran
b11a30c49c fix(launcher_bar): crashing if there is a non-launcher note (closes #8218) 2026-01-08 16:55:51 +02:00
Elian Doran
4625efda7f fix(note_list): skip rendering of included notes for performance (closes #8017) 2026-01-08 16:50:27 +02:00
Elian Doran
3c168d750d fix(client): cycle in include causing infinite loop (closes #8294) 2026-01-08 16:44:35 +02:00
Elian Doran
5cc7b259ce fix(client): max content width not preserved (closes #8065) 2026-01-08 15:59:57 +02:00
Elian Doran
f7ae046b20 fix(mermaid): error container not scrollable (closes #8299) 2026-01-08 15:52:19 +02:00
Elian Doran
02f43d6239 fix(mermaid): code not scrollable (closes #8299) 2026-01-08 15:33:16 +02:00
Elian Doran
53e1fa1047 fix(mermaid) diagrams not saving content and SVG attachment (#8220) 2026-01-08 15:22:07 +02:00
Elian Doran
b1dc0e234f fix(popupEditor): fix closing of popupEditor when inserting note link (#8224) 2026-01-08 15:21:23 +02:00
Elian Doran
9d380dd828 fix(sql_console): cannot copy table data (#8268) 2026-01-08 15:20:36 +02:00
Elian Doran
1f77540dbb fix(text): Title is not selected when creating a note via the launcher (#8292) 2026-01-08 15:20:14 +02:00
Elian Doran
716612680d Translations update from Hosted Weblate (#8293) 2026-01-07 19:35:42 +02:00
Michael
3800fb85eb Translated using Weblate (German)
Currently translated at 95.4% (1672 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-07 18:32:56 +01:00
Rafa Osuna
d807984be4 Translated using Weblate (Spanish)
Currently translated at 92.7% (1624 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-07 18:32:56 +01:00
Giovi
2c92ae8898 Translated using Weblate (Italian)
Currently translated at 100.0% (1751 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-01-07 18:32:55 +01:00
Argann Bonneau
3d8cbc81c4 Translated using Weblate (French)
Currently translated at 94.5% (1656 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/fr/
2026-01-07 18:32:54 +01:00
Yatrik Patel
d747c94450 Translated using Weblate (Hindi)
Currently translated at 3.4% (4 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/hi/
2026-01-07 18:32:53 +01:00
pythaac
a627d1f96e Translated using Weblate (Korean)
Currently translated at 76.3% (116 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2026-01-07 18:32:53 +01:00
Yatrik Patel
869db5e478 Translated using Weblate (Hindi)
Currently translated at 0.9% (17 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-01-07 18:32:52 +01:00
Yatrik Patel
73e94d385e Translated using Weblate (Hindi)
Currently translated at 5.9% (23 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/hi/
2026-01-07 18:32:51 +01:00
Kim Nøglegaard
8f4ebeb335 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-07 18:32:51 +01:00
Yatrik Patel
263ee864be Translated using Weblate (Hindi)
Currently translated at 9.2% (14 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-07 18:32:50 +01:00
Elian Doran
f078732624 fix(text): Title is not selected when creating a note via the launcher (#8292) 2026-01-07 19:32:37 +02:00
SngAbc
fac1f6b16c fix(text): Title is not focused when creating a note via the launcher
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-01-07 10:33:17 +08:00
SiriusXT
a5841c1423 fix(text): Title is not focused when creating a note via the launcher 2026-01-07 10:11:24 +08:00
SiriusXT
80404b83b0 Merge branch 'main' into feat/extra-window 2026-01-05 11:28:47 +08:00
SiriusXT
c612bdbfc1 fix(window): normalize closedAt of OpenNoteContexts for abnormally closed windows 2026-01-05 11:21:40 +08:00
SiriusXT
3a9e686533 chore(window): simplify replacement logic for open windows 2026-01-04 18:49:08 +08:00
SiriusXT
9e8d89a170 chore(window): avoid missing windowId 2026-01-04 15:18:57 +08:00
SiriusXT
31c70938d6 Merge branch 'main' into feat/extra-window 2026-01-04 14:13:25 +08:00
SiriusXT
07f3c48d0b chore(window): import randomString only when needed 2026-01-04 14:13:18 +08:00
SiriusXT
2821b6da9d chore(window): add TS type WindowState 2026-01-03 20:04:17 +08:00
SiriusXT
daba7c398d Merge branch 'main' into feat/extra-window 2026-01-03 20:04:11 +08:00
SiriusXT
de1ef5b98b chore(test): fix errors caused by layout changes 2026-01-03 19:04:34 +08:00
SiriusXT
1bb206d978 chore(i18n): tabs total 2026-01-03 18:15:45 +08:00
SiriusXT
2fd5ddab86 chore(window): optimize the replacement logic for old window notes 2026-01-03 11:07:48 +08:00
SiriusXT
27dc662636 fix(window): a window with no open notes appears blank. 2026-01-02 18:17:05 +08:00
SiriusXT
52691b5c8c Merge branch 'main' into feat/extra-window 2026-01-02 17:44:39 +08:00
SiriusXT
8087ed5688 Merge branch 'main' into feat/extra-window 2026-01-02 14:58:10 +08:00
SiriusXT
79e2c97882 chore(window): initialize closed time of openNoteContents to 0 2026-01-01 16:09:03 +08:00
SiriusXT
1078107776 chore(window): initialize closed time of openNoteContents to 0 2026-01-01 14:30:01 +08:00
SiriusXT
9c9e123e3d Merge branch 'main' into feat/extra-window 2026-01-01 14:04:45 +08:00
SiriusXT
a8c2947062 Merge branch 'main' into feat/extra-window 2025-12-31 14:23:42 +08:00
SiriusXT
366166a561 fix(window): avoid invalid fallback value for openNoteContexts 2025-12-30 09:44:58 +08:00
SiriusXT
4d2b02eddb Merge branch 'main' into feat/extra-window 2025-12-30 09:03:26 +08:00
SiriusXT
07871853a5 fix(window): cannot save when switching between multiple windows 2025-12-29 19:24:53 +08:00
SiriusXT
254145f0e5 chore(window): handle potential JSON parsing failures 2025-12-29 16:26:50 +08:00
SiriusXT
c28f11336e chore(window_db): fix potential migration error 2025-12-29 16:11:49 +08:00
SiriusXT
2e30683b7b chore(window): avoid reduce error when no candidates 2025-12-29 16:08:29 +08:00
SiriusXT
0af7b8b145 chore(window): use MAX_SAVED_WINDOWS constant 2025-12-29 15:56:04 +08:00
SiriusXT
5d39b84886 fix(window): Fix incorrect noteContents error 2025-12-29 15:28:27 +08:00
SiriusXT
537c4051cc feat(window): add class to extra windows 2025-12-29 15:27:35 +08:00
SiriusXT
d0a22bc517 fix(window): Fix empty array issue during openNoteContents data migration 2025-12-29 15:27:11 +08:00
SiriusXT
19a75acf3f Merge branch 'main' into feat/extra-window 2025-12-29 14:44:25 +08:00
SiriusXT
3f0abce874 feat(window_db): migrate openNoteContexts to structured format with window metadata 2025-12-29 14:43:49 +08:00
SiriusXT
36dd29f919 feat(window): add class to extra windows 2025-12-29 14:37:40 +08:00
SiriusXT
d7838f0b67 feat(window): restore recently closed windows from tray 2025-12-29 14:37:35 +08:00
SiriusXT
3353d4f436 feat(window): record openNoteContents of recently closed windows 2025-12-29 14:33:34 +08:00
SiriusXT
7740154bdc feat(window): add windowId for extra windows 2025-12-29 14:32:53 +08:00
61 changed files with 1019 additions and 189 deletions

View File

@@ -11,6 +11,14 @@ concurrency:
cancel-in-progress: true
jobs:
sanity-check:
name: Sanity Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check version consistency
run: pnpm tsx ${{ github.workspace }}/scripts/check-version-consistency.ts ${{ github.ref_name }}
make-electron:
name: Make Electron
strategy:

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.101.1",
"version": "0.101.3",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",

View File

@@ -541,6 +541,7 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
export class AppContext extends Component {
isMainWindow: boolean;
windowId: string;
components: Component[];
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
tabManager!: TabManager;
@@ -549,10 +550,11 @@ export class AppContext extends Component {
lastSearchString?: string;
constructor(isMainWindow: boolean) {
constructor(isMainWindow: boolean, windowId: string) {
super();
this.isMainWindow = isMainWindow;
this.windowId = windowId;
// non-widget/layout components needed for the application
this.components = [];
this.beforeUnloadListeners = [];
@@ -682,8 +684,7 @@ export class AppContext extends Component {
this.beforeUnloadListeners = this.beforeUnloadListeners.filter(l => l !== listener);
}
}
const appContext = new AppContext(window.glob.isMainWindow);
const appContext = new AppContext(window.glob.isMainWindow, window.glob.windowId);
// we should save all outstanding changes before the page/app is closed
$(window).on("beforeunload", () => {

View File

@@ -142,14 +142,15 @@ export default class Entrypoints extends Component {
}
async openInWindowCommand({ notePath, hoistedNoteId, viewScope }: NoteCommandData) {
const extraWindowId = utils.randomString(4);
const extraWindowHash = linkService.calculateHash({ notePath, hoistedNoteId, viewScope });
if (utils.isElectron()) {
const { ipcRenderer } = utils.dynamicRequire("electron");
ipcRenderer.send("create-extra-window", { extraWindowHash });
ipcRenderer.send("create-extra-window", { extraWindowId, extraWindowHash });
} else {
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1${extraWindowHash}`;
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=${extraWindowId}${extraWindowHash}`;
window.open(url, "", "width=1000,height=800");
}

View File

@@ -11,6 +11,8 @@ import linkService from "../services/link.js";
import type { EventData } from "./app_context.js";
import type FNote from "../entities/fnote.js";
const MAX_SAVED_WINDOWS = 10;
interface TabState {
contexts: NoteContext[];
position: number;
@@ -25,6 +27,13 @@ interface NoteContextState {
viewScope: Record<string, any>;
}
interface WindowState {
windowId: string;
createdAt: number;
closedAt: number;
contexts: NoteContextState[];
}
export default class TabManager extends Component {
public children: NoteContext[];
public mutex: Mutex;
@@ -41,9 +50,6 @@ export default class TabManager extends Component {
this.recentlyClosedTabs = [];
this.tabsUpdate = new SpacedUpdate(async () => {
if (!appContext.isMainWindow) {
return;
}
if (options.is("databaseReadonly")) {
return;
}
@@ -52,9 +58,21 @@ export default class TabManager extends Component {
.map((nc) => nc.getPojoState())
.filter((t) => !!t);
await server.put("options", {
openNoteContexts: JSON.stringify(openNoteContexts)
});
// Update the current windows openNoteContexts in options
const savedWindows = options.getJson("openNoteContexts") || [];
const win = savedWindows.find((w: WindowState) => w.windowId === appContext.windowId);
if (win) {
win.contexts = openNoteContexts;
} else {
savedWindows.push({
windowId: appContext.windowId,
createdAt: Date.now(),
closedAt: 0,
contexts: openNoteContexts
} as WindowState);
}
await options.save("openNoteContexts", JSON.stringify(savedWindows));
});
appContext.addBeforeUnloadListener(this);
@@ -69,8 +87,13 @@ export default class TabManager extends Component {
}
async loadTabs() {
// Get the current windows openNoteContexts
const savedWindows = options.getJson("openNoteContexts") || [];
const currentWin = savedWindows.find(w => w.windowId === appContext.windowId);
const openNoteContexts = currentWin ? currentWin.contexts : undefined;
try {
const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || [];
const noteContextsToOpen = openNoteContexts || [];
// preload all notes at once
await froca.getNotes([...noteContextsToOpen.flatMap((tab: NoteContextState) =>
@@ -119,6 +142,51 @@ export default class TabManager extends Component {
}
});
// Save window contents
if (currentWin as WindowState) {
currentWin.createdAt = Date.now();
currentWin.closedAt = 0;
currentWin.contexts = filteredNoteContexts;
} else {
if (savedWindows?.length >= MAX_SAVED_WINDOWS) {
// Filter out the oldest entry
// 1) Never remove the "main" window
// 2) Prefer removing the oldest closed window (closedAt !== 0)
// 3) If no closed window exists, remove the window with the oldest created window
let oldestClosedIndex = -1;
let oldestClosedTime = Infinity;
let oldestCreatedIndex = -1;
let oldestCreatedTime = Infinity;
savedWindows.forEach((w: WindowState, i: number) => {
if (w.windowId === "main") return;
if (w.closedAt !== 0) {
if (w.closedAt < oldestClosedTime) {
oldestClosedTime = w.closedAt;
oldestClosedIndex = i;
}
} else {
if (w.createdAt < oldestCreatedTime) {
oldestCreatedTime = w.createdAt;
oldestCreatedIndex = i;
}
}
});
const indexToRemove = oldestClosedIndex !== -1 ? oldestClosedIndex : oldestCreatedIndex;
if (indexToRemove !== -1) {
savedWindows.splice(indexToRemove, 1);
}
}
savedWindows.push({
windowId: appContext.windowId,
createdAt: Date.now(),
closedAt: 0,
contexts: filteredNoteContexts
} as WindowState);
}
await options.save("openNoteContexts", JSON.stringify(savedWindows));
// if there's a notePath in the URL, make sure it's open and active
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
if (parsedFromUrl.notePath) {

View File

@@ -23,6 +23,12 @@ export interface RenderOptions {
imageHasZoom?: boolean;
/** If enabled, it will prevent the default behavior in which an empty note would display a list of children. */
noChildrenList?: boolean;
/** If enabled, it will prevent rendering of included notes. */
noIncludedNotes?: boolean;
/** If enabled, it will include archived notes when rendering children list. */
includeArchivedNotes?: boolean;
/** Set of note IDs that have already been seen during rendering to prevent infinite recursion. */
seenNoteIds?: Set<string>;
}
const CODE_MIME_TYPES = new Set(["application/json"]);

View File

@@ -0,0 +1,132 @@
import { trimIndentation } from "@triliumnext/commons";
import { describe, expect, it } from "vitest";
import { buildNote } from "../test/easy-froca";
import renderText from "./content_renderer_text";
describe("Text content renderer", () => {
it("renders included note", async () => {
const contentEl = document.createElement("div");
const includedNote = buildNote({
title: "Included note",
content: "<p>This is the included note.</p>"
});
const note = buildNote({
title: "New note",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="${includedNote.noteId}" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl));
expect(contentEl.querySelectorAll("section.include-note").length).toBe(1);
expect(contentEl.querySelectorAll("section.include-note p").length).toBe(1);
});
it("skips rendering included note", async () => {
const contentEl = document.createElement("div");
const includedNote = buildNote({
title: "Included note",
content: "<p>This is the included note.</p>"
});
const note = buildNote({
title: "New note",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="${includedNote.noteId}" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl), { noIncludedNotes: true });
expect(contentEl.querySelectorAll("section.include-note").length).toBe(0);
});
it("doesn't enter infinite loop on direct recursion", async () => {
const contentEl = document.createElement("div");
const note = buildNote({
title: "New note",
id: "Y7mBwmRjQyb4",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="Y7mBwmRjQyb4" data-box-size="medium">
&nbsp;
</section>
<section class="include-note" data-note-id="Y7mBwmRjQyb4" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl));
expect(contentEl.querySelectorAll("section.include-note").length).toBe(0);
});
it("doesn't enter infinite loop on indirect recursion", async () => {
const contentEl = document.createElement("div");
buildNote({
id: "first",
title: "Included note",
content: trimIndentation`\
<p>This is the included note.</p>
<section class="include-note" data-note-id="second" data-box-size="medium">
&nbsp;
</section>
`
});
const note = buildNote({
id: "second",
title: "New note",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="first" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl));
expect(contentEl.querySelectorAll("section.include-note").length).toBe(1);
});
it("renders children list when note is empty", async () => {
const contentEl = document.createElement("div");
const parentNote = buildNote({
title: "Parent note",
children: [
{ title: "Child note 1" },
{ title: "Child note 2" }
]
});
await renderText(parentNote, $(contentEl));
const items = contentEl.querySelectorAll("a");
expect(items.length).toBe(2);
expect(items[0].textContent).toBe("Child note 1");
expect(items[1].textContent).toBe("Child note 2");
});
it("skips archived notes in children list", async () => {
const contentEl = document.createElement("div");
const parentNote = buildNote({
title: "Parent note",
children: [
{ title: "Child note 1" },
{ title: "Child note 2", "#archived": "" },
{ title: "Child note 3" }
]
});
await renderText(parentNote, $(contentEl));
const items = contentEl.querySelectorAll("a");
expect(items.length).toBe(2);
expect(items[0].textContent).toBe("Child note 1");
expect(items[1].textContent).toBe("Child note 3");
});
});

View File

@@ -15,7 +15,14 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
if (blob && !isHtmlEmpty(blob.content)) {
$renderedContent.append($('<div class="ck-content">').html(blob.content));
await renderIncludedNotes($renderedContent[0]);
const seenNoteIds = options.seenNoteIds ?? new Set<string>();
seenNoteIds.add("noteId" in note ? note.noteId : note.attachmentId);
if (!options.noIncludedNotes) {
await renderIncludedNotes($renderedContent[0], seenNoteIds);
} else {
$renderedContent.find("section.include-note").remove();
}
if ($renderedContent.find("span.math-tex").length > 0) {
renderMathInElement($renderedContent[0], { trust: true });
@@ -35,11 +42,11 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
await rewriteMermaidDiagramsInContainer($renderedContent[0] as HTMLDivElement);
await formatCodeBlocks($renderedContent);
} else if (note instanceof FNote && !options.noChildrenList) {
await renderChildrenList($renderedContent, note);
await renderChildrenList($renderedContent, note, options.includeArchivedNotes ?? false);
}
}
async function renderIncludedNotes(contentEl: HTMLElement) {
async function renderIncludedNotes(contentEl: HTMLElement, seenNoteIds: Set<string>) {
// TODO: Consider duplicating with server's share/content_renderer.ts.
const includeNoteEls = contentEl.querySelectorAll("section.include-note");
@@ -66,8 +73,18 @@ async function renderIncludedNotes(contentEl: HTMLElement) {
continue;
}
const renderedContent = (await content_renderer.getRenderedContent(note)).$renderedContent;
if (seenNoteIds.has(noteId)) {
console.warn(`Skipping inclusion of ${noteId} to avoid circular reference.`);
includeNoteEl.remove();
continue;
}
const renderedContent = (await content_renderer.getRenderedContent(note, {
seenNoteIds
})).$renderedContent;
includeNoteEl.replaceChildren(...renderedContent);
seenNoteIds.add(noteId);
}
}
@@ -98,7 +115,7 @@ export async function applyInlineMermaid(container: HTMLDivElement) {
}
}
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote) {
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote, includeArchivedNotes: boolean) {
let childNoteIds = note.getChildNoteIds();
if (!childNoteIds.length) {
@@ -108,14 +125,16 @@ async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: F
$renderedContent.css("padding", "10px");
$renderedContent.addClass("text-with-ellipsis");
// just load the first 10 child notes
if (childNoteIds.length > 10) {
childNoteIds = childNoteIds.slice(0, 10);
}
// just load the first 10 child notes
const childNotes = await froca.getNotes(childNoteIds);
for (const childNote of childNotes) {
if (childNote.isArchived && !includeArchivedNotes) continue;
$renderedContent.append(
await link.createLink(`${note.noteId}/${childNote.noteId}`, {
showTooltip: false,

View File

@@ -27,10 +27,6 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
loadResults.addRevision(ec.entityId, ec.noteId, ec.componentId);
} else if (ec.entityName === "options") {
const attributeEntity = ec.entity as FAttributeRow;
if (attributeEntity.name === "openNoteContexts") {
continue; // only noise
}
options.set(attributeEntity.name as OptionNames, attributeEntity.value);
loadResults.addOption(attributeEntity.name as OptionNames);
} else if (ec.entityName === "attachments") {

View File

@@ -13,7 +13,8 @@ function injectGlobals() {
uncheckedWindow.$ = $;
uncheckedWindow.WebSocket = () => {};
uncheckedWindow.glob = {
isMainWindow: true
isMainWindow: true,
windowId: "main"
};
}

View File

@@ -25,7 +25,8 @@
},
"widget-list-error": {
"title": "Abruf der Liste von Widgets vom Server ist fehlgeschlagen"
}
},
"open-script-note": "Script-Notiz öffnen"
},
"add_link": {
"add_link": "Link hinzufügen",
@@ -208,7 +209,8 @@
"info": {
"modalTitle": "Infonachricht",
"closeButton": "Schließen",
"okButton": "OK"
"okButton": "OK",
"copy_to_clipboard": "In die Zwischenablage kopieren"
},
"jump_to_note": {
"search_button": "Suche im Volltext",
@@ -695,7 +697,9 @@
"export_as_image": "Als Bild exportieren",
"export_as_image_png": "PNG (Raster)",
"export_as_image_svg": "SVG (Vektor)",
"note_map": "Notizen Karte"
"note_map": "Notizen Karte",
"view_revisions": "Notizrevisionen",
"advanced": "Erweitert"
},
"onclick_button": {
"no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler"

View File

@@ -21,7 +21,13 @@
},
"bundle-error": {
"title": "Hubo un fallo al cargar un script personalizado",
"message": "El script de la nota con ID \"{{id}}\", titulado \"{{title}}\" no pudo ser ejecutado debido a:\n\n{{message}}"
"message": "El script no pudo ser ejecutado debido a:\n\n{{message}}"
},
"widget-list-error": {
"title": "Hubo un fallo al obtener la lista de widgets del servidor"
},
"widget-render-error": {
"title": "Hubo un fallo al renderizar un widget personalizado de React"
}
},
"add_link": {
@@ -162,7 +168,8 @@
"other": "Otro",
"quickSearch": "centrarse en la entrada de búsqueda rápida",
"inPageSearch": "búsqueda en la página",
"title": "Hoja de ayuda"
"title": "Hoja de ayuda",
"editShortcuts": "Editar atajos de teclado"
},
"import": {
"importIntoNote": "Importar a nota",
@@ -690,7 +697,7 @@
"convert_into_attachment_successful": "La nota '{{title}}' ha sido convertida a un archivo adjunto.",
"convert_into_attachment_prompt": "¿Está seguro que desea convertir la nota '{{title}}' en un archivo adjunto de la nota padre?",
"print_pdf": "Exportar como PDF...",
"open_note_on_server": "Abrir nota en el servidor"
"open_note_on_server": "Abrir nota en servidor"
},
"onclick_button": {
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
@@ -736,7 +743,7 @@
"zpetne_odkazy": {
"relation": "relación",
"backlink_one": "{{count}} Vínculo de retroceso",
"backlink_many": "",
"backlink_many": "{{count}} Vínculos de retroceso",
"backlink_other": "{{count}} vínculos de retroceso"
},
"mobile_detail_menu": {
@@ -749,7 +756,10 @@
"note_icon": {
"change_note_icon": "Cambiar icono de nota",
"search": "Búsqueda:",
"reset-default": "Restablecer a icono por defecto"
"reset-default": "Restablecer a icono por defecto",
"search_placeholder_one": "Buscar {{number}} icono a través de {{count}} paquetes",
"search_placeholder_many": "Buscar {{number}} iconos a través de {{count}} paquetes",
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes"
},
"basic_properties": {
"note_type": "Tipo de nota",
@@ -789,7 +799,7 @@
"file_type": "Tipo de archivo",
"file_size": "Tamaño del archivo",
"download": "Descargar",
"open": "Abrir",
"open": "Abrir externamente",
"upload_new_revision": "Subir nueva revisión",
"upload_success": "Se ha subido una nueva revisión de archivo.",
"upload_failed": "Error al cargar una nueva revisión de archivo.",
@@ -1302,11 +1312,11 @@
"code_mime_types": {
"title": "Tipos MIME disponibles en el menú desplegable",
"tooltip_syntax_highlighting": "Resaltado de sintaxis",
"tooltip_code_block_syntax": "Bloques de código en notas de texto",
"tooltip_code_note_syntax": "Notas de código"
"tooltip_code_block_syntax": "Bloques de Código en notas de Texto",
"tooltip_code_note_syntax": "Notas de Código"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Atajos de teclas de Vim",
"use_vim_keybindings_in_code_notes": "Combinaciones de teclas Vim",
"enable_vim_keybindings": "Habilitar los atajos de teclas de Vim en la notas de código (no es modo ex)"
},
"wrap_lines": {
@@ -1571,7 +1581,7 @@
"will_be_deleted_in": "Este archivo adjunto se eliminará automáticamente en {{time}}",
"will_be_deleted_soon": "Este archivo adjunto se eliminará automáticamente pronto",
"deletion_reason": ", porque el archivo adjunto no está vinculado en el contenido de la nota. Para evitar la eliminación, vuelva a agregar el enlace del archivo adjunto al contenido o convierta el archivo adjunto en una nota.",
"role_and_size": "Rol: {{role}}, Tamaño: {{size}}",
"role_and_size": "Rol: {{role}}, tamaño: {{size}}, MIME: {{- mimeType}}",
"link_copied": "Enlace del archivo adjunto copiado al portapapeles.",
"unrecognized_role": "Rol de archivo adjunto no reconocido '{{role}}'."
},
@@ -1622,7 +1632,7 @@
"import-into-note": "Importar a nota",
"apply-bulk-actions": "Aplicar acciones en lote",
"converted-to-attachments": "{{count}} notas han sido convertidas en archivos adjuntos.",
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres?",
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres? Esta operación solo aplica a notas de Imagen, otras notas serán omitidas.",
"open-in-popup": "Edición rápida",
"archive": "Archivar",
"unarchive": "Desarchivar"
@@ -1717,7 +1727,10 @@
"note_detail": {
"could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'",
"printing": "Impresión en curso...",
"printing_pdf": "Exportando a PDF en curso.."
"printing_pdf": "Exportando a PDF en curso..",
"print_report_collection_content_one": "{{count}} nota en la colección no se puede imprimir porque no son compatibles o está protegida.",
"print_report_collection_content_many": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas."
},
"note_title": {
"placeholder": "escriba el título de la nota aquí..."
@@ -1929,7 +1942,7 @@
"unknown_widget": "Widget desconocido para \"{{id}}\"."
},
"note_language": {
"not_set": "No establecido",
"not_set": "Idioma no establecido",
"configure-languages": "Configurar idiomas..."
},
"content_language": {
@@ -1968,7 +1981,7 @@
"hide-weekends": "Ocultar fines de semana",
"show-scale": "Mostrar escala",
"display-week-numbers": "Mostrar números de semana",
"map-style": "Estilo de mapa:",
"map-style": "Estilo de mapa",
"max-nesting-depth": "Máxima profundidad de anidamiento:",
"vector_light": "Vector (claro)",
"vector_dark": "Vector (oscuro)",
@@ -2097,5 +2110,36 @@
"clear-color": "Borrar color de nota",
"set-color": "Asignar color de nota",
"set-custom-color": "Asignar color de nota personalizado"
},
"status_bar": {
"backlinks_one": "{{count}} vínculo de retroceso",
"backlinks_many": "{{count}} vínculos de retroceso",
"backlinks_other": "{{count}} vínculos de retroceso",
"backlinks_title_one": "Ver vínculo de retroceso",
"backlinks_title_many": "Ver vínculos de retroceso",
"backlinks_title_other": "Ver vínculos de retroceso",
"attachments_one": "{{count}} adjunto",
"attachments_many": "{{count}} adjuntos",
"attachments_other": "{{count}} adjuntos",
"attachments_title_one": "Ver adjunto en una nueva pestaña",
"attachments_title_many": "Ver adjuntos en una nueva pestaña",
"attachments_title_other": "Ver adjuntos en una nueva pestaña",
"attributes_one": "{{count}} atributo",
"attributes_many": "{{count}} atributos",
"attributes_other": "{{count}} atributos",
"note_paths_one": "{{count}} ruta",
"note_paths_many": "{{count}} rutas",
"note_paths_other": "{{count}} rutas"
},
"pdf": {
"attachments_one": "{{count}} adjunto",
"attachments_many": "{{count}} adjuntos",
"attachments_other": "{{count}} adjuntos",
"layers_one": "{{count}} capa",
"layers_many": "{{count}} capas",
"layers_other": "{{count}} capas",
"pages_one": "{{count}} página",
"pages_many": "{{count}} páginas",
"pages_other": "{{count}} páginas"
}
}

View File

@@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "Echec du chargement d'un script personnalisé",
"message": "Le script de la note avec l'ID \"{{id}}\", intitulé \"{{title}}\" n'a pas pu être exécuté à cause de\n\n{{message}}"
"message": "Le script n'a pas pu être exécuté à cause de\n\n{{message}}"
},
"widget-list-error": {
"title": "Impossible d'obtenir la liste des widgets depuis le serveur"

View File

@@ -31,5 +31,17 @@
},
"add_link": {
"note": "नोट"
},
"bulk_actions": {
"other": "अन्य"
},
"clone_to": {
"search_for_note_by_its_name": "नोट क नाम से नोट खोजें"
},
"confirm": {
"also_delete_note": "नोट भी डिलीट करें"
},
"delete_notes": {
"delete_notes_preview": "नोट्स प्रिव्यू डिलीट करें"
}
}

View File

@@ -1895,7 +1895,11 @@
"create-child-note": "Crea nota figlio",
"unhoist": "Sganciare",
"toggle-sidebar": "Attiva/disattiva la barra laterale",
"dropping-not-allowed": "Non è consentito lasciare appunti in questa posizione."
"dropping-not-allowed": "Non è consentito lasciare appunti in questa posizione.",
"clone-indicator-tooltip": "Questa nota ha {{- count}} genitori: {{- parents}}",
"clone-indicator-tooltip-single": "Questa nota è stata clonata (1 genitore aggiuntivo: {{- parent}})",
"shared-indicator-tooltip": "Questa nota è condivisa pubblicamente",
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}"
},
"title_bar_buttons": {
"window-on-top": "Mantieni la finestra in primo piano"
@@ -2200,7 +2204,14 @@
"execute_sql_description": "Questa nota è una nota SQL. Clicca per eseguire la query SQL.",
"shared_copy_to_clipboard": "Copia link negli appunti",
"shared_open_in_browser": "Apri il link nel browser",
"shared_unshare": "Rimuovi condivisione"
"shared_unshare": "Rimuovi condivisione",
"save_status_saved": "Salvato",
"save_status_saving": "Salvataggio in corso...",
"save_status_unsaved": "Non salvato",
"save_status_error": "Salvataggio non riuscito",
"save_status_saving_tooltip": "Le modifiche sono state salvate.",
"save_status_unsaved_tooltip": "Ci sono modifiche non salvate. Verranno salvate automaticamente tra un attimo.",
"save_status_error_tooltip": "Si è verificato un errore durante il salvataggio della nota. Se possibile, prova a copiare il contenuto della nota altrove e a ricaricare l'applicazione."
},
"breadcrumb": {
"workspace_badge": "Area di lavoro",
@@ -2243,5 +2254,18 @@
"empty_button": "Nascondi il pannello",
"toggle": "Attiva/disattiva pannello destro",
"custom_widget_go_to_source": "Vai al codice sorgente"
},
"pdf": {
"attachments_one": "{{count}} allegato",
"attachments_many": "{{count}} allegati",
"attachments_other": "{{count}} allegati",
"layers_one": "{{count}} livello",
"layers_many": "{{count}} livelli",
"layers_other": "{{count}} livelli",
"pages_one": "{{count}} pagina",
"pages_many": "{{count}} pagine",
"pages_other": "{{count}} pagine",
"pages_alt": "Pagina {{pageNumber}}",
"pages_loading": "Caricamento in corso..."
}
}

View File

@@ -36,6 +36,7 @@ interface CustomGlobals {
isProtectedSessionAvailable: boolean;
isDev: boolean;
isMainWindow: boolean;
windowId: string;
maxEntityChangeIdAtLoad: number;
maxEntityChangeSyncIdAtLoad: number;
assetPath: string;

View File

@@ -215,7 +215,7 @@ export default function NoteDetail() {
return (
<div
ref={containerRef}
class={`note-detail ${isFullHeight ? "full-height" : ""}`}
class={`component note-detail ${isFullHeight ? "full-height" : ""}`}
>
{Object.entries(noteTypesToRender).map(([ itemType, Element ]) => {
return <NoteDetailWrapper

View File

@@ -44,6 +44,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
return (
<div class="note-list grid-view">
@@ -52,7 +53,7 @@ export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} />
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} includeArchived={includeArchived} />
))}
</div>
@@ -94,14 +95,16 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
</h5>
{isExpanded && <>
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList />
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
</>}
</div>
);
}
function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined }) {
function GridNoteCard({ note, parentNote, highlightedTokens, includeArchived }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined, includeArchived: boolean }) {
const titleRef = useRef<HTMLSpanElement>(null);
const [ noteTitle, setNoteTitle ] = useState<string>();
const notePath = getNotePath(parentNote, note);
return (
@@ -120,6 +123,7 @@ function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, pa
note={note}
trim
highlightedTokens={highlightedTokens}
includeArchivedNotes={includeArchived}
/>
</div>
);
@@ -136,14 +140,22 @@ function NoteAttributes({ note }: { note: FNote }) {
return <span className="note-list-attributes" ref={ref} />;
}
function NoteContent({ note, trim, noChildrenList, highlightedTokens }: { note: FNote, trim?: boolean, noChildrenList?: boolean, highlightedTokens: string[] | null | undefined }) {
function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
note: FNote;
trim?: boolean;
noChildrenList?: boolean;
highlightedTokens: string[] | null | undefined;
includeArchivedNotes: boolean;
}) {
const contentRef = useRef<HTMLDivElement>(null);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
useEffect(() => {
content_renderer.getRenderedContent(note, {
trim,
noChildrenList
noChildrenList,
noIncludedNotes: true,
includeArchivedNotes
})
.then(({ $renderedContent, type }) => {
if (!contentRef.current) return;

View File

@@ -1,17 +1,18 @@
import { useCallback, useLayoutEffect, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { isDesktop, isMobile } from "../../services/utils";
import CalendarWidget from "./CalendarWidget";
import SpacerWidget from "./SpacerWidget";
import BookmarkButtons from "./BookmarkButtons";
import ProtectedSessionStatusWidget from "./ProtectedSessionStatusWidget";
import SyncStatus from "./SyncStatus";
import HistoryNavigationButton from "./HistoryNavigation";
import { AiChatButton, CommandButton, CustomWidget, NoteLauncher, QuickSearchLauncherWidget, ScriptLauncher, TodayLauncher } from "./LauncherDefinitions";
import { useTriliumEvent } from "../react/hooks";
import { onWheelHorizontalScroll } from "../widget_utils";
import BookmarkButtons from "./BookmarkButtons";
import CalendarWidget from "./CalendarWidget";
import HistoryNavigationButton from "./HistoryNavigation";
import { LaunchBarContext } from "./launch_bar_widgets";
import { AiChatButton, CommandButton, CustomWidget, NoteLauncher, QuickSearchLauncherWidget, ScriptLauncher, TodayLauncher } from "./LauncherDefinitions";
import ProtectedSessionStatusWidget from "./ProtectedSessionStatusWidget";
import SpacerWidget from "./SpacerWidget";
import SyncStatus from "./SyncStatus";
export default function LauncherContainer({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
const childNotes = useLauncherChildNotes();
@@ -34,18 +35,19 @@ export default function LauncherContainer({ isHorizontalLayout }: { isHorizontal
}}>
{childNotes?.map(childNote => {
if (childNote.type !== "launcher") {
throw new Error(`Note '${childNote.noteId}' '${childNote.title}' is not a launcher even though it's in the launcher subtree`);
console.warn(`Note '${childNote.noteId}' '${childNote.title}' is not a launcher even though it's in the launcher subtree`);
return false;
}
if (!isDesktop() && childNote.isLabelTruthy("desktopOnly")) {
return false;
}
return <Launcher key={childNote.noteId} note={childNote} isHorizontalLayout={isHorizontalLayout} />
return <Launcher key={childNote.noteId} note={childNote} isHorizontalLayout={isHorizontalLayout} />;
})}
</LaunchBarContext.Provider>
</div>
)
);
}
function Launcher({ note, isHorizontalLayout }: { note: FNote, isHorizontalLayout: boolean }) {
@@ -72,7 +74,7 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
const builtinWidget = note.getLabelValue("builtinWidget");
switch (builtinWidget) {
case "calendar":
return <CalendarWidget launcherNote={note} />
return <CalendarWidget launcherNote={note} />;
case "spacer":
// || has to be inside since 0 is a valid value
const baseSize = parseInt(note.getLabelValue("baseSize") || "40");
@@ -86,15 +88,15 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
case "syncStatus":
return <SyncStatus />;
case "backInHistoryButton":
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />;
case "forwardInHistoryButton":
return <HistoryNavigationButton launcherNote={note} command="forwardInNoteHistory" />
return <HistoryNavigationButton launcherNote={note} command="forwardInNoteHistory" />;
case "todayInJournal":
return <TodayLauncher launcherNote={note} />
return <TodayLauncher launcherNote={note} />;
case "quickSearch":
return <QuickSearchLauncherWidget />
return <QuickSearchLauncherWidget />;
case "aiChatLauncher":
return <AiChatButton launcherNote={note} />
return <AiChatButton launcherNote={note} />;
default:
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
}

View File

@@ -2,12 +2,14 @@ import "./TableOfContents.css";
import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5";
import clsx from "clsx";
import { useCallback, useEffect, useState } from "preact/hooks";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import math from "../../services/math";
import { randomString } from "../../services/utils";
import { useActiveNoteContext, useContentElement, useGetContextData, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks";
import Icon from "../react/Icon";
import RawHtml from "../react/RawHtml";
import RightPanelWidget from "./RightPanelWidget";
//#region Generic impl.
@@ -80,6 +82,22 @@ function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
}) {
const [ collapsed, setCollapsed ] = useState(false);
const isActive = heading.id === activeHeadingId;
const contentRef = useRef<HTMLElement>(null);
// Render math equations after component mounts/updates
useEffect(() => {
if (!contentRef.current) return;
const mathElements = contentRef.current.querySelectorAll(".ck-math-tex");
for (const mathEl of mathElements ?? []) {
try {
math.render(mathEl.textContent || "", mathEl as HTMLElement);
} catch (e) {
console.warn("Failed to render math in TOC:", e);
}
}
}, [heading.text]);
return (
<>
<li className={clsx(collapsed && "collapsed", isActive && "active")}>
@@ -90,10 +108,12 @@ function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
onClick={() => setCollapsed(!collapsed)}
/>
)}
<span
<RawHtml
containerRef={contentRef}
className="item-content"
onClick={() => scrollToHeading(heading)}
>{heading.text}</span>
html={heading.text}
/>
</li>
{heading.children && (
<ol>
@@ -189,9 +209,23 @@ function extractTocFromTextEditor(editor: CKTextEditor) {
if (type !== "elementStart" || !item.is('element') || !item.name.startsWith('heading')) continue;
const level = Number(item.name.replace( 'heading', '' ));
const text = Array.from( item.getChildren() )
.map( c => c.is( '$text' ) ? c.data : '' )
.join( '' );
// Convert model element to view, then to DOM to get HTML
const viewEl = editor.editing.mapper.toViewElement(item);
let text = '';
if (viewEl) {
const domEl = editor.editing.view.domConverter.mapViewToDom(viewEl);
if (domEl instanceof HTMLElement) {
text = domEl.innerHTML;
}
}
// Fallback to plain text if conversion fails
if (!text) {
text = Array.from( item.getChildren() )
.map( c => c.is( '$text' ) ? c.data : '' )
.join( '' );
}
// Assign a unique ID
let tocId = item.getAttribute(TOC_ID) as string | undefined;

View File

@@ -15,6 +15,8 @@
.note-detail-split .note-detail-split-editor {
width: 100%;
flex-grow: 1;
min-width: 0;
min-height: 0;
}
.note-detail-split .note-detail-split-editor .note-detail-code {
@@ -30,6 +32,7 @@
margin: 5px;
white-space: pre-wrap;
font-size: 0.85em;
overflow: auto;
}
.note-detail-split .note-detail-split-preview {

View File

@@ -286,7 +286,7 @@ function useWatchdogCrashHandling() {
const currentState = watchdog.state;
logInfo(`CKEditor state changed to ${currentState}`);
if (currentState === "ready") {
if (currentState === "ready" && hasCrashed.current) {
hasCrashed.current = false;
watchdog.editor?.focus();
}

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/desktop",
"version": "0.101.1",
"version": "0.101.3",
"description": "Build your personal knowledge base with Trilium Notes",
"private": true,
"main": "src/main.ts",

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -6,6 +6,7 @@ import sqlInit from "@triliumnext/server/src/services/sql_init.js";
import windowService from "@triliumnext/server/src/services/window.js";
import tray from "@triliumnext/server/src/services/tray.js";
import options from "@triliumnext/server/src/services/options.js";
import electronDebug from "electron-debug";
import electronDl from "electron-dl";
import { PRODUCT_NAME } from "./app-info";
@@ -69,10 +70,12 @@ async function main() {
globalShortcut.unregisterAll();
});
app.on("second-instance", (event, commandLine) => {
app.on("second-instance", async (event, commandLine) => {
const lastFocusedWindow = windowService.getLastFocusedWindow();
if (commandLine.includes("--new-window")) {
windowService.createExtraWindow("");
const randomString = (await import("@triliumnext/server/src/services/utils.js")).randomString;
const extraWindowId = randomString(4);
windowService.createExtraWindow(extraWindowId, "");
} else if (lastFocusedWindow) {
if (lastFocusedWindow.isMinimized()) {
lastFocusedWindow.restore();
@@ -124,7 +127,8 @@ async function onReady() {
}
});
}
await normalizeOpenNoteContexts();
tray.createTray();
} else {
await windowService.createSetupWindow();
@@ -133,6 +137,30 @@ async function onReady() {
await windowService.registerGlobalShortcuts();
}
/**
* Some windows may have closed abnormally, leaving closedAt as 0 in openNoteContexts.
* This function normalizes those timestamps to the current time for correct sorting/filtering.
*/
async function normalizeOpenNoteContexts() {
const savedWindows = options.getOptionJson("openNoteContexts") || [];
const now = Date.now();
let changed = false;
for (const win of savedWindows) {
if (win.windowId !== "main" && win.closedAt === 0) {
win.closedAt = now;
changed = true;
}
}
if (changed) {
const { default: cls } = (await import("@triliumnext/server/src/services/cls.js"));
cls.wrap(() => {
options.setOption("openNoteContexts", JSON.stringify(savedWindows));
})();
}
}
function getElectronLocale() {
const uiLocale = options.getOptionOrNull("locale");
const formattingLocale = options.getOptionOrNull("formattingLocale");

View File

@@ -1,12 +1,12 @@
import test, { BrowserContext, expect, Page } from "@playwright/test";
import test, { expect, Page } from "@playwright/test";
import App from "../support/app";
test.beforeEach(async ({ page, context }) => {
const app = await setLayout({ page, context }, true);
const app = new App(page, context);
await app.goto();
await app.setOption("rightPaneCollapsedItems", "[]");
});
test.afterEach(async ({ page, context }) => await setLayout({ page, context }, false));
test("Table of contents works", async ({ page, context }) => {
const app = new App(page, context);
@@ -73,13 +73,15 @@ test("Attachments listing works", async ({ page, context }) => {
test("Download original PDF works", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.goToNoteInNewTab("Dacia Logan.pdf");
await app.goToNoteInNewTab("Layers test.pdf");
const pdfHelper = new PdfHelper(app);
await pdfHelper.toBeInitialized();
const downloadButton = app.currentNoteSplit.locator(".icon-action.bx.bx-download");
await expect(downloadButton).toBeVisible();
const [ download ] = await Promise.all([
page.waitForEvent("download"),
app.currentNoteSplit.locator(".icon-action.bx.bx-download").click()
downloadButton.click()
]);
expect(download).toBeDefined();
});
@@ -105,13 +107,6 @@ test("Layers listing works", async ({ page, context }) => {
await expect(layersList.locator(".pdf-layer-item")).toHaveCount(0);
});
async function setLayout({ page, context}: { page: Page; context: BrowserContext }, newLayout: boolean) {
const app = new App(page, context);
await app.goto();
await app.setOption("newLayout", newLayout ? "true" : "false");
return app;
}
class PdfHelper {
private contentFrame: ReturnType<Page["frameLocator"]>;
@@ -125,5 +120,6 @@ class PdfHelper {
async toBeInitialized() {
await expect(this.contentFrame.locator("#pageNumber")).toBeVisible();
await expect(this.contentFrame.locator(".page")).toBeVisible();
}
}

View File

@@ -1,4 +1,5 @@
import { test, expect, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import App from "../support/app";
test("Table of contents is displayed", async ({ page, context }) => {
@@ -8,7 +9,7 @@ test("Table of contents is displayed", async ({ page, context }) => {
await app.goToNoteInNewTab("Table of contents");
await expect(app.sidebar).toContainText("Table of Contents");
const rootList = app.sidebar.locator(".toc-widget > span > ol");
const rootList = app.sidebar.locator(".toc > ol");
// Heading 1.1
// Heading 1.1
@@ -42,7 +43,7 @@ test("Highlights list is displayed", async ({ page, context }) => {
await app.closeAllTabs();
await app.goToNoteInNewTab("Highlights list");
await expect(app.sidebar).toContainText("Highlights List");
await expect(app.sidebar).toContainText(/highlights/i);
const rootList = app.sidebar.locator(".highlights-list ol");
let index = 0;
for (const highlightedEl of ["Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2"]) {

View File

@@ -37,7 +37,7 @@ export default class App {
this.noteTreeHoistedNote = this.noteTree.locator(".fancytree-node", { has: page.locator(".unhoist-button") });
this.launcherBar = page.locator("#launcher-container");
this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)");
this.currentNoteSplitTitle = this.currentNoteSplit.locator(".note-title");
this.currentNoteSplitTitle = this.currentNoteSplit.locator(".note-title").first();
this.currentNoteSplitContent = this.currentNoteSplit.locator(".note-detail-printable.visible");
this.sidebar = page.locator("#right-pane");
}
@@ -59,7 +59,7 @@ export default class App {
// Wait for the page to load.
if (url === "/") {
await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test");
await expect(this.noteTree).toContainText("Trilium Integration Test");
if (!preserveTabs) {
await this.closeAllTabs();
}

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/server",
"version": "0.101.1",
"version": "0.101.3",
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
"private": true,
"main": "./src/main.ts",

Binary file not shown.

View File

@@ -382,6 +382,8 @@
"tooltip": "Trilium Notes",
"close": "Quit Trilium",
"recents": "Recent notes",
"recently-closed-windows": "Recently closed windows",
"tabs-total": "{{number}} tabs total",
"bookmarks": "Bookmarks",
"today": "Open today's journal note",
"new-note": "New note",

View File

@@ -11,6 +11,17 @@
"move-note-up": "नोट को ऊपर ले जाएं",
"move-note-down": "नोट को नीचे ले जाएं",
"note-clipboard": "नोट क्लिपबोर्ड",
"duplicate-subtree": "डुप्लिकेट सबट्री"
"duplicate-subtree": "डुप्लिकेट सबट्री",
"open-new-tab": "नया टैब खोलें",
"second-tab": "लिस्ट में दूसरी टैब एक्टिवेट करें",
"third-tab": "लिस्ट में तीसरी टैब एक्टिवेट करें",
"fourth-tab": "लिस्ट में चौथी टैब एक्टिवेट करें",
"sixth-tab": "लिस्ट में छठी टैब एक्टिवेट करें",
"seventh-tab": "लिस्ट में सातवीं टैब एक्टिवेट करें",
"eight-tab": "लिस्ट में आठवीं टैब एक्टिवेट करें",
"ninth-tab": "लिस्ट में नौवीं टैब एक्टिवेट करें",
"last-tab": "लिस्ट में आखिरी टैब एक्टिवेट करें",
"show-sql-console": "\"SQL कंसोल\" पेज खोलें",
"show-backend-log": "\"बैकेंड लॉग\" पेज खोलें"
}
}

View File

@@ -15,7 +15,7 @@
</head>
<body
id="trilium-app"
class="desktop heading-style-<%= headingStyle %> layout-<%= layoutOrientation %> platform-<%= platform %> <%= isElectron ? 'electron' : '' %> <%= hasNativeTitleBar ? 'native-titlebar' : '' %> <%= hasBackgroundEffects ? 'background-effects' : '' %>"
class="desktop heading-style-<%= headingStyle %> layout-<%= layoutOrientation %> platform-<%= platform %> <%= isElectron ? 'electron' : '' %> <%= hasNativeTitleBar ? 'native-titlebar' : '' %> <%= hasBackgroundEffects ? 'background-effects' : '' %> <%= isMainWindow ? '' : 'extra-window' %>"
lang="<%= currentLocale.id %>" dir="<%= currentLocale.rtl ? 'rtl' : 'ltr' %>"
>
<noscript><%= t("javascript-required") %></noscript>

View File

@@ -12,6 +12,7 @@
isDev: <%= isDev %>,
appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>,
isMainWindow: <%= isMainWindow %>,
windowId: "<%= windowId %>",
isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>,
triliumVersion: "<%= triliumVersion %>",
assetPath: "<%= assetPath %>",

View File

@@ -0,0 +1,48 @@
import cls from "../services/cls.js";
import sql from "../services/sql.js";
export default () => {
cls.init(() => {
const row = sql.getRow<{ value: string }>(
`SELECT value FROM options WHERE name = 'openNoteContexts'`
);
if (!row || !row.value) {
return;
}
let parsed: any;
try {
parsed = JSON.parse(row.value);
} catch {
return;
}
// Already in new format (array + windowId), skip
if (
Array.isArray(parsed) &&
parsed.length > 0 &&
parsed[0] &&
typeof parsed[0] === "object" &&
parsed[0].windowId
) {
return;
}
// Old format: just contexts
const migrated = [
{
windowId: "main",
createdAt: 0,
closedAt: 0,
contexts: parsed
}
];
sql.execute(
`UPDATE options SET value = ? WHERE name = 'openNoteContexts'`,
[JSON.stringify(migrated)]
);
});
};

View File

@@ -6,6 +6,11 @@
// Migrations should be kept in descending order, so the latest migration is first.
const MIGRATIONS: (SqlMigration | JsMigration)[] = [
// Migrate openNoteContexts option to the new structured format with window metadata
{
version: 234,
module: async () => import("./0234__migrate_open_note_contexts_format")
},
// Migrate geo map to collection
{
version: 233,

View File

@@ -56,6 +56,7 @@ function index(req: Request, res: Response) {
appCssNoteIds: getAppCssNoteIds(),
isDev,
isMainWindow: view === "mobile" ? true : !req.query.extraWindow,
windowId: view !== "mobile" && req.query.extraWindow ? req.query.extraWindow : "main",
isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
triliumVersion: packageJson.version,
assetPath,

View File

@@ -4,7 +4,7 @@ import packageJson from "../../package.json" with { type: "json" };
import dataDir from "./data_dir.js";
import { AppInfo } from "@triliumnext/commons";
const APP_DB_VERSION = 233;
const APP_DB_VERSION = 234;
const SYNC_VERSION = 36;
const CLIPPER_PROTOCOL_VERSION = "1.0";

View File

@@ -72,6 +72,19 @@ function getOptionBool(name: FilterOptionsByType<boolean>): boolean {
return val === "true";
}
function getOptionJson(name: OptionNames) {
const val = getOptionOrNull(name);
if (typeof val !== "string") {
return null;
}
try {
return JSON.parse(val);
} catch (e) {
return null;
}
}
function setOption<T extends OptionNames>(name: T, value: string | OptionDefinitions[T]) {
const option = becca.getOption(name);
@@ -137,6 +150,7 @@ export default {
getOption,
getOptionInt,
getOptionBool,
getOptionJson,
setOption,
createOption,
getOptions,

View File

@@ -45,8 +45,15 @@ async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts =
"openNoteContexts",
JSON.stringify([
{
notePath: "root",
active: true
windowId: "main",
createdAt: 0,
closedAt: 0,
contexts: [
{
notePath: "root",
active: true
}
]
}
]),
false
@@ -257,8 +264,15 @@ function initStartupOptions() {
"openNoteContexts",
JSON.stringify([
{
notePath: process.env.TRILIUM_START_NOTE_ID || "root",
active: true
windowId: "main",
createdAt: 0,
closedAt: 0,
contexts: [
{
notePath: process.env.TRILIUM_START_NOTE_ID || "root",
active: true
}
]
}
])
);

View File

@@ -147,8 +147,15 @@ async function createInitialDatabase(skipDemoDb?: boolean) {
"openNoteContexts",
JSON.stringify([
{
notePath: startNoteId,
active: true
windowId: "main",
createdAt: 0,
closedAt: 0,
contexts: [
{
notePath: startNoteId,
active: true
}
]
}
])
);

View File

@@ -196,6 +196,39 @@ function updateTrayMenu() {
return menuItems;
}
function buildClosedWindowsMenu() {
const savedWindows = optionService.getOptionJson("openNoteContexts") || [];
const openedWindowIds = windowService.getAllWindowIds();
const closedWindows = savedWindows
.filter(win => !openedWindowIds.includes(win.windowId))
.sort((a, b) => { return a.closedAt - b.closedAt; }); // sort by time in ascending order
const menuItems: Electron.MenuItemConstructorOptions[] = [];
for (let i = closedWindows.length - 1; i >= 0; i--) {
const win = closedWindows[i];
const activeCtx = win.contexts.find(c => c.active === true);
const activateNotePath = (activeCtx ?? win.contexts[0])?.notePath;
const activateNoteId = activateNotePath?.split("/").pop() ?? null;
if (!activateNoteId) continue;
// Get the title of the closed window
const winTitle = (() => {
const raw = becca_service.getNoteTitle(activateNoteId);
const truncated = raw.length > 20 ? `${raw.slice(0, 17)}` : raw;
const tabCount = win.contexts.filter(ctx => ctx.mainNtxId === null).length;
return tabCount > 1 ? `${truncated} (${t("tray.tabs-total", { number: tabCount })})` : truncated;
})();
menuItems.push({
label: winTitle,
type: "normal",
click: () => win.windowId !== "main" ? windowService.createExtraWindow(win.windowId, "") : windowService.createMainWindow()
});
}
return menuItems;
}
const windowVisibilityMenuItems: Electron.MenuItemConstructorOptions[] = [];
// Only call getWindowTitle if windowVisibilityMap has more than one window
@@ -258,6 +291,12 @@ function updateTrayMenu() {
icon: getIconPath("recents"),
submenu: buildRecentNotesMenu()
},
{
label: t("tray.recently-closed-windows"),
type: "submenu",
icon: getIconPath("closed-windows"),
submenu: buildClosedWindowsMenu()
},
{ type: "separator" },
{
label: t("tray.close"),

View File

@@ -16,28 +16,45 @@ import { formatDownloadTitle, isMac, isWindows } from "./utils.js";
// Prevent the window being garbage collected
let mainWindow: BrowserWindow | null;
let setupWindow: BrowserWindow | null;
let allWindows: BrowserWindow[] = []; // // Used to store all windows, sorted by the order of focus.
function trackWindowFocus(win: BrowserWindow) {
interface WindowEntry {
window: BrowserWindow;
windowId: string; // custom window ID
}
let allWindowEntries: WindowEntry[] = [];
function trackWindowFocus(win: BrowserWindow, windowId: string) {
// We need to get the last focused window from allWindows. If the last window is closed, we return the previous window.
// Therefore, we need to push the window into the allWindows array every time it gets focused.
win.on("focus", () => {
allWindows = allWindows.filter(w => !w.isDestroyed() && w !== win);
allWindows.push(win);
allWindowEntries = allWindowEntries.filter(w => !w.window.isDestroyed() && w.window !== win);
allWindowEntries.push({ window: win, windowId: windowId });
if (!optionService.getOptionBool("disableTray")) {
electron.ipcMain.emit("reload-tray");
}
});
win.on("closed", () => {
allWindows = allWindows.filter(w => !w.isDestroyed());
cls.wrap(() => {
const savedWindows = optionService.getOptionJson("openNoteContexts") || [];
const win = savedWindows.find(w => w.windowId === windowId);
if (win) {
win.closedAt = Date.now();
}
optionService.setOption("openNoteContexts", JSON.stringify(savedWindows));
})();
allWindowEntries = allWindowEntries.filter(w => !w.window.isDestroyed());
if (!optionService.getOptionBool("disableTray")) {
electron.ipcMain.emit("reload-tray");
}
});
}
async function createExtraWindow(extraWindowHash: string) {
async function createExtraWindow(extraWindowId: string, extraWindowHash: string) {
const spellcheckEnabled = optionService.getOptionBool("spellCheckEnabled");
const { BrowserWindow } = await import("electron");
@@ -56,15 +73,15 @@ async function createExtraWindow(extraWindowHash: string) {
});
win.setMenuBarVisibility(false);
win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1${extraWindowHash}`);
win.loadURL(`http://127.0.0.1:${port}/?extraWindow=${extraWindowId}${extraWindowHash}`);
configureWebContents(win.webContents, spellcheckEnabled);
trackWindowFocus(win);
trackWindowFocus(win, extraWindowId);
}
electron.ipcMain.on("create-extra-window", (event, arg) => {
createExtraWindow(arg.extraWindowHash);
createExtraWindow(arg.extraWindowId, arg.extraWindowHash);
});
interface PrintOpts {
@@ -168,8 +185,8 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac
return { browserWindow, printReport };
}
async function createMainWindow(app: App) {
if ("setUserTasks" in app) {
async function createMainWindow(app?: App) {
if (app && "setUserTasks" in app) {
app.setUserTasks([
{
program: process.execPath,
@@ -219,7 +236,7 @@ async function createMainWindow(app: App) {
mainWindow.on("closed", () => (mainWindow = null));
configureWebContents(mainWindow.webContents, spellcheckEnabled);
trackWindowFocus(mainWindow);
trackWindowFocus(mainWindow, "main");
}
function getWindowExtraOpts() {
@@ -381,11 +398,15 @@ function getMainWindow() {
}
function getLastFocusedWindow() {
return allWindows.length > 0 ? allWindows[allWindows.length - 1] : null;
return allWindowEntries.length > 0 ? allWindowEntries[allWindowEntries.length - 1]?.window : null;
}
function getAllWindows() {
return allWindows;
return allWindowEntries.map(e => e.window);
}
function getAllWindowIds(): string[] {
return allWindowEntries.map(e => e.windowId);
}
export default {
@@ -396,5 +417,6 @@ export default {
registerGlobalShortcuts,
getMainWindow,
getLastFocusedWindow,
getAllWindows
getAllWindows,
getAllWindowIds
};

View File

@@ -115,7 +115,7 @@
},
"social_buttons": {
"github": "GitHub",
"github_discussions": "GitHub Discussions",
"github_discussions": "Discusiones de GitHub",
"matrix": "Matrix",
"reddit": "Reddit"
},

View File

@@ -6,7 +6,9 @@
},
"hero_section": {
"title": "अपने विचारों को व्यवस्थित करें। अपना व्यक्तिगत नॉलेज बेस बनाएं।",
"screenshot_alt": "ट्रिलियम नोट्स डेस्कटॉप एप्लिकेशन का स्क्रीनशॉट"
"screenshot_alt": "ट्रिलियम नोट्स डेस्कटॉप एप्लिकेशन का स्क्रीनशॉट",
"get_started": "शुरू करें",
"github": "गिटहब"
},
"organization_benefits": {
"note_structure_title": "नोट संरचना",
@@ -15,5 +17,78 @@
"productivity_benefits": {
"protected_notes_title": "संरक्षित नोट्स",
"web_clipper_title": "वेब क्लिपर"
},
"note_types": {
"canvas_title": "कैनवास",
"mindmap_title": "माइंडमैप"
},
"extensibility_benefits": {
"share_title": "वेब पर नोट्स शेयर करें"
},
"collections": {
"calendar_title": "कैलेंडर",
"table_title": "टेबल"
},
"download_now": {
"linux_small": "लिनक्स के लिए",
"more_platforms": "अधिक प्लेटफॉर्म और सर्वर सेटअप"
},
"header": {
"get-started": "शुरू करें",
"support-us": "हमें सपोर्ट करें"
},
"social_buttons": {
"github": "गिटहब",
"matrix": "मैट्रिक्स",
"reddit": "रेडिट"
},
"support_us": {
"title": "हमें सपोर्ट करें"
},
"404": {
"description": "आप जो पेज खोज रहे थे वह नहीं मिल पाया। शायद वह डिलीट हो चुका है या यूआरएल (URL) गलत है।"
},
"download_helper_desktop_windows": {
"title_x64": "Windows 64-bit",
"title_arm64": "ARM पर Windows",
"quick_start": "Winget द्वारा इंस्टॉल करने के लिए:",
"download_exe": "इंस्टॉलर (.exe) डाउनलोड करें",
"download_zip": "पोर्टेबल (.zip)"
},
"download_helper_desktop_linux": {
"title_x64": "Linux 64-bit",
"title_arm64": "ARM पर लिनक्स",
"download_deb": ".deb",
"download_rpm": ".rpm",
"download_flatpak": ".flatpak",
"download_zip": "पोर्टेबल (.zip)",
"download_nixpkgs": "nixpkgs"
},
"download_helper_desktop_macos": {
"title_x64": "Intel के लिए macOS",
"title_arm64": "Apple Silicon के लिए macOS",
"description_x64": "macOS Monterey या उसके बाद के वर्ज़न पर चलने वाले Intel-आधारित Macs के लिए।",
"description_arm64": "Apple Silicon Macs के लिए, जैसे कि M1 और M2 चिप्स वाले मॉडल।",
"quick_start": "Homebrew द्वारा इंस्टॉल करने के लिए:",
"download_dmg": "इंस्टॉलर (.dmg) डाउनलोड करें",
"download_homebrew_cask": "Homebrew Cask",
"download_zip": "पोर्टेबल (.zip)"
},
"download_helper_server_docker": {
"title": "Docker द्वारा सेल्फ-होस्टेड",
"description": "Docker कंटेनर का उपयोग करके Windows, Linux या macOS पर आसानी से डिप्लॉय करें।",
"download_ghcr": "ghcr.io"
},
"download_helper_server_linux": {
"title": "Linux पर सेल्फ-होस्टेड",
"description": "ट्रिलियम नोट्स को अपने खुद के सर्वर या VPS पर डिप्लॉय करें, जो अधिकांश डिस्ट्रीब्यूशनो के साथ कम्पेटिबल है।",
"download_tar_x64": "x64 (.tar.xz)",
"download_tar_arm64": "ARM (.tar.xz)",
"download_nixos": "NixOS मॉड्यूल"
},
"download_helper_server_hosted": {
"title": "पेड होस्टिंग",
"download_pikapod": "PikaPods पर सेटअप करें",
"download_triliumcc": "वैकल्पिक रूप से trilium.cc देखें"
}
}

View File

@@ -130,6 +130,23 @@
"mobile_question": "모바일 앱이 있나요?",
"mobile_answer": "현재 공식적인 모바일 앱은 없습니다. 하지만, 서버 인스턴스를 가지고 있다면 웹 브라우저를 이용해 접근하거나 PWA로 설치할 수 있습니다. 안드로이드에는 (데스크탑 클라이언트처럼)오프라인에서도 작동하는 TriliumDroid라는 비공식 앱이 있습니다.",
"database_question": "어디에 데이터가 저장되나요?",
"server_question": "Trilium을 사용하기 위해 서버가 필요한가요?"
"server_question": "Trilium을 사용하기 위해 서버가 필요한가요?",
"title": "자주 묻는 질문",
"database_answer": "모든 노트는 애플리케이션 폴더의 SQLite 데이터베이스에 저장됩니다. Trilium이 텍스트 파일 대신 데이터베이스를 사용하는 이유는 성능과 기능 모두 구현하기 훨씬 어렵기 때문입니다(트리 여러 위치에 같은 노트를 두는 Clone과 같은 기능). 애플리케이션 폴더를 찾으려면 About 창으로 가세요.",
"server_answer": "아니요, 서버는 웹 브라우저를 통해 접속할 수 있도록 허용하며, 여러 기기를 사용하는 경우 동기화를 관리합니다. 시작하려면 데스크톱 애플리케이션을 다운로드하여 사용하기만 하면 됩니다.",
"scaling_question": "이 애플리케이션은 얼마나 많은 노트를 처리할 수 있나요?",
"scaling_answer": "사용량에 따라 다르겠지만, 이 애플리케이션은 최소 10만 개의 노트를 문제없이 처리할 수 있습니다. 다만, Trilium은 (NextCloud와 같은) 파일 저장소라기보다는 지식 기반 애플리케이션에 가깝기 때문에, 대용량 파일(파일당 1GB 이상)을 많이 업로드할 경우 동기화 과정이 실패할 수 있다는 점에 유의하십시오.",
"network_share_question": "내 데이터베이스를 네트워크 드라이브로 공유할 수 있나요?",
"network_share_answer": "아니요, 일반적으로 SQLite 데이터베이스를 네트워크 드라이브로 공유하는 것은 좋지 않습니다. 경우에 따라 작동할 수도 있지만, 네트워크를 통한 파일 잠금이 완벽하지 않아 데이터베이스가 손상될 가능성이 있습니다.",
"security_question": "내 데이터는 어떻게 보호되나요?",
"security_answer": "기본적으로 노트는 암호화되지 않으며 데이터베이스에서 직접 읽을 수 있습니다. 노트를 암호화 대상으로 표시하면, AES-128-CBC를 사용하여 암호화됩니다."
},
"final_cta": {
"title": "Trilium Notes를 시작할 준비가 되셨나요?",
"description": "강력한 기능과 완벽한 개인 정보 보호를 통해 나만의 지식 기반을 구축하세요.",
"get_started": "시작하기"
},
"components": {
"link_learn_more": "자세히 알아보기..."
}
}

View File

@@ -51,13 +51,19 @@
"file_description": "Integrer multimediafiler som PDFer, bilder og video med forhåndsvisning i programmet.",
"mermaid_description": "Lag diagrammer som flytskjema, klasse- og sekvensdiagrammer, Ganttdiagrammer og mye mer ved hjelp av Mermaidsyntaks.",
"mindmap_description": "Organiser dine tanker visuelt eller gjør en brainstorming.",
"others_list": "og andre: <0>notatkart</0>, <1>relasjonskart</1>, <2>lagrede søk</2>, <3>rendret notat</3>, og <4>web view</4>."
"others_list": "og andre: <0>notatkart</0>, <1>relasjonskart</1>, <2>lagrede søk</2>, <3>rendret notat</3>, og <4>web view</4>.",
"canvas_description": "Arranger figurer, bilder og tekst på et uendelig lerret som bruker samme teknologi som excalidraw.com. Ideelt for diagrammer, skisser og visuell planlegging."
},
"extensibility_benefits": {
"import_export_title": "Import/eksport",
"scripting_title": "Avansert skripting",
"api_title": "REST API",
"title": "Deling og utvidbarhet"
"title": "Deling og utvidbarhet",
"share_title": "Del notater på nett",
"share_description": "Hvis du har en server, kan den brukes til å dele valgfrie notater med andre.",
"scripting_description": "Lag dine egne integrasjoner i Trilium med egendefinerte widgets, eller serversidelogikk.",
"import_export_description": "Samhandle med andre programmer ved hjelp av Markdown, ENEX og OML.",
"api_description": "Ved hjelp av den innebygde REST-APIen kan du programmatisk samhandle med Trilium."
},
"collections": {
"title": "Samlinger",
@@ -66,7 +72,11 @@
"geomap_title": "Geokart",
"presentation_title": "Presentasjon",
"board_title": "Kanbantavle",
"geomap_description": "Planlegg ferien din eller merk deg dine interessepunkter på et geografisk kart ved hjelp av definerbare markører. Vis lagrede GPX-spor for å se reisen din."
"geomap_description": "Planlegg ferien din eller merk deg dine interessepunkter på et geografisk kart ved hjelp av definerbare markører. Vis lagrede GPX-spor for å se reisen din.",
"calendar_description": "Organiser dine personlige eller jobb-arrangement ved hjelp av kalender, med støtte for heldags- og flerdagsarrangement. Få rask oversikt over dine arrangementer med ukes- måneds- og årsvisning. Dra og slipp hendelser for enkelt å gjøre endringer.",
"table_description": "Vis og rediger informasjon om notater i tabellform, med ulike kolonnetyper som tekst, nummer, avkrysningsbokser, dato og tid, lenker, farger og støtte for relasjoner. Du kan også vise notater i et hierarkisk tre i tabellen.",
"board_description": "Organiser oppgaver eller prosjekter i en Kanbantavle hvor du enkelt kan lage nye elementer og kolonner, og endre status på elementer ved å dra de rundt på tavlen.",
"presentation_description": "Organiser informasjon i lysbilder og presenter dem i fullskjermmodus med myke overganger. Lysbildene kan også eksporteres til PDF for enkel deling."
},
"header": {
"documentation": "Dokumentasjon",
@@ -84,14 +94,19 @@
"title": "Støtt oss",
"financial_donations_title": "Finansiell donasjon",
"github_sponsors": "GitHub Sponsors",
"financial_donations_description": "Trilium er bygget og vedlikeholdt med <Link>flere hundre timers arbeid</Link>. Ditt bidrag hjelper å holde det åpen kildekode, forbedre funksjonalitet og dekker driftskostnader."
"financial_donations_description": "Trilium er bygget og vedlikeholdt med <Link>flere hundre timers arbeid</Link>. Ditt bidrag hjelper å holde det åpen kildekode, forbedre funksjonalitet og dekker driftskostnader.",
"financial_donations_cta": "Vurder gjerne å støtte hovedutvikleren (<Link>eliandoran</Link>) av programmet via:",
"buy_me_a_coffee": "Buy Me A Coffee"
},
"download_helper_desktop_windows": {
"download_scoop": "Scoop",
"title_x64": "Windows 64-bit",
"download_zip": "Portable (.zip)",
"title_arm64": "Windows på ARM",
"download_exe": "Last ned installasjonsprogram (.exe)"
"download_exe": "Last ned installasjonsprogram (.exe)",
"description_x64": "Kompatibel med Intel- eller AMD-enheter som kjører Windows 10 og 11.",
"description_arm64": "Kompatibel med ARM-enheter (for eksempel Qualcomm Snapdragon).",
"quick_start": "For å installere via Winget:"
},
"download_helper_desktop_linux": {
"download_deb": ".deb",
@@ -101,21 +116,31 @@
"download_aur": "AUR",
"title_x64": "Linux 64-bit",
"download_zip": "Portable (.zip)",
"title_arm64": "Linux på ARM"
"title_arm64": "Linux på ARM",
"description_x64": "For de fleste Linux-distribusjoner, kompatibelt med x86_64-arkitektur.",
"description_arm64": "For ARM-baserte Linux-distribusjoner, kompatibelt med aarch64-arkitektur.",
"quick_start": "Velg egnet pakkeformat avhengig av din distribusjon:"
},
"download_helper_server_docker": {
"download_ghcr": "ghcr.io",
"download_dockerhub": "Docker Hub",
"title": "Selv-hostet med Docker"
"title": "Selv-hostet med Docker",
"description": "Installer enkelt på Windows, Linux eller macOS ved bruk av en Docker-container."
},
"download_helper_desktop_macos": {
"download_homebrew_cask": "Homebrew Cask",
"download_zip": "Portable (.zip)",
"title_x64": "macOS for Intel",
"download_dmg": "Last ned installasjonsprogram (.dmg)"
"download_dmg": "Last ned installasjonsprogram (.dmg)",
"title_arm64": "macOS for Apple Silicon",
"description_x64": "For Intel-baserte Mac-er med macOS Monterey eller nyere.",
"description_arm64": "For Apple Silicon Mac-er som de med M1- og M2-chiper.",
"quick_start": "For å installere via Homebrew:"
},
"final_cta": {
"get_started": "Kom i gang"
"get_started": "Kom i gang",
"title": "Klar for å begynne med Trilium Notes?",
"description": "Skap din personlige kunnskapsbase med kraftig funksjonalitet og fullt personvern."
},
"components": {
"link_learn_more": "Lær mer..."
@@ -125,7 +150,8 @@
"platform_small": "for {{platform}}",
"linux_small": "for Linux",
"platform_big": "v{{version}} for {{platform}}",
"linux_big": "v{{version}} for Linux"
"linux_big": "v{{version}} for Linux",
"more_platforms": "Flere plattformer og serveroppsett"
},
"footer": {
"copyright_and_the": " og ",
@@ -135,16 +161,40 @@
"download_tar_x64": "x64 (.tar.xz)",
"download_tar_arm64": "ARM (.tar.xz)",
"download_nixos": "NixOS modul",
"title": "Selv-hostet på Linux"
"title": "Selv-hostet på Linux",
"description": "Installer Trilium Notes på din egen server eller VPS, kompatibel med de fleste distribusjoner."
},
"download_helper_server_hosted": {
"title": "Betalt hosting",
"download_triliumcc": "Alternativt sjekk trilium.cc"
"download_triliumcc": "Alternativt sjekk trilium.cc",
"description": "Trilium Notes driftet på PikaPods, en betalt tjeneste for enkel tilgang og administrasjon. Ikke direkte tilknyttet Trilium-teamet.",
"download_pikapod": "Installer på PikaPods"
},
"faq": {
"title": "Ofte stilte spørsmål"
"title": "Ofte stilte spørsmål",
"mobile_question": "Finnes det en mobil applikasjon?",
"mobile_answer": "Foreløpig er det ikke noe offisiell mobil applikasjon. Men hvis du har en serverinstans kan du koble til denne med en nettleser, og også installere den som en progressiv web-app. For Android finnes det en uoffisiell applikasjon med navn TriliumDroid som også fungerer offline (samme som en skrivebordsklient).",
"database_question": "Hvor lagres dataene?",
"database_answer": "Alle notater lagres i en SQLite-database i en programmappe. Årsaken til at Trilium bruker database i stedet for rene tekstfiler er både ytelse og at visse funksjoner ellers ville vært vanskelig å implementere, slik som klonede notater (samme notat flere steder). For å finne programmappen, åpne \"om\"-vinduet i programmet.",
"server_question": "Trenger jeg en server for å bruke Trilium?",
"server_answer": "Nei, serveren tillater tilgang via nettleser og håndterer synkronisering hvis du har flere enheter. For å komme i gang er det nok å laste ned skrivebordsprogrammet og begynne med det.",
"scaling_question": "Hvor godt skalerer programmet med store mengder notater?",
"scaling_answer": "Avhengig av bruk burde programmet kunne håndtere minst 100.000 notater uten problemer. Merk at synkroniseringen noen ganger kan feile ved opplasting av mange store filer (1GB per fil) siden Trilium er ment for å være en kunnskapsbase mer enn et fillager (som for eksempel NextCloud).",
"network_share_question": "Kan jeg dele databasen min over nettverksdeling?",
"network_share_answer": "Nei, det er stort sett ikke en god ide å dele en SQLite-database over nettverksdeling. Selv om det kan fungere, er det sjanser for at databasen kan bli ødelagt grunnet problemer med fillåsing over nettverk.",
"security_question": "Hvordan er mine data beskyttet?",
"security_answer": "Som standard blir ikke notater kryptert og kan leses direkte fra databasen. Når et notat er markert kryptert, blir det kryptert med AES-128-CBC."
},
"404": {
"title": "404: Siden ble ikke funnet"
"title": "404: Siden ble ikke funnet",
"description": "Siden ble ikke funnet. Den kan ha blitt slettet eller adressen er feil."
},
"contribute": {
"title": "Andre måter å bidra",
"way_translate": "Oversett programmet til ditt språk via <Link>Weblate</Link>.",
"way_community": "Ta del i felleskapet på <Discussions>GitHub Discussions</Discussions> eller på <Matrix>Matrix</Matrix>.",
"way_reports": "Meld feil via <Link>GitHub issues</Link>.",
"way_document": "Hjelp oss å forbedre dokumentasjonen ved å fortelle om mangler, eller bidra med veiledninger, Ofte Stilte Spørsmål eller tutorials.",
"way_market": "Spre ordet: Del Trilium Notes med venner, på blogger eller i sosiale media."
}
}

4
docs/README-hi.md vendored
View File

@@ -107,7 +107,7 @@ Our documentation is available in multiple formats:
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
[note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map)
for visualizing notes and their relations
* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/)
* [Mind Elixir](https://docs.mind-elixir.com/) पर आधारित माइंड मैप्स
* [Geo maps](https://docs.triliumnotes.org/user-guide/collections/geomap) with
location pins and GPX tracks
* [Scripting](https://docs.triliumnotes.org/user-guide/scripts) - see [Advanced
@@ -157,7 +157,7 @@ compatible with the latest zadam/trilium version of
versions of TriliumNext/Trilium have their sync versions incremented which
prevents direct migration.
## 💬 Discuss with us
## 💬 हमारे साथ चर्चा करें
Feel free to join our official conversations. We would love to hear what
features, suggestions, or issues you may have!

View File

@@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.101.1",
"appVersion": "0.101.3",
"files": [
{
"isClone": false,
@@ -61,6 +61,58 @@
"attachments": [],
"dirFileName": "Release Notes",
"children": [
{
"isClone": false,
"noteId": "IlBzLeN3MJhw",
"notePath": [
"hD3V4hiu2VW4",
"IlBzLeN3MJhw"
],
"title": "v0.101.3",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "template",
"value": "wyurrlcDl416",
"isInheritable": false,
"position": 60
}
],
"format": "markdown",
"dataFileName": "v0.101.3.md",
"attachments": []
},
{
"isClone": false,
"noteId": "vcBthaXcwAm6",
"notePath": [
"hD3V4hiu2VW4",
"vcBthaXcwAm6"
],
"title": "v0.101.2",
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "template",
"value": "wyurrlcDl416",
"isInheritable": false,
"position": 60
}
],
"format": "markdown",
"dataFileName": "v0.101.2.md",
"attachments": []
},
{
"isClone": false,
"noteId": "AgUcrU9nFXuW",
@@ -69,7 +121,7 @@
"AgUcrU9nFXuW"
],
"title": "v0.101.1",
"notePosition": 10,
"notePosition": 30,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -95,7 +147,7 @@
"uYwlZ594eyJu"
],
"title": "v0.101.0",
"notePosition": 20,
"notePosition": 40,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -121,7 +173,7 @@
"iPGKEk7pwJXK"
],
"title": "v0.100.0",
"notePosition": 30,
"notePosition": 50,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -147,7 +199,7 @@
"7HKMTjmopLcM"
],
"title": "v0.99.5",
"notePosition": 40,
"notePosition": 60,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -173,7 +225,7 @@
"RMBaNYPsRpIr"
],
"title": "v0.99.4",
"notePosition": 50,
"notePosition": 70,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -199,7 +251,7 @@
"yuroLztFfpu5"
],
"title": "v0.99.3",
"notePosition": 60,
"notePosition": 80,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -225,7 +277,7 @@
"z207sehwMJ6C"
],
"title": "v0.99.2",
"notePosition": 70,
"notePosition": 90,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -251,7 +303,7 @@
"WGQsXq2jNyTi"
],
"title": "v0.99.1",
"notePosition": 80,
"notePosition": 100,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -277,7 +329,7 @@
"cyw2Yue9vXf3"
],
"title": "v0.99.0",
"notePosition": 90,
"notePosition": 110,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -303,7 +355,7 @@
"QOJwjruOUr4k"
],
"title": "v0.98.1",
"notePosition": 100,
"notePosition": 120,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -329,7 +381,7 @@
"PLUoryywi0BC"
],
"title": "v0.98.0",
"notePosition": 110,
"notePosition": 130,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -355,7 +407,7 @@
"lvOuiWsLDv8F"
],
"title": "v0.97.2",
"notePosition": 120,
"notePosition": 140,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -381,7 +433,7 @@
"OtFZ6Nd9vM3n"
],
"title": "v0.97.1",
"notePosition": 130,
"notePosition": 150,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -407,7 +459,7 @@
"SJZ5PwfzHSQ1"
],
"title": "v0.97.0",
"notePosition": 140,
"notePosition": 160,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -433,7 +485,7 @@
"mYXFde3LuNR7"
],
"title": "v0.96.0",
"notePosition": 150,
"notePosition": 170,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -459,7 +511,7 @@
"jthwbL0FdaeU"
],
"title": "v0.95.0",
"notePosition": 160,
"notePosition": 180,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -485,7 +537,7 @@
"7HGYsJbLuhnv"
],
"title": "v0.94.1",
"notePosition": 170,
"notePosition": 190,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -511,7 +563,7 @@
"Neq53ujRGBqv"
],
"title": "v0.94.0",
"notePosition": 180,
"notePosition": 200,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -537,7 +589,7 @@
"VN3xnce1vLkX"
],
"title": "v0.93.0",
"notePosition": 190,
"notePosition": 210,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -555,7 +607,7 @@
"WRaBfQqPr6qo"
],
"title": "v0.92.7",
"notePosition": 200,
"notePosition": 220,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -581,7 +633,7 @@
"a2rwfKNmUFU1"
],
"title": "v0.92.6",
"notePosition": 210,
"notePosition": 230,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -599,7 +651,7 @@
"fEJ8qErr0BKL"
],
"title": "v0.92.5-beta",
"notePosition": 220,
"notePosition": 240,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -617,7 +669,7 @@
"kkkZQQGSXjwy"
],
"title": "v0.92.4",
"notePosition": 230,
"notePosition": 250,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -635,7 +687,7 @@
"vAroNixiezaH"
],
"title": "v0.92.3-beta",
"notePosition": 240,
"notePosition": 260,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -653,7 +705,7 @@
"mHEq1wxAKNZd"
],
"title": "v0.92.2-beta",
"notePosition": 250,
"notePosition": 270,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -671,7 +723,7 @@
"IykjoAmBpc61"
],
"title": "v0.92.1-beta",
"notePosition": 260,
"notePosition": 280,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -689,7 +741,7 @@
"dq2AJ9vSBX4Y"
],
"title": "v0.92.0-beta",
"notePosition": 270,
"notePosition": 290,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -707,7 +759,7 @@
"3a8aMe4jz4yM"
],
"title": "v0.91.6",
"notePosition": 280,
"notePosition": 300,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -725,7 +777,7 @@
"8djQjkiDGESe"
],
"title": "v0.91.5",
"notePosition": 290,
"notePosition": 310,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -743,7 +795,7 @@
"OylxVoVJqNmr"
],
"title": "v0.91.4-beta",
"notePosition": 300,
"notePosition": 320,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -761,7 +813,7 @@
"tANGQDvnyhrj"
],
"title": "v0.91.3-beta",
"notePosition": 310,
"notePosition": 330,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -779,7 +831,7 @@
"hMoBfwSoj1SC"
],
"title": "v0.91.2-beta",
"notePosition": 320,
"notePosition": 340,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -797,7 +849,7 @@
"a2XMSKROCl9z"
],
"title": "v0.91.1-beta",
"notePosition": 330,
"notePosition": 350,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -815,7 +867,7 @@
"yqXFvWbLkuMD"
],
"title": "v0.90.12",
"notePosition": 340,
"notePosition": 360,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -833,7 +885,7 @@
"veS7pg311yJP"
],
"title": "v0.90.11-beta",
"notePosition": 350,
"notePosition": 370,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -851,7 +903,7 @@
"sq5W9TQxRqMq"
],
"title": "v0.90.10-beta",
"notePosition": 360,
"notePosition": 380,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -869,7 +921,7 @@
"yFEGVCUM9tPx"
],
"title": "v0.90.9-beta",
"notePosition": 370,
"notePosition": 390,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -887,7 +939,7 @@
"o4wAGqOQuJtV"
],
"title": "v0.90.8",
"notePosition": 380,
"notePosition": 400,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -920,7 +972,7 @@
"i4A5g9iOg9I0"
],
"title": "v0.90.7-beta",
"notePosition": 390,
"notePosition": 410,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -938,7 +990,7 @@
"ThNf2GaKgXUs"
],
"title": "v0.90.6-beta",
"notePosition": 400,
"notePosition": 420,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -956,7 +1008,7 @@
"G4PAi554kQUr"
],
"title": "v0.90.5-beta",
"notePosition": 410,
"notePosition": 430,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -983,7 +1035,7 @@
"zATRobGRCmBn"
],
"title": "v0.90.4",
"notePosition": 420,
"notePosition": 440,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -1001,7 +1053,7 @@
"sCDLf8IKn3Iz"
],
"title": "v0.90.3",
"notePosition": 430,
"notePosition": 450,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -1019,7 +1071,7 @@
"VqqyBu4AuTjC"
],
"title": "v0.90.2-beta",
"notePosition": 440,
"notePosition": 460,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -1037,7 +1089,7 @@
"RX3Nl7wInLsA"
],
"title": "v0.90.1-beta",
"notePosition": 450,
"notePosition": 470,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -1055,7 +1107,7 @@
"GyueACukPWjk"
],
"title": "v0.90.0-beta",
"notePosition": 460,
"notePosition": 480,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -1073,7 +1125,7 @@
"kzjHexDTTeVB"
],
"title": "v0.48",
"notePosition": 470,
"notePosition": 490,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -1140,7 +1192,7 @@
"wyurrlcDl416"
],
"title": "Release Template",
"notePosition": 480,
"notePosition": 500,
"prefix": null,
"isExpanded": false,
"type": "text",

View File

@@ -0,0 +1,22 @@
# v0.101.2
> [!NOTE]
> If you are interested in an [official mobile application](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/7447)  ([#7447](https://github.com/TriliumNext/Trilium/issues/7447)) or [multi-user support](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/4956) ([#4956](https://github.com/TriliumNext/Trilium/issues/4956)), consider offering financial support via IssueHunt (see links).
> [!IMPORTANT]
> If you enjoyed this release, consider showing a token of appreciation by:
>
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Trilium) (top-right).
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
## 🐞 Bugfixes
* [SQL Console: cannot copy table data](https://github.com/TriliumNext/Trilium/pull/8268) by @SiriusXT
* [Title is not selected when creating a note via the launcher](https://github.com/TriliumNext/Trilium/pull/8292) by @SiriusXT
* [Popup editor closing after inserting a note link](https://github.com/TriliumNext/Trilium/pull/8224) by @SiriusXT
* [New Mermaid diagrams do not save content](https://github.com/TriliumNext/Trilium/pull/8220) by @lzinga
* [Can't scroll mermaid diagram code](https://github.com/TriliumNext/Trilium/issues/8299)
* [Max content width is not respected when switching between note types in the same tab](https://github.com/TriliumNext/Trilium/issues/8065)
* [Crash When a Note Includes Itself](https://github.com/TriliumNext/Trilium/issues/8294)
* [Severe Performance Degradation and Crash Issues Due to Recursive Inclusion in Included Notes](https://github.com/TriliumNext/Trilium/issues/8017)
* [<note> is not a launcher even though it's in the launcher subtree](https://github.com/TriliumNext/Trilium/issues/8218)
* [Archived subnotes of direct children appear in grid view without #includeArchived](https://github.com/TriliumNext/Trilium/issues/8184)

View File

@@ -0,0 +1,24 @@
# v0.101.3
> [!NOTE]
> If you are interested in an [official mobile application](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/7447)  ([#7447](https://github.com/TriliumNext/Trilium/issues/7447)) or [multi-user support](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/4956) ([#4956](https://github.com/TriliumNext/Trilium/issues/4956)), consider offering financial support via IssueHunt (see links).
> [!IMPORTANT]
> If you enjoyed this release, consider showing a token of appreciation by:
>
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Trilium) (top-right).
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
This is a re-release of v0.101.2, which had a cache invalidation issue.
## 🐞 Bugfixes
* [SQL Console: cannot copy table data](https://github.com/TriliumNext/Trilium/pull/8268) by @SiriusXT
* [Title is not selected when creating a note via the launcher](https://github.com/TriliumNext/Trilium/pull/8292) by @SiriusXT
* [Popup editor closing after inserting a note link](https://github.com/TriliumNext/Trilium/pull/8224) by @SiriusXT
* [New Mermaid diagrams do not save content](https://github.com/TriliumNext/Trilium/pull/8220) by @lzinga
* [Can't scroll mermaid diagram code](https://github.com/TriliumNext/Trilium/issues/8299)
* [Max content width is not respected when switching between note types in the same tab](https://github.com/TriliumNext/Trilium/issues/8065)
* [Crash When a Note Includes Itself](https://github.com/TriliumNext/Trilium/issues/8294)
* [Severe Performance Degradation and Crash Issues Due to Recursive Inclusion in Included Notes](https://github.com/TriliumNext/Trilium/issues/8017)
* [is not a launcher even though it's in the launcher subtree](https://github.com/TriliumNext/Trilium/issues/8218)
* [Archived subnotes of direct children appear in grid view without #includeArchived](https://github.com/TriliumNext/Trilium/issues/8184)

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/source",
"version": "0.101.1",
"version": "0.101.3",
"description": "Build your personal knowledge base with Trilium Notes",
"directories": {
"doc": "docs"

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/commons",
"version": "0.101.1",
"version": "0.101.3",
"description": "Shared library between the clients (e.g. browser, Electron) and the server, mostly for type definitions and utility methods.",
"private": true,
"type": "module",

View File

@@ -0,0 +1,33 @@
import { readFileSync } from "fs";
import { join } from "path";
const projectRoot = join(__dirname, '..');
const filesToCheck = [
'package.json',
'apps/server/package.json',
'apps/client/package.json',
'apps/desktop/package.json',
'packages/commons/package.json',
]
function main() {
const expectedVersion = process.argv[2];
if (!expectedVersion) {
console.error('Expected version argument is missing.');
process.exit(1);
}
for (const fileToCheck of filesToCheck) {
const packageJsonPath = join(projectRoot, fileToCheck);
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const version = packageJson.version;
if (version !== expectedVersion) {
console.error(`Version mismatch in ${fileToCheck}: expected ${expectedVersion}, found ${version}`);
process.exit(1);
}
}
console.log('All versions are consistent:', expectedVersion);
}
main();