diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 5727032e6..7bc544e7e 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -270,6 +270,7 @@ export type CommandMappings = { closeThisNoteSplit: CommandData; moveThisNoteSplit: CommandData & { isMovingLeft: boolean }; jumpToNote: CommandData; + openTodayNote: CommandData; commandPalette: CommandData; // Keyboard shortcuts diff --git a/apps/client/src/components/entrypoints.ts b/apps/client/src/components/entrypoints.ts index 7989960a6..8a902666f 100644 --- a/apps/client/src/components/entrypoints.ts +++ b/apps/client/src/components/entrypoints.ts @@ -159,6 +159,16 @@ export default class Entrypoints extends Component { this.openInWindowCommand({ notePath: "", hoistedNoteId: "root" }); } + async openTodayNoteCommand() { + const todayNote = await dateNoteService.getTodayNote(); + if (!todayNote) { + console.warn("Missing today note."); + return; + } + + await appContext.tabManager.openInSameTab(todayNote.noteId); + } + async runActiveNoteCommand() { const noteContext = appContext.tabManager.getActiveContext(); if (!noteContext) { diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts index bcb6c408e..6d0a15506 100644 --- a/apps/client/src/entities/fnote.ts +++ b/apps/client/src/entities/fnote.ts @@ -417,7 +417,7 @@ export default class FNote { return notePaths; } - getSortedNotePathRecords(hoistedNoteId = "root"): NotePathRecord[] { + getSortedNotePathRecords(hoistedNoteId = "root", activeNotePath: string | null = null): NotePathRecord[] { const isHoistedRoot = hoistedNoteId === "root"; const notePaths: NotePathRecord[] = this.getAllNotePaths().map((path) => ({ @@ -428,7 +428,23 @@ export default class FNote { isHidden: path.includes("_hidden") })); + // Calculate the length of the prefix match between two arrays + const prefixMatchLength = (path: string[], target: string[]) => { + const diffIndex = path.findIndex((seg, i) => seg !== target[i]); + return diffIndex === -1 ? Math.min(path.length, target.length) : diffIndex; + }; + notePaths.sort((a, b) => { + if (activeNotePath) { + const activeSegments = activeNotePath.split('/'); + const aOverlap = prefixMatchLength(a.notePath, activeSegments); + const bOverlap = prefixMatchLength(b.notePath, activeSegments); + // Paths with more matching prefix segments are prioritized + // when the match count is equal, other criteria are used for sorting + if (bOverlap !== aOverlap) { + return bOverlap - aOverlap; + } + } if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { return a.isInHoistedSubTree ? -1 : 1; } else if (a.isArchived !== b.isArchived) { @@ -449,10 +465,11 @@ export default class FNote { * Returns the note path considered to be the "best" * * @param {string} [hoistedNoteId='root'] + * @param {string|null} [activeNotePath=null] * @return {string[]} array of noteIds constituting the particular note path */ - getBestNotePath(hoistedNoteId = "root") { - return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; + getBestNotePath(hoistedNoteId = "root", activeNotePath: string | null = null) { + return this.getSortedNotePathRecords(hoistedNoteId, activeNotePath)[0]?.notePath; } /** diff --git a/apps/client/src/services/tree.ts b/apps/client/src/services/tree.ts index fc54c3c75..ec5bc0191 100644 --- a/apps/client/src/services/tree.ts +++ b/apps/client/src/services/tree.ts @@ -26,21 +26,12 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root } const path = notePath.split("/").reverse(); - - if (!path.includes("root")) { - path.push("root"); - } - const effectivePathSegments: string[] = []; let childNoteId: string | null = null; let i = 0; - while (true) { - if (i >= path.length) { - break; - } - - const parentNoteId = path[i++]; + for (let i = 0; i < path.length; i++) { + const parentNoteId = path[i]; if (childNoteId !== null) { const child = await froca.getNote(childNoteId, !logErrors); @@ -65,7 +56,7 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root return null; } - if (!parents.some((p) => p.noteId === parentNoteId)) { + if (!parents.some(p => p.noteId === parentNoteId) || (i === path.length - 1 && parentNoteId !== 'root')) { if (logErrors) { const parent = froca.getNoteFromCache(parentNoteId); @@ -77,7 +68,8 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root ); } - const bestNotePath = child.getBestNotePath(hoistedNoteId); + const activeNotePath = appContext.tabManager.getActiveContextNotePath(); + const bestNotePath = child.getBestNotePath(hoistedNoteId, activeNotePath); if (bestNotePath) { const pathToRoot = bestNotePath.reverse().slice(1); @@ -108,7 +100,9 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root if (!note) { throw new Error(`Unable to find note: ${notePath}.`); } - const bestNotePath = note.getBestNotePath(hoistedNoteId); + + const activeNotePath = appContext.tabManager.getActiveContextNotePath(); + const bestNotePath = note.getBestNotePath(hoistedNoteId, activeNotePath); if (!bestNotePath) { throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`); diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 0f17bdc79..f5e037be5 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -11,7 +11,11 @@ export function reloadFrontendApp(reason?: string) { logInfo(`Frontend app reload: ${reason}`); } - window.location.reload(); + if (isElectron()) { + dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow()?.reload(); + } else { + window.location.reload(); + } } export function restartDesktopApp() { diff --git a/apps/server/src/routes/login.spec.ts b/apps/server/src/routes/login.spec.ts index 69e2cff6a..85754d63c 100644 --- a/apps/server/src/routes/login.spec.ts +++ b/apps/server/src/routes/login.spec.ts @@ -4,6 +4,7 @@ import type { Application } from "express"; import dayjs from "dayjs"; import { type SQLiteSessionStore } from "./session_parser.js"; import { SessionData } from "express-session"; +import cls from "../services/cls.js"; let app: Application; let sessionStore: SQLiteSessionStore; @@ -106,7 +107,7 @@ describe("Login Route test", () => { expect(expiry).toBeTruthy(); vi.setSystemTime(expiry!); - vi.advanceTimersByTime(CLEAN_UP_INTERVAL); + cls.init(() => vi.advanceTimersByTime(CLEAN_UP_INTERVAL)); ({ session } = await getSessionFromCookie(setCookieHeader)); expect(session).toBeFalsy(); }); diff --git a/apps/server/src/services/keyboard_actions.ts b/apps/server/src/services/keyboard_actions.ts index 6a11242c4..fb97be84c 100644 --- a/apps/server/src/services/keyboard_actions.ts +++ b/apps/server/src/services/keyboard_actions.ts @@ -41,6 +41,14 @@ function getDefaultKeyboardActions() { scope: "window", ignoreFromCommandPalette: true }, + { + actionName: "openTodayNote", + friendlyName: t("hidden-subtree.open-today-journal-note-title"), + iconClass: "bx bx-calendar", + defaultShortcuts: [], + description: t("hidden-subtree.open-today-journal-note-title"), + scope: "window" + }, { actionName: "commandPalette", friendlyName: t("keyboard_action_names.command-palette"), diff --git a/apps/website/src/translations/pt/translation.json b/apps/website/src/translations/pt/translation.json index 626710d3d..a4436fba7 100644 --- a/apps/website/src/translations/pt/translation.json +++ b/apps/website/src/translations/pt/translation.json @@ -14,6 +14,9 @@ "screenshot_alt": "Captura de ecrã da aplicação Trilium Notes para computador" }, "organization_benefits": { - "title": "Organização" + "title": "Organização", + "note_structure_description": "As notas podem ser organizadas de forma hierárquica. Não há necessidade de pastas, pois cada nota pode conter sub notas. Uma única nota pode ser adicionada em vários locais da hierarquia.", + "attributes_description": "Utiliza relações entre notas ou adiciona etiquetas para uma categorização fácil. Usa atributos promovidos para inserir informação estruturada, que pode ser utilizada em tabelas ou quadros.", + "hoisting_description": "Separa facilmente as tuas notas pessoais e de trabalho agrupando-as num espaço de trabalho, que focaliza a árvore de notas para mostrar apenas um conjunto específico de notas." } } diff --git a/docs/README-pt.md b/docs/README-pt.md index ef24d1da1..f1f35ec64 100644 --- a/docs/README-pt.md +++ b/docs/README-pt.md @@ -54,7 +54,7 @@ A nossa documentação está disponível em múltiplos formatos: - **GitHub**: Navigate through the [User Guide](./docs/User%20Guide/User%20Guide/) in this repository -### Quick Links +### Links rápidos - [Getting Started Guide](https://docs.triliumnotes.org/) - [Installation Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md) diff --git a/packages/commons/src/lib/keyboard_actions_interface.ts b/packages/commons/src/lib/keyboard_actions_interface.ts index c3de7e0db..ce2defcd6 100644 --- a/packages/commons/src/lib/keyboard_actions_interface.ts +++ b/packages/commons/src/lib/keyboard_actions_interface.ts @@ -35,6 +35,7 @@ const enum KeyboardActionNamesEnum { activateNextTab, activatePreviousTab, openNewWindow, + openTodayNote, toggleTray, toggleZenMode, firstTab,