From 022c967781c2be3f39db5812be80da959b4d65be Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 21 Jan 2026 16:25:17 -0800 Subject: [PATCH 001/154] feat(etapi): add revisions route and "undelete" route to etapi --- apps/server/etapi.openapi.yaml | 214 ++++++++++++++++++ .../spec/etapi/get-note-revisions.spec.ts | 77 +++++++ apps/server/spec/etapi/get-revision.spec.ts | 71 ++++++ apps/server/spec/etapi/note-history.spec.ts | 94 ++++++++ .../spec/etapi/revision-content.spec.ts | 64 ++++++ apps/server/spec/etapi/undelete-note.spec.ts | 103 +++++++++ apps/server/src/etapi/etapi_utils.ts | 13 +- apps/server/src/etapi/mappers.ts | 22 +- apps/server/src/etapi/revisions.ts | 178 +++++++++++++++ apps/server/src/routes/routes.ts | 3 + 10 files changed, 837 insertions(+), 2 deletions(-) create mode 100644 apps/server/spec/etapi/get-note-revisions.spec.ts create mode 100644 apps/server/spec/etapi/get-revision.spec.ts create mode 100644 apps/server/spec/etapi/note-history.spec.ts create mode 100644 apps/server/spec/etapi/revision-content.spec.ts create mode 100644 apps/server/spec/etapi/undelete-note.spec.ts create mode 100644 apps/server/src/etapi/revisions.ts diff --git a/apps/server/etapi.openapi.yaml b/apps/server/etapi.openapi.yaml index f35d9ad926..af05bdbe57 100644 --- a/apps/server/etapi.openapi.yaml +++ b/apps/server/etapi.openapi.yaml @@ -337,6 +337,130 @@ paths: application/json; charset=utf-8: schema: $ref: "#/components/schemas/Error" + /notes/{noteId}/revisions: + parameters: + - name: noteId + in: path + required: true + schema: + $ref: "#/components/schemas/EntityId" + get: + description: Returns all revisions for a note identified by its ID + operationId: getNoteRevisions + responses: + "200": + description: list of revisions + content: + application/json; charset=utf-8: + schema: + type: array + items: + $ref: "#/components/schemas/Revision" + default: + description: unexpected error + content: + application/json; charset=utf-8: + schema: + $ref: "#/components/schemas/Error" + /notes/{noteId}/undelete: + parameters: + - name: noteId + in: path + required: true + schema: + $ref: "#/components/schemas/EntityId" + post: + description: Restore a deleted note. The note must be deleted and must have at least one undeleted parent. + operationId: undeleteNote + responses: + "200": + description: note restored successfully + content: + application/json; charset=utf-8: + schema: + type: object + properties: + success: + type: boolean + example: true + default: + description: unexpected error + content: + application/json; charset=utf-8: + schema: + $ref: "#/components/schemas/Error" + /notes/history: + get: + description: Returns recent changes including note creations, modifications, and deletions + operationId: getNoteHistory + parameters: + - name: ancestorNoteId + in: query + required: false + description: Limit changes to a subtree identified by this note ID. Defaults to "root" (all notes). + schema: + $ref: "#/components/schemas/EntityId" + responses: + "200": + description: list of recent changes + content: + application/json; charset=utf-8: + schema: + type: array + items: + $ref: "#/components/schemas/RecentChange" + default: + description: unexpected error + content: + application/json; charset=utf-8: + schema: + $ref: "#/components/schemas/Error" + /revisions/{revisionId}: + parameters: + - name: revisionId + in: path + required: true + schema: + $ref: "#/components/schemas/EntityId" + get: + description: Returns a revision identified by its ID + operationId: getRevisionById + responses: + "200": + description: revision response + content: + application/json; charset=utf-8: + schema: + $ref: "#/components/schemas/Revision" + default: + description: unexpected error + content: + application/json; charset=utf-8: + schema: + $ref: "#/components/schemas/Error" + /revisions/{revisionId}/content: + parameters: + - name: revisionId + in: path + required: true + schema: + $ref: "#/components/schemas/EntityId" + get: + description: Returns revision content identified by its ID + operationId: getRevisionContent + responses: + "200": + description: revision content response + content: + text/html: + schema: + type: string + default: + description: unexpected error + content: + application/json; charset=utf-8: + schema: + $ref: "#/components/schemas/Error" /branches: post: description: > @@ -1186,3 +1310,93 @@ components: type: string description: Human readable error, potentially with more details, example: Note 'evnnmvHTCgIn' is protected and cannot be modified through ETAPI + Revision: + type: object + description: Revision represents a snapshot of note's title and content at some point in the past. + properties: + revisionId: + $ref: "#/components/schemas/EntityId" + readOnly: true + noteId: + $ref: "#/components/schemas/EntityId" + readOnly: true + type: + type: string + enum: + [ + text, + code, + render, + file, + image, + search, + relationMap, + book, + noteMap, + mermaid, + webView, + shortcut, + doc, + contentWidget, + launcher, + ] + mime: + type: string + isProtected: + type: boolean + readOnly: true + title: + type: string + blobId: + type: string + description: ID of the blob object which effectively serves as a content hash + dateLastEdited: + $ref: "#/components/schemas/LocalDateTime" + readOnly: true + dateCreated: + $ref: "#/components/schemas/LocalDateTime" + readOnly: true + utcDateLastEdited: + $ref: "#/components/schemas/UtcDateTime" + readOnly: true + utcDateCreated: + $ref: "#/components/schemas/UtcDateTime" + readOnly: true + utcDateModified: + $ref: "#/components/schemas/UtcDateTime" + readOnly: true + contentLength: + type: integer + format: int32 + readOnly: true + RecentChange: + type: object + description: Represents a recent change event (creation, modification, or deletion). + properties: + noteId: + $ref: "#/components/schemas/EntityId" + readOnly: true + title: + type: string + description: Title at the time of the change (may be "[protected]" for protected notes) + current_title: + type: string + description: Current title of the note (may be "[protected]" for protected notes) + current_isDeleted: + type: boolean + description: Whether the note is currently deleted + current_deleteId: + type: string + description: Delete ID if the note is deleted + current_isProtected: + type: boolean + description: Whether the note is protected + utcDate: + $ref: "#/components/schemas/UtcDateTime" + description: UTC timestamp of the change + date: + $ref: "#/components/schemas/LocalDateTime" + description: Local timestamp of the change + canBeUndeleted: + type: boolean + description: Whether the note can be undeleted (only present for deleted notes) diff --git a/apps/server/spec/etapi/get-note-revisions.spec.ts b/apps/server/spec/etapi/get-note-revisions.spec.ts new file mode 100644 index 0000000000..acf2bccf54 --- /dev/null +++ b/apps/server/spec/etapi/get-note-revisions.spec.ts @@ -0,0 +1,77 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { createNote, login } from "./utils.js"; +import config from "../../src/services/config.js"; + +let app: Application; +let token: string; + +const USER = "etapi"; +let createdNoteId: string; + +describe("etapi/get-note-revisions", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + createdNoteId = await createNote(app, token); + + // Create a revision by updating the note content + await supertest(app) + .put(`/etapi/notes/${createdNoteId}/content`) + .auth(USER, token, { "type": "basic" }) + .set("Content-Type", "text/plain") + .send("Updated content for revision") + .expect(204); + + // Force create a revision + await supertest(app) + .post(`/etapi/notes/${createdNoteId}/revision`) + .auth(USER, token, { "type": "basic" }) + .expect(204); + }); + + it("gets revisions for a note", async () => { + const response = await supertest(app) + .get(`/etapi/notes/${createdNoteId}/revisions`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBeGreaterThan(0); + + const revision = response.body[0]; + expect(revision).toHaveProperty("revisionId"); + expect(revision).toHaveProperty("noteId", createdNoteId); + expect(revision).toHaveProperty("type"); + expect(revision).toHaveProperty("mime"); + expect(revision).toHaveProperty("title"); + expect(revision).toHaveProperty("isProtected"); + expect(revision).toHaveProperty("blobId"); + expect(revision).toHaveProperty("utcDateCreated"); + }); + + it("returns empty array for note with no revisions", async () => { + // Create a new note without any revisions + const newNoteId = await createNote(app, token, "Brand new content"); + + const response = await supertest(app) + .get(`/etapi/notes/${newNoteId}/revisions`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + // New notes may or may not have revisions depending on settings + }); + + it("returns 404 for non-existent note", async () => { + const response = await supertest(app) + .get("/etapi/notes/nonexistentnote/revisions") + .auth(USER, token, { "type": "basic" }) + .expect(404); + + expect(response.body.code).toStrictEqual("NOTE_NOT_FOUND"); + }); +}); diff --git a/apps/server/spec/etapi/get-revision.spec.ts b/apps/server/spec/etapi/get-revision.spec.ts new file mode 100644 index 0000000000..641f5255a7 --- /dev/null +++ b/apps/server/spec/etapi/get-revision.spec.ts @@ -0,0 +1,71 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { createNote, login } from "./utils.js"; +import config from "../../src/services/config.js"; + +let app: Application; +let token: string; + +const USER = "etapi"; +let createdNoteId: string; +let revisionId: string; + +describe("etapi/get-revision", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + createdNoteId = await createNote(app, token, "Initial content"); + + // Update content to create a revision + await supertest(app) + .put(`/etapi/notes/${createdNoteId}/content`) + .auth(USER, token, { "type": "basic" }) + .set("Content-Type", "text/plain") + .send("Updated content") + .expect(204); + + // Force create a revision + await supertest(app) + .post(`/etapi/notes/${createdNoteId}/revision`) + .auth(USER, token, { "type": "basic" }) + .expect(204); + + // Get the revision ID + const revisionsResponse = await supertest(app) + .get(`/etapi/notes/${createdNoteId}/revisions`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(revisionsResponse.body.length).toBeGreaterThan(0); + revisionId = revisionsResponse.body[0].revisionId; + }); + + it("gets revision metadata by ID", async () => { + const response = await supertest(app) + .get(`/etapi/revisions/${revisionId}`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(response.body).toHaveProperty("revisionId", revisionId); + expect(response.body).toHaveProperty("noteId", createdNoteId); + expect(response.body).toHaveProperty("type", "text"); + expect(response.body).toHaveProperty("mime", "text/html"); + expect(response.body).toHaveProperty("title", "Hello"); + expect(response.body).toHaveProperty("isProtected", false); + expect(response.body).toHaveProperty("blobId"); + expect(response.body).toHaveProperty("utcDateCreated"); + expect(response.body).toHaveProperty("utcDateModified"); + }); + + it("returns 404 for non-existent revision", async () => { + const response = await supertest(app) + .get("/etapi/revisions/nonexistentrevision") + .auth(USER, token, { "type": "basic" }) + .expect(404); + + expect(response.body.code).toStrictEqual("REVISION_NOT_FOUND"); + }); +}); diff --git a/apps/server/spec/etapi/note-history.spec.ts b/apps/server/spec/etapi/note-history.spec.ts new file mode 100644 index 0000000000..7696c00516 --- /dev/null +++ b/apps/server/spec/etapi/note-history.spec.ts @@ -0,0 +1,94 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { createNote, login } from "./utils.js"; +import config from "../../src/services/config.js"; + +let app: Application; +let token: string; + +const USER = "etapi"; +let createdNoteId: string; + +describe("etapi/note-history", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + + // Create a note to ensure there's some history + createdNoteId = await createNote(app, token, "History test content"); + + // Create a revision to ensure history has entries + await supertest(app) + .post(`/etapi/notes/${createdNoteId}/revision`) + .auth(USER, token, { "type": "basic" }) + .expect(204); + }); + + it("gets recent changes history", async () => { + const response = await supertest(app) + .get("/etapi/notes/history") + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBeGreaterThan(0); + + // Check that history entries have expected properties + const entry = response.body[0]; + expect(entry).toHaveProperty("noteId"); + expect(entry).toHaveProperty("title"); + expect(entry).toHaveProperty("utcDate"); + expect(entry).toHaveProperty("date"); + expect(entry).toHaveProperty("current_isDeleted"); + expect(entry).toHaveProperty("current_isProtected"); + }); + + it("filters history by ancestor note", async () => { + const response = await supertest(app) + .get("/etapi/notes/history?ancestorNoteId=root") + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + // All results should be descendants of root (which is everything) + }); + + it("returns empty array for non-existent ancestor", async () => { + const response = await supertest(app) + .get("/etapi/notes/history?ancestorNoteId=nonexistentancestor") + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + // Should be empty since no notes are descendants of a non-existent note + expect(response.body.length).toBe(0); + }); + + it("includes canBeUndeleted for deleted notes", async () => { + // Create and delete a note + const noteToDeleteId = await createNote(app, token, "Note to delete for history test"); + + await supertest(app) + .delete(`/etapi/notes/${noteToDeleteId}`) + .auth(USER, token, { "type": "basic" }) + .expect(204); + + // Check history - deleted note should appear with canBeUndeleted property + const response = await supertest(app) + .get("/etapi/notes/history") + .auth(USER, token, { "type": "basic" }) + .expect(200); + + const deletedEntry = response.body.find( + (entry: any) => entry.noteId === noteToDeleteId && entry.current_isDeleted === true + ); + + // Deleted entries should have canBeUndeleted property + if (deletedEntry) { + expect(deletedEntry).toHaveProperty("canBeUndeleted"); + } + }); +}); diff --git a/apps/server/spec/etapi/revision-content.spec.ts b/apps/server/spec/etapi/revision-content.spec.ts new file mode 100644 index 0000000000..5d7d5e558a --- /dev/null +++ b/apps/server/spec/etapi/revision-content.spec.ts @@ -0,0 +1,64 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { createNote, login } from "./utils.js"; +import config from "../../src/services/config.js"; + +let app: Application; +let token: string; + +const USER = "etapi"; +let createdNoteId: string; +let revisionId: string; + +describe("etapi/revision-content", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + createdNoteId = await createNote(app, token, "Initial revision content"); + + // Update content to ensure we have content in the revision + await supertest(app) + .put(`/etapi/notes/${createdNoteId}/content`) + .auth(USER, token, { "type": "basic" }) + .set("Content-Type", "text/plain") + .send("Content after first update") + .expect(204); + + // Force create a revision + await supertest(app) + .post(`/etapi/notes/${createdNoteId}/revision`) + .auth(USER, token, { "type": "basic" }) + .expect(204); + + // Get the revision ID + const revisionsResponse = await supertest(app) + .get(`/etapi/notes/${createdNoteId}/revisions`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(revisionsResponse.body.length).toBeGreaterThan(0); + revisionId = revisionsResponse.body[0].revisionId; + }); + + it("gets revision content", async () => { + const response = await supertest(app) + .get(`/etapi/revisions/${revisionId}/content`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(response.headers["content-type"]).toMatch(/text\/html/); + expect(response.text).toBeTruthy(); + }); + + it("returns 404 for non-existent revision content", async () => { + const response = await supertest(app) + .get("/etapi/revisions/nonexistentrevision/content") + .auth(USER, token, { "type": "basic" }) + .expect(404); + + expect(response.body.code).toStrictEqual("REVISION_NOT_FOUND"); + }); +}); diff --git a/apps/server/spec/etapi/undelete-note.spec.ts b/apps/server/spec/etapi/undelete-note.spec.ts new file mode 100644 index 0000000000..236539f4a1 --- /dev/null +++ b/apps/server/spec/etapi/undelete-note.spec.ts @@ -0,0 +1,103 @@ +import { Application } from "express"; +import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { login } from "./utils.js"; +import config from "../../src/services/config.js"; +import { randomInt } from "crypto"; + +let app: Application; +let token: string; + +const USER = "etapi"; + +describe("etapi/undelete-note", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + }); + + it("undeletes a deleted note", async () => { + // Create a note + const noteId = `testNote${randomInt(10000)}`; + await supertest(app) + .post("/etapi/create-note") + .auth(USER, token, { "type": "basic" }) + .send({ + "noteId": noteId, + "parentNoteId": "root", + "title": "Note to delete and restore", + "type": "text", + "content": "Content to restore" + }) + .expect(201); + + // Verify note exists + await supertest(app) + .get(`/etapi/notes/${noteId}`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + // Delete the note + await supertest(app) + .delete(`/etapi/notes/${noteId}`) + .auth(USER, token, { "type": "basic" }) + .expect(204); + + // Verify note is deleted (should return 404) + await supertest(app) + .get(`/etapi/notes/${noteId}`) + .auth(USER, token, { "type": "basic" }) + .expect(404); + + // Undelete the note + const response = await supertest(app) + .post(`/etapi/notes/${noteId}/undelete`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(response.body).toHaveProperty("success", true); + + // Verify note is restored + const restoredResponse = await supertest(app) + .get(`/etapi/notes/${noteId}`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(restoredResponse.body.title).toStrictEqual("Note to delete and restore"); + }); + + it("returns 404 for non-existent note", async () => { + const response = await supertest(app) + .post("/etapi/notes/nonexistentnote/undelete") + .auth(USER, token, { "type": "basic" }) + .expect(404); + + expect(response.body.code).toStrictEqual("NOTE_NOT_FOUND"); + }); + + it("returns 400 when trying to undelete a non-deleted note", async () => { + // Create a note + const noteId = `testNote${randomInt(10000)}`; + await supertest(app) + .post("/etapi/create-note") + .auth(USER, token, { "type": "basic" }) + .send({ + "noteId": noteId, + "parentNoteId": "root", + "title": "Note not deleted", + "type": "text", + "content": "Content" + }) + .expect(201); + + // Try to undelete a note that isn't deleted + const response = await supertest(app) + .post(`/etapi/notes/${noteId}/undelete`) + .auth(USER, token, { "type": "basic" }) + .expect(400); + + expect(response.body.code).toStrictEqual("NOTE_NOT_DELETED"); + }); +}); diff --git a/apps/server/src/etapi/etapi_utils.ts b/apps/server/src/etapi/etapi_utils.ts index 1319162575..9bafdf731a 100644 --- a/apps/server/src/etapi/etapi_utils.ts +++ b/apps/server/src/etapi/etapi_utils.ts @@ -121,6 +121,16 @@ function getAndCheckAttribute(attributeId: string) { } } +function getAndCheckRevision(revisionId: string) { + const revision = becca.getRevision(revisionId); + + if (revision) { + return revision; + } else { + throw new EtapiError(404, "REVISION_NOT_FOUND", `Revision '${revisionId}' not found.`); + } +} + function validateAndPatch(target: any, source: any, allowedProperties: ValidatorMap) { for (const key of Object.keys(source)) { if (!(key in allowedProperties)) { @@ -152,5 +162,6 @@ export default { getAndCheckNote, getAndCheckBranch, getAndCheckAttribute, - getAndCheckAttachment + getAndCheckAttachment, + getAndCheckRevision }; diff --git a/apps/server/src/etapi/mappers.ts b/apps/server/src/etapi/mappers.ts index 735e767c29..4748122390 100644 --- a/apps/server/src/etapi/mappers.ts +++ b/apps/server/src/etapi/mappers.ts @@ -2,6 +2,7 @@ import type BAttachment from "../becca/entities/battachment.js"; import type BAttribute from "../becca/entities/battribute.js"; import type BBranch from "../becca/entities/bbranch.js"; import type BNote from "../becca/entities/bnote.js"; +import type BRevision from "../becca/entities/brevision.js"; function mapNoteToPojo(note: BNote) { return { @@ -64,9 +65,28 @@ function mapAttachmentToPojo(attachment: BAttachment) { }; } +function mapRevisionToPojo(revision: BRevision) { + return { + revisionId: revision.revisionId, + noteId: revision.noteId, + type: revision.type, + mime: revision.mime, + isProtected: revision.isProtected, + title: revision.title, + blobId: revision.blobId, + dateLastEdited: revision.dateLastEdited, + dateCreated: revision.dateCreated, + utcDateLastEdited: revision.utcDateLastEdited, + utcDateCreated: revision.utcDateCreated, + utcDateModified: revision.utcDateModified, + contentLength: revision.contentLength + }; +} + export default { mapNoteToPojo, mapBranchToPojo, mapAttributeToPojo, - mapAttachmentToPojo + mapAttachmentToPojo, + mapRevisionToPojo }; diff --git a/apps/server/src/etapi/revisions.ts b/apps/server/src/etapi/revisions.ts new file mode 100644 index 0000000000..713af44f9f --- /dev/null +++ b/apps/server/src/etapi/revisions.ts @@ -0,0 +1,178 @@ +import becca from "../becca/becca.js"; +import sql from "../services/sql.js"; +import eu from "./etapi_utils.js"; +import mappers from "./mappers.js"; +import noteService from "../services/notes.js"; +import TaskContext from "../services/task_context.js"; +import protectedSessionService from "../services/protected_session.js"; +import utils from "../services/utils.js"; +import type { Router } from "express"; +import type { NoteRow, RecentChangeRow } from "@triliumnext/commons"; + +function register(router: Router) { + // GET /etapi/notes/history - must be registered before /etapi/notes/:noteId routes + eu.route(router, "get", "/etapi/notes/history", (req, res, next) => { + const ancestorNoteId = (req.query.ancestorNoteId as string) || "root"; + + let recentChanges: RecentChangeRow[] = []; + + const revisionRows = sql.getRows(` + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + revisions.title, + revisions.utcDateCreated AS utcDate, + revisions.dateCreated AS date + FROM + revisions + JOIN notes USING(noteId)`); + + for (const revisionRow of revisionRows) { + const note = becca.getNote(revisionRow.noteId); + + // for deleted notes, the becca note is null, and it's not possible to (easily) determine if it belongs to a subtree + if (ancestorNoteId === "root" || note?.hasAncestor(ancestorNoteId)) { + recentChanges.push(revisionRow); + } + } + + // now we need to also collect date points not represented in note revisions: + // 1. creation for all notes (dateCreated) + // 2. deletion for deleted notes (dateModified) + const noteRows = sql.getRows(` + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + notes.title, + notes.utcDateCreated AS utcDate, + notes.dateCreated AS date + FROM notes + UNION ALL + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + notes.title, + notes.utcDateModified AS utcDate, + notes.dateModified AS date + FROM notes + WHERE notes.isDeleted = 1`); + + for (const noteRow of noteRows) { + const note = becca.getNote(noteRow.noteId); + + // for deleted notes, the becca note is null, and it's not possible to (easily) determine if it belongs to a subtree + if (ancestorNoteId === "root" || note?.hasAncestor(ancestorNoteId)) { + recentChanges.push(noteRow); + } + } + + recentChanges.sort((a, b) => (a.utcDate > b.utcDate ? -1 : 1)); + + recentChanges = recentChanges.slice(0, Math.min(500, recentChanges.length)); + + for (const change of recentChanges) { + if (change.current_isProtected) { + if (protectedSessionService.isProtectedSessionAvailable()) { + change.title = protectedSessionService.decryptString(change.title) || "[protected]"; + change.current_title = protectedSessionService.decryptString(change.current_title) || "[protected]"; + } else { + change.title = change.current_title = "[protected]"; + } + } + + if (change.current_isDeleted) { + const deleteId = change.current_deleteId; + + const undeletedParentBranchIds = noteService.getUndeletedParentBranchIds(change.noteId, deleteId); + + // note (and the subtree) can be undeleted if there's at least one undeleted parent (whose branch would be undeleted by this op) + change.canBeUndeleted = undeletedParentBranchIds.length > 0; + } + } + + res.json(recentChanges); + }); + + // GET /etapi/notes/:noteId/revisions - List all revisions for a note + eu.route(router, "get", "/etapi/notes/:noteId/revisions", (req, res, next) => { + const note = eu.getAndCheckNote(req.params.noteId); + + const revisions = becca.getRevisionsFromQuery( + `SELECT revisions.*, LENGTH(blobs.content) AS contentLength + FROM revisions + JOIN blobs USING (blobId) + WHERE noteId = ? + ORDER BY utcDateCreated DESC`, + [note.noteId] + ); + + res.json(revisions.map((revision) => mappers.mapRevisionToPojo(revision))); + }); + + // POST /etapi/notes/:noteId/undelete - Restore a deleted note + eu.route(router, "post", "/etapi/notes/:noteId/undelete", (req, res, next) => { + const { noteId } = req.params; + + const noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); + + if (!noteRow) { + throw new eu.EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found.`); + } + + if (!noteRow.isDeleted || !noteRow.deleteId) { + throw new eu.EtapiError(400, "NOTE_NOT_DELETED", `Note '${noteId}' is not deleted.`); + } + + const undeletedParentBranchIds = noteService.getUndeletedParentBranchIds(noteId, noteRow.deleteId); + + if (undeletedParentBranchIds.length === 0) { + throw new eu.EtapiError(400, "CANNOT_UNDELETE", `Cannot undelete note '${noteId}' - no undeleted parent found.`); + } + + const taskContext = new TaskContext("no-progress-reporting", "undeleteNotes", null); + noteService.undeleteNote(noteId, taskContext); + + res.json({ success: true }); + }); + + // GET /etapi/revisions/:revisionId - Get revision metadata + eu.route(router, "get", "/etapi/revisions/:revisionId", (req, res, next) => { + const revision = eu.getAndCheckRevision(req.params.revisionId); + + if (revision.isProtected) { + throw new eu.EtapiError(400, "REVISION_IS_PROTECTED", `Revision '${req.params.revisionId}' is protected and cannot be read through ETAPI.`); + } + + res.json(mappers.mapRevisionToPojo(revision)); + }); + + // GET /etapi/revisions/:revisionId/content - Get revision content + eu.route(router, "get", "/etapi/revisions/:revisionId/content", (req, res, next) => { + const revision = eu.getAndCheckRevision(req.params.revisionId); + + if (revision.isProtected) { + throw new eu.EtapiError(400, "REVISION_IS_PROTECTED", `Revision '${req.params.revisionId}' is protected and content cannot be read through ETAPI.`); + } + + const filename = utils.formatDownloadTitle(revision.title, revision.type, revision.mime); + + res.setHeader("Content-Disposition", utils.getContentDisposition(filename)); + res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + res.setHeader("Content-Type", revision.mime); + + res.send(revision.getContent()); + }); +} + +export default { + register +}; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index 9e31d1bcac..96c3ebd84d 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -12,6 +12,7 @@ import etapiMetricsRoute from "../etapi/metrics.js"; import etapiNoteRoutes from "../etapi/notes.js"; import etapiSpecRoute from "../etapi/spec.js"; import etapiSpecialNoteRoutes from "../etapi/special_notes.js"; +import etapiRevisionsRoutes from "../etapi/revisions.js"; import auth from "../services/auth.js"; import openID from '../services/open_id.js'; import { isElectron } from "../services/utils.js"; @@ -361,6 +362,8 @@ function register(app: express.Application) { etapiAttachmentRoutes.register(router); etapiAttributeRoutes.register(router); etapiBranchRoutes.register(router); + // Register revisions routes BEFORE notes routes so /etapi/notes/history is matched before /etapi/notes/:noteId + etapiRevisionsRoutes.register(router); etapiNoteRoutes.register(router); etapiSpecialNoteRoutes.register(router); etapiSpecRoute.register(router); From 60c61f553a451414a47bb50798d98bbc6bdcab22 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 21 Jan 2026 16:30:37 -0800 Subject: [PATCH 002/154] feat(etapi): put filtering for revisions mainly in the db layer --- apps/server/src/etapi/revisions.ts | 111 ++++++++++++++++++----------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/apps/server/src/etapi/revisions.ts b/apps/server/src/etapi/revisions.ts index 713af44f9f..6451b18a15 100644 --- a/apps/server/src/etapi/revisions.ts +++ b/apps/server/src/etapi/revisions.ts @@ -14,35 +14,23 @@ function register(router: Router) { eu.route(router, "get", "/etapi/notes/history", (req, res, next) => { const ancestorNoteId = (req.query.ancestorNoteId as string) || "root"; - let recentChanges: RecentChangeRow[] = []; + let recentChanges: RecentChangeRow[]; - const revisionRows = sql.getRows(` - SELECT - notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, - revisions.title, - revisions.utcDateCreated AS utcDate, - revisions.dateCreated AS date - FROM - revisions - JOIN notes USING(noteId)`); - - for (const revisionRow of revisionRows) { - const note = becca.getNote(revisionRow.noteId); - - // for deleted notes, the becca note is null, and it's not possible to (easily) determine if it belongs to a subtree - if (ancestorNoteId === "root" || note?.hasAncestor(ancestorNoteId)) { - recentChanges.push(revisionRow); - } - } - - // now we need to also collect date points not represented in note revisions: - // 1. creation for all notes (dateCreated) - // 2. deletion for deleted notes (dateModified) - const noteRows = sql.getRows(` + if (ancestorNoteId === "root") { + // Optimized path: no ancestor filtering needed, fetch directly from DB + recentChanges = sql.getRows(` + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + revisions.title, + revisions.utcDateCreated AS utcDate, + revisions.dateCreated AS date + FROM revisions + JOIN notes USING(noteId) + UNION ALL SELECT notes.noteId, notes.isDeleted AS current_isDeleted, @@ -53,7 +41,7 @@ function register(router: Router) { notes.utcDateCreated AS utcDate, notes.dateCreated AS date FROM notes - UNION ALL + UNION ALL SELECT notes.noteId, notes.isDeleted AS current_isDeleted, @@ -64,21 +52,60 @@ function register(router: Router) { notes.utcDateModified AS utcDate, notes.dateModified AS date FROM notes - WHERE notes.isDeleted = 1`); - - for (const noteRow of noteRows) { - const note = becca.getNote(noteRow.noteId); - - // for deleted notes, the becca note is null, and it's not possible to (easily) determine if it belongs to a subtree - if (ancestorNoteId === "root" || note?.hasAncestor(ancestorNoteId)) { - recentChanges.push(noteRow); - } + WHERE notes.isDeleted = 1 + ORDER BY utcDate DESC + LIMIT 500`); + } else { + // Use recursive CTE to find all descendants, then filter at DB level + // This pushes filtering to the database for much better performance + recentChanges = sql.getRows(` + WITH RECURSIVE descendants(noteId) AS ( + SELECT ? + UNION + SELECT branches.noteId + FROM branches + JOIN descendants ON branches.parentNoteId = descendants.noteId + ) + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + revisions.title, + revisions.utcDateCreated AS utcDate, + revisions.dateCreated AS date + FROM revisions + JOIN notes USING(noteId) + WHERE notes.noteId IN (SELECT noteId FROM descendants) + UNION ALL + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + notes.title, + notes.utcDateCreated AS utcDate, + notes.dateCreated AS date + FROM notes + WHERE notes.noteId IN (SELECT noteId FROM descendants) + UNION ALL + SELECT + notes.noteId, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, + notes.title, + notes.utcDateModified AS utcDate, + notes.dateModified AS date + FROM notes + WHERE notes.isDeleted = 1 AND notes.noteId IN (SELECT noteId FROM descendants) + ORDER BY utcDate DESC + LIMIT 500`, [ancestorNoteId]); } - recentChanges.sort((a, b) => (a.utcDate > b.utcDate ? -1 : 1)); - - recentChanges = recentChanges.slice(0, Math.min(500, recentChanges.length)); - for (const change of recentChanges) { if (change.current_isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { From 0650be664d99858a55f476d92bf08dc99e2bad35 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 21 Jan 2026 16:33:42 -0800 Subject: [PATCH 003/154] feat(etapi): resolve suggestions for norms from gemini --- apps/server/etapi.openapi.yaml | 15 +++--- apps/server/spec/etapi/note-history.spec.ts | 6 +-- apps/server/src/etapi/revisions.ts | 58 ++++++++++----------- packages/commons/src/lib/server_api.ts | 8 +-- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/apps/server/etapi.openapi.yaml b/apps/server/etapi.openapi.yaml index af05bdbe57..2e6f941eb2 100644 --- a/apps/server/etapi.openapi.yaml +++ b/apps/server/etapi.openapi.yaml @@ -446,15 +446,16 @@ paths: schema: $ref: "#/components/schemas/EntityId" get: - description: Returns revision content identified by its ID + description: Returns revision content identified by its ID. The Content-Type header will match the revision's MIME type. operationId: getRevisionContent responses: "200": - description: revision content response + description: Revision content. The Content-Type header will be set to the revision's actual MIME type (e.g., text/html, text/plain, image/png). content: - text/html: + application/octet-stream: schema: type: string + format: binary default: description: unexpected error content: @@ -1379,16 +1380,16 @@ components: title: type: string description: Title at the time of the change (may be "[protected]" for protected notes) - current_title: + currentTitle: type: string description: Current title of the note (may be "[protected]" for protected notes) - current_isDeleted: + currentIsDeleted: type: boolean description: Whether the note is currently deleted - current_deleteId: + currentDeleteId: type: string description: Delete ID if the note is deleted - current_isProtected: + currentIsProtected: type: boolean description: Whether the note is protected utcDate: diff --git a/apps/server/spec/etapi/note-history.spec.ts b/apps/server/spec/etapi/note-history.spec.ts index 7696c00516..c78e8d359c 100644 --- a/apps/server/spec/etapi/note-history.spec.ts +++ b/apps/server/spec/etapi/note-history.spec.ts @@ -42,8 +42,8 @@ describe("etapi/note-history", () => { expect(entry).toHaveProperty("title"); expect(entry).toHaveProperty("utcDate"); expect(entry).toHaveProperty("date"); - expect(entry).toHaveProperty("current_isDeleted"); - expect(entry).toHaveProperty("current_isProtected"); + expect(entry).toHaveProperty("currentIsDeleted"); + expect(entry).toHaveProperty("currentIsProtected"); }); it("filters history by ancestor note", async () => { @@ -83,7 +83,7 @@ describe("etapi/note-history", () => { .expect(200); const deletedEntry = response.body.find( - (entry: any) => entry.noteId === noteToDeleteId && entry.current_isDeleted === true + (entry: any) => entry.noteId === noteToDeleteId && entry.currentIsDeleted === true ); // Deleted entries should have canBeUndeleted property diff --git a/apps/server/src/etapi/revisions.ts b/apps/server/src/etapi/revisions.ts index 6451b18a15..e76877ddc5 100644 --- a/apps/server/src/etapi/revisions.ts +++ b/apps/server/src/etapi/revisions.ts @@ -21,10 +21,10 @@ function register(router: Router) { recentChanges = sql.getRows(` SELECT notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, + notes.isDeleted AS currentIsDeleted, + notes.deleteId AS currentDeleteId, + notes.title AS currentTitle, + notes.isProtected AS currentIsProtected, revisions.title, revisions.utcDateCreated AS utcDate, revisions.dateCreated AS date @@ -33,10 +33,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, + notes.isDeleted AS currentIsDeleted, + notes.deleteId AS currentDeleteId, + notes.title AS currentTitle, + notes.isProtected AS currentIsProtected, notes.title, notes.utcDateCreated AS utcDate, notes.dateCreated AS date @@ -44,10 +44,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, + notes.isDeleted AS currentIsDeleted, + notes.deleteId AS currentDeleteId, + notes.title AS currentTitle, + notes.isProtected AS currentIsProtected, notes.title, notes.utcDateModified AS utcDate, notes.dateModified AS date @@ -68,10 +68,10 @@ function register(router: Router) { ) SELECT notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, + notes.isDeleted AS currentIsDeleted, + notes.deleteId AS currentDeleteId, + notes.title AS currentTitle, + notes.isProtected AS currentIsProtected, revisions.title, revisions.utcDateCreated AS utcDate, revisions.dateCreated AS date @@ -81,10 +81,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, + notes.isDeleted AS currentIsDeleted, + notes.deleteId AS currentDeleteId, + notes.title AS currentTitle, + notes.isProtected AS currentIsProtected, notes.title, notes.utcDateCreated AS utcDate, notes.dateCreated AS date @@ -93,10 +93,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS current_isDeleted, - notes.deleteId AS current_deleteId, - notes.title AS current_title, - notes.isProtected AS current_isProtected, + notes.isDeleted AS currentIsDeleted, + notes.deleteId AS currentDeleteId, + notes.title AS currentTitle, + notes.isProtected AS currentIsProtected, notes.title, notes.utcDateModified AS utcDate, notes.dateModified AS date @@ -107,17 +107,17 @@ function register(router: Router) { } for (const change of recentChanges) { - if (change.current_isProtected) { + if (change.currentIsProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { change.title = protectedSessionService.decryptString(change.title) || "[protected]"; - change.current_title = protectedSessionService.decryptString(change.current_title) || "[protected]"; + change.currentTitle = protectedSessionService.decryptString(change.currentTitle) || "[protected]"; } else { - change.title = change.current_title = "[protected]"; + change.title = change.currentTitle = "[protected]"; } } - if (change.current_isDeleted) { - const deleteId = change.current_deleteId; + if (change.currentIsDeleted) { + const deleteId = change.currentDeleteId; const undeletedParentBranchIds = noteService.getUndeletedParentBranchIds(change.noteId, deleteId); diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index a15192fd28..7c197a7614 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -56,10 +56,10 @@ export interface RevisionPojo { export interface RecentChangeRow { noteId: string; - current_isDeleted: boolean; - current_deleteId: string; - current_title: string; - current_isProtected: boolean; + currentIsDeleted: boolean; + currentDeleteId: string; + currentTitle: string; + currentIsProtected: boolean; title: string; utcDate: string; date: string; From 280697f2f7ef363d6ca85a7bbbbfe2ff4cc83479 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 21 Jan 2026 16:37:02 -0800 Subject: [PATCH 004/154] Revert "feat(etapi): resolve suggestions for norms from gemini" This reverts commit 0650be664d99858a55f476d92bf08dc99e2bad35. --- apps/server/etapi.openapi.yaml | 15 +++--- apps/server/spec/etapi/note-history.spec.ts | 6 +-- apps/server/src/etapi/revisions.ts | 58 ++++++++++----------- packages/commons/src/lib/server_api.ts | 8 +-- 4 files changed, 43 insertions(+), 44 deletions(-) diff --git a/apps/server/etapi.openapi.yaml b/apps/server/etapi.openapi.yaml index 2e6f941eb2..af05bdbe57 100644 --- a/apps/server/etapi.openapi.yaml +++ b/apps/server/etapi.openapi.yaml @@ -446,16 +446,15 @@ paths: schema: $ref: "#/components/schemas/EntityId" get: - description: Returns revision content identified by its ID. The Content-Type header will match the revision's MIME type. + description: Returns revision content identified by its ID operationId: getRevisionContent responses: "200": - description: Revision content. The Content-Type header will be set to the revision's actual MIME type (e.g., text/html, text/plain, image/png). + description: revision content response content: - application/octet-stream: + text/html: schema: type: string - format: binary default: description: unexpected error content: @@ -1380,16 +1379,16 @@ components: title: type: string description: Title at the time of the change (may be "[protected]" for protected notes) - currentTitle: + current_title: type: string description: Current title of the note (may be "[protected]" for protected notes) - currentIsDeleted: + current_isDeleted: type: boolean description: Whether the note is currently deleted - currentDeleteId: + current_deleteId: type: string description: Delete ID if the note is deleted - currentIsProtected: + current_isProtected: type: boolean description: Whether the note is protected utcDate: diff --git a/apps/server/spec/etapi/note-history.spec.ts b/apps/server/spec/etapi/note-history.spec.ts index c78e8d359c..7696c00516 100644 --- a/apps/server/spec/etapi/note-history.spec.ts +++ b/apps/server/spec/etapi/note-history.spec.ts @@ -42,8 +42,8 @@ describe("etapi/note-history", () => { expect(entry).toHaveProperty("title"); expect(entry).toHaveProperty("utcDate"); expect(entry).toHaveProperty("date"); - expect(entry).toHaveProperty("currentIsDeleted"); - expect(entry).toHaveProperty("currentIsProtected"); + expect(entry).toHaveProperty("current_isDeleted"); + expect(entry).toHaveProperty("current_isProtected"); }); it("filters history by ancestor note", async () => { @@ -83,7 +83,7 @@ describe("etapi/note-history", () => { .expect(200); const deletedEntry = response.body.find( - (entry: any) => entry.noteId === noteToDeleteId && entry.currentIsDeleted === true + (entry: any) => entry.noteId === noteToDeleteId && entry.current_isDeleted === true ); // Deleted entries should have canBeUndeleted property diff --git a/apps/server/src/etapi/revisions.ts b/apps/server/src/etapi/revisions.ts index e76877ddc5..6451b18a15 100644 --- a/apps/server/src/etapi/revisions.ts +++ b/apps/server/src/etapi/revisions.ts @@ -21,10 +21,10 @@ function register(router: Router) { recentChanges = sql.getRows(` SELECT notes.noteId, - notes.isDeleted AS currentIsDeleted, - notes.deleteId AS currentDeleteId, - notes.title AS currentTitle, - notes.isProtected AS currentIsProtected, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, revisions.title, revisions.utcDateCreated AS utcDate, revisions.dateCreated AS date @@ -33,10 +33,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS currentIsDeleted, - notes.deleteId AS currentDeleteId, - notes.title AS currentTitle, - notes.isProtected AS currentIsProtected, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, notes.title, notes.utcDateCreated AS utcDate, notes.dateCreated AS date @@ -44,10 +44,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS currentIsDeleted, - notes.deleteId AS currentDeleteId, - notes.title AS currentTitle, - notes.isProtected AS currentIsProtected, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, notes.title, notes.utcDateModified AS utcDate, notes.dateModified AS date @@ -68,10 +68,10 @@ function register(router: Router) { ) SELECT notes.noteId, - notes.isDeleted AS currentIsDeleted, - notes.deleteId AS currentDeleteId, - notes.title AS currentTitle, - notes.isProtected AS currentIsProtected, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, revisions.title, revisions.utcDateCreated AS utcDate, revisions.dateCreated AS date @@ -81,10 +81,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS currentIsDeleted, - notes.deleteId AS currentDeleteId, - notes.title AS currentTitle, - notes.isProtected AS currentIsProtected, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, notes.title, notes.utcDateCreated AS utcDate, notes.dateCreated AS date @@ -93,10 +93,10 @@ function register(router: Router) { UNION ALL SELECT notes.noteId, - notes.isDeleted AS currentIsDeleted, - notes.deleteId AS currentDeleteId, - notes.title AS currentTitle, - notes.isProtected AS currentIsProtected, + notes.isDeleted AS current_isDeleted, + notes.deleteId AS current_deleteId, + notes.title AS current_title, + notes.isProtected AS current_isProtected, notes.title, notes.utcDateModified AS utcDate, notes.dateModified AS date @@ -107,17 +107,17 @@ function register(router: Router) { } for (const change of recentChanges) { - if (change.currentIsProtected) { + if (change.current_isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { change.title = protectedSessionService.decryptString(change.title) || "[protected]"; - change.currentTitle = protectedSessionService.decryptString(change.currentTitle) || "[protected]"; + change.current_title = protectedSessionService.decryptString(change.current_title) || "[protected]"; } else { - change.title = change.currentTitle = "[protected]"; + change.title = change.current_title = "[protected]"; } } - if (change.currentIsDeleted) { - const deleteId = change.currentDeleteId; + if (change.current_isDeleted) { + const deleteId = change.current_deleteId; const undeletedParentBranchIds = noteService.getUndeletedParentBranchIds(change.noteId, deleteId); diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index 7c197a7614..a15192fd28 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -56,10 +56,10 @@ export interface RevisionPojo { export interface RecentChangeRow { noteId: string; - currentIsDeleted: boolean; - currentDeleteId: string; - currentTitle: string; - currentIsProtected: boolean; + current_isDeleted: boolean; + current_deleteId: string; + current_title: string; + current_isProtected: boolean; title: string; utcDate: string; date: string; From a45fb975c09e6b71c413b5810d0004dd75d46571 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 15:53:19 +0200 Subject: [PATCH 005/154] chore(mobile/tab_switcher): add button in launch bar --- apps/client/src/layouts/mobile_layout.tsx | 2 ++ .../src/widgets/mobile_widgets/TabSwitcher.tsx | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index da66ffa130..5c7270bfd0 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -13,6 +13,7 @@ import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js"; import ScreenContainer from "../widgets/mobile_widgets/screen_container.js"; import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js"; +import TabSwitcher from "../widgets/mobile_widgets/TabSwitcher.jsx"; import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx"; import NoteTitleWidget from "../widgets/note_title.js"; import NoteTreeWidget from "../widgets/note_tree.js"; @@ -184,6 +185,7 @@ export default class MobileLayout { .class("horizontal") .css("height", "53px") .child() + .child() .child() .id("launcher-pane")) ) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx new file mode 100644 index 0000000000..50d74c5ad5 --- /dev/null +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -0,0 +1,15 @@ +import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; + +export default function TabSwitcher() { + return ( + <> + { + + }} + /> + + ); +} From 3a9b448a833ed958a14c0789e3798fb0bb9fe90b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 15:59:38 +0200 Subject: [PATCH 006/154] chore(mobile/tab_switcher): create empty modal --- .../widgets/mobile_widgets/TabSwitcher.css | 6 ++++ .../widgets/mobile_widgets/TabSwitcher.tsx | 31 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 apps/client/src/widgets/mobile_widgets/TabSwitcher.css diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css new file mode 100644 index 0000000000..d9e49f89e0 --- /dev/null +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -0,0 +1,6 @@ +.modal.tab-bar-modal { + .modal-dialog { + max-height: unset; + height: 95vh; + } +} diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 50d74c5ad5..735dbfc98d 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -1,15 +1,40 @@ +import "./TabSwitcher.css"; + +import { createPortal } from "preact/compat"; +import { useState } from "preact/hooks"; + import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; +import Modal from "../react/Modal"; export default function TabSwitcher() { + const [ shown, setShown ] = useState(false); + return ( <> { - - }} + onClick={() => setShown(true)} /> + {createPortal(, document.body)} ); } + + +function TabBarModal({ shown, setShown }: { + shown: boolean; + setShown: (newValue: boolean) => void; +}) { + return ( + setShown(false)} + > + Hi + + ); +} From 49fc5e1559051891ca21d7c7bd62421e270967d5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 16:12:27 +0200 Subject: [PATCH 007/154] feat(mobile/tab_switcher): basic listing of tabs --- .../widgets/mobile_widgets/TabSwitcher.css | 6 +++ .../widgets/mobile_widgets/TabSwitcher.tsx | 39 +++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index d9e49f89e0..16121f8b02 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -3,4 +3,10 @@ max-height: unset; height: 95vh; } + + .tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1em; + } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 735dbfc98d..89a40eda2a 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -1,13 +1,15 @@ import "./TabSwitcher.css"; import { createPortal } from "preact/compat"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; +import appContext from "../../components/app_context"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; +import { useTriliumEvent } from "../react/hooks"; import Modal from "../react/Modal"; export default function TabSwitcher() { - const [ shown, setShown ] = useState(false); + const [ shown, setShown ] = useState(true); return ( <> @@ -21,7 +23,6 @@ export default function TabSwitcher() { ); } - function TabBarModal({ shown, setShown }: { shown: boolean; setShown: (newValue: boolean) => void; @@ -34,7 +35,37 @@ function TabBarModal({ shown, setShown }: { show={shown} onHidden={() => setShown(false)} > - Hi + ); } + +function TabBarModelContent() { + const mainNoteContexts = useMainNoteContexts(); + + useTriliumEvent("contextsReopened", () => { + console.log("Reopened contexts"); + }); + + return ( +
+ {mainNoteContexts.map((tabContext) => ( + {tabContext.note?.title} + ))} +
+ ); +} + +function useMainNoteContexts() { + const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts()); + + useTriliumEvent("newNoteContextCreated", ({ noteContext }) => { + if (noteContext.mainNtxId) return; + setNoteContexts([ + ...noteContexts, + noteContext + ]); + }); + + return noteContexts; +} From 3367bb2e5b166850c237cb2e9ac3a96ac9f3367b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 16:21:27 +0200 Subject: [PATCH 008/154] feat(mobile/tab_switcher): basic rendering of tab content --- .../collections/legacy/ListOrGridView.tsx | 2 +- .../widgets/mobile_widgets/TabSwitcher.css | 17 ++++++++++++ .../widgets/mobile_widgets/TabSwitcher.tsx | 26 +++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx index 61f79cc8da..61c7193a6e 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx @@ -155,7 +155,7 @@ function NoteAttributes({ note }: { note: FNote }) { return ; } -function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: { +export function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: { note: FNote; trim?: boolean; noChildrenList?: boolean; diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 16121f8b02..db4cc74a3c 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -8,5 +8,22 @@ display: grid; grid-template-columns: 1fr 1fr; gap: 1em; + + .tab-card { + background: var(--card-background-color); + border-radius: 1em; + + header { + padding: 0.5em 1em; + border-bottom: 1px solid var(--main-border-color); + } + + .tab-preview { + height: 180px; + overflow: hidden; + font-size: 0.5em; + padding: 1em; + } + } } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 89a40eda2a..5429e244cc 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -4,6 +4,8 @@ import { createPortal } from "preact/compat"; import { useEffect, useState } from "preact/hooks"; import appContext from "../../components/app_context"; +import NoteContext from "../../components/note_context"; +import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; import { useTriliumEvent } from "../react/hooks"; import Modal from "../react/Modal"; @@ -49,13 +51,33 @@ function TabBarModelContent() { return (
- {mainNoteContexts.map((tabContext) => ( - {tabContext.note?.title} + {mainNoteContexts.map((noteContext) => ( + ))}
); } +function Tab({ noteContext }: { + noteContext: NoteContext; +}) { + const { note } = noteContext; + + return ( +
+
{noteContext.note?.title}
+
+ {note && } +
+
+ ); +} + function useMainNoteContexts() { const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts()); From 1aae4098d6b54edf20939247629bab4db2dd018e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 16:27:35 +0200 Subject: [PATCH 009/154] feat(mobile/tab_switcher): --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 7 +++++++ apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index db4cc74a3c..4769b833e0 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -23,6 +23,13 @@ overflow: hidden; font-size: 0.5em; padding: 1em; + + p { margin-bottom: 0.2em;} + h2 { font-size: 1.20em; } + h3 { font-size: 1.15em; } + h4 { font-size: 1.10em; } + h5 { font-size: 1.05em} + h6 { font-size: 1em; } } } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 5429e244cc..1f6cbed1a9 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -45,10 +45,6 @@ function TabBarModal({ shown, setShown }: { function TabBarModelContent() { const mainNoteContexts = useMainNoteContexts(); - useTriliumEvent("contextsReopened", () => { - console.log("Reopened contexts"); - }); - return (
{mainNoteContexts.map((noteContext) => ( From 13aebc060e4de741e47c965d555ed0ebefddf79a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 17:01:22 +0200 Subject: [PATCH 010/154] feat(mobile/tab_switcher): display margins only for text --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 5 ++++- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 4769b833e0..0540f2b540 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -22,7 +22,10 @@ height: 180px; overflow: hidden; font-size: 0.5em; - padding: 1em; + + &.type-text { + padding: 10px; + } p { margin-bottom: 0.2em;} h2 { font-size: 1.20em; } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 1f6cbed1a9..c32b742287 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -1,5 +1,6 @@ import "./TabSwitcher.css"; +import clsx from "clsx"; import { createPortal } from "preact/compat"; import { useEffect, useState } from "preact/hooks"; @@ -62,7 +63,7 @@ function Tab({ noteContext }: { return (
{noteContext.note?.title}
-
+
{note && Date: Sat, 31 Jan 2026 17:04:19 +0200 Subject: [PATCH 011/154] feat(mobile/tab_switcher): clip note title --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 10 ++++++++++ apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 0540f2b540..c281c04d9d 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -12,10 +12,20 @@ .tab-card { background: var(--card-background-color); border-radius: 1em; + min-width: 0; header { padding: 0.5em 1em; border-bottom: 1px solid var(--main-border-color); + display: flex; + overflow: hidden; + + .title { + overflow: hidden; + text-overflow: ellipsis; + text-wrap: nowrap; + font-size: 0.9em; + } } .tab-preview { diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index c32b742287..4ce8ed4bf6 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -62,7 +62,9 @@ function Tab({ noteContext }: { return (
-
{noteContext.note?.title}
+
+ {noteContext.note?.title} +
{note && Date: Sat, 31 Jan 2026 17:07:32 +0200 Subject: [PATCH 012/154] fix(mobile/tab_switcher): no title if empty tab --- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 4ce8ed4bf6..edbe10bbec 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import NoteContext from "../../components/note_context"; +import { t } from "../../services/i18n"; import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; import { useTriliumEvent } from "../react/hooks"; @@ -63,7 +64,7 @@ function Tab({ noteContext }: { return (
- {noteContext.note?.title} + {noteContext.note?.title ?? t("tab_row.new_tab")}
{note && Date: Sat, 31 Jan 2026 17:13:36 +0200 Subject: [PATCH 013/154] feat(mobile/tab_switcher): click to activate --- .../widgets/mobile_widgets/TabSwitcher.css | 2 ++ .../widgets/mobile_widgets/TabSwitcher.tsx | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index c281c04d9d..19360d8c15 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -32,6 +32,8 @@ height: 180px; overflow: hidden; font-size: 0.5em; + user-select: none; + pointer-events: none; &.type-text { padding: 10px; diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index edbe10bbec..cb7c79af1d 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -2,7 +2,7 @@ import "./TabSwitcher.css"; import clsx from "clsx"; import { createPortal } from "preact/compat"; -import { useEffect, useState } from "preact/hooks"; +import { useCallback, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import NoteContext from "../../components/note_context"; @@ -31,6 +31,11 @@ function TabBarModal({ shown, setShown }: { shown: boolean; setShown: (newValue: boolean) => void; }) { + const selectTab = useCallback((noteContextToActivate: NoteContext) => { + appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId); + setShown(false); + }, [ setShown ]); + return ( setShown(false)} > - + ); } -function TabBarModelContent() { +function TabBarModelContent({ selectTab }: { + selectTab: (noteContextToActivate: NoteContext) => void; +}) { const mainNoteContexts = useMainNoteContexts(); return (
{mainNoteContexts.map((noteContext) => ( - + ))}
); } -function Tab({ noteContext }: { +function Tab({ noteContext, selectTab }: { noteContext: NoteContext; + selectTab: (noteContextToActivate: NoteContext) => void; }) { const { note } = noteContext; return ( -
+
selectTab(noteContext)} + >
{noteContext.note?.title ?? t("tab_row.new_tab")}
From a02bbdc550ca0d269639cb5bdeda0ec0f3b9e0e7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 17:25:05 +0200 Subject: [PATCH 014/154] feat(mobile/tab_switcher): indicate active tab --- .../src/widgets/mobile_widgets/TabSwitcher.css | 9 +++++++++ .../src/widgets/mobile_widgets/TabSwitcher.tsx | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 19360d8c15..f188656259 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -14,6 +14,15 @@ border-radius: 1em; min-width: 0; + &.active { + outline: 4px solid var(--more-accented-background-color); + background: var(--card-background-hover-color); + + .title { + font-weight: bold; + } + } + header { padding: 0.5em 1em; border-bottom: 1px solid var(--main-border-color); diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index cb7c79af1d..572cfbbe50 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -9,7 +9,7 @@ import NoteContext from "../../components/note_context"; import { t } from "../../services/i18n"; import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; -import { useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useTriliumEvent } from "../react/hooks"; import Modal from "../react/Modal"; export default function TabSwitcher() { @@ -53,25 +53,34 @@ function TabBarModelContent({ selectTab }: { selectTab: (noteContextToActivate: NoteContext) => void; }) { const mainNoteContexts = useMainNoteContexts(); + const activeNoteContext = useActiveNoteContext(); return (
{mainNoteContexts.map((noteContext) => ( - + ))}
); } -function Tab({ noteContext, selectTab }: { +function Tab({ noteContext, selectTab, activeNtxId }: { noteContext: NoteContext; selectTab: (noteContextToActivate: NoteContext) => void; + activeNtxId: string | null | undefined; }) { const { note } = noteContext; return (
selectTab(noteContext)} >
From 325b8b886cd45bb305085f072e79b2120cc08b5b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 17:25:33 +0200 Subject: [PATCH 015/154] fix(mobile/tab_switcher): clipped borders --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index f188656259..210dcc1491 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -13,6 +13,7 @@ background: var(--card-background-color); border-radius: 1em; min-width: 0; + overflow: hidden; &.active { outline: 4px solid var(--more-accented-background-color); From bf0fc57493ad23257e74da87302d747960595860 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 17:29:24 +0200 Subject: [PATCH 016/154] feat(mobile/tab_switcher): display note icon --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 7 ++++++- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 210dcc1491..94d80c0227 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -25,10 +25,15 @@ } header { - padding: 0.5em 1em; + padding: 0.4em 0.5em; border-bottom: 1px solid var(--main-border-color); display: flex; overflow: hidden; + align-items: center; + + >.tn-icon { + margin-inline-end: 0.4em; + } .title { overflow: hidden; diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 572cfbbe50..ff481b2ed3 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -9,7 +9,8 @@ import NoteContext from "../../components/note_context"; import { t } from "../../services/i18n"; import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; -import { useActiveNoteContext, useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useNoteIcon, useTriliumEvent } from "../react/hooks"; +import Icon from "../react/Icon"; import Modal from "../react/Modal"; export default function TabSwitcher() { @@ -75,6 +76,7 @@ function Tab({ noteContext, selectTab, activeNtxId }: { activeNtxId: string | null | undefined; }) { const { note } = noteContext; + const iconClass = useNoteIcon(note); return (
selectTab(noteContext)} >
+ {noteContext.note?.title ?? t("tab_row.new_tab")}
From 43f147ec60f3f7ed023f309b64c829fbc60a7c28 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 17:31:30 +0200 Subject: [PATCH 017/154] feat(mobile/tab_switcher): improve display of content widget & search --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 94d80c0227..d1ae03be2f 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -54,6 +54,15 @@ padding: 10px; } + &.type-contentWidget, + &.type-search { + display: flex; + align-items: center; + justify-content: center; + font-size: 1.25em; + color: var(--muted-text-color); + } + p { margin-bottom: 0.2em;} h2 { font-size: 1.20em; } h3 { font-size: 1.15em; } From 6b70412f6e29d16643532126d6f882e3f2e631df Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 17:50:52 +0200 Subject: [PATCH 018/154] feat(mobile/tab_switcher): respect note color class --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 1 + apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index d1ae03be2f..a8771562ce 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -30,6 +30,7 @@ display: flex; overflow: hidden; align-items: center; + color: var(--custom-color, inherit); >.tn-icon { margin-inline-end: 0.4em; diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index ff481b2ed3..c14cfa7e60 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -77,10 +77,11 @@ function Tab({ noteContext, selectTab, activeNtxId }: { }) { const { note } = noteContext; const iconClass = useNoteIcon(note); + const colorClass = note?.getColorClass() || ''; return (
selectTab(noteContext)} From 740b1093d7b96eb4fab982145c345930f9e0432a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 18:09:04 +0200 Subject: [PATCH 019/154] feat(mobile/tab_switcher): respect workspace background color --- apps/client/src/services/css_class_manager.ts | 4 ++-- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/client/src/services/css_class_manager.ts b/apps/client/src/services/css_class_manager.ts index de1c98b87d..06ff7b7045 100644 --- a/apps/client/src/services/css_class_manager.ts +++ b/apps/client/src/services/css_class_manager.ts @@ -49,7 +49,7 @@ function createClassForColor(colorString: string | null) { return clsx("use-note-color", className, colorsWithHue.has(className) && "with-hue"); } -function parseColor(color: string) { +export function parseColor(color: string) { try { return Color(color.toLowerCase()); } catch (ex) { @@ -77,7 +77,7 @@ function adjustColorLightness(color: ColorInstance, lightThemeMaxLightness: numb } /** Returns the hue of the specified color, or undefined if the color is grayscale. */ -function getHue(color: ColorInstance) { +export function getHue(color: ColorInstance) { const hslColor = color.hsl(); if (hslColor.saturationl() > 0) { return hslColor.hue(); diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index a8771562ce..f4fc31f3c7 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -15,6 +15,11 @@ min-width: 0; overflow: hidden; + &.with-hue { + background-color: hsl(var(--bg-hue), 8.8%, 11.2%); + border-color: hsl(var(--bg-hue), 9.4%, 25.1%); + } + &.active { outline: 4px solid var(--more-accented-background-color); background: var(--card-background-hover-color); @@ -26,7 +31,7 @@ header { padding: 0.4em 0.5em; - border-bottom: 1px solid var(--main-border-color); + border-bottom: 1px solid rgba(150, 150, 150, 0.1); display: flex; overflow: hidden; align-items: center; From 2a38af5db60b7d2ac795c3e1cefee6bb8042fdc0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 18:25:01 +0200 Subject: [PATCH 020/154] feat(mobile/tab_switcher): scroll to active tab --- .../widgets/mobile_widgets/TabSwitcher.tsx | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index c14cfa7e60..e9efacecbe 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -2,10 +2,12 @@ import "./TabSwitcher.css"; import clsx from "clsx"; import { createPortal } from "preact/compat"; -import { useCallback, useState } from "preact/hooks"; +import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import NoteContext from "../../components/note_context"; +import { getHue, parseColor } from "../../services/css_class_manager"; +import froca from "../../services/froca"; import { t } from "../../services/i18n"; import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; @@ -32,6 +34,7 @@ function TabBarModal({ shown, setShown }: { shown: boolean; setShown: (newValue: boolean) => void; }) { + const [ fullyShown, setFullyShown ] = useState(false); const selectTab = useCallback((noteContextToActivate: NoteContext) => { appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId); setShown(false); @@ -43,18 +46,33 @@ function TabBarModal({ shown, setShown }: { size="xl" title="Tabs" show={shown} - onHidden={() => setShown(false)} + onShown={() => setFullyShown(true)} + onHidden={() => { + setShown(false); + setFullyShown(false); + }} > - + ); } -function TabBarModelContent({ selectTab }: { +function TabBarModelContent({ selectTab, shown }: { + shown: boolean; selectTab: (noteContextToActivate: NoteContext) => void; }) { const mainNoteContexts = useMainNoteContexts(); const activeNoteContext = useActiveNoteContext(); + const tabRefs = useRef>({}); + + // Scroll to active tab. + useEffect(() => { + if (!shown || !activeNoteContext?.ntxId) return; + const correspondingEl = tabRefs.current[activeNoteContext.ntxId]; + requestAnimationFrame(() => { + correspondingEl?.scrollIntoView(); + }); + }, [ activeNoteContext, shown ]); return (
@@ -64,13 +82,15 @@ function TabBarModelContent({ selectTab }: { noteContext={noteContext} activeNtxId={activeNoteContext.ntxId} selectTab={selectTab} + containerRef={el => (tabRefs.current[noteContext.ntxId ?? ""] = el)} /> ))}
); } -function Tab({ noteContext, selectTab, activeNtxId }: { +function Tab({ noteContext, containerRef, selectTab, activeNtxId }: { + containerRef: (el: HTMLDivElement | null) => void; noteContext: NoteContext; selectTab: (noteContextToActivate: NoteContext) => void; activeNtxId: string | null | undefined; @@ -78,15 +98,21 @@ function Tab({ noteContext, selectTab, activeNtxId }: { const { note } = noteContext; const iconClass = useNoteIcon(note); const colorClass = note?.getColorClass() || ''; + const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext); return (
selectTab(noteContext)} + style={{ + "--bg-hue": workspaceTabBackgroundColorHue + }} > -
+
{noteContext.note?.title ?? t("tab_row.new_tab")}
@@ -102,6 +128,23 @@ function Tab({ noteContext, selectTab, activeNtxId }: { ); } +function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) { + if (!noteContext.hoistedNoteId) return; + const hoistedNote = froca.getNoteFromCache(noteContext.hoistedNoteId); + if (!hoistedNote) return; + + const workspaceTabBackgroundColor = hoistedNote.getWorkspaceTabBackgroundColor(); + if (!workspaceTabBackgroundColor) return; + + try { + const parsedColor = parseColor(workspaceTabBackgroundColor); + if (!parsedColor) return; + return getHue(parsedColor); + } catch (e) { + // Colors are non-critical, simply ignore. + } +} + function useMainNoteContexts() { const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts()); From 48db6e1756299ec59ba51fbe5c4a7566a5a082ae Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 18:40:01 +0200 Subject: [PATCH 021/154] feat(mobile/tab_switcher): button to close tab --- .../src/translations/en/translation.json | 3 +++ .../widgets/mobile_widgets/TabSwitcher.css | 5 +++++ .../widgets/mobile_widgets/TabSwitcher.tsx | 22 ++++++++++++------- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d7d91b2443..74ba61d284 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2271,5 +2271,8 @@ }, "platform_indicator": { "available_on": "Available on {{platform}}" + }, + "mobile_tab_switcher": { + "close_tab": "Close tab" } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index f4fc31f3c7..cc48541376 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -46,6 +46,11 @@ text-overflow: ellipsis; text-wrap: nowrap; font-size: 0.9em; + flex-grow: 1; + } + + .icon-action { + flex-shrink: 0; } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index e9efacecbe..86079fa840 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -2,7 +2,7 @@ import "./TabSwitcher.css"; import clsx from "clsx"; import { createPortal } from "preact/compat"; -import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks"; +import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import NoteContext from "../../components/note_context"; @@ -11,7 +11,8 @@ import froca from "../../services/froca"; import { t } from "../../services/i18n"; import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; -import { useActiveNoteContext, useNoteIcon, useTriliumEvent } from "../react/hooks"; +import ActionButton from "../react/ActionButton"; +import { useActiveNoteContext, useNoteIcon, useTriliumEvents } from "../react/hooks"; import Icon from "../react/Icon"; import Modal from "../react/Modal"; @@ -115,6 +116,15 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
{noteContext.note?.title ?? t("tab_row.new_tab")} + { + // We are closing a tab, so we need to prevent propagation for click (activate tab). + e.stopPropagation(); + appContext.tabManager.removeNoteContext(noteContext.ntxId); + }} + />
{note && { - if (noteContext.mainNtxId) return; - setNoteContexts([ - ...noteContexts, - noteContext - ]); + useTriliumEvents([ "newNoteContextCreated", "noteContextRemoved" ] , () => { + setNoteContexts(appContext.tabManager.getMainNoteContexts()); }); return noteContexts; From 0a7b2e330476bc1f92d758bcabb18656b4d51e78 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 18:49:25 +0200 Subject: [PATCH 022/154] feat(mobile/tab_switcher): integrate into launch bar --- apps/client/src/layouts/mobile_layout.tsx | 2 -- .../widgets/launch_bar/LauncherContainer.tsx | 3 +++ .../src/assets/translations/en/server.json | 3 ++- .../services/hidden_subtree_launcherbar.ts | 24 ++++++++++++------- packages/commons/src/lib/hidden_subtree.ts | 3 ++- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index 5c7270bfd0..da66ffa130 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -13,7 +13,6 @@ import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js"; import ScreenContainer from "../widgets/mobile_widgets/screen_container.js"; import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js"; -import TabSwitcher from "../widgets/mobile_widgets/TabSwitcher.jsx"; import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx"; import NoteTitleWidget from "../widgets/note_title.js"; import NoteTreeWidget from "../widgets/note_tree.js"; @@ -185,7 +184,6 @@ export default class MobileLayout { .class("horizontal") .css("height", "53px") .child() - .child() .child() .id("launcher-pane")) ) diff --git a/apps/client/src/widgets/launch_bar/LauncherContainer.tsx b/apps/client/src/widgets/launch_bar/LauncherContainer.tsx index 3450a4c013..d202feaf35 100644 --- a/apps/client/src/widgets/launch_bar/LauncherContainer.tsx +++ b/apps/client/src/widgets/launch_bar/LauncherContainer.tsx @@ -3,6 +3,7 @@ import { useCallback, useLayoutEffect, useState } from "preact/hooks"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; import { isDesktop, isMobile } from "../../services/utils"; +import TabSwitcher from "../mobile_widgets/TabSwitcher"; import { useTriliumEvent } from "../react/hooks"; import { onWheelHorizontalScroll } from "../widget_utils"; import BookmarkButtons from "./BookmarkButtons"; @@ -97,6 +98,8 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) { return ; case "aiChatLauncher": return ; + case "mobileTabSwitcher": + return ; default: throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`); } diff --git a/apps/server/src/assets/translations/en/server.json b/apps/server/src/assets/translations/en/server.json index e6fa04b116..f6b23d489d 100644 --- a/apps/server/src/assets/translations/en/server.json +++ b/apps/server/src/assets/translations/en/server.json @@ -356,7 +356,8 @@ "visible-launchers-title": "Visible Launchers", "user-guide": "User Guide", "localization": "Language & Region", - "inbox-title": "Inbox" + "inbox-title": "Inbox", + "tab-switcher-title": "Tab Switcher" }, "notes": { "new-note": "New note", diff --git a/apps/server/src/services/hidden_subtree_launcherbar.ts b/apps/server/src/services/hidden_subtree_launcherbar.ts index d68c10c1c5..55a3b6f70f 100644 --- a/apps/server/src/services/hidden_subtree_launcherbar.ts +++ b/apps/server/src/services/hidden_subtree_launcherbar.ts @@ -48,7 +48,7 @@ export default function buildLaunchBarConfig() { id: "_lbBackInHistory", ...sharedLaunchers.backInHistory }, - { + { id: "_lbForwardInHistory", ...sharedLaunchers.forwardInHistory }, @@ -59,12 +59,12 @@ export default function buildLaunchBarConfig() { command: "commandPalette", icon: "bx bx-chevron-right-square" }, - { + { id: "_lbBackendLog", title: t("hidden-subtree.backend-log-title"), type: "launcher", targetNoteId: "_backendLog", - icon: "bx bx-detail" + icon: "bx bx-detail" }, { id: "_zenMode", @@ -128,7 +128,7 @@ export default function buildLaunchBarConfig() { baseSize: "50", growthFactor: "0" }, - { + { id: "_lbBookmarks", title: t("hidden-subtree.bookmarks-title"), type: "launcher", @@ -139,7 +139,7 @@ export default function buildLaunchBarConfig() { id: "_lbToday", ...sharedLaunchers.openToday }, - { + { id: "_lbSpacer2", title: t("hidden-subtree.spacer-title"), type: "launcher", @@ -179,7 +179,11 @@ export default function buildLaunchBarConfig() { const mobileAvailableLaunchers: HiddenSubtreeItem[] = [ { id: "_lbMobileNewNote", ...sharedLaunchers.newNote }, - { id: "_lbMobileToday", ...sharedLaunchers.openToday } + { id: "_lbMobileToday", ...sharedLaunchers.openToday }, + { + id: "_lbMobileRecentChanges", + ...sharedLaunchers.recentChanges + } ]; const mobileVisibleLaunchers: HiddenSubtreeItem[] = [ @@ -203,8 +207,10 @@ export default function buildLaunchBarConfig() { ...sharedLaunchers.calendar }, { - id: "_lbMobileRecentChanges", - ...sharedLaunchers.recentChanges + id: "_lbMobileTabSwitcher", + title: t("hidden-subtree.tab-switcher-title"), + type: "launcher", + builtinWidget: "mobileTabSwitcher" } ]; @@ -214,4 +220,4 @@ export default function buildLaunchBarConfig() { mobileAvailableLaunchers, mobileVisibleLaunchers }; -} \ No newline at end of file +} diff --git a/packages/commons/src/lib/hidden_subtree.ts b/packages/commons/src/lib/hidden_subtree.ts index 2fcd68f119..91e46b7080 100644 --- a/packages/commons/src/lib/hidden_subtree.ts +++ b/packages/commons/src/lib/hidden_subtree.ts @@ -45,7 +45,8 @@ export interface HiddenSubtreeItem { | "quickSearch" | "aiChatLauncher" | "commandPalette" - | "toggleZenMode"; + | "toggleZenMode" + | "mobileTabSwitcher"; command?: keyof typeof Command; /** * If set to true, then branches will be enforced to be in the correct place. From 39648b6df89507767678c4aa03b9f4d8e853472e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:03:15 +0200 Subject: [PATCH 023/154] chore(mobile/tab_switcher): remove old tab bar --- apps/client/src/layouts/mobile_layout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index da66ffa130..dbe88ade84 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -179,7 +179,6 @@ export default class MobileLayout { new FlexContainer("column") .contentSized() .id("mobile-bottom-bar") - .child(new TabRowWidget().css("height", "40px")) .child(new FlexContainer("row") .class("horizontal") .css("height", "53px") From 5abd27f252da8ae3f7df2895f92255d6c721eff4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:04:47 +0200 Subject: [PATCH 024/154] chore(mobile/tab_switcher): improve modal fit when in browser --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index cc48541376..25cbb22380 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -1,7 +1,6 @@ .modal.tab-bar-modal { .modal-dialog { - max-height: unset; - height: 95vh; + height: 100%; } .tabs { From b6f107b85bcf76e3dd9e472e3427ed2219325f94 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:08:29 +0200 Subject: [PATCH 025/154] feat(mobile/tab_switcher): display number of tabs in modal title --- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 74ba61d284..e7f48008ce 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2273,6 +2273,7 @@ "available_on": "Available on {{platform}}" }, "mobile_tab_switcher": { - "close_tab": "Close tab" + "title_one": "{{count}} tab", + "title_other": "{{count}} tabs" } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 86079fa840..31d08dd25e 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -36,6 +36,7 @@ function TabBarModal({ shown, setShown }: { setShown: (newValue: boolean) => void; }) { const [ fullyShown, setFullyShown ] = useState(false); + const mainNoteContexts = useMainNoteContexts(); const selectTab = useCallback((noteContextToActivate: NoteContext) => { appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId); setShown(false); @@ -45,7 +46,7 @@ function TabBarModal({ shown, setShown }: { setFullyShown(true)} onHidden={() => { @@ -53,16 +54,16 @@ function TabBarModal({ shown, setShown }: { setFullyShown(false); }} > - + ); } -function TabBarModelContent({ selectTab, shown }: { +function TabBarModelContent({ mainNoteContexts, selectTab, shown }: { + mainNoteContexts: NoteContext[]; shown: boolean; selectTab: (noteContextToActivate: NoteContext) => void; }) { - const mainNoteContexts = useMainNoteContexts(); const activeNoteContext = useActiveNoteContext(); const tabRefs = useRef>({}); From e8158aadec7330dfecef08e5429d094f5a4deb98 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:19:39 +0200 Subject: [PATCH 026/154] feat(mobile/tab_switcher): display number of tabs in launch bar --- .../src/widgets/launch_bar/launch_bar_widgets.tsx | 5 +++-- .../src/widgets/mobile_widgets/TabSwitcher.css | 14 ++++++++++++++ .../src/widgets/mobile_widgets/TabSwitcher.tsx | 9 ++++++--- apps/client/src/widgets/react/ActionButton.tsx | 11 ++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx b/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx index 0cb7c9906f..bb4a053c89 100644 --- a/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx +++ b/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx @@ -1,3 +1,4 @@ +import clsx from "clsx"; import { createContext } from "preact"; import { useContext } from "preact/hooks"; @@ -18,12 +19,12 @@ export interface LauncherNoteProps { launcherNote: FNote; } -export function LaunchBarActionButton(props: Omit) { +export function LaunchBarActionButton({ className, ...props }: Omit) { const { isHorizontalLayout } = useContext(LaunchBarContext); return ( setShown(true)} + data-tab-count={mainNoteContexts.length > 99 ? "∞" : mainNoteContexts.length} /> - {createPortal(, document.body)} + {createPortal(, document.body)} ); } -function TabBarModal({ shown, setShown }: { +function TabBarModal({ mainNoteContexts, shown, setShown }: { + mainNoteContexts: NoteContext[]; shown: boolean; setShown: (newValue: boolean) => void; }) { const [ fullyShown, setFullyShown ] = useState(false); - const mainNoteContexts = useMainNoteContexts(); const selectTab = useCallback((noteContextToActivate: NoteContext) => { appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId); setShown(false); diff --git a/apps/client/src/widgets/react/ActionButton.tsx b/apps/client/src/widgets/react/ActionButton.tsx index ba5430f381..feb5972ef4 100644 --- a/apps/client/src/widgets/react/ActionButton.tsx +++ b/apps/client/src/widgets/react/ActionButton.tsx @@ -1,10 +1,11 @@ -import { useEffect, useRef, useState } from "preact/hooks"; -import { CommandNames } from "../../components/app_context"; -import { useStaticTooltip } from "./hooks"; -import keyboard_actions from "../../services/keyboard_actions"; import { HTMLAttributes } from "preact"; +import { useEffect, useRef, useState } from "preact/hooks"; -export interface ActionButtonProps extends Pick, "onClick" | "onAuxClick" | "onContextMenu"> { +import { CommandNames } from "../../components/app_context"; +import keyboard_actions from "../../services/keyboard_actions"; +import { useStaticTooltip } from "./hooks"; + +export interface ActionButtonProps extends Pick, "onClick" | "onAuxClick" | "onContextMenu" | "style"> { text: string; titlePosition?: "top" | "right" | "bottom" | "left"; icon: string; From a486f5951e91f4d20ab5fa61af3624a4636f4e62 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:29:06 +0200 Subject: [PATCH 027/154] feat(mobile/tab_switcher): new tab button --- .../client/src/widgets/mobile_widgets/TabSwitcher.css | 9 +++++++++ .../client/src/widgets/mobile_widgets/TabSwitcher.tsx | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index e37b364a96..37a68c5b15 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -96,4 +96,13 @@ } } } + + .modal-footer { + .tn-link { + color: var(--main-text-color); + width: 40%; + text-align: center; + text-decoration: none; + } + } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 33ae931513..7f80360ee8 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -14,6 +14,7 @@ import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; import ActionButton from "../react/ActionButton"; import { useActiveNoteContext, useNoteIcon, useTriliumEvents } from "../react/hooks"; import Icon from "../react/Icon"; +import LinkButton from "../react/LinkButton"; import Modal from "../react/Modal"; export default function TabSwitcher() { @@ -52,6 +53,16 @@ function TabBarModal({ mainNoteContexts, shown, setShown }: { title={t("mobile_tab_switcher.title", { count: mainNoteContexts.length})} show={shown} onShown={() => setFullyShown(true)} + footer={<> + { + appContext.triggerCommand("openNewTab"); + setShown(false); + }} + /> + } + scrollable onHidden={() => { setShown(false); setFullyShown(false); From bec7943e058fb8b1d16610ed71eb314c58cb2395 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:36:31 +0200 Subject: [PATCH 028/154] feat(mobile/tab_switcher): hide icon for new tab --- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 7f80360ee8..c779a58042 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -129,7 +129,7 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: { }} >
- + {note && } {noteContext.note?.title ?? t("tab_row.new_tab")} Date: Sat, 31 Jan 2026 19:43:53 +0200 Subject: [PATCH 029/154] feat(mobile/tab_switcher): improve display of collections --- .../widgets/mobile_widgets/TabSwitcher.css | 5 ++++ .../widgets/mobile_widgets/TabSwitcher.tsx | 25 ++++++++++++++----- .../note_bars/CollectionProperties.tsx | 5 +--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 37a68c5b15..0bf9c51fd5 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -78,6 +78,7 @@ padding: 10px; } + &.type-book, &.type-contentWidget, &.type-search { display: flex; @@ -87,6 +88,10 @@ color: var(--muted-text-color); } + .preview-placeholder { + font-size: 500%; + } + p { margin-bottom: 0.2em;} h2 { font-size: 1.20em; } h3 { font-size: 1.15em; } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index c779a58042..45d7ee5754 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -11,6 +11,7 @@ import froca from "../../services/froca"; import { t } from "../../services/i18n"; import { NoteContent } from "../collections/legacy/ListOrGridView"; import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets"; +import { ICON_MAPPINGS } from "../note_bars/CollectionProperties"; import ActionButton from "../react/ActionButton"; import { useActiveNoteContext, useNoteIcon, useTriliumEvents } from "../react/hooks"; import Icon from "../react/Icon"; @@ -142,17 +143,29 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: { />
- {note && } + {note?.type === "book" + ? + : note && }
); } +function PreviewPlaceholder({ icon}: { + icon: string; +}) { + return ( +
+ +
+ ); +} + function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) { if (!noteContext.hoistedNoteId) return; const hoistedNote = froca.getNoteFromCache(noteContext.hoistedNoteId); diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index d466e813cd..5dba675e6d 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -6,10 +6,7 @@ import { useContext, useRef } from "preact/hooks"; import { Fragment } from "preact/jsx-runtime"; import FNote from "../../entities/fnote"; -import { getHelpUrlForNote } from "../../services/in_app_help"; -import { openInAppHelpFromUrl } from "../../services/utils"; import { ViewTypeOptions } from "../collections/interface"; -import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import FormTextBox from "../react/FormTextBox"; @@ -19,7 +16,7 @@ import { ParentComponent } from "../react/react_utils"; import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; -const ICON_MAPPINGS: Record = { +export const ICON_MAPPINGS: Record = { grid: "bx bxs-grid", list: "bx bx-list-ul", calendar: "bx bx-calendar", From 8f3545624ec25f63a0dc08e58839d991beffaafb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:46:15 +0200 Subject: [PATCH 030/154] feat(mobile/tab_switcher): improve display of empty tabs --- .../widgets/mobile_widgets/TabSwitcher.css | 3 +- .../widgets/mobile_widgets/TabSwitcher.tsx | 31 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 0bf9c51fd5..f0a17fd04c 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -80,7 +80,8 @@ &.type-book, &.type-contentWidget, - &.type-search { + &.type-search, + &.type-empty { display: flex; align-items: center; justify-content: center; diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 45d7ee5754..c716731821 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import NoteContext from "../../components/note_context"; +import FNote from "../../entities/fnote"; import { getHue, parseColor } from "../../services/css_class_manager"; import froca from "../../services/froca"; import { t } from "../../services/i18n"; @@ -143,19 +144,33 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: { />
- {note?.type === "book" - ? - : note && } +
); } +function TabPreviewContent({ note }: { + note: FNote | null +}) { + if (!note) { + return ; + } + + if (note.type === "book") { + return ; + } + + return ( + + ); +} + function PreviewPlaceholder({ icon}: { icon: string; }) { From a72d4f425a63ed4cb4e5b0707b5d9d48e3680a78 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 19:54:42 +0200 Subject: [PATCH 031/154] feat(mobile/tab_switcher): add context menu item for closing all tabs --- .../src/translations/en/translation.json | 3 ++- .../widgets/mobile_widgets/TabSwitcher.tsx | 23 +++++++++++++++++- apps/client/src/widgets/react/Modal.tsx | 24 +++++++++---------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index e7f48008ce..6bd13a3346 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2274,6 +2274,7 @@ }, "mobile_tab_switcher": { "title_one": "{{count}} tab", - "title_other": "{{count}} tabs" + "title_other": "{{count}} tabs", + "more_options": "More options" } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index c716731821..994fc834d5 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -4,9 +4,10 @@ import clsx from "clsx"; import { createPortal } from "preact/compat"; import { useCallback, useEffect, useRef, useState } from "preact/hooks"; -import appContext from "../../components/app_context"; +import appContext, { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; +import contextMenu from "../../menus/context_menu"; import { getHue, parseColor } from "../../services/css_class_manager"; import froca from "../../services/froca"; import { t } from "../../services/i18n"; @@ -55,6 +56,26 @@ function TabBarModal({ mainNoteContexts, shown, setShown }: { title={t("mobile_tab_switcher.title", { count: mainNoteContexts.length})} show={shown} onShown={() => setFullyShown(true)} + customTitleBarButtons={[ + { + iconClassName: "bx bx-dots-vertical-rounded", + title: t("mobile_tab_switcher.more_options"), + onClick(e) { + contextMenu.show({ + x: e.pageX, + y: e.pageY, + items: [ + { title: t("tab_row.close_all_tabs"), command: "closeAllTabs", uiIcon: "bx bx-empty" }, + ], + selectMenuItemHandler: ({ command }) => { + if (command) { + appContext.triggerCommand(command); + } + } + }); + }, + } + ]} footer={<> void; + onClick: (e: MouseEvent) => void; } export interface ModalProps { @@ -80,7 +81,7 @@ export interface ModalProps { noFocus?: boolean; } -export default function Modal({ children, className, size, title, customTitleBarButtons: titleBarButtons, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable, keepInDom, noFocus }: ModalProps) { +export default function Modal({ children, className, size, title, customTitleBarButtons: titleBarButtons, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable, keepInDom, noFocus }: ModalProps) { const modalRef = useSyncedRef(externalModalRef); const modalInstanceRef = useRef(); const elementToFocus = useRef(); @@ -116,7 +117,7 @@ export default function Modal({ children, className, size, title, customTitleBar focus: !noFocus }).then(($widget) => { modalInstanceRef.current = BootstrapModal.getOrCreateInstance($widget[0]); - }) + }); } else { modalInstanceRef.current?.hide(); } @@ -159,13 +160,12 @@ export default function Modal({ children, className, size, title, customTitleBar {titleBarButtons?.filter((b) => b !== null).map((titleBarButton) => ( + className={clsx("custom-title-bar-button bx", titleBarButton.iconClassName)} + title={titleBarButton.title} + onClick={titleBarButton.onClick} /> ))} - +
From 3cb74bb84455bfff5bbdec194fd9f6dca903cbef Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:00:56 +0200 Subject: [PATCH 032/154] fix(mobile): context menu won't dismiss due to missing cover --- apps/client/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/index.html b/apps/client/index.html index e1db353327..12f653666f 100644 --- a/apps/client/index.html +++ b/apps/client/index.html @@ -13,6 +13,7 @@ +
From 20eaa790795332e69f15e38c3ad31244bbb69ec5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:04:00 +0200 Subject: [PATCH 033/154] fix(mobile): cover not working properly in modals --- apps/client/src/stylesheets/style.css | 2 +- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 17e431e542..a8d5c75006 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1255,7 +1255,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href inset-inline-start: 0; inset-inline-end: 0; bottom: 0; - z-index: 1000; + z-index: 2500; background: rgba(0, 0, 0, 0.1); } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 994fc834d5..78107ec810 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -65,7 +65,10 @@ function TabBarModal({ mainNoteContexts, shown, setShown }: { x: e.pageX, y: e.pageY, items: [ - { title: t("tab_row.close_all_tabs"), command: "closeAllTabs", uiIcon: "bx bx-empty" }, + { title: t("tab_row.new_tab"), command: "openNewTab", uiIcon: "bx bx-plus" }, + { title: t("tab_row.reopen_last_tab"), command: "reopenLastTab", uiIcon: "bx bx-undo", enabled: appContext.tabManager.recentlyClosedTabs.length !== 0 }, + { kind: "separator" }, + { title: t("tab_row.close_all_tabs"), command: "closeAllTabs", uiIcon: "bx bx-trash destructive-action-icon" }, ], selectMenuItemHandler: ({ command }) => { if (command) { From d2abde714fa2e928de7ce85537f4b7ccfe1a6e8b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:06:16 +0200 Subject: [PATCH 034/154] chore(mobile/tab_switcher): enforce same height --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index f0a17fd04c..47736a72f6 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -14,7 +14,7 @@ .modal.tab-bar-modal { .modal-dialog { - height: 100%; + min-height: 85vh; } .tabs { From e7f356b87c06064b2bb918489b2693d12d2d0fe7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:21:11 +0200 Subject: [PATCH 035/154] feat(mobile/tab_switcher): display note splits --- .../widgets/mobile_widgets/TabSwitcher.css | 17 +++++++- .../widgets/mobile_widgets/TabSwitcher.tsx | 42 +++++++++++-------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 47736a72f6..4518d76c2c 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -27,6 +27,9 @@ border-radius: 1em; min-width: 0; overflow: hidden; + height: 200px; + display: flex; + flex-direction: column; &.with-hue { background-color: hsl(var(--bg-hue), 8.8%, 11.2%); @@ -49,6 +52,11 @@ overflow: hidden; align-items: center; color: var(--custom-color, inherit); + flex-shrink: 0; + + &:not(:first-of-type) { + border-top: 1px solid rgba(150, 150, 150, 0.1); + } >.tn-icon { margin-inline-end: 0.4em; @@ -68,7 +76,8 @@ } .tab-preview { - height: 180px; + flex-grow: 1; + height: 100%; overflow: hidden; font-size: 0.5em; user-select: none; @@ -100,6 +109,12 @@ h5 { font-size: 1.05em} h6 { font-size: 1em; } } + + &.with-split { + .preview-placeholder { + font-size: 250%; + } + } } } diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index 78107ec810..c3d32129cc 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -1,7 +1,7 @@ import "./TabSwitcher.css"; import clsx from "clsx"; -import { createPortal } from "preact/compat"; +import { createPortal, Fragment } from "preact/compat"; import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; @@ -141,35 +141,41 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: { const iconClass = useNoteIcon(note); const colorClass = note?.getColorClass() || ''; const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext); + const subContexts = noteContext.getSubContexts(); return (
1 })} onClick={() => selectTab(noteContext)} style={{ "--bg-hue": workspaceTabBackgroundColorHue }} > -
- {note && } - {noteContext.note?.title ?? t("tab_row.new_tab")} - { - // We are closing a tab, so we need to prevent propagation for click (activate tab). - e.stopPropagation(); - appContext.tabManager.removeNoteContext(noteContext.ntxId); - }} - /> -
-
- -
+ {subContexts.map(subContext => ( + +
+ {subContext.note && } + {subContext.note?.title ?? t("tab_row.new_tab")} + {subContext.isMainContext() && { + // We are closing a tab, so we need to prevent propagation for click (activate tab). + e.stopPropagation(); + appContext.tabManager.removeNoteContext(subContext.ntxId); + }} + />} +
+
+ +
+
+ ))}
); } From e9b826e49838b0bf93f6c7ca929fad77e8ff6476 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:22:28 +0200 Subject: [PATCH 036/154] chore(mobile/tab_switcher): stop auto-showing --- apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx index c3d32129cc..6ee84f0465 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx @@ -21,7 +21,7 @@ import LinkButton from "../react/LinkButton"; import Modal from "../react/Modal"; export default function TabSwitcher() { - const [ shown, setShown ] = useState(true); + const [ shown, setShown ] = useState(false); const mainNoteContexts = useMainNoteContexts(); return ( @@ -225,6 +225,7 @@ function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) { return getHue(parsedColor); } catch (e) { // Colors are non-critical, simply ignore. + console.warn(e); } } From a5306b2067542aee21c8e69d145c722aefb8d0fd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:35:16 +0200 Subject: [PATCH 037/154] fix(mobile): modals on tablet view --- apps/client/src/stylesheets/style.css | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index a8d5c75006..9ac3995d09 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -224,10 +224,6 @@ body.mobile .modal .modal-dialog { width: 100%; } -body.mobile .modal .modal-content { - border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0; -} - .component { contain: size; } @@ -1614,6 +1610,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { body.mobile .modal-content { overflow-y: auto; + border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0; } body.mobile .modal-footer { @@ -1650,6 +1647,17 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { } } +@media (min-width: 992px) { + .modal-dialog { + margin: var(--bs-modal-margin); + max-width: 80%; + } + + .modal-content { + height: 100%; + } +} + /* Mobile, tablet mode */ @media (min-width: 992px) { body.mobile #root-widget { From 0d99cf9fb9e8283da0826a54f34701d774f371c0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:41:53 +0200 Subject: [PATCH 038/154] chore(mobile/tab_switcher): improve layout on tablet view --- apps/client/src/widgets/mobile_widgets/TabSwitcher.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css index 4518d76c2c..f401ee61b0 100644 --- a/apps/client/src/widgets/mobile_widgets/TabSwitcher.css +++ b/apps/client/src/widgets/mobile_widgets/TabSwitcher.css @@ -22,6 +22,10 @@ grid-template-columns: 1fr 1fr; gap: 1em; + @media (min-width: 850px) { + grid-template-columns: 1fr 1fr 1fr; + } + .tab-card { background: var(--card-background-color); border-radius: 1em; From ff80154fda4518a55c7494518813d11f30075d30 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 20:47:07 +0200 Subject: [PATCH 039/154] chore(mobile/tab_switcher): address requested changes --- apps/client/src/stylesheets/style.css | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 9ac3995d09..69632dd29e 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1647,17 +1647,6 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { } } -@media (min-width: 992px) { - .modal-dialog { - margin: var(--bs-modal-margin); - max-width: 80%; - } - - .modal-content { - height: 100%; - } -} - /* Mobile, tablet mode */ @media (min-width: 992px) { body.mobile #root-widget { @@ -1677,6 +1666,15 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { #detail-container { background: var(--main-background-color); } + + .modal-dialog { + margin: var(--bs-modal-margin); + max-width: 80%; + } + + .modal-content { + height: 100%; + } } @media (max-width: 991px) { From 5f20ce87a701c5d475249758f312a53da67fcd2f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 31 Jan 2026 21:17:08 +0200 Subject: [PATCH 040/154] chore(mobile/tab_switcher): bypass weird regression in typecheck regarding React types --- .../client/src/widgets/layout/InlineTitle.tsx | 1 + apps/client/src/widgets/note_icon.tsx | 7 ++- apps/client/src/widgets/react/Modal.tsx | 5 +- .../widgets/type_widgets/options/other.tsx | 58 ++++++++++--------- .../type_widgets/options/text_notes.tsx | 1 + 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/apps/client/src/widgets/layout/InlineTitle.tsx b/apps/client/src/widgets/layout/InlineTitle.tsx index 9070902535..f6e5a51556 100644 --- a/apps/client/src/widgets/layout/InlineTitle.tsx +++ b/apps/client/src/widgets/layout/InlineTitle.tsx @@ -5,6 +5,7 @@ import { Tooltip } from "bootstrap"; import clsx from "clsx"; import { ComponentChild } from "preact"; import { useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; +import type React from "react"; import { Trans } from "react-i18next"; import FNote from "../../entities/fnote"; diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 7bbbd3e345..51204b015e 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -6,6 +6,7 @@ import clsx from "clsx"; import { t } from "i18next"; import { CSSProperties, RefObject } from "preact"; import { useEffect, useMemo, useRef, useState } from "preact/hooks"; +import type React from "react"; import { CellComponentProps, Grid } from "react-window"; import FNote from "../entities/fnote"; @@ -153,10 +154,10 @@ function NoteIconList({ note, dropdownRef }: { function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{ filteredIcons: IconWithName[]; -}>): React.JSX.Element { +}>) { const iconIndex = rowIndex * 12 + columnIndex; const iconData = filteredIcons[iconIndex] as IconWithName | undefined; - if (!iconData) return <>; + if (!iconData) return <> as React.ReactElement; const { id, terms, iconPack } = iconData; return ( @@ -166,7 +167,7 @@ function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellCompo title={t("note_icon.icon_tooltip", { name: terms?.[0] ?? id, iconPack })} style={style as CSSProperties} /> - ); + ) as React.ReactElement; } function IconFilterContent({ filterByPrefix, setFilterByPrefix }: { diff --git a/apps/client/src/widgets/react/Modal.tsx b/apps/client/src/widgets/react/Modal.tsx index 917c3bd996..e7a7c721f8 100644 --- a/apps/client/src/widgets/react/Modal.tsx +++ b/apps/client/src/widgets/react/Modal.tsx @@ -1,9 +1,8 @@ import { Modal as BootstrapModal } from "bootstrap"; import clsx from "clsx"; -import { ComponentChildren } from "preact"; -import type { CSSProperties, RefObject } from "preact/compat"; +import { ComponentChildren, CSSProperties, RefObject } from "preact"; import { memo } from "preact/compat"; -import { useEffect, useMemo,useRef } from "preact/hooks"; +import { useEffect, useMemo, useRef } from "preact/hooks"; import { openDialog } from "../../services/dialog"; import { t } from "../../services/i18n"; diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index 6ac92b4207..e6813f8d2b 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -1,20 +1,22 @@ +import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons"; +import { useMemo } from "preact/hooks"; +import type React from "react"; import { Trans } from "react-i18next"; + import { t } from "../../../services/i18n"; +import search from "../../../services/search"; import server from "../../../services/server"; import toast from "../../../services/toast"; +import { isElectron } from "../../../services/utils"; import Button from "../../react/Button"; -import FormText from "../../react/FormText"; -import OptionsSection from "./components/OptionsSection"; -import TimeSelector from "./components/TimeSelector"; -import { useMemo } from "preact/hooks"; -import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks"; -import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons"; import FormCheckbox from "../../react/FormCheckbox"; import FormGroup from "../../react/FormGroup"; -import search from "../../../services/search"; -import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox"; import FormSelect from "../../react/FormSelect"; -import { isElectron } from "../../../services/utils"; +import FormText from "../../react/FormText"; +import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox"; +import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks"; +import OptionsSection from "./components/OptionsSection"; +import TimeSelector from "./components/TimeSelector"; export default function OtherSettings() { return ( @@ -31,7 +33,7 @@ export default function OtherSettings() { - ) + ); } function SearchEngineSettings() { @@ -82,7 +84,7 @@ function SearchEngineSettings() { /> - ) + ); } function TrayOptionsSettings() { @@ -97,7 +99,7 @@ function TrayOptionsSettings() { onChange={trayEnabled => setDisableTray(!trayEnabled)} /> - ) + ); } function NoteErasureTimeout() { @@ -105,13 +107,13 @@ function NoteErasureTimeout() { {t("note_erasure_timeout.note_erasure_description")} - {t("note_erasure_timeout.manual_erasing_description")} - +
} } noDropdownListStyle - onShown={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover") : undefined} - onHidden={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover") : undefined} + mobileBackdrop > diff --git a/apps/client/src/widgets/react/Dropdown.tsx b/apps/client/src/widgets/react/Dropdown.tsx index 5af2f62280..407b14a63a 100644 --- a/apps/client/src/widgets/react/Dropdown.tsx +++ b/apps/client/src/widgets/react/Dropdown.tsx @@ -3,6 +3,7 @@ import { ComponentChildren, HTMLAttributes } from "preact"; import { CSSProperties, HTMLProps } from "preact/compat"; import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks"; +import { isMobile } from "../../services/utils"; import { useTooltip, useUniqueName } from "./hooks"; type DataAttributes = { @@ -32,9 +33,10 @@ export interface DropdownProps extends Pick, "id" | "c dropdownRef?: MutableRef; titlePosition?: "top" | "right" | "bottom" | "left"; titleOptions?: Partial; + mobileBackdrop?: boolean; } -export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions }: DropdownProps) { +export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions, mobileBackdrop }: DropdownProps) { const containerRef = useRef(null); const triggerRef = useRef(null); const dropdownContainerRef = useRef(null); @@ -74,12 +76,18 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi setShown(true); externalOnShown?.(); hideTooltip(); - }, [ hideTooltip ]); + if (mobileBackdrop && isMobile()) { + document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover"); + } + }, [ hideTooltip, mobileBackdrop ]); const onHidden = useCallback(() => { setShown(false); externalOnHidden?.(); - }, []); + if (mobileBackdrop && isMobile()) { + document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover"); + } + }, [ mobileBackdrop ]); useEffect(() => { if (!containerRef.current) return; diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 266ca38f93..607f55a0ad 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -104,6 +104,7 @@ export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContex noDropdownListStyle iconAction onHidden={() => itemToFocusRef.current = null } + mobileBackdrop > {isReadOnly && <> From 2a4280b5bf12280febea83b41c75d8891bc94dc8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 20:59:50 +0200 Subject: [PATCH 068/154] feat(mobile/note_actions): remove bottom rounded corners --- apps/client/src/widgets/ribbon/NoteActions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 607f55a0ad..6aa05234db 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -99,6 +99,7 @@ export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContex dropdownRef={dropdownRef} buttonClassName={ isNewLayout ? "bx bx-dots-horizontal-rounded" : "bx bx-dots-vertical-rounded" } className="note-actions" + dropdownContainerClassName="mobile-bottom-menu" hideToggleArrow noSelectButtonStyle noDropdownListStyle From e88c0f732635bc34c159b8467bf9a15e854cf576 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:02:46 +0200 Subject: [PATCH 069/154] fix(mobile/note_actions): toggles on two rows --- apps/client/src/stylesheets/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 3a24830077..1bed8cbef0 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -454,7 +454,7 @@ body.desktop .tabulator-popup-container, visibility: hidden; } -body.desktop .dropdown-menu:not(#context-menu-container) .dropdown-item, +.dropdown-menu:not(#context-menu-container) .dropdown-item, body.desktop .dropdown-menu .dropdown-toggle, body #context-menu-container .dropdown-item > span, body.mobile .dropdown .dropdown-submenu > span { From c06a90913af5bc424ed2e05ade3acf7e2ffd32ad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:12:50 +0200 Subject: [PATCH 070/154] fix(mobile/note_actions): submenus not working --- apps/client/src/stylesheets/style.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 1bed8cbef0..93163c06d4 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -462,6 +462,15 @@ body.mobile .dropdown .dropdown-submenu > span { align-items: center; } + +body.mobile .dropdown .dropdown-submenu { + flex-wrap: wrap; + + & > span { + flex-grow: 1; + } +} + .dropdown-item span.keyboard-shortcut, .dropdown-item *:not(.keyboard-shortcut) > kbd { flex-grow: 1; @@ -1531,7 +1540,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { @media (max-width: 991px) { body.mobile #launcher-pane .dropdown.global-menu > .dropdown-menu.show, body.mobile #launcher-container .dropdown > .dropdown-menu.show, - body.mobile .dropdown.note-actions .dropdown-menu.show { + body.mobile .dropdown.note-actions > .dropdown-menu.show { --dropdown-bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size)); position: fixed !important; bottom: var(--dropdown-bottom) !important; From e2363d860c58c9fa3cf6e94a6bc7aac547271aae Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:25:40 +0200 Subject: [PATCH 071/154] fix(mobile/note_actions): font too big --- apps/client/src/layouts/mobile_layout.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index dbe88ade84..37fcf2f684 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -27,7 +27,6 @@ import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx"; import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx"; import SearchResult from "../widgets/search_result.jsx"; import SharedInfoWidget from "../widgets/shared_info.js"; -import TabRowWidget from "../widgets/tab_row.js"; import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx"; import { applyModals } from "./layout_commons.js"; @@ -148,7 +147,6 @@ export default class MobileLayout { .child( new FlexContainer("row") .contentSized() - .css("font-size", "larger") .css("align-items", "center") .child() .child() From 76492475e3160143dfa9946e14a473ffdca86095 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:27:24 +0200 Subject: [PATCH 072/154] fix(mobile/note_actions): find not working --- apps/client/src/layouts/mobile_layout.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index 37fcf2f684..7fb925533d 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -7,6 +7,7 @@ import FlexContainer from "../widgets/containers/flex_container.js"; import RootContainer from "../widgets/containers/root_container.js"; import ScrollingContainer from "../widgets/containers/scrolling_container.js"; import SplitNoteContainer from "../widgets/containers/split_note_container.js"; +import FindWidget from "../widgets/find.js"; import FloatingButtons from "../widgets/FloatingButtons.jsx"; import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx"; import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; @@ -169,6 +170,7 @@ export default class MobileLayout { .child() ) .child() + .child(new FindWidget()) ) ) ) From 92991cc03c4c50b1145acea2f6041b7cd71c93ce Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:37:20 +0200 Subject: [PATCH 073/154] fix(promoted_attributes): displayed in non-default view modes --- apps/client/src/widgets/PromotedAttributes.tsx | 11 ++++++----- apps/client/src/widgets/layout/NoteTitleActions.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/PromotedAttributes.tsx b/apps/client/src/widgets/PromotedAttributes.tsx index a9618d3a63..cbe5737256 100644 --- a/apps/client/src/widgets/PromotedAttributes.tsx +++ b/apps/client/src/widgets/PromotedAttributes.tsx @@ -5,6 +5,7 @@ import clsx from "clsx"; import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact"; import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks"; +import NoteContext from "../components/note_context"; import FAttribute from "../entities/fattribute"; import FNote from "../entities/fnote"; import { Attribute } from "../services/attribute_parser"; @@ -40,8 +41,8 @@ type OnChangeEventData = TargetedEvent | InputEvent | J type OnChangeListener = (e: OnChangeEventData) => Promise; export default function PromotedAttributes() { - const { note, componentId } = useNoteContext(); - const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + const { note, componentId, noteContext } = useNoteContext(); + const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext); return ; } @@ -74,12 +75,12 @@ export function PromotedAttributesContent({ note, componentId, cells, setCells } * * The cells are returned as a state since they can also be altered internally if needed, for example to add a new empty cell. */ -export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch> ] { +export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string, noteContext: NoteContext | undefined): [ Cell[] | undefined, Dispatch> ] { const [ viewType ] = useNoteLabel(note, "viewType"); const [ cells, setCells ] = useState(); function refresh() { - if (!note || viewType === "table") { + if (!note || viewType === "table" || noteContext?.viewScope?.viewMode !== "default") { setCells([]); return; } @@ -124,7 +125,7 @@ export function usePromotedAttributeData(note: FNote | null | undefined, compone setCells(cells); } - useEffect(refresh, [ note, viewType ]); + useEffect(refresh, [ note, viewType, noteContext ]); useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { refresh(); diff --git a/apps/client/src/widgets/layout/NoteTitleActions.tsx b/apps/client/src/widgets/layout/NoteTitleActions.tsx index 6886acc7e6..96a2e92963 100644 --- a/apps/client/src/widgets/layout/NoteTitleActions.tsx +++ b/apps/client/src/widgets/layout/NoteTitleActions.tsx @@ -48,7 +48,7 @@ function PromotedAttributes({ note, componentId, noteContext }: { componentId: string, noteContext: NoteContext | undefined }) { - const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext); const [ expanded, setExpanded ] = useState(false); useEffect(() => { From 35ac5fc51426f2ff10d4cbb9c9fe0baedb09fa55 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:42:48 +0200 Subject: [PATCH 074/154] fix(mobile/note_actions): reintroduce insert child note --- .../widgets/mobile_widgets/mobile_detail_menu.tsx | 14 ++++++++++++-- apps/client/src/widgets/ribbon/NoteActions.tsx | 9 +++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 9fc3f9d3b1..c096e1d64e 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,12 +1,22 @@ +import { t } from "../../services/i18n"; +import { FormDropdownDivider } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; -import { NoteContextMenu } from "../ribbon/NoteActions"; +import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { const { note, noteContext } = useNoteContext(); return (
- {note && } + {note && ( + + + + } + /> + )}
); } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 6aa05234db..39c3ebe354 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -1,6 +1,6 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons"; import { Dropdown as BootstrapDropdown } from "bootstrap"; -import { RefObject } from "preact"; +import { ComponentChildren, RefObject } from "preact"; import { useContext, useEffect, useRef } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; @@ -14,7 +14,7 @@ import { t } from "../../services/i18n"; import protected_session from "../../services/protected_session"; import server from "../../services/server"; import toast from "../../services/toast"; -import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils"; +import { isElectron as getIsElectron, isMac as getIsMac, isMobile } from "../../services/utils"; import ws from "../../services/ws"; import ClosePaneButton from "../buttons/close_pane_button"; import CreatePaneButton from "../buttons/create_pane_button"; @@ -63,7 +63,7 @@ function RevisionsButton({ note }: { note: FNote }) { type ItemToFocus = "basic-properties"; -export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { +export function NoteContextMenu({ note, noteContext, extraItems }: { note: FNote, noteContext?: NoteContext, extraItems?: ComponentChildren; }) { const dropdownRef = useRef(null); const parentComponent = useContext(ParentComponent); const noteType = useNoteProperty(note, "type") ?? ""; @@ -107,6 +107,7 @@ export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContex onHidden={() => itemToFocusRef.current = null } mobileBackdrop > + {extraItems} {isReadOnly && <> void), disabled?: boolean, destructive?: boolean }) { +export function CommandItem({ icon, text, title, command, disabled }: { icon: string, text: string, title?: string, command: CommandNames | (() => void), disabled?: boolean, destructive?: boolean }) { return Date: Sun, 1 Feb 2026 21:45:43 +0200 Subject: [PATCH 075/154] fix(mobile/note_actions): reintroduce help button --- .../src/widgets/mobile_widgets/mobile_detail_menu.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index c096e1d64e..5bb1f434e5 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,10 +1,13 @@ import { t } from "../../services/i18n"; -import { FormDropdownDivider } from "../react/FormList"; +import { getHelpUrlForNote } from "../../services/in_app_help"; +import { openInAppHelpFromUrl } from "../../services/utils"; +import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { const { note, noteContext } = useNoteContext(); + const helpUrl = getHelpUrlForNote(note); return (
@@ -14,6 +17,11 @@ export default function MobileDetailMenu() { extraItems={<> + {helpUrl && openInAppHelpFromUrl(helpUrl)} + >{t("help-button.title")}} + } /> )} From c702fb273ce9cf1b6a7f48a1959909537df79dfa Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Sun, 1 Feb 2026 11:46:03 -0800 Subject: [PATCH 076/154] feat(tests): add tests for new attachments endpoint --- .../spec/etapi/get-note-attachments.spec.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 apps/server/spec/etapi/get-note-attachments.spec.ts diff --git a/apps/server/spec/etapi/get-note-attachments.spec.ts b/apps/server/spec/etapi/get-note-attachments.spec.ts new file mode 100644 index 0000000000..5e95463bba --- /dev/null +++ b/apps/server/spec/etapi/get-note-attachments.spec.ts @@ -0,0 +1,82 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { createNote, login } from "./utils.js"; +import config from "../../src/services/config.js"; + +let app: Application; +let token: string; + +const USER = "etapi"; +let createdNoteId: string; +let createdAttachmentId: string; + +describe("etapi/get-note-attachments", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + + createdNoteId = await createNote(app, token); + + // Create an attachment for the note + const response = await supertest(app) + .post(`/etapi/attachments`) + .auth(USER, token, { "type": "basic" }) + .send({ + "ownerId": createdNoteId, + "role": "file", + "mime": "text/plain", + "title": "test-attachment.txt", + "content": "test content", + "position": 10 + }); + createdAttachmentId = response.body.attachmentId; + expect(createdAttachmentId).toBeTruthy(); + }); + + it("gets attachments for a note", async () => { + const response = await supertest(app) + .get(`/etapi/notes/${createdNoteId}/attachments`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBeGreaterThan(0); + + const attachment = response.body[0]; + expect(attachment).toHaveProperty("attachmentId", createdAttachmentId); + expect(attachment).toHaveProperty("ownerId", createdNoteId); + expect(attachment).toHaveProperty("role", "file"); + expect(attachment).toHaveProperty("mime", "text/plain"); + expect(attachment).toHaveProperty("title", "test-attachment.txt"); + expect(attachment).toHaveProperty("position", 10); + expect(attachment).toHaveProperty("blobId"); + expect(attachment).toHaveProperty("dateModified"); + expect(attachment).toHaveProperty("utcDateModified"); + expect(attachment).toHaveProperty("contentLength"); + }); + + it("returns empty array for note with no attachments", async () => { + // Create a new note without any attachments + const newNoteId = await createNote(app, token, "Note without attachments"); + + const response = await supertest(app) + .get(`/etapi/notes/${newNoteId}/attachments`) + .auth(USER, token, { "type": "basic" }) + .expect(200); + + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBe(0); + }); + + it("returns 404 for non-existent note", async () => { + const response = await supertest(app) + .get("/etapi/notes/nonexistentnote/attachments") + .auth(USER, token, { "type": "basic" }) + .expect(404); + + expect(response.body.code).toStrictEqual("NOTE_NOT_FOUND"); + }); +}); From ce9ca1917db01bb91e2703d5b80d007bdd8367a3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:59:19 +0200 Subject: [PATCH 077/154] fix(mobile/note_actions): reintroduce split buttons --- .../mobile_widgets/mobile_detail_menu.tsx | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 5bb1f434e5..3458dcb0cf 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -6,8 +6,10 @@ import { useNoteContext } from "../react/hooks"; import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { - const { note, noteContext } = useNoteContext(); + const { note, noteContext, parentComponent, ntxId } = useNoteContext(); const helpUrl = getHelpUrlForNote(note); + const subContexts = noteContext?.getMainContext().getSubContexts() ?? []; + const isMainContext = noteContext?.isMainContext(); return (
@@ -16,11 +18,27 @@ export default function MobileDetailMenu() { note={note} noteContext={noteContext} extraItems={<> - - {helpUrl && openInAppHelpFromUrl(helpUrl)} - >{t("help-button.title")}} + {helpUrl && <> + + openInAppHelpFromUrl(helpUrl)} + >{t("help-button.title")} + } + {subContexts.length < 2 && <> + + parentComponent.triggerCommand("openNewNoteSplit", { ntxId })} + icon="bx bx-dock-right" + >{t("create_pane_button.create_new_split")} + } + {!isMainContext && <> + + parentComponent.triggerCommand("closeThisNoteSplit", { ntxId })} + >{t("close_pane_button.close_this_pane")} + } } /> From fd6f9108247dc08c0063746b5a065dcfd5c00884 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:02:54 +0200 Subject: [PATCH 078/154] fix(mobile/note_actions): backdrop remains when closing split --- .../src/widgets/mobile_widgets/mobile_detail_menu.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 3458dcb0cf..3db1738feb 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -36,7 +36,12 @@ export default function MobileDetailMenu() { parentComponent.triggerCommand("closeThisNoteSplit", { ntxId })} + onClick={() => { + // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. + requestAnimationFrame(() => { + parentComponent.triggerCommand("closeThisNoteSplit", { ntxId }); + }); + }} >{t("close_pane_button.close_this_pane")} } From 841fab77a805b033b3d0feb1845500283b3d244a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:22:25 +0200 Subject: [PATCH 079/154] chore(client): address requested changes --- apps/client/src/services/experimental_features.ts | 2 +- apps/client/src/widgets/ribbon/NoteActions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index b6211b4d10..8cfbe126e8 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -30,7 +30,7 @@ export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): export function getEnabledExperimentalFeatureIds() { const values = [ ...getEnabledFeatures().values() ]; - if (options.is("newLayout")) { + if (isMobile() || options.is("newLayout")) { values.push("new-layout"); } return values; diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 39c3ebe354..51788085d2 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -14,7 +14,7 @@ import { t } from "../../services/i18n"; import protected_session from "../../services/protected_session"; import server from "../../services/server"; import toast from "../../services/toast"; -import { isElectron as getIsElectron, isMac as getIsMac, isMobile } from "../../services/utils"; +import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils"; import ws from "../../services/ws"; import ClosePaneButton from "../buttons/close_pane_button"; import CreatePaneButton from "../buttons/create_pane_button"; From c36ce3ea143df67a17b4275846652777503d8d91 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:29:43 +0200 Subject: [PATCH 080/154] fix(mobile/note_actions): insert child note not working --- .../src/widgets/mobile_widgets/mobile_detail_menu.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 3db1738feb..7db00d0bcd 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,9 +1,10 @@ import { t } from "../../services/i18n"; import { getHelpUrlForNote } from "../../services/in_app_help"; +import note_create from "../../services/note_create"; import { openInAppHelpFromUrl } from "../../services/utils"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; -import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; +import { NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { const { note, noteContext, parentComponent, ntxId } = useNoteContext(); @@ -17,7 +18,10 @@ export default function MobileDetailMenu() { - + noteContext?.notePath && note_create.createNote(noteContext.notePath)} + icon="bx bx-plus" + >{t("mobile_detail_menu.insert_child_note")} {helpUrl && <> Date: Mon, 2 Feb 2026 01:51:29 +0000 Subject: [PATCH 081/154] chore(deps): update dependency webdriverio to v9.23.3 --- packages/ckeditor5-admonition/package.json | 2 +- packages/ckeditor5-footnotes/package.json | 2 +- .../ckeditor5-keyboard-marker/package.json | 2 +- packages/ckeditor5-math/package.json | 2 +- packages/ckeditor5-mermaid/package.json | 2 +- pnpm-lock.yaml | 95 ++++++++++--------- 6 files changed, 54 insertions(+), 51 deletions(-) diff --git a/packages/ckeditor5-admonition/package.json b/packages/ckeditor5-admonition/package.json index 5b68539da2..2175a0c349 100644 --- a/packages/ckeditor5-admonition/package.json +++ b/packages/ckeditor5-admonition/package.json @@ -39,7 +39,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", "vitest": "4.0.18", - "webdriverio": "9.23.2" + "webdriverio": "9.23.3" }, "peerDependencies": { "ckeditor5": "47.4.0" diff --git a/packages/ckeditor5-footnotes/package.json b/packages/ckeditor5-footnotes/package.json index d49d404ea9..dddd60910e 100644 --- a/packages/ckeditor5-footnotes/package.json +++ b/packages/ckeditor5-footnotes/package.json @@ -40,7 +40,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", "vitest": "4.0.18", - "webdriverio": "9.23.2" + "webdriverio": "9.23.3" }, "peerDependencies": { "ckeditor5": "47.4.0" diff --git a/packages/ckeditor5-keyboard-marker/package.json b/packages/ckeditor5-keyboard-marker/package.json index 7a5acc2d85..b34179332b 100644 --- a/packages/ckeditor5-keyboard-marker/package.json +++ b/packages/ckeditor5-keyboard-marker/package.json @@ -42,7 +42,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", "vitest": "4.0.18", - "webdriverio": "9.23.2" + "webdriverio": "9.23.3" }, "peerDependencies": { "ckeditor5": "47.4.0" diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index a64a097a60..dce972d960 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -42,7 +42,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", "vitest": "4.0.18", - "webdriverio": "9.23.2" + "webdriverio": "9.23.3" }, "peerDependencies": { "ckeditor5": "47.4.0" diff --git a/packages/ckeditor5-mermaid/package.json b/packages/ckeditor5-mermaid/package.json index 3949f4b312..4eae79a3ce 100644 --- a/packages/ckeditor5-mermaid/package.json +++ b/packages/ckeditor5-mermaid/package.json @@ -42,7 +42,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", "vitest": "4.0.18", - "webdriverio": "9.23.2" + "webdriverio": "9.23.3" }, "peerDependencies": { "ckeditor5": "47.4.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8118438a0..4024fdde29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,7 +73,7 @@ importers: version: 24.10.9 '@vitest/browser-webdriverio': specifier: 4.0.18 - version: 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + version: 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/coverage-v8': specifier: 4.0.18 version: 4.0.18(@vitest/browser@4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18))(vitest@4.0.18) @@ -980,8 +980,8 @@ importers: specifier: 4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.23.2 - version: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.3 + version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-footnotes: devDependencies: @@ -1040,8 +1040,8 @@ importers: specifier: 4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.23.2 - version: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.3 + version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-keyboard-marker: devDependencies: @@ -1100,8 +1100,8 @@ importers: specifier: 4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.23.2 - version: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.3 + version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-math: dependencies: @@ -1167,8 +1167,8 @@ importers: specifier: 4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.23.2 - version: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.3 + version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-mermaid: dependencies: @@ -1234,8 +1234,8 @@ importers: specifier: 4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.23.2 - version: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.3 + version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/codemirror: dependencies: @@ -6151,27 +6151,27 @@ packages: '@vue/shared@3.5.14': resolution: {integrity: sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==} - '@wdio/config@9.23.2': - resolution: {integrity: sha512-19Z+AIQ1NUpr6ncTumjSthm6A7c3DbaGTp+VCdcyN+vHYOK4WsWIomSk+uSbFosYFQVGRjCaHaeGSnC8GNPGYQ==} + '@wdio/config@9.23.3': + resolution: {integrity: sha512-tQCT1R6R3hdib7Qb+82Dxgn/sB+CiR8+GS4zyJh5vU0dzLGeYsCo2B5W89VLItvRjveTmsmh8NOQGV2KH0FHTQ==} engines: {node: '>=18.20.0'} '@wdio/logger@9.18.0': resolution: {integrity: sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==} engines: {node: '>=18.20.0'} - '@wdio/protocols@9.23.2': - resolution: {integrity: sha512-pmCYOYI2N89QCC8IaiHwaWyP0mR8T1iKkEGpoTq2XVihp7VK/lfPvieyeZT5/e28MadYLJsDQ603pbu5J1NRDg==} + '@wdio/protocols@9.23.3': + resolution: {integrity: sha512-QfA3Gfl9/3QRX1FnH7x2+uZrgpkwYcksgk1bxGLzl/E0Qefp3BkhgHAfSB1+iKsiYIw9iFOLVx+x+zh0F4BSeg==} '@wdio/repl@9.16.2': resolution: {integrity: sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==} engines: {node: '>=18.20.0'} - '@wdio/types@9.23.2': - resolution: {integrity: sha512-ryfrERGsNp+aCcrTE1rFU6cbmDj8GHZ04R9k52KNt2u1a6bv3Eh5A/cUA0hXuMdEUfsc8ePLYdwQyOLFydZ0ig==} + '@wdio/types@9.23.3': + resolution: {integrity: sha512-Ufjh06DAD7cGTMORUkq5MTZLw1nAgBSr2y8OyiNNuAfPGCwHEU3EwEfhG/y0V7S7xT5pBxliqWi7AjRrCgGcIA==} engines: {node: '>=18.20.0'} - '@wdio/utils@9.23.2': - resolution: {integrity: sha512-+QfgXUWeA940AXT5l5UlrBKoHBk9GLSQE3BA+7ra1zWuFvv6SHG6M2mwplcPlOlymJMqXy8e7ZgLEoLkXuvC1Q==} + '@wdio/utils@9.23.3': + resolution: {integrity: sha512-LO/cTpOcb3r49psjmWTxjFduHUMHDOhVfSzL1gfBCS5cGv6h3hAWOYw/94OrxLn1SIOgZu/hyLwf3SWeZB529g==} engines: {node: '>=18.20.0'} '@webassemblyjs/ast@1.14.1': @@ -14746,12 +14746,12 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - webdriver@9.23.2: - resolution: {integrity: sha512-HZy3eydZbmex0pbyLwHaDsAyZ+S+V4XQTdGK/nAOi4uPa74U6yT9vXqtb+3B+5/LDM7L8kTD6Z3b1y4gB4pmTw==} + webdriver@9.23.3: + resolution: {integrity: sha512-8FdXOhzkxqDI6F1dyIsQONhKLDZ9HPSEwNBnH3bD1cHnj/6nVvyYrUtDPo/+J324BuwOa1IVTH3m8mb3B2hTlA==} engines: {node: '>=18.20.0'} - webdriverio@9.23.2: - resolution: {integrity: sha512-VjfTw1bRJdBrzjoCu7BGThxn1JK2V7mAGvxibaBrCNIayPPQjLhVDNJPOVEiR7txM6zmOUWxhkCDxHjhMYirfQ==} + webdriverio@9.23.3: + resolution: {integrity: sha512-1dhMsBx/GLHJsDLhg/xuEQ48JZPrbldz7qdFT+MXQZADj9CJ4bJywWtVBME648MmVMfgDvLc5g2ThGIOupSLvQ==} engines: {node: '>=18.20.0'} peerDependencies: puppeteer-core: '>=22.x || <=24.x' @@ -16254,6 +16254,8 @@ snapshots: '@ckeditor/ckeditor5-table': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-emoji@47.4.0': dependencies: @@ -16436,6 +16438,8 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-icons@47.4.0': {} @@ -16934,8 +16938,6 @@ snapshots: '@ckeditor/ckeditor5-icons': 47.4.0 '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-upload@47.4.0': dependencies: @@ -21484,11 +21486,11 @@ snapshots: - bufferutil - utf-8-validate - '@vitest/browser-webdriverio@4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@vitest/browser-webdriverio@4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: '@vitest/browser': 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18) vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - webdriverio: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + webdriverio: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil - msw @@ -21642,14 +21644,15 @@ snapshots: '@vue/shared@3.5.14': {} - '@wdio/config@9.23.2': + '@wdio/config@9.23.3': dependencies: '@wdio/logger': 9.18.0 - '@wdio/types': 9.23.2 - '@wdio/utils': 9.23.2 + '@wdio/types': 9.23.3 + '@wdio/utils': 9.23.3 deepmerge-ts: 7.1.5 glob: 13.0.0 import-meta-resolve: 4.2.0 + jiti: 2.6.1 transitivePeerDependencies: - bare-buffer - supports-color @@ -21662,21 +21665,21 @@ snapshots: safe-regex2: 5.0.0 strip-ansi: 7.1.2 - '@wdio/protocols@9.23.2': {} + '@wdio/protocols@9.23.3': {} '@wdio/repl@9.16.2': dependencies: '@types/node': 20.19.25 - '@wdio/types@9.23.2': + '@wdio/types@9.23.3': dependencies: '@types/node': 20.19.25 - '@wdio/utils@9.23.2': + '@wdio/utils@9.23.3': dependencies: '@puppeteer/browsers': 2.10.10 '@wdio/logger': 9.18.0 - '@wdio/types': 9.23.2 + '@wdio/types': 9.23.3 decamelize: 6.0.1 deepmerge-ts: 7.1.5 edgedriver: 6.1.2 @@ -32040,7 +32043,7 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 24.10.9 - '@vitest/browser-webdriverio': 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + '@vitest/browser-webdriverio': 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/ui': 4.0.18(vitest@4.0.18) happy-dom: 20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -32155,15 +32158,15 @@ snapshots: web-streams-polyfill@3.3.3: {} - webdriver@9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5): + webdriver@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 20.19.25 '@types/ws': 8.18.1 - '@wdio/config': 9.23.2 + '@wdio/config': 9.23.3 '@wdio/logger': 9.18.0 - '@wdio/protocols': 9.23.2 - '@wdio/types': 9.23.2 - '@wdio/utils': 9.23.2 + '@wdio/protocols': 9.23.3 + '@wdio/types': 9.23.3 + '@wdio/utils': 9.23.3 deepmerge-ts: 7.1.5 https-proxy-agent: 7.0.6 undici: 6.23.0 @@ -32174,16 +32177,16 @@ snapshots: - supports-color - utf-8-validate - webdriverio@9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5): + webdriverio@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 20.19.25 '@types/sinonjs__fake-timers': 8.1.5 - '@wdio/config': 9.23.2 + '@wdio/config': 9.23.3 '@wdio/logger': 9.18.0 - '@wdio/protocols': 9.23.2 + '@wdio/protocols': 9.23.3 '@wdio/repl': 9.16.2 - '@wdio/types': 9.23.2 - '@wdio/utils': 9.23.2 + '@wdio/types': 9.23.3 + '@wdio/utils': 9.23.3 archiver: 7.0.1 aria-query: 5.3.2 cheerio: 1.2.0 @@ -32200,7 +32203,7 @@ snapshots: rgb2hex: 0.2.5 serialize-error: 12.0.0 urlpattern-polyfill: 10.1.0 - webdriver: 9.23.2(bufferutil@4.0.9)(utf-8-validate@6.0.5) + webdriver: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bare-buffer - bufferutil From eb4bbd49fbf6c6d1edd57990dc3c5169a6146f38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 01:52:08 +0000 Subject: [PATCH 082/154] fix(deps): update dependency globals to v17.3.0 --- apps/client/package.json | 2 +- pnpm-lock.yaml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index e0840ccd04..41880160cb 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -43,7 +43,7 @@ "debounce": "3.0.0", "draggabilly": "3.0.0", "force-graph": "1.51.0", - "globals": "17.2.0", + "globals": "17.3.0", "i18next": "25.8.0", "i18next-http-backend": "3.0.2", "jquery": "4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8118438a0..3a96e4631f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -261,8 +261,8 @@ importers: specifier: 1.51.0 version: 1.51.0 globals: - specifier: 17.2.0 - version: 17.2.0 + specifier: 17.3.0 + version: 17.3.0 i18next: specifier: 25.8.0 version: 25.8.0(typescript@5.9.3) @@ -9107,8 +9107,8 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - globals@17.2.0: - resolution: {integrity: sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==} + globals@17.3.0: + resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} engines: {node: '>=18'} globalthis@1.0.4: @@ -16076,8 +16076,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-watchdog': 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-dev-build-tools@54.3.2(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -16254,6 +16252,8 @@ snapshots: '@ckeditor/ckeditor5-table': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-emoji@47.4.0': dependencies: @@ -16436,6 +16436,8 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-icons@47.4.0': {} @@ -16934,8 +16936,6 @@ snapshots: '@ckeditor/ckeditor5-icons': 47.4.0 '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-upload@47.4.0': dependencies: @@ -25476,7 +25476,7 @@ snapshots: globals@16.5.0: {} - globals@17.2.0: {} + globals@17.3.0: {} globalthis@1.0.4: dependencies: From a54fe62643b06fbda5df5404a8c85a815131678b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 04:31:50 +0000 Subject: [PATCH 083/154] fix(deps): update dependency mind-elixir to v5.7.1 --- apps/client/package.json | 2 +- pnpm-lock.yaml | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index e0840ccd04..de990d05bb 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -56,7 +56,7 @@ "mark.js": "8.11.1", "marked": "17.0.1", "mermaid": "11.12.2", - "mind-elixir": "5.6.1", + "mind-elixir": "5.7.1", "normalize.css": "8.0.1", "panzoom": "9.4.3", "preact": "10.28.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8118438a0..2af9c7a085 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,7 +208,7 @@ importers: version: 0.2.0(mermaid@11.12.2) '@mind-elixir/node-menu': specifier: 5.0.1 - version: 5.0.1(mind-elixir@5.6.1) + version: 5.0.1(mind-elixir@5.7.1) '@popperjs/core': specifier: 2.11.8 version: 2.11.8 @@ -300,8 +300,8 @@ importers: specifier: 11.12.2 version: 11.12.2 mind-elixir: - specifier: 5.6.1 - version: 5.6.1 + specifier: 5.7.1 + version: 5.7.1 normalize.css: specifier: 8.0.1 version: 8.0.1 @@ -10904,8 +10904,8 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - mind-elixir@5.6.1: - resolution: {integrity: sha512-FTpP5yfyjqXxtHbUAcJVrzBgbU9en0dQIbYx4lQc1C7aWOnjHGHr9iVccgMqU4gh9jVIBpunm4++2DpN753mGg==} + mind-elixir@5.7.1: + resolution: {integrity: sha512-T9RRECITVzT0A64ZpUJiM/y5PrsiwCDSoJKUGL8iylREn2I9d+5cjrW6dcRwpVOs1kSiLjsFBubVECmO+tBaTw==} mini-css-extract-plugin@2.9.4: resolution: {integrity: sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==} @@ -16011,6 +16011,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -16279,6 +16281,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-engine': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-essentials@47.4.0': dependencies: @@ -16737,6 +16741,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@47.4.0': dependencies: @@ -16934,8 +16940,6 @@ snapshots: '@ckeditor/ckeditor5-icons': 47.4.0 '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-upload@47.4.0': dependencies: @@ -19030,9 +19034,9 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} - '@mind-elixir/node-menu@5.0.1(mind-elixir@5.6.1)': + '@mind-elixir/node-menu@5.0.1(mind-elixir@5.7.1)': dependencies: - mind-elixir: 5.6.1 + mind-elixir: 5.7.1 '@mixmark-io/domino@2.2.0': {} @@ -27662,7 +27666,7 @@ snapshots: mimic-response@3.1.0: {} - mind-elixir@5.6.1: {} + mind-elixir@5.7.1: {} mini-css-extract-plugin@2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: From 72d6b83ec5eef84f80c56d32e3c68df015c0223e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 27 Jan 2026 09:16:10 +0200 Subject: [PATCH 084/154] docs(user): add nightly release script for Windows --- .../Advanced Usage/Nightly release.html | 29 ++++++++++++++-- .../Installation & Setup/Web Clipper.html | 34 ++++++++----------- .../Developer Guide/Documentation.md | 2 +- .../Advanced Usage/Nightly release.md | 34 +++++++++++++++++-- 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Nightly release.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Nightly release.html index b2bf30c816..2ac344c9bb 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Nightly release.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Nightly release.html @@ -26,10 +26,35 @@

Automatically download and install the latest nightly

This is pretty useful if you are a beta tester that wants to periodically update their version:

-

On Ubuntu:

#!/usr/bin/env bash
+

On Ubuntu (Bash)

#!/usr/bin/env bash
 
 name=TriliumNotes-linux-x64-nightly.deb
 rm -f $name*
 wget https://github.com/TriliumNext/Trilium/releases/download/nightly/$name
 sudo apt-get install ./$name
-rm $name
\ No newline at end of file +rm $name
+

On Windows (PowerShell)

if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
+  $arch = "arm64";
+} else {
+  $arch = "x64";
+}
+
+$exeUrl = "https://github.com/TriliumNext/Trilium/releases/download/nightly/TriliumNotes-main-windows-$($arch).exe";
+Write-Host "Downloading $($exeUrl)"
+
+# Generate a unique path in the temp dir
+$guid = [guid]::NewGuid().ToString()
+$destination = Join-Path -Path $env:TEMP -ChildPath "$guid.exe"
+
+try {
+    $ProgressPreference = 'SilentlyContinue'
+    Invoke-WebRequest -Uri $exeUrl -OutFile $destination
+    $process = Start-Process -FilePath $destination
+} catch {
+    Write-Error "An error occurred: $_"
+} finally {
+    # Clean up
+    if (Test-Path $destination) {
+        Remove-Item -Path $destination -Force
+    }
+}
\ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html index 5aa0d98b96..daed786e9c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html @@ -24,8 +24,7 @@
  • click on an image or link and save it through context menu
  • save whole page from the popup or context menu
  • save screenshot (with crop tool) from either popup or context menu
  • -
  • create short text note from popup
  • +
  • create short text note from popup
  • Location of clippings

    Trilium will save these clippings as a new child note under a "clipper @@ -40,10 +39,8 @@

    Keyboard shortcuts are available for most functions:

    • Save selected text: Ctrl+Shift+S (Mac: ++S)
    • -
    • Save whole page: Alt+Shift+S (Mac: ++S)
    • -
    • Save screenshot: Ctrl+Shift+E (Mac: ++E)
    • +
    • Save whole page: Alt+Shift+S (Mac: ++S)
    • +
    • Save screenshot: Ctrl+Shift+E (Mac: ++E)

    To set custom shortcuts, follow the directions for your browser.

    For Chrome

    1. Download trilium-web-clipper-[x.y.z]-chrome.zip.
    2. -
    3. Extract the archive.
    4. -
    5. In Chrome, navigate to chrome://extensions/ -
    6. -
    7. Toggle Developer Mode in top-right of the page.
    8. -
    9. Press the Load unpacked button near the header.
    10. -
    11. Point to the extracted directory from step (2).
    12. +
    13. Extract the archive.
    14. +
    15. In Chrome, navigate to chrome://extensions/ +
    16. +
    17. Toggle Developer Mode in top-right of the page.
    18. +
    19. Press the Load unpacked button near the header.
    20. +
    21. Point to the extracted directory from step (2).

    For Firefox

    @@ -130,7 +166,7 @@ function NoteIconList({ note, dropdownRef }: { const attributeToSet = note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass"; attributes.setLabel(note.noteId, attributeToSet, iconClass); } - dropdownRef?.current?.hide(); + onHide(); }} > {filteredIcons.length ? ( From c4d131dd23fb1470faa4e2462e1a7f8cbaf59339 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 13:13:13 +0200 Subject: [PATCH 089/154] feat(mobile): improve note icon selector fit --- apps/client/src/widgets/note_icon.css | 14 ++++++++++++++ apps/client/src/widgets/note_icon.tsx | 14 +++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 4ed6df6482..237f14076c 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -117,3 +117,17 @@ body.experimental-feature-new-layout { } } } + +body.mobile .modal.icon-switcher { + .modal-body { + padding: 0; + + > .filter-row { + padding: 0 var(--bs-modal-padding) var(--bs-modal-padding) var(--bs-modal-padding); + } + } + + .icon-list { + margin: auto; + } +} diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 9c503c9fd5..019995a9f7 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -55,7 +55,7 @@ export default function NoteIcon() { hideToggleArrow disabled={viewScope?.viewMode !== "default"} > - { note && dropdownRef?.current?.hide()} /> } + { note && dropdownRef?.current?.hide()} columnCount={12} /> } ); } @@ -82,16 +82,17 @@ function MobileNoteIconSwitcher({ note, icon }: { show={modalShown} onHidden={() => setModalShown(false)} className="icon-switcher note-icon-widget" > - {note && setModalShown(false)} />} + {note && setModalShown(false)} columnCount={7} />} ), document.body)}
    ); } -function NoteIconList({ note, onHide }: { +function NoteIconList({ note, onHide, columnCount }: { note: FNote; onHide: () => void; + columnCount: number; }) { const searchBoxRef = useRef(null); const iconListRef = useRef(null); @@ -156,6 +157,9 @@ function NoteIconList({ note, onHide }: {
    { // Make sure we are not clicking on something else than a button. const clickedTarget = e.target as HTMLElement; @@ -171,9 +175,9 @@ function NoteIconList({ note, onHide }: { > {filteredIcons.length ? ( Date: Mon, 2 Feb 2026 13:14:42 +0200 Subject: [PATCH 090/154] fix(mobile/note_icon): small horizontal scroll --- apps/client/src/widgets/note_icon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 019995a9f7..5ca80fa01d 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -158,7 +158,7 @@ function NoteIconList({ note, onHide, columnCount }: { class="icon-list" ref={iconListRef} style={{ - width: columnCount * 48, + width: (columnCount * 48 + 10), }} onClick={(e) => { // Make sure we are not clicking on something else than a button. From b090eb93599b7dcd40073a823e78e81c9c4b0376 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 13:22:37 +0200 Subject: [PATCH 091/154] feat(mobile/note_icon): calculate number of columns dynamically --- apps/client/src/widgets/note_icon.css | 8 ++++++++ apps/client/src/widgets/note_icon.tsx | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 237f14076c..70e2f018de 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -119,6 +119,14 @@ body.experimental-feature-new-layout { } body.mobile .modal.icon-switcher { + .modal-dialog { + left: 0; + right: 0; + margin: unset; + transform: unset; + max-width: 100%; + } + .modal-body { padding: 0; diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 5ca80fa01d..96c26a8c6a 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -18,7 +18,7 @@ import ActionButton from "./react/ActionButton"; import Dropdown from "./react/Dropdown"; import { FormDropdownDivider, FormListItem } from "./react/FormList"; import FormTextBox from "./react/FormTextBox"; -import { useNoteContext, useNoteLabel, useStaticTooltip } from "./react/hooks"; +import { useNoteContext, useNoteLabel, useStaticTooltip, useWindowSize } from "./react/hooks"; import Modal from "./react/Modal"; interface IconToCountCache { @@ -65,6 +65,7 @@ function MobileNoteIconSwitcher({ note, icon }: { icon: string | null | undefined; }) { const [ modalShown, setModalShown ] = useState(false); + const { windowWidth } = useWindowSize(); return (
    @@ -82,7 +83,7 @@ function MobileNoteIconSwitcher({ note, icon }: { show={modalShown} onHidden={() => setModalShown(false)} className="icon-switcher note-icon-widget" > - {note && setModalShown(false)} columnCount={7} />} + {note && setModalShown(false)} columnCount={Math.floor(windowWidth / 48)} />} ), document.body)}
    From 490d940cd1488f56219cbded36f108b4de9bfb23 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 13:23:55 +0200 Subject: [PATCH 092/154] feat(mobile/note_icon): improve height and fit --- apps/client/src/widgets/note_icon.css | 4 ++++ apps/client/src/widgets/note_icon.tsx | 1 + 2 files changed, 5 insertions(+) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 70e2f018de..2e2cf3dc0a 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -129,6 +129,8 @@ body.mobile .modal.icon-switcher { .modal-body { padding: 0; + display: flex; + flex-direction: column; > .filter-row { padding: 0 var(--bs-modal-padding) var(--bs-modal-padding) var(--bs-modal-padding); @@ -137,5 +139,7 @@ body.mobile .modal.icon-switcher { .icon-list { margin: auto; + flex-grow: 1; + height: 100%; } } diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 96c26a8c6a..2b78f052eb 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -82,6 +82,7 @@ function MobileNoteIconSwitcher({ note, icon }: { size="xl" show={modalShown} onHidden={() => setModalShown(false)} className="icon-switcher note-icon-widget" + scrollable > {note && setModalShown(false)} columnCount={Math.floor(windowWidth / 48)} />} From 0382a4b30e94617d29b89a1940424dfdf4b10d8e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 13:27:34 +0200 Subject: [PATCH 093/154] fix(mobile/note_icon): consistent height and proper margins --- apps/client/src/widgets/note_icon.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 2e2cf3dc0a..db99ecd9e1 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -125,6 +125,7 @@ body.mobile .modal.icon-switcher { margin: unset; transform: unset; max-width: 100%; + height: 100%; } .modal-body { @@ -133,7 +134,8 @@ body.mobile .modal.icon-switcher { flex-direction: column; > .filter-row { - padding: 0 var(--bs-modal-padding) var(--bs-modal-padding) var(--bs-modal-padding); + padding: 0.25em var(--bs-modal-padding) 0.5em var(--bs-modal-padding); + border-bottom: 1px solid var(--main-border-color); } } From 90bb162a889941795fa231e51dd2d679dc0dd2d1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 13:31:29 +0200 Subject: [PATCH 094/154] feat(mobile/note_icon): bigger touch area --- apps/client/src/widgets/note_icon.css | 4 ++++ apps/client/src/widgets/note_icon.tsx | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index db99ecd9e1..cea05cdcd9 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -143,5 +143,9 @@ body.mobile .modal.icon-switcher { margin: auto; flex-grow: 1; height: 100%; + + span { + padding: 12px; + } } } diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 2b78f052eb..cdf66b5735 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -4,7 +4,7 @@ import { IconRegistry } from "@triliumnext/commons"; import { Dropdown as BootstrapDropdown } from "bootstrap"; import clsx from "clsx"; import { t } from "i18next"; -import { CSSProperties, RefObject } from "preact"; +import { CSSProperties } from "preact"; import { createPortal } from "preact/compat"; import { useEffect, useMemo, useRef, useState } from "preact/hooks"; import type React from "react"; @@ -21,6 +21,8 @@ import FormTextBox from "./react/FormTextBox"; import { useNoteContext, useNoteLabel, useStaticTooltip, useWindowSize } from "./react/hooks"; import Modal from "./react/Modal"; +const ICON_SIZE = isMobile() ? 56 : 48; + interface IconToCountCache { iconClassToCountMap: Record; } @@ -84,7 +86,7 @@ function MobileNoteIconSwitcher({ note, icon }: { className="icon-switcher note-icon-widget" scrollable > - {note && setModalShown(false)} columnCount={Math.floor(windowWidth / 48)} />} + {note && setModalShown(false)} columnCount={Math.floor(windowWidth / ICON_SIZE)} />} ), document.body)}
    @@ -160,7 +162,7 @@ function NoteIconList({ note, onHide, columnCount }: { class="icon-list" ref={iconListRef} style={{ - width: (columnCount * 48 + 10), + width: (columnCount * ICON_SIZE + 10), }} onClick={(e) => { // Make sure we are not clicking on something else than a button. @@ -178,9 +180,9 @@ function NoteIconList({ note, onHide, columnCount }: { {filteredIcons.length ? ( Date: Mon, 2 Feb 2026 13:45:13 +0200 Subject: [PATCH 095/154] fix(mobile/note_icon): wrong icons displayed --- apps/client/src/widgets/note_icon.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index cdf66b5735..b5afb54717 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -185,7 +185,8 @@ function NoteIconList({ note, onHide, columnCount }: { rowHeight={ICON_SIZE} cellComponent={IconItemCell} cellProps={{ - filteredIcons + filteredIcons, + columnCount }} /> ) : ( @@ -196,10 +197,11 @@ function NoteIconList({ note, onHide, columnCount }: { ); } -function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{ +function IconItemCell({ rowIndex, columnIndex, style, filteredIcons, columnCount }: CellComponentProps<{ filteredIcons: IconWithName[]; + columnCount: number; }>) { - const iconIndex = rowIndex * 12 + columnIndex; + const iconIndex = rowIndex * columnCount + columnIndex; const iconData = filteredIcons[iconIndex] as IconWithName | undefined; if (!iconData) return <> as React.ReactElement; From f89718d88a54313765836b0b054f2d3d6f56fadf Mon Sep 17 00:00:00 2001 From: hulmgulm Date: Mon, 2 Feb 2026 14:38:11 +0100 Subject: [PATCH 096/154] Add subtreeHidden and map:* attributes to Labels.html --- .../User Guide/Advanced Usage/Attributes/Labels.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html index 94a0ce7893..d2a6764802 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html @@ -354,6 +354,11 @@ Hides the Note List for that particular note. + + subtreeHidden + + Hides all child notes of this note from the tree of notes. + printLandscape @@ -372,6 +377,11 @@ Geo Map. + + map:* + + Defines specific options for the Geo Map View. + calendar:* @@ -384,4 +394,4 @@ href="#root/_help_0ESUbbAxVnoK">Note List for more information. - \ No newline at end of file + From 734efaf40c83f62ba2969bdf1c28cbfae2f4958b Mon Sep 17 00:00:00 2001 From: hulmgulm Date: Mon, 2 Feb 2026 14:41:38 +0100 Subject: [PATCH 097/154] Update apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../User Guide/User Guide/Advanced Usage/Attributes/Labels.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html index d2a6764802..d79004f8bc 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html @@ -357,7 +357,7 @@ subtreeHidden - Hides all child notes of this note from the tree of notes. + Hides all child notes of this note from the tree, displaying a badge with the count of hidden children. Children remain accessible via search or direct links. printLandscape From d48473ab878f6bfdb6c884f22e4070020e13d4a6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 16:38:12 +0200 Subject: [PATCH 098/154] feat(mobile/note_icon): single menu for filtering & resetting --- apps/client/src/stylesheets/style.css | 6 +- apps/client/src/widgets/note_icon.tsx | 140 +++++++++++++++++--------- 2 files changed, 100 insertions(+), 46 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 23a9869850..f05441db3a 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1540,7 +1540,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { @media (max-width: 991px) { body.mobile #launcher-pane .dropdown.global-menu > .dropdown-menu.show, body.mobile #launcher-container .dropdown > .dropdown-menu.show, - body.mobile .dropdown.note-actions > .dropdown-menu.show { + body.mobile .dropdown-menu.mobile-bottom-menu.show { --dropdown-bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size)); position: fixed !important; bottom: var(--dropdown-bottom) !important; @@ -1552,6 +1552,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { max-height: calc(var(--tn-modal-max-height) - var(--dropdown-bottom)); } + body.mobile .modal-dialog .dropdown-menu.mobile-bottom-menu.show { + --dropdown-bottom: 0; + } + #mobile-sidebar-container { position: fixed; top: 0; diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index b5afb54717..5cc97d5c1f 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -13,7 +13,7 @@ import { CellComponentProps, Grid } from "react-window"; import FNote from "../entities/fnote"; import attributes from "../services/attributes"; import server from "../services/server"; -import { isMobile } from "../services/utils"; +import { isDesktop, isMobile } from "../services/utils"; import ActionButton from "./react/ActionButton"; import Dropdown from "./react/Dropdown"; import { FormDropdownDivider, FormListItem } from "./react/FormList"; @@ -98,7 +98,6 @@ function NoteIconList({ note, onHide, columnCount }: { onHide: () => void; columnCount: number; }) { - const searchBoxRef = useRef(null); const iconListRef = useRef(null); const [ search, setSearch ] = useState(); const [ filterByPrefix, setFilterByPrefix ] = useState(null); @@ -114,49 +113,15 @@ function NoteIconList({ note, onHide, columnCount }: { return ( <> -
    - {t("note_icon.search")} - s.prefix === filterByPrefix)?.name ?? "" - }) - : t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })} - currentValue={search} onChange={setSearch} - autoFocus - /> - - {getIconLabels(note).length > 0 && ( -
    - { - if (!note) return; - for (const label of getIconLabels(note)) { - attributes.removeAttributeById(note.noteId, label.attributeId); - } - onHide(); - }} - /> -
    - )} - - {glob.iconRegistry.sources.length > 0 && - - } -
    +
    void; + setFilterByPrefix: (value: string | null) => void; + filteredIcons: IconWithName[]; + onHide: () => void; +}) { + const searchBoxRef = useRef(null); + const hasIconPacks = glob.iconRegistry.sources.length > 0; + const hasCustomIcon = getIconLabels(note).length > 0; + + function resetToDefaultIcon() { + if (!note) return; + for (const label of getIconLabels(note)) { + attributes.removeAttributeById(note.noteId, label.attributeId); + } + onHide(); + } + + return ( +
    + {t("note_icon.search")} + s.prefix === filterByPrefix)?.name ?? "" + }) + : t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })} + currentValue={search} onChange={setSearch} + autoFocus + /> + + {isDesktop() + ? <> + {hasCustomIcon && ( +
    + +
    + )} + + {hasIconPacks && + + } + : ( + + {hasIconPacks && <> + {t("note_icon.reset-default")} + + } + + + + )} +
    + ); +} + function IconItemCell({ rowIndex, columnIndex, style, filteredIcons, columnCount }: CellComponentProps<{ filteredIcons: IconWithName[]; columnCount: number; From 0e5aa401ef70b82ad89dd6c40e8ea00b9b996005 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 16:44:08 +0200 Subject: [PATCH 099/154] chore(mobile/note_icon): redundant separator --- apps/client/src/widgets/note_icon.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 5cc97d5c1f..bc4094439e 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -172,7 +172,6 @@ function FilterRow({ note, filterByPrefix, search, setSearch, setFilterByPrefix, onHide: () => void; }) { const searchBoxRef = useRef(null); - const hasIconPacks = glob.iconRegistry.sources.length > 0; const hasCustomIcon = getIconLabels(note).length > 0; function resetToDefaultIcon() { @@ -212,7 +211,7 @@ function FilterRow({ note, filterByPrefix, search, setSearch, setFilterByPrefix,
    )} - {hasIconPacks && - {hasIconPacks && <> + {hasCustomIcon && <> setFilterByPrefix("bx")} >{t("note_icon.filter-default")} - + {glob.iconRegistry.sources.length > 1 && } {glob.iconRegistry.sources.map(({ prefix, name, icon }) => ( prefix !== "bx" && Date: Mon, 2 Feb 2026 16:51:57 +0200 Subject: [PATCH 100/154] chore(mobile): slightly smaller note title icon --- apps/client/src/widgets/note_title.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 50be6afc7c..e525910521 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -109,4 +109,8 @@ body.experimental-feature-new-layout { --input-focus-color: initial; } } + + &.mobile .title-row .note-icon-widget .note-icon { + --icon-button-size: 24px; + } } From 911f78867f36851ec4e2a2737470c214b34107ad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 16:58:06 +0200 Subject: [PATCH 101/154] chore(mobile/header): make icons easier to press --- .../src/stylesheets/theme-next/shell.css | 2 +- apps/client/src/widgets/note_title.css | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 7ff132e1af..5a45ddf436 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -1322,7 +1322,7 @@ body.mobile .note-title { margin-inline-start: 0; } -.title-row { +body.desktop .title-row { /* Aligns the "Create new split" button with the note menu button (the three dots button) */ padding-inline-end: 3px; } diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index e525910521..3e1a0d9686 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -110,7 +110,27 @@ body.experimental-feature-new-layout { } } - &.mobile .title-row .note-icon-widget .note-icon { - --icon-button-size: 24px; + &.mobile .title-row { + .icon-action:not(.note-icon) { + height: 45px; + width: 45px; + flex-shrink: 0; + } + + .note-actions { + width: auto; + } + + .note-badges { + margin-inline: 0.5em; + } + + .note-icon-widget { + margin-inline: 0.5em; + + .note-icon { + --icon-button-size: 24px; + } + } } } From e9c90fcde8ee0e413cb04b63f43019916acf6393 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 17:02:32 +0200 Subject: [PATCH 102/154] chore(mobile/header): prevent badges from shrinking --- apps/client/src/widgets/note_title.css | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 3e1a0d9686..dc80a5ec3e 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -123,6 +123,7 @@ body.experimental-feature-new-layout { .note-badges { margin-inline: 0.5em; + flex-shrink: 0; } .note-icon-widget { From bbc5ebd76ba7df39b3369435601fe73f6fa1a3a6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 17:06:27 +0200 Subject: [PATCH 103/154] chore(mobile/header): improve button sizes --- apps/client/src/widgets/note_title.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index dc80a5ec3e..57f2619f2d 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -112,8 +112,8 @@ body.experimental-feature-new-layout { &.mobile .title-row { .icon-action:not(.note-icon) { - height: 45px; - width: 45px; + --icon-button-size: 45px; + --icon-button-icon-ratio: 0.5; flex-shrink: 0; } From 2d4022044d79b924d29f11d749da5a500d48c304 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 17:30:35 +0200 Subject: [PATCH 104/154] chore(mobile): get rid of floating buttons --- apps/client/src/layouts/mobile_layout.tsx | 3 --- apps/client/src/widgets/FloatingButtonsDefinitions.tsx | 8 -------- apps/client/src/widgets/dialogs/PopupEditor.tsx | 8 ++++---- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index 487532e5ca..27b0779921 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -7,8 +7,6 @@ import RootContainer from "../widgets/containers/root_container.js"; import ScrollingContainer from "../widgets/containers/scrolling_container.js"; import SplitNoteContainer from "../widgets/containers/split_note_container.js"; import FindWidget from "../widgets/find.js"; -import FloatingButtons from "../widgets/FloatingButtons.jsx"; -import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx"; import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; import InlineTitle from "../widgets/layout/InlineTitle.jsx"; import NoteBadges from "../widgets/layout/NoteBadges.jsx"; @@ -157,7 +155,6 @@ export default class MobileLayout { .child() .child() ) - .child() .child() .child( new ScrollingContainer() diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index be8a41c46c..0b337687a0 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -60,14 +60,6 @@ export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [ Backlinks ]; -export const MOBILE_FLOATING_BUTTONS: FloatingButtonsList = [ - RefreshBackendLogButton, - EditButton, - RelationMapButtons, - ExportImageButtons, - Backlinks -]; - /** * Floating buttons that should be hidden in popup editor (Quick edit). */ diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index e8e73ae785..a7f3fde393 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -5,13 +5,15 @@ import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import NoteContext from "../../components/note_context"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import froca from "../../services/froca"; import { t } from "../../services/i18n"; import tree from "../../services/tree"; import utils from "../../services/utils"; import NoteList from "../collections/NoteList"; import FloatingButtons from "../FloatingButtons"; -import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions"; +import { DESKTOP_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions"; +import NoteBadges from "../layout/NoteBadges"; import NoteIcon from "../note_icon"; import NoteTitleWidget from "../note_title"; import NoteDetail from "../NoteDetail"; @@ -23,8 +25,6 @@ import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar"; import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter"; import FormattingToolbar from "../ribbon/FormattingToolbar"; import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar"; -import NoteBadges from "../layout/NoteBadges"; -import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -34,7 +34,7 @@ export default function PopupEditor() { const [ noteContext, setNoteContext ] = useState(new NoteContext("_popup-editor")); const isMobile = utils.isMobile(); const items = useMemo(() => { - const baseItems = isMobile ? MOBILE_FLOATING_BUTTONS : DESKTOP_FLOATING_BUTTONS; + const baseItems = isMobile ? [] : DESKTOP_FLOATING_BUTTONS; return baseItems.filter(item => !POPUP_HIDDEN_FLOATING_BUTTONS.includes(item)); }, [ isMobile ]); From 411a59ec5425f9215e7299b1b10efadd89f291ca Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 17:46:18 +0200 Subject: [PATCH 105/154] feat(mobile): display custom note actions in note actions --- .../mobile_widgets/mobile_detail_menu.tsx | 2 + .../src/widgets/ribbon/NoteActionsCustom.tsx | 45 ++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 7db00d0bcd..91e16d62ab 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -5,6 +5,7 @@ import { openInAppHelpFromUrl } from "../../services/utils"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; import { NoteContextMenu } from "../ribbon/NoteActions"; +import NoteActionsCustom from "../ribbon/NoteActionsCustom"; export default function MobileDetailMenu() { const { note, noteContext, parentComponent, ntxId } = useNoteContext(); @@ -18,6 +19,7 @@ export default function MobileDetailMenu() { + {noteContext && ntxId && } noteContext?.notePath && note_create.createNote(noteContext.notePath)} icon="bx bx-plus" diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index edaf043bf7..4438561ec2 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -7,11 +7,11 @@ import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import { getHelpUrlForNote } from "../../services/in_app_help"; import { downloadFileNote, openNoteExternally } from "../../services/open"; -import { openInAppHelpFromUrl } from "../../services/utils"; +import { isMobile, openInAppHelpFromUrl } from "../../services/utils"; import { ViewTypeOptions } from "../collections/interface"; import { buildSaveSqlToNoteHandler } from "../FloatingButtonsDefinitions"; -import ActionButton from "../react/ActionButton"; -import { FormFileUploadActionButton } from "../react/FormFileUpload"; +import ActionButton, { ActionButtonProps } from "../react/ActionButton"; +import { FormListItem } from "../react/FormList"; import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab"; @@ -115,7 +115,7 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps onChange: (files: FileList | null) => void; }) { return ( - parentComponent?.triggerEvent("copyImageReferenceToClipboard", { ntxId })} @@ -161,7 +161,7 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not const isEnabled = (note.noteId === "_backendLog" || noteType === "render") && isDefaultViewMode; return (isEnabled && - parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })} @@ -174,7 +174,7 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: N const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation"); const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal"; - return isShown && setSplitEditorOrientation(upcomingOrientation)} @@ -188,7 +188,7 @@ export function ToggleReadOnlyButton({ note, isDefaultViewMode }: NoteActionsCus const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || isSavedSqlite) && note.isContentAvailable() && isDefaultViewMode; - return isEnabled && setReadOnly(!isReadOnly)} @@ -197,7 +197,7 @@ export function ToggleReadOnlyButton({ note, isDefaultViewMode }: NoteActionsCus function RunActiveNoteButton({ noteMime }: NoteActionsCustomInnerProps) { const isEnabled = noteMime.startsWith("application/javascript") || noteMime === "text/x-sqlite;schema=trilium"; - return isEnabled && openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} @@ -239,7 +239,7 @@ function InAppHelpButton({ note }: NoteActionsCustomInnerProps) { const isEnabled = !!helpUrl; return isEnabled && ( - helpUrl && openInAppHelpFromUrl(helpUrl)} @@ -249,7 +249,7 @@ function InAppHelpButton({ note }: NoteActionsCustomInnerProps) { function AddChildButton({ parentComponent, noteType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) { if (noteType === "relationMap") { - return parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })} @@ -258,3 +258,18 @@ function AddChildButton({ parentComponent, noteType, ntxId, isReadOnly }: NoteAc } } //#endregion +const cachedIsMobile = isMobile(); + +function NoteAction({ text, ...props }: Pick & { + onClick?: ((e: MouseEvent) => void) | undefined; +}) { + if (cachedIsMobile) { + return {text}; + } + return ; + +} + +function NoteActionWithFileUpload() { + return "Not implemented."; +} From 8a923700426f9b074333b96ebfd9fe4bf3a9b996 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 17:57:30 +0200 Subject: [PATCH 106/154] fix(mobile/custom_note_actions): missing separator --- apps/client/src/widgets/ribbon/NoteActionsCustom.css | 4 ++++ apps/client/src/widgets/ribbon/NoteActionsCustom.tsx | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 apps/client/src/widgets/ribbon/NoteActionsCustom.css diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.css b/apps/client/src/widgets/ribbon/NoteActionsCustom.css new file mode 100644 index 0000000000..e0ad0eb8fb --- /dev/null +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.css @@ -0,0 +1,4 @@ +body.mobile .note-actions-custom:not(:empty) { + margin-bottom: calc(var(--bs-dropdown-divider-margin-y) * 2); + border-top: 1px solid var(--bs-dropdown-divider-bg); +} diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index 4438561ec2..3a64e8c9c5 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -1,3 +1,5 @@ +import "./NoteActionsCustom.css"; + import { NoteType } from "@triliumnext/commons"; import { useContext, useEffect, useRef, useState } from "preact/hooks"; From 6a313b99e41590b01cc7b8f71b3b87e70a8a69c5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 18:02:47 +0200 Subject: [PATCH 107/154] fix(mobile/custom_note_actions): missing file upload --- .../src/widgets/react/FormFileUpload.tsx | 25 ++++++++++++++++++- apps/client/src/widgets/react/FormList.tsx | 2 +- .../src/widgets/ribbon/NoteActionsCustom.tsx | 17 +++++++------ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/react/FormFileUpload.tsx b/apps/client/src/widgets/react/FormFileUpload.tsx index e97e73184b..42d9c908eb 100644 --- a/apps/client/src/widgets/react/FormFileUpload.tsx +++ b/apps/client/src/widgets/react/FormFileUpload.tsx @@ -3,8 +3,9 @@ import { useEffect, useRef } from "preact/hooks"; import ActionButton, { ActionButtonProps } from "./ActionButton"; import Button, { ButtonProps } from "./Button"; +import { FormListItem, FormListItemOpts } from "./FormList"; -interface FormFileUploadProps { +export interface FormFileUploadProps { name?: string; onChange: (files: FileList | null) => void; multiple?: boolean; @@ -75,3 +76,25 @@ export function FormFileUploadActionButton({ onChange, ...buttonProps }: Omit ); } + +/** + * Similar to {@link FormFileUploadButton}, but uses an {@link FormListItem} instead of a normal {@link Button}. + * @param param the change listener for the file upload and the properties for the button. + */ +export function FormFileUploadFormListItem({ onChange, children, ...buttonProps }: Omit & Pick) { + const inputRef = useRef(null); + + return ( + <> + inputRef.current?.click()} + >{children} + - {helpUrl && <> - - openInAppHelpFromUrl(helpUrl)} - >{t("help-button.title")} - } {subContexts.length < 2 && <> Date: Mon, 2 Feb 2026 18:23:54 +0200 Subject: [PATCH 110/154] chore(mobile/custom_note_actions): disable split orientation button --- apps/client/src/widgets/ribbon/NoteActionsCustom.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index 7de9fdf08b..2864e50eaf 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -175,7 +175,7 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not } function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: NoteActionsCustomInnerProps) { - const isShown = note.type === "mermaid" && note.isContentAvailable() && isDefaultViewMode; + const isShown = note.type === "mermaid" && !cachedIsMobile && note.isContentAvailable() && isDefaultViewMode; const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation"); const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal"; From c8a0c9fd231c9cf2be0b857ddf517bc6f80ff0d5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 18:26:40 +0200 Subject: [PATCH 111/154] chore(mobile/custom_note_actions): text not fitting --- apps/client/src/translations/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 6bd13a3346..7ae81be1ec 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -745,7 +745,7 @@ "button_title": "Export diagram as SVG" }, "relation_map_buttons": { - "create_child_note_title": "Create new child note and add it into this relation map", + "create_child_note_title": "Create child note and add it to map", "reset_pan_zoom_title": "Reset pan & zoom to initial coordinates and magnification", "zoom_in_title": "Zoom In", "zoom_out_title": "Zoom Out" From 0d444daaca592d6e46f6a62f590c81777543e897 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 18:29:21 +0200 Subject: [PATCH 112/154] fix(mobile/custom_note_actions): note icon shown in empty note --- apps/client/src/widgets/note_icon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index bc4094439e..6c79bc8017 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -69,7 +69,7 @@ function MobileNoteIconSwitcher({ note, icon }: { const [ modalShown, setModalShown ] = useState(false); const { windowWidth } = useWindowSize(); - return ( + return (note &&
    Date: Mon, 2 Feb 2026 18:34:22 +0200 Subject: [PATCH 113/154] style/text editor: fix layout issues --- apps/client/src/widgets/containers/scrolling_container.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/containers/scrolling_container.css b/apps/client/src/widgets/containers/scrolling_container.css index 2a2adb1475..2cded53e6e 100644 --- a/apps/client/src/widgets/containers/scrolling_container.css +++ b/apps/client/src/widgets/containers/scrolling_container.css @@ -4,11 +4,14 @@ position: relative; > .inline-title, - > .note-detail > .note-detail-editable-text, + > .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor, > .note-list-widget:not(.full-height) .note-list-wrapper { padding-inline: 24px; } + > .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor { + overflow: unset; + } } .note-split.type-code:not(.mime-text-x-sqlite) { From 76f36e2fd32f3352d9267ac7320e361519e78d99 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 18:39:40 +0200 Subject: [PATCH 114/154] fix(mobile/custom_note_actions): unable to close empty pane --- .../mobile_widgets/mobile_detail_menu.tsx | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index ed69af6f25..417f69e89b 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,7 +1,6 @@ import { t } from "../../services/i18n"; -import { getHelpUrlForNote } from "../../services/in_app_help"; import note_create from "../../services/note_create"; -import { openInAppHelpFromUrl } from "../../services/utils"; +import ActionButton from "../react/ActionButton"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; import { NoteContextMenu } from "../ribbon/NoteActions"; @@ -12,9 +11,16 @@ export default function MobileDetailMenu() { const subContexts = noteContext?.getMainContext().getSubContexts() ?? []; const isMainContext = noteContext?.isMainContext(); + function closePane() { + // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. + requestAnimationFrame(() => { + parentComponent.triggerCommand("closeThisNoteSplit", { ntxId }); + }); + } + return (
    - {note && ( + {note ? ( @@ -34,17 +40,18 @@ export default function MobileDetailMenu() { { - // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. - requestAnimationFrame(() => { - parentComponent.triggerCommand("closeThisNoteSplit", { ntxId }); - }); - }} + onClick={closePane} >{t("close_pane_button.close_this_pane")} } } /> + ) : ( + )}
    ); From a9c5b99ae862979717e117d9bf40f673bc5297f6 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 2 Feb 2026 20:06:05 +0200 Subject: [PATCH 115/154] style/text editor/block toolbar button: fix position and z-index --- apps/client/src/stylesheets/theme-next/notes/text.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next/notes/text.css b/apps/client/src/stylesheets/theme-next/notes/text.css index dc025bb66d..a291360c82 100644 --- a/apps/client/src/stylesheets/theme-next/notes/text.css +++ b/apps/client/src/stylesheets/theme-next/notes/text.css @@ -47,9 +47,11 @@ } /* The toolbar show / hide button for the current text block */ -.ck.ck-block-toolbar-button { +:root .ck.ck-block-toolbar-button { --ck-color-button-on-background: transparent; --ck-color-button-on-color: currentColor; + translate: -30% 0; + z-index: 5000; } :root .ck.ck-toolbar .ck-button:not(.ck-disabled):active, From 12b641b5221f09b97c5ef139652611de43ee1dc7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 20:10:20 +0200 Subject: [PATCH 116/154] feat(mobile/note_actions): basic integration of backlinks --- .../mobile_widgets/mobile_detail_menu.tsx | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 417f69e89b..c01e6051e3 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,15 +1,22 @@ +import { createPortal, useState } from "preact/compat"; + +import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; +import { ViewScope } from "../../services/link"; import note_create from "../../services/note_create"; +import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import ActionButton from "../react/ActionButton"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; +import Modal from "../react/Modal"; import { NoteContextMenu } from "../ribbon/NoteActions"; import NoteActionsCustom from "../ribbon/NoteActionsCustom"; export default function MobileDetailMenu() { - const { note, noteContext, parentComponent, ntxId } = useNoteContext(); + const { note, noteContext, parentComponent, ntxId, viewScope } = useNoteContext(); const subContexts = noteContext?.getMainContext().getSubContexts() ?? []; const isMainContext = noteContext?.isMainContext(); + const [ modalShown, setModalShown ] = useState(false); function closePane() { // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. @@ -24,6 +31,8 @@ export default function MobileDetailMenu() { + + {noteContext && ntxId && } noteContext?.notePath && note_create.createNote(noteContext.notePath)} @@ -53,6 +62,38 @@ export default function MobileDetailMenu() { text={t("close_pane_button.close_this_pane")} /> )} + + {createPortal(( + + ), document.body)}
    ); } + +function Backlinks({ note, viewScope, setModalShown }: { note: FNote, viewScope?: ViewScope, setModalShown: (shown: boolean) => void }) { + const count = useBacklinkCount(note, viewScope?.viewMode === "default"); + + return count > 0 && ( + <> + setModalShown(true)} + >{t("status_bar.backlinks", { count })} + + + ); +} + +function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined, modalShown: boolean, setModalShown: (shown: boolean) => void }) { + return ( + setModalShown(false)} + > + {note && } + + ); +} From 348c00f86d98d55f6053596a0ede92dfc0cef74f Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 2 Feb 2026 20:13:00 +0200 Subject: [PATCH 117/154] style/text editor: fix layout issues --- apps/client/src/widgets/containers/scrolling_container.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/containers/scrolling_container.css b/apps/client/src/widgets/containers/scrolling_container.css index 2cded53e6e..a4be33cf29 100644 --- a/apps/client/src/widgets/containers/scrolling_container.css +++ b/apps/client/src/widgets/containers/scrolling_container.css @@ -6,7 +6,7 @@ > .inline-title, > .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor, > .note-list-widget:not(.full-height) .note-list-wrapper { - padding-inline: 24px; + margin-inline: 24px; } > .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor { From 220ca8a57087ad47c61072c64a03e5c7a54b054e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 20:28:40 +0200 Subject: [PATCH 118/154] chore(mobile/note_actions): use new layout styling for backlinks --- apps/client/src/widgets/Backlinks.css | 83 +++++++++++++++++++ .../widgets/FloatingButtonsDefinitions.tsx | 2 + apps/client/src/widgets/layout/StatusBar.css | 78 ----------------- apps/client/src/widgets/layout/StatusBar.tsx | 2 +- .../mobile_widgets/mobile_detail_menu.tsx | 6 +- 5 files changed, 90 insertions(+), 81 deletions(-) create mode 100644 apps/client/src/widgets/Backlinks.css diff --git a/apps/client/src/widgets/Backlinks.css b/apps/client/src/widgets/Backlinks.css new file mode 100644 index 0000000000..edcd9097de --- /dev/null +++ b/apps/client/src/widgets/Backlinks.css @@ -0,0 +1,83 @@ +.tn-backlinks-widget .backlinks-items { + list-style-type: none; + margin: 0; + padding: 0; + position: static; + width: unset; + + > li { + --border-radius: 8px; + + max-width: 600px; + padding: 10px 20px; + background: var(--card-background-color); + + & + li { + margin-top: 2px; + } + + &:first-child { + border-radius: var(--border-radius) var(--border-radius) 0 0; + } + + &:last-child { + border-radius: 0 0 var(--border-radius) var(--border-radius); + } + + /* Card header */ + & > span:first-child { + display: block; + + > span { + display: flex; + flex-wrap: wrap; + align-items: center; + + /* Note path */ + > small { + flex: 100%; + order: -1; + font-size: .65rem; + + .note-path { + padding: 0; + } + } + + /* Note icon */ + > .tn-icon { + color: var(--menu-item-icon-color); + } + + /* Note title */ + > a { + margin-inline-start: 4px; + color: currentColor; + font-weight: 500; + } + } + } + + /* Card content - excerpt */ + & > span:nth-child(2) > div { + all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */ + display: block; + + margin: 8px 0; + border-radius: 4px; + background: var(--quick-search-result-content-background); + padding: 8px; + font-size: .75rem; + + a { + background: transparent; + color: var(--quick-search-result-highlight-color); + text-decoration: underline; + } + + p { + margin: 0; + } + } + } +} diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 0b337687a0..5d19389cf1 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -1,3 +1,5 @@ +import "./Backlinks.css"; + import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons"; import { VNode } from "preact"; import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index c421c3d65e..b3fe412d95 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -160,84 +160,6 @@ max-height: 60vh; overflow-y: scroll; - - /* Backlink card */ - li { - --border-radius: 8px; - - max-width: 600px; - padding: 10px 20px; - background: var(--card-background-color); - - & + li { - margin-top: 2px; - } - - &:first-child { - border-radius: var(--border-radius) var(--border-radius) 0 0; - } - - &:last-child { - border-radius: 0 0 var(--border-radius) var(--border-radius); - } - - /* Card header */ - & > span:first-child { - display: block; - - > span { - display: flex; - flex-wrap: wrap; - align-items: center; - - /* Note path */ - > small { - flex: 100%; - order: -1; - font-size: .65rem; - - .note-path { - padding: 0; - } - } - - /* Note icon */ - > .tn-icon { - color: var(--menu-item-icon-color); - } - - /* Note title */ - > a { - margin-inline-start: 4px; - color: currentColor; - font-weight: 500; - } - } - } - - /* Card content - excerpt */ - & > span:nth-child(2) > div { - all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */ - display: block; - - margin: 8px 0; - border-radius: 4px; - background: var(--quick-search-result-content-background); - padding: 8px; - font-size: .75rem; - - a { - background: transparent; - color: var(--quick-search-result-highlight-color); - text-decoration: underline; - } - - p { - margin: 0; - } - } - - } } } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 903aeca3b2..d05fa113df 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -300,7 +300,7 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) { const count = useBacklinkCount(note, viewScope?.viewMode === "default"); return (note && count > 0 && setModalShown(true)} + onClick={() => setModalShown(true)} >{t("status_bar.backlinks", { count })} @@ -93,7 +93,9 @@ function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | nul show={modalShown} onHidden={() => setModalShown(false)} > - {note && } +
      + {note && } +
    ); } From 79649805b8e9c5818bec771769e8be793b74e9d5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 20:34:52 +0200 Subject: [PATCH 119/154] chore(mobile/note_actions): missing translation for backlinks --- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 7ae81be1ec..3c2c5f6c0f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -760,7 +760,8 @@ "delete_this_note": "Delete this note", "note_revisions": "Note revisions", "error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'", - "error_unrecognized_command": "Unrecognized command {{command}}" + "error_unrecognized_command": "Unrecognized command {{command}}", + "backlinks": "Backlinks" }, "note_icon": { "change_note_icon": "Change note icon", diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 1bac0abfc0..8d2d2a3f67 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -89,7 +89,7 @@ function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | nul setModalShown(false)} > From 6c163b547952dd109451234ca051264ad0782ec4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 20:39:37 +0200 Subject: [PATCH 120/154] chore(mobile/note_actions): flickerless backlinks item --- apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 8d2d2a3f67..af4a6cabc1 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -73,11 +73,12 @@ export default function MobileDetailMenu() { function Backlinks({ note, viewScope, setModalShown }: { note: FNote, viewScope?: ViewScope, setModalShown: (shown: boolean) => void }) { const count = useBacklinkCount(note, viewScope?.viewMode === "default"); - return count > 0 && ( + return ( <> setModalShown(true)} + disabled={count === 0} >{t("status_bar.backlinks", { count })} From ba17ec4be41540717fbb58b953b262ad85eeab3f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 20:44:48 +0200 Subject: [PATCH 121/154] chore(backlinks): show multiple excerpts properly --- apps/client/src/widgets/Backlinks.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/Backlinks.css b/apps/client/src/widgets/Backlinks.css index edcd9097de..9e8ced45ba 100644 --- a/apps/client/src/widgets/Backlinks.css +++ b/apps/client/src/widgets/Backlinks.css @@ -59,7 +59,7 @@ } /* Card content - excerpt */ - & > span:nth-child(2) > div { + .backlink-excerpt { all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */ display: block; From facd56cdf4f4311f3bee3dcdd7eec049a3dfdef7 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 2 Feb 2026 20:57:38 +0200 Subject: [PATCH 122/154] style/text editor/block toolbar button: tweak --- apps/client/src/stylesheets/theme-next/notes/text.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/notes/text.css b/apps/client/src/stylesheets/theme-next/notes/text.css index a291360c82..91fe7f9b1b 100644 --- a/apps/client/src/stylesheets/theme-next/notes/text.css +++ b/apps/client/src/stylesheets/theme-next/notes/text.css @@ -48,10 +48,13 @@ /* The toolbar show / hide button for the current text block */ :root .ck.ck-block-toolbar-button { + --ck-color-block-toolbar-button: var(--muted-text-color); --ck-color-button-on-background: transparent; - --ck-color-button-on-color: currentColor; - translate: -30% 0; - z-index: 5000; + --ck-color-button-on-color: var(--ck-editor-toolbar-button-on-color); + translate: -40% 0; + min-width: 0; + padding: 0; + z-index: 1600; } :root .ck.ck-toolbar .ck-button:not(.ck-disabled):active, From 171d948a0065484d979b93b7e5550d5105233794 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 21:00:05 +0200 Subject: [PATCH 123/154] feat(mobile/note_actions): basic integration of note paths --- apps/client/src/widgets/layout/StatusBar.tsx | 2 +- .../mobile_widgets/mobile_detail_menu.tsx | 57 ++++++++++++++----- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index d05fa113df..bfc9b02648 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -56,7 +56,7 @@ export default function StatusBar() { similarNotesShown: activePane === "similar-notes", setSimilarNotesShown: (shown) => setActivePane(shown && "similar-notes") }; - const isHiddenNote = note?.isInHiddenSubtree(); + const isHiddenNote = note?.isHiddenCompletely(); return (
    diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index af4a6cabc1..8116eceb6e 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,6 +1,6 @@ import { createPortal, useState } from "preact/compat"; -import FNote from "../../entities/fnote"; +import FNote, { NotePathRecord } from "../../entities/fnote"; import { t } from "../../services/i18n"; import { ViewScope } from "../../services/link"; import note_create from "../../services/note_create"; @@ -11,12 +11,15 @@ import { useNoteContext } from "../react/hooks"; import Modal from "../react/Modal"; import { NoteContextMenu } from "../ribbon/NoteActions"; import NoteActionsCustom from "../ribbon/NoteActionsCustom"; +import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab"; export default function MobileDetailMenu() { - const { note, noteContext, parentComponent, ntxId, viewScope } = useNoteContext(); + const { note, noteContext, parentComponent, ntxId, viewScope, hoistedNoteId } = useNoteContext(); const subContexts = noteContext?.getMainContext().getSubContexts() ?? []; const isMainContext = noteContext?.isMainContext(); - const [ modalShown, setModalShown ] = useState(false); + const [ backlinksModalShown, setBacklinksModalShown ] = useState(false); + const [ notePathsModalShown, setNotePathsModalShown ] = useState(false); + const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId); function closePane() { // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. @@ -31,7 +34,14 @@ export default function MobileDetailMenu() { - + + + setNotePathsModalShown(true)} + disabled={(sortedNotePaths?.length ?? 0) <= 1} + >{t("status_bar.note_paths", { count: sortedNotePaths?.length })} + {noteContext && ntxId && } + <> + + + ), document.body)}
    ); @@ -74,14 +87,11 @@ function Backlinks({ note, viewScope, setModalShown }: { note: FNote, viewScope? const count = useBacklinkCount(note, viewScope?.viewMode === "default"); return ( - <> - setModalShown(true)} - disabled={count === 0} - >{t("status_bar.backlinks", { count })} - - + setModalShown(true)} + disabled={count === 0} + >{t("status_bar.backlinks", { count })} ); } @@ -100,3 +110,24 @@ function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | nul
    ); } + +function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalShown }: { note: FNote | null | undefined, modalShown: boolean, sortedNotePaths: NotePathRecord[] | undefined, notePath: string | null | undefined, setModalShown: (shown: boolean) => void }) { + return ( + setModalShown(false)} + > +
      + {note && ( + + )} +
    +
    + ); +} From 5db298f031638e1c48391cf54efffe1ac199e396 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 2 Feb 2026 21:03:02 +0200 Subject: [PATCH 124/154] style/quick edit: increase the horizontal padding of the text to make space for the block toolbar button --- apps/client/src/widgets/dialogs/PopupEditor.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index 197ed94065..262143ceaf 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -92,7 +92,7 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { } .modal.popup-editor-dialog .note-detail-editable-text { - padding: 0 1em; + padding: 0 28px; } .modal.popup-editor-dialog .note-detail-file { From c7381d058a9502d7b4cb441f9ca410ca94dceb71 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 21:05:25 +0200 Subject: [PATCH 125/154] feat(mobile/note_actions): proper styling of note paths --- apps/client/src/widgets/layout/StatusBar.css | 62 ------------------ .../mobile_widgets/mobile_detail_menu.tsx | 14 ++--- .../src/widgets/ribbon/NotePathsTab.css | 63 +++++++++++++++++++ .../src/widgets/ribbon/NotePathsTab.tsx | 13 ++-- 4 files changed, 76 insertions(+), 76 deletions(-) create mode 100644 apps/client/src/widgets/ribbon/NotePathsTab.css diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index b3fe412d95..6418630dba 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -91,68 +91,6 @@ .note-paths-widget { padding: 0.5em; } - - .note-path-intro { - color: var(--muted-text-color); - } - - .note-path-list { - margin: 12px 0; - padding: 0; - list-style: none; - - /* Note path card */ - li { - --border-radius: 6px; - - position: relative; - background: var(--card-background-color); - padding: 8px 20px 8px 25px; - - &:first-child { - border-radius: var(--border-radius) var(--border-radius) 0 0; - } - - &:last-child { - border-radius: 0 0 var(--border-radius) var(--border-radius); - } - - & + li { - margin-top: 2px; - } - - /* Current path arrow */ - &.path-current::before { - position: absolute; - display: flex; - justify-content: flex-end; - align-items: center; - content: "\ee8f"; - top: 0; - left: 0; - width: 20px; - bottom: 0; - font-family: "boxicons"; - font-size: .75em; - color: var(--menu-item-icon-color); - } - } - - /* Note path segment */ - a { - margin-inline: 2px; - padding-inline: 2px; - color: currentColor; - font-weight: normal; - text-decoration: none; - - /* The last segment of the note path */ - &.basename { - color: var(--muted-text-color); - } - } - - } } .backlinks-widget > .dropdown-menu { diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 8116eceb6e..d1a1977404 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -120,14 +120,12 @@ function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalS show={modalShown} onHidden={() => setModalShown(false)} > -
      - {note && ( - - )} -
    + {note && ( + + )} ); } diff --git a/apps/client/src/widgets/ribbon/NotePathsTab.css b/apps/client/src/widgets/ribbon/NotePathsTab.css new file mode 100644 index 0000000000..64c7374480 --- /dev/null +++ b/apps/client/src/widgets/ribbon/NotePathsTab.css @@ -0,0 +1,63 @@ +body.experimental-feature-new-layout .note-paths-widget { + .note-path-intro { + color: var(--muted-text-color); + } + + .note-path-list { + margin: 12px 0; + padding: 0; + list-style: none; + + /* Note path card */ + li { + --border-radius: 6px; + + position: relative; + background: var(--card-background-color); + padding: 8px 20px 8px 25px; + + &:first-child { + border-radius: var(--border-radius) var(--border-radius) 0 0; + } + + &:last-child { + border-radius: 0 0 var(--border-radius) var(--border-radius); + } + + & + li { + margin-top: 2px; + } + + /* Current path arrow */ + &.path-current::before { + position: absolute; + display: flex; + justify-content: flex-end; + align-items: center; + content: "\ee8f"; + top: 0; + left: 0; + width: 20px; + bottom: 0; + font-family: "boxicons"; + font-size: .75em; + color: var(--menu-item-icon-color); + } + } + + /* Note path segment */ + a { + margin-inline: 2px; + padding-inline: 2px; + color: currentColor; + font-weight: normal; + text-decoration: none; + + /* The last segment of the note path */ + &.basename { + color: var(--muted-text-color); + } + } + + } +} diff --git a/apps/client/src/widgets/ribbon/NotePathsTab.tsx b/apps/client/src/widgets/ribbon/NotePathsTab.tsx index 19b361a5de..0b81ebe036 100644 --- a/apps/client/src/widgets/ribbon/NotePathsTab.tsx +++ b/apps/client/src/widgets/ribbon/NotePathsTab.tsx @@ -1,15 +1,16 @@ +import "./NotePathsTab.css"; + +import clsx from "clsx"; import { useEffect, useMemo, useState } from "preact/hooks"; import FNote, { NotePathRecord } from "../../entities/fnote"; import { t } from "../../services/i18n"; import { NOTE_PATH_TITLE_SEPARATOR } from "../../services/tree"; import { useTriliumEvent } from "../react/hooks"; +import LinkButton from "../react/LinkButton"; import NoteLink from "../react/NoteLink"; import { joinElements } from "../react/react_utils"; import { TabContext } from "./ribbon-interface"; -import LinkButton from "../react/LinkButton"; -import clsx from "clsx"; - export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabContext) { const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId); @@ -112,9 +113,9 @@ function NotePath({ currentNotePath, notePathRecord }: { currentNotePath?: strin
  • {joinElements(fullNotePaths.map((notePath, index, arr) => ( + className={clsx({"basename": (index === arr.length - 1)})} + notePath={notePath} + noPreview /> )), NOTE_PATH_TITLE_SEPARATOR)} {icons.map(({ icon, title }) => ( From 979fa0359a28c3df771c1579d86aecbfc74f22fd Mon Sep 17 00:00:00 2001 From: hulmgulm Date: Mon, 2 Feb 2026 20:08:55 +0100 Subject: [PATCH 126/154] updated texts --- .vscode/settings.json | 5 ++++- .../User Guide/Advanced Usage/Attributes/Labels.html | 8 +++++--- docs/Developer Guide/Developer Guide/Documentation.md | 2 +- docs/Developer Guide/Developer Guide/Environment Setup.md | 2 +- .../User Guide/Advanced Usage/Attributes/Labels.md | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 57d22dcb8e..974a4ff64e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,5 +42,8 @@ }, "eslint.rules.customizations": [ { "rule": "*", "severity": "warn" } + ], + "cSpell.words": [ + "Trilium" ] -} \ No newline at end of file +} diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html index d79004f8bc..8757e0085b 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html @@ -357,7 +357,9 @@ subtreeHidden - Hides all child notes of this note from the tree, displaying a badge with the count of hidden children. Children remain accessible via search or direct links. + Hides all child notes of this note from the tree, displaying a badge with + the count of hidden children. Children remain accessible via search or + direct links. printLandscape @@ -380,7 +382,7 @@ map:* - Defines specific options for the Geo Map View. + Defines specific options for the Geo Map. calendar:* @@ -394,4 +396,4 @@ href="#root/_help_0ESUbbAxVnoK">Note List for more information. - + \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Documentation.md b/docs/Developer Guide/Developer Guide/Documentation.md index bf9ed0c44c..16d1494028 100644 --- a/docs/Developer Guide/Developer Guide/Documentation.md +++ b/docs/Developer Guide/Developer Guide/Documentation.md @@ -1,5 +1,5 @@ # Documentation -There are multiple types of documentation for Trilium: +There are multiple types of documentation for Trilium: * The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing F1. * The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers. diff --git a/docs/Developer Guide/Developer Guide/Environment Setup.md b/docs/Developer Guide/Developer Guide/Environment Setup.md index bf7f44adb8..f958b4eec0 100644 --- a/docs/Developer Guide/Developer Guide/Environment Setup.md +++ b/docs/Developer Guide/Developer Guide/Environment Setup.md @@ -24,7 +24,7 @@ As a quick heads-up of some differences when compared to `npm`: ## Installing dependencies -Run `pnpm i` at the top of the `Notes` repository to install the dependencies. +Run `pnpm i` at the top of the `Trilium` repository to install the dependencies. > [!NOTE] > Dependencies are kept up to date periodically in the project. Generally it's a good rule to do `pnpm i` after each `git pull` on the main branch. diff --git a/docs/User Guide/User Guide/Advanced Usage/Attributes/Labels.md b/docs/User Guide/User Guide/Advanced Usage/Attributes/Labels.md index cb68dab93d..89f1445ecd 100644 --- a/docs/User Guide/User Guide/Advanced Usage/Attributes/Labels.md +++ b/docs/User Guide/User Guide/Advanced Usage/Attributes/Labels.md @@ -39,4 +39,4 @@ This is a list of labels that Trilium natively supports. > [!TIP] > Some labels presented here end with a `*`. That means that there are multiple labels with the same prefix, consult the specific page linked in the description of that label for more information. -
    LabelDescription
    disableVersioningDisables automatic creation of Note Revisions for a particular note. Useful for e.g. large, but unimportant notes - e.g. large JS libraries used for scripting.
    versioningLimitLimits the maximum number of Note Revisions for a particular note, overriding the global settings.
    calendarRootMarks the note which should be used as root for Day Notes. Only one should be marked as such.
    archivedHides notes from default search results and dialogs. Archived notes can optionally be hidden in the Note Tree.
    excludeFromExportExcludes this note and its children when exporting.
    run, runOnInstance, runAtHourSee Events.
    disableInclusionScripts with this label won't be included into parent script execution.
    sorted

    Keeps child notes sorted by title alphabetically.

    When given a value, it will sort by the value of another label instead. If one of the child notes doesn't have the specified label, the title will be used for them instead.

    sortDirection

    If sorted is applied, specifies the direction of the sort:

    • ASC, ascending (default)
    • DESC, descending
    sortFoldersFirstIf sorted is applied, folders (notes with children) will be sorted as a group at the top, and the rest will be sorted.
    topIf sorted is applied to the parent note, keeps given note on top in its parent.
    hidePromotedAttributesHide Promoted Attributes on this note. Generally useful when defining inherited attributes, but the parent note doesn't need them.
    readOnlyMarks a note to be always be read-only, if it's a supported note (text, code, mermaid).
    autoReadOnlyDisabledDisables automatic read-only mode for the given note.
    appCssMarks CSS notes which are loaded into the Trilium application and can thus be used to modify Trilium's looks. See Custom app-wide CSS for more info.
    appThemeMarks CSS notes which are full Trilium themes and are thus available in Trilium options. See Theme development for more information.
    appThemeBaseSet to next, next-light, or next-dark to use the corresponding TriliumNext theme (auto, light or dark) as the base for a custom theme, instead of the legacy one. See Customize the Next theme for more information.
    cssClassValue of this label is then added as CSS class to the node representing given note in the Note Tree. This can be useful for advanced theming. Can be used in template notes.
    iconClassvalue of this label is added as a CSS class to the icon on the tree which can help visually distinguish the notes in the tree. Example might be bx bx-home - icons are taken from boxicons. Can be used in template notes.
    pageSizeSpecifies the number of items per page in Note List.
    customRequestHandlerSee Custom Request Handler.
    customResourceProviderSee Custom Resource Providers.
    widgetMarks this note as a custom widget which will be added to the Trilium component tree. See Custom Widgets for more information.
    searchHomeNew search notes will be created as children of this note (see Saved Search).
    workspace and related attributesSee Workspaces.
    inboxdefault inbox location for new notes - when you create a note using new note button in the sidebar, notes will be created as child notes in the note marked as with #inbox label.
    sqlConsoleHomeDefault location of SQL Console notes
    bookmarkedIndicates this note is a bookmark.
    bookmarkFolderNote with this label will appear in bookmarks as folder (allowing access to its children). See Bookmarks for more information.
    share*See the attribute reference in Sharing.
    displayRelations, hideRelationsComma delimited names of relations which should be displayed/hidden in a Relation Map (both the note type and the Note Map (Link map, Tree map) general functionality).
    titleTemplate

    Default title of notes created as children of this note. This value is evaluated as a JavaScript string and thus can be enriched with dynamic content via the injected now and parentNote variables.

    Examples:

    • \({parentNote.getLabel('authorName')}'s literary works
    • Log for \){now.format('YYYY-MM-DD HH:mm:ss')}
    • to mirror the parent's template.

    See Default Note Title for more info.

    templateThis note will appear in the selection of available template when creating new note. See Templates for more information.
    tocControls the display of the Table of contents for a given note. #toc or #toc=show to always display the table of contents, #toc=false to always hide it.
    colordefines color of the note in note tree, links etc. Use any valid CSS color value like 'red' or #a13d5f
    Note: this color may be automatically adjusted when displayed to ensure sufficient contrast with the background.
    keyboardShortcutDefines a keyboard shortcut which will immediately jump to this note. Example: 'ctrl+alt+e'. Requires frontend reload for the change to take effect.
    keepCurrentHoistingOpening this link won't change hoisting even if the note is not displayable in the current hoisted subtree.
    executeButtonTitle of the button which will execute the current code note
    executeDescriptionLonger description of the current code note displayed together with the execute button
    excludeFromNoteMapNotes with this label will be hidden from the Note Map.
    newNotesOnTopNew notes will be created at the top of the parent note, not on the bottom.
    hideHighlightWidgetHides the Highlights list widget
    hideChildrenOverviewHides the Note List for that particular note.
    printLandscapeWhen exporting to PDF, changes the orientation of the page to landscape instead of portrait.
    printPageSizeWhen exporting to PDF, changes the size of the page. Supported values: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.
    geolocationIndicates the latitude and longitude of a note, to be displayed in a Geo Map.
    calendar:*Defines specific options for the Calendar View.
    viewTypeSets the view of child notes (e.g. grid or list). See Note List for more information.
    \ No newline at end of file +
    LabelDescription
    disableVersioningDisables automatic creation of Note Revisions for a particular note. Useful for e.g. large, but unimportant notes - e.g. large JS libraries used for scripting.
    versioningLimitLimits the maximum number of Note Revisions for a particular note, overriding the global settings.
    calendarRootMarks the note which should be used as root for Day Notes. Only one should be marked as such.
    archivedHides notes from default search results and dialogs. Archived notes can optionally be hidden in the Note Tree.
    excludeFromExportExcludes this note and its children when exporting.
    run, runOnInstance, runAtHourSee Events.
    disableInclusionScripts with this label won't be included into parent script execution.
    sorted

    Keeps child notes sorted by title alphabetically.

    When given a value, it will sort by the value of another label instead. If one of the child notes doesn't have the specified label, the title will be used for them instead.

    sortDirection

    If sorted is applied, specifies the direction of the sort:

    • ASC, ascending (default)
    • DESC, descending
    sortFoldersFirstIf sorted is applied, folders (notes with children) will be sorted as a group at the top, and the rest will be sorted.
    topIf sorted is applied to the parent note, keeps given note on top in its parent.
    hidePromotedAttributesHide Promoted Attributes on this note. Generally useful when defining inherited attributes, but the parent note doesn't need them.
    readOnlyMarks a note to be always be read-only, if it's a supported note (text, code, mermaid).
    autoReadOnlyDisabledDisables automatic read-only mode for the given note.
    appCssMarks CSS notes which are loaded into the Trilium application and can thus be used to modify Trilium's looks. See Custom app-wide CSS for more info.
    appThemeMarks CSS notes which are full Trilium themes and are thus available in Trilium options. See Theme development for more information.
    appThemeBaseSet to next, next-light, or next-dark to use the corresponding TriliumNext theme (auto, light or dark) as the base for a custom theme, instead of the legacy one. See Customize the Next theme for more information.
    cssClassValue of this label is then added as CSS class to the node representing given note in the Note Tree. This can be useful for advanced theming. Can be used in template notes.
    iconClassvalue of this label is added as a CSS class to the icon on the tree which can help visually distinguish the notes in the tree. Example might be bx bx-home - icons are taken from boxicons. Can be used in template notes.
    pageSizeSpecifies the number of items per page in Note List.
    customRequestHandlerSee Custom Request Handler.
    customResourceProviderSee Custom Resource Providers.
    widgetMarks this note as a custom widget which will be added to the Trilium component tree. See Custom Widgets for more information.
    searchHomeNew search notes will be created as children of this note (see Saved Search).
    workspace and related attributesSee Workspaces.
    inboxdefault inbox location for new notes - when you create a note using new note button in the sidebar, notes will be created as child notes in the note marked as with #inbox label.
    sqlConsoleHomeDefault location of SQL Console notes
    bookmarkedIndicates this note is a bookmark.
    bookmarkFolderNote with this label will appear in bookmarks as folder (allowing access to its children). See Bookmarks for more information.
    share*See the attribute reference in Sharing.
    displayRelations, hideRelationsComma delimited names of relations which should be displayed/hidden in a Relation Map (both the note type and the Note Map (Link map, Tree map) general functionality).
    titleTemplate

    Default title of notes created as children of this note. This value is evaluated as a JavaScript string and thus can be enriched with dynamic content via the injected now and parentNote variables.

    Examples:

    • \({parentNote.getLabel('authorName')}'s literary works
    • Log for \){now.format('YYYY-MM-DD HH:mm:ss')}
    • to mirror the parent's template.

    See Default Note Title for more info.

    templateThis note will appear in the selection of available template when creating new note. See Templates for more information.
    tocControls the display of the Table of contents for a given note. #toc or #toc=show to always display the table of contents, #toc=false to always hide it.
    colordefines color of the note in note tree, links etc. Use any valid CSS color value like 'red' or #a13d5f
    Note: this color may be automatically adjusted when displayed to ensure sufficient contrast with the background.
    keyboardShortcutDefines a keyboard shortcut which will immediately jump to this note. Example: 'ctrl+alt+e'. Requires frontend reload for the change to take effect.
    keepCurrentHoistingOpening this link won't change hoisting even if the note is not displayable in the current hoisted subtree.
    executeButtonTitle of the button which will execute the current code note
    executeDescriptionLonger description of the current code note displayed together with the execute button
    excludeFromNoteMapNotes with this label will be hidden from the Note Map.
    newNotesOnTopNew notes will be created at the top of the parent note, not on the bottom.
    hideHighlightWidgetHides the Highlights list widget
    hideChildrenOverviewHides the Note List for that particular note.
    subtreeHiddenHides all child notes of this note from the tree, displaying a badge with the count of hidden children. Children remain accessible via search or direct links.
    printLandscapeWhen exporting to PDF, changes the orientation of the page to landscape instead of portrait.
    printPageSizeWhen exporting to PDF, changes the size of the page. Supported values: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.
    geolocationIndicates the latitude and longitude of a note, to be displayed in a Geo Map.
    map:*Defines specific options for the Geo Map.
    calendar:*Defines specific options for the Calendar View.
    viewTypeSets the view of child notes (e.g. grid or list). See Note List for more information.
    \ No newline at end of file From 49d33ea19ab72c1b9fb707e37ba406f799f6e6c7 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 2 Feb 2026 21:10:00 +0200 Subject: [PATCH 127/154] style/quick edit: allow object selection rectangle to go outside of the editor area --- apps/client/src/widgets/dialogs/PopupEditor.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index 262143ceaf..f20a12bf30 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -95,6 +95,10 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { padding: 0 28px; } +.modal.popup-editor-dialog .note-detail-editable-text-editor { + overflow: visible; /* Allow selection rectangle to go outside of the editor area */ +} + .modal.popup-editor-dialog .note-detail-file { padding: 0; } From 7340709111abe4a2b1fa3ed86852164ce16b7d27 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 2 Feb 2026 21:13:16 +0200 Subject: [PATCH 128/154] style/quick edit: refactor --- apps/client/src/widgets/dialogs/PopupEditor.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index f20a12bf30..3357b00f3e 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -91,11 +91,8 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { height: 100%; } -.modal.popup-editor-dialog .note-detail-editable-text { - padding: 0 28px; -} - .modal.popup-editor-dialog .note-detail-editable-text-editor { + margin: 0 28px; overflow: visible; /* Allow selection rectangle to go outside of the editor area */ } From c02642d0f9eae0933871fad521a8698d3dc38df5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 21:16:02 +0200 Subject: [PATCH 129/154] feat(mobile/note_actions): display backlinks & note paths on same row --- .../mobile_widgets/mobile_detail_menu.tsx | 36 +++++++++---------- apps/client/src/widgets/react/FormList.css | 10 ++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index d1a1977404..dc8eb0415e 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -20,6 +20,7 @@ export default function MobileDetailMenu() { const [ backlinksModalShown, setBacklinksModalShown ] = useState(false); const [ notePathsModalShown, setNotePathsModalShown ] = useState(false); const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId); + const backlinksCount = useBacklinkCount(note, viewScope?.viewMode === "default"); function closePane() { // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. @@ -34,13 +35,22 @@ export default function MobileDetailMenu() { - - - setNotePathsModalShown(true)} - disabled={(sortedNotePaths?.length ?? 0) <= 1} - >{t("status_bar.note_paths", { count: sortedNotePaths?.length })} +
    +
    + setBacklinksModalShown(true)} + disabled={backlinksCount === 0} + >{t("status_bar.backlinks", { count: backlinksCount })} +
    +
    + setNotePathsModalShown(true)} + disabled={(sortedNotePaths?.length ?? 0) <= 1} + >{t("status_bar.note_paths", { count: sortedNotePaths?.length })} +
    +
    {noteContext && ntxId && } @@ -83,18 +93,6 @@ export default function MobileDetailMenu() { ); } -function Backlinks({ note, viewScope, setModalShown }: { note: FNote, viewScope?: ViewScope, setModalShown: (shown: boolean) => void }) { - const count = useBacklinkCount(note, viewScope?.viewMode === "default"); - - return ( - setModalShown(true)} - disabled={count === 0} - >{t("status_bar.backlinks", { count })} - ); -} - function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined, modalShown: boolean, setModalShown: (shown: boolean) => void }) { return ( Date: Mon, 2 Feb 2026 21:19:35 +0200 Subject: [PATCH 130/154] chore(client): address requested changes --- apps/client/src/widgets/note_icon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 6c79bc8017..5f9c1fb81d 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -86,7 +86,7 @@ function MobileNoteIconSwitcher({ note, icon }: { className="icon-switcher note-icon-widget" scrollable > - {note && setModalShown(false)} columnCount={Math.floor(windowWidth / ICON_SIZE)} />} + {note && setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />} ), document.body)}
  • From d83a824812e2e4c3ef156b2143708c39e7457195 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 2 Feb 2026 21:43:27 +0200 Subject: [PATCH 131/154] chore(client): address requested changes --- apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx | 1 - apps/client/src/widgets/note_icon.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index dc8eb0415e..dc0c5e89cd 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -2,7 +2,6 @@ import { createPortal, useState } from "preact/compat"; import FNote, { NotePathRecord } from "../../entities/fnote"; import { t } from "../../services/i18n"; -import { ViewScope } from "../../services/link"; import note_create from "../../services/note_create"; import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import ActionButton from "../react/ActionButton"; diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 5f9c1fb81d..9df9ad48f4 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -86,7 +86,7 @@ function MobileNoteIconSwitcher({ note, icon }: { className="icon-switcher note-icon-widget" scrollable > - {note && setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />} + setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} /> ), document.body)}
    From 654fa18ab1a96062631c87a7fc04704fe01004cb Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 1 Feb 2026 21:34:51 +0100 Subject: [PATCH 132/154] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ --- docs/README-ga.md | 468 ++++++++++++++++++++++++---------------------- 1 file changed, 243 insertions(+), 225 deletions(-) diff --git a/docs/README-ga.md b/docs/README-ga.md index 0da3a37bf9..42d3ee6e1b 100644 --- a/docs/README-ga.md +++ b/docs/README-ga.md @@ -11,14 +11,14 @@ # Trilium Notes -![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran) -![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)\ -![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/trilium) -![GitHub Downloads (all assets, all -releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\ +![Urraitheoirí GitHub](https://img.shields.io/github/sponsors/eliandoran) +![Pátrúin LiberaPay](https://img.shields.io/liberapay/patrons/ElianDoran)\ +![Tarraingtí Docker](https://img.shields.io/docker/pulls/triliumnext/trilium) +![Íoslódálacha GitHub (gach sócmhainn, gach +eisiúint)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\ [![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) -[![Translation -status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/) +[![Stádas +aistriúcháin](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/) @@ -29,216 +29,232 @@ script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md) [Spanish](./README-es.md) -Trilium Notes is a free and open-source, cross-platform hierarchical note taking -application with focus on building large personal knowledge bases. +Is feidhmchlár saor in aisce agus foinse oscailte, tras-ardán, ordlathach é +Trilium Notes chun nótaí a thógáil le fócas ar bhunachair mhóra eolais +phearsanta a thógáil. Trilium Screenshot -## ⏬ Download -- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) – - stable version, recommended for most users. -- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – - unstable development version, updated daily with the latest features and - fixes. +## ⏬ Íoslódáil +- [An leagan is déanaí](https://github.com/TriliumNext/Trilium/releases/latest) + – leagan cobhsaí, molta do fhormhór na n-úsáideoirí. +- [Tógáil oíche](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – + leagan forbartha éagobhsaí, a nuashonraítear go laethúil leis na gnéithe agus + na socruithe is déanaí. -## 📚 Documentation +## 📚 Doiciméadú -**Visit our comprehensive documentation at +**Tabhair cuairt ar ár ndoiciméadacht chuimsitheach ag [docs.triliumnotes.org](https://docs.triliumnotes.org/)** -Our documentation is available in multiple formats: -- **Online Documentation**: Browse the full documentation at +Tá ár ndoiciméadacht ar fáil i bhformáidí éagsúla: +- **Doiciméadacht Ar Líne**: Brabhsáil an doiciméadacht iomlán ag [docs.triliumnotes.org](https://docs.triliumnotes.org/) -- **In-App Help**: Press `F1` within Trilium to access the same documentation - directly in the application -- **GitHub**: Navigate through the [User Guide](./User%20Guide/User%20Guide/) in - this repository +- **Cabhair san Aip**: Brúigh `F1` laistigh de Trilium chun rochtain a fháil ar + an doiciméadacht chéanna go díreach san fheidhmchlár +- **GitHub**: Nascleanúint tríd an [Treoir + Úsáideora](./User%20Guide/User%20Guide/) sa stórlann seo -### Quick Links -- [Getting Started Guide](https://docs.triliumnotes.org/) -- [Installation Instructions](https://docs.triliumnotes.org/user-guide/setup) -- [Docker - Setup](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) -- [Upgrading +### Naisc Thapa +- [Treoir Tosaithe](https://docs.triliumnotes.org/) +- [Treoracha Suiteála](https://docs.triliumnotes.org/user-guide/setup) +- [Socrú + Docker](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) +- [Uasghrádú TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading) -- [Basic Concepts and - Features](https://docs.triliumnotes.org/user-guide/concepts/notes) -- [Patterns of Personal Knowledge - Base](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge) +- [Coincheapa agus Gnéithe + Bunúsacha](https://docs.triliumnotes.org/user-guide/concepts/notes) +- [Patrúin de Bhunachar Eolais + Phearsanta](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge) -## 🎁 Features +## 🎁 Gnéithe -* Notes can be arranged into arbitrarily deep tree. Single note can be placed - into multiple places in the tree (see - [cloning](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning)) -* Rich WYSIWYG note editor including e.g. tables, images and - [math](https://docs.triliumnotes.org/user-guide/note-types/text) with markdown +* Is féidir nótaí a shocrú i gcrann domhain treallach. Is féidir nóta aonair a + chur in áiteanna éagsúla sa chrann (féach + [clónáil](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning)) +* Eagarthóir nótaí WYSIWYG saibhir lena n-áirítear táblaí, íomhánna agus + [matamaitic](https://docs.triliumnotes.org/user-guide/note-types/text) le + marcáil síos [autoformat](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting) -* Support for editing [notes with source - code](https://docs.triliumnotes.org/user-guide/note-types/code), including - syntax highlighting -* Fast and easy [navigation between - notes](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation), - full text search and [note - hoisting](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) -* Seamless [note - versioning](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions) -* Note - [attributes](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) - can be used for note organization, querying and advanced - [scripting](https://docs.triliumnotes.org/user-guide/scripts) -* UI available in English, German, Spanish, French, Romanian, and Chinese - (simplified and traditional) -* Direct [OpenID and TOTP - integration](https://docs.triliumnotes.org/user-guide/setup/server/mfa) for - more secure login -* [Synchronization](https://docs.triliumnotes.org/user-guide/setup/synchronization) - with self-hosted sync server - * there are [3rd party services for hosting synchronisation - server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) -* [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) - (publishing) notes to public internet -* Strong [note - encryption](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) - with per-note granularity -* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type - "canvas") -* [Relation - 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/) -* [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 - showcases](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases) -* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) for - automation -* Scales well in both usability and performance upwards of 100 000 notes -* Touch optimized [mobile - frontend](https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) for - smartphones and tablets -* Built-in [dark - theme](https://docs.triliumnotes.org/user-guide/concepts/themes), support for - user themes +* Tacaíocht le haghaidh eagarthóireacht [nótaí le cód + foinse](https://docs.triliumnotes.org/user-guide/note-types/code), lena + n-áirítear aibhsiú comhréire +* Nascleanúint thapa agus éasca idir + nótaí(https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation), + cuardach téacs iomlán agus [ardú + nótaí](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) +* Gan uaim [leaganú + nótaí](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions) +* Is féidir nótaí + [tréithe](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) + a úsáid chun nótaí a eagrú, fiosrúcháin a dhéanamh agus [scriptiú] + ardleibhéil(https://docs.triliumnotes.org/user-guide/scripts) +* Tá an comhéadan úsáideora ar fáil i mBéarla, i nGearmáinis, i Spáinnis, i + bhFraincis, i Rómáinis, agus i Sínis (simplithe agus traidisiúnta) +* Díreach [Comhtháthú OpenID agus + TOTP](https://docs.triliumnotes.org/user-guide/setup/server/mfa) le haghaidh + logáil isteach níos sláine +* [Sioncrónú](https://docs.triliumnotes.org/user-guide/setup/synchronization) le + freastalaí sioncrónaithe féinóstáilte + * tá [seirbhísí tríú páirtí ann chun freastalaí sioncrónaithe a + óstáil](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) +* Nótaí [Ag + roinnt](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) (ag + foilsiú) ar an idirlíon poiblí +* [Criptiú nótaí] + láidir(https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) + le mionsonraí in aghaidh an nóta +* Léaráidí sceitseála, bunaithe ar [Excalidraw](https://excalidraw.com/) + (tabhair faoi deara cineál "canbhás") +* [Léarscáileanna + caidrimh](https://docs.triliumnotes.org/user-guide/note-types/relation-map) + agus [léarscáileanna + nótaí/naisc](https://docs.triliumnotes.org/user-guide/note-types/note-map) + chun nótaí agus a gcaidrimh a léirshamhlú +* Léarscáileanna intinne, bunaithe ar [Mind + Elixir](https://docs.mind-elixir.com/) +* [Léarscáileanna + geo](https://docs.triliumnotes.org/user-guide/collections/geomap) le bioráin + suímh agus rianta GPX +* [Scriptiú](https://docs.triliumnotes.org/user-guide/scripts) - féach + [Taispeántais + Ardleibhéil](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases) +* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) le + haghaidh uathoibrithe +* Scálann go maith i dtéarmaí inúsáidteachta agus feidhmíochta araon os cionn + 100,000 nóta +* Tadhall-optamaithe [comhéadan soghluaiste] + (https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) le haghaidh + fóin chliste agus táibléad +* Téama dorcha + ionsuite(https://docs.triliumnotes.org/user-guide/concepts/themes), tacaíocht + do théamaí úsáideora * [Evernote](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote) - and [Markdown import & - export](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown) -* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) for - easy saving of web content -* Customizable UI (sidebar buttons, user-defined widgets, ...) + agus [Iompórtáil & Easpórtáil + Markdown](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown) +* [Gearrthóir + Gréasáin](https://docs.triliumnotes.org/user-guide/setup/web-clipper) le + haghaidh sábháil éasca ar ábhar gréasáin +* Comhéadan úsáideora saincheaptha (cnaipí taobhbharra, giuirléidí sainithe ag + an úsáideoir, ...) * [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), - along with a Grafana Dashboard. + mar aon le Painéal Grafana. -✨ Check out the following third-party resources/communities for more TriliumNext -related goodies: +✨ Féach ar na hacmhainní/pobail tríú páirtí seo a leanas le haghaidh tuilleadh +earraí gaolmhara le TriliumNext: -- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party - themes, scripts, plugins and more. -- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more. +- [awesome-trilium](https://github.com/Nriver/awesome-trilium) le haghaidh + téamaí, scripteanna, breiseáin agus tuilleadh ó thríú páirtithe. +- [TriliumRocks!](https://trilium.rocks/) le haghaidh ranganna teagaisc, + treoracha, agus i bhfad níos mó. -## ❓Why TriliumNext? +## ❓Cén fáth TriliumNext? -The original Trilium developer ([Zadam](https://github.com/zadam)) has -graciously given the Trilium repository to the community project which resides -at https://github.com/TriliumNext +Bhronn forbróir bunaidh Trilium ([Zadam](https://github.com/zadam)) stórlann +Trilium go fial ar an tionscadal pobail atá le fáil ag +https://github.com/TriliumNext -### ⬆️Migrating from Zadam/Trilium? +### ⬆️Ag dul ar imirce ó Zadam/Trilium? -There are no special migration steps to migrate from a zadam/Trilium instance to -a TriliumNext/Trilium instance. Simply [install -TriliumNext/Trilium](#-installation) as usual and it will use your existing -database. +Níl aon chéimeanna imirce speisialta ann chun imirce ó shampla zadam/Trilium go +sampla TriliumNext/Trilium. Níl le déanamh ach [TriliumNext/Trilium a +shuiteáil](#-installation) mar is gnách agus úsáidfidh sé do bhunachar sonraí +atá ann cheana féin. -Versions up to and including -[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are -compatible with the latest zadam/trilium version of -[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later -versions of TriliumNext/Trilium have their sync versions incremented which -prevents direct migration. +Tá leaganacha suas go dtí agus lena n-áirítear +[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) +comhoiriúnach leis an leagan is déanaí de zadam/trilium de +[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Méadaítear +leaganacha sioncrónaithe aon leaganacha níos déanaí de TriliumNext/Trilium rud a +chuireann cosc ar aistriú díreach. -## 💬 Discuss with us +## 💬 Pléigh linn -Feel free to join our official conversations. We would love to hear what -features, suggestions, or issues you may have! +Ná bíodh drogall ort páirt a ghlacadh inár gcomhráite oifigiúla. Ba bhreá linn +cloisteáil faoi na gnéithe, na moltaí nó na fadhbanna a d'fhéadfadh a bheith +agat! -- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous - discussions.) - - The `General` Matrix room is also bridged to - [XMPP](xmpp:discuss@trilium.thisgreat.party?join) -- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For - asynchronous discussions.) -- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug - reports and feature requests.) +- [Maitrís](https://matrix.to/#/#triliumnext:matrix.org) (Le haghaidh plé + sioncrónach.) + - Tá droichead idir seomra an Mhaitrís `Ginearálta` agus + [XMPP](xmpp:discuss@trilium.thisgreat.party?join) freisin +- [Plé Github](https://github.com/TriliumNext/Trilium/discussions) (Le haghaidh + plé neamhshioncrónach.) +- [Fadhbanna Github](https://github.com/TriliumNext/Trilium/issues) (Le haghaidh + tuairiscí fabhtanna agus iarratais ar ghnéithe.) -## 🏗 Installation +## 🏗 Suiteáil ### Windows / MacOS -Download the binary release for your platform from the [latest release -page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package -and run the `trilium` executable. +Íoslódáil an scaoileadh dénártha do d'ardán ón [leathanach scaoileadh is +déanaí](https://github.com/TriliumNext/Trilium/releases/latest), dízipeáil an +pacáiste agus rith an comhad inrite `trilium`. ### Linux -If your distribution is listed in the table below, use your distribution's -package. +Más liostaithe sa tábla thíos atá do dháileadh, bain úsáid as pacáiste do +dháilte. -[![Packaging -status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) +[![Stádas +pacáistithe](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) -You may also download the binary release for your platform from the [latest -release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the -package and run the `trilium` executable. +Féadfaidh tú an scaoileadh dénártha do d'ardán a íoslódáil ón [leathanach +scaoileadh is déanaí](https://github.com/TriliumNext/Trilium/releases/latest) +freisin, an pacáiste a dhízipeáil agus an comhad inrite `trilium` a rith. -TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. +Cuirtear TriliumNext ar fáil mar Flatpak freisin, ach níl sé foilsithe ar +FlatHub go fóill. -### Browser (any OS) +### Brabhsálaí (aon chóras oibriúcháin) -If you use a server installation (see below), you can directly access the web -interface (which is almost identical to the desktop app). +Má úsáideann tú suiteáil freastalaí (féach thíos), is féidir leat rochtain +dhíreach a fháil ar an gcomhéadan gréasáin (atá beagnach mar an gcéanna leis an +aip deisce). -Currently only the latest versions of Chrome & Firefox are supported (and -tested). +Faoi láthair ní thacaítear (agus déantar tástáil ar) ach leis na leaganacha is +déanaí de Chrome agus Firefox. -### Mobile +### Soghluaiste -To use TriliumNext on a mobile device, you can use a mobile web browser to -access the mobile interface of a server installation (see below). +Chun TriliumNext a úsáid ar ghléas soghluaiste, is féidir leat brabhsálaí +gréasáin soghluaiste a úsáid chun rochtain a fháil ar chomhéadan soghluaiste +suiteála freastalaí (féach thíos). -See issue https://github.com/TriliumNext/Trilium/issues/4962 for more -information on mobile app support. +Féach ar an eagrán https://github.com/TriliumNext/Trilium/issues/4962 le +haghaidh tuilleadh eolais faoi thacaíocht d’aipeanna soghluaiste. -If you prefer a native Android app, you can use -[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid). -Report bugs and missing features at [their -repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to -disable automatic updates on your server installation (see below) when using -TriliumDroid since the sync version must match between Trilium and TriliumDroid. +Más fearr leat aip dhúchasach Android, is féidir leat +[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid) +a úsáid. Tuairiscigh fabhtanna agus gnéithe atá ar iarraidh ag [a +stór](https://github.com/FliegendeWurst/TriliumDroid). Tabhair faoi deara: Is +fearr nuashonruithe uathoibríocha a dhíchumasú ar do shuiteáil freastalaí (féach +thíos) agus TriliumDroid in úsáid agat ós rud é go gcaithfidh an leagan +sioncrónaithe a bheith mar an gcéanna idir Trilium agus TriliumDroid. -### Server +### Freastalaí -To install TriliumNext on your own server (including via Docker from -[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server -installation docs](https://docs.triliumnotes.org/user-guide/setup/server). +Chun TriliumNext a shuiteáil ar do fhreastalaí féin (lena n-áirítear trí Docker +ó [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) lean [na doiciméid +suiteála freastalaí](https://docs.triliumnotes.org/user-guide/setup/server). -## 💻 Contribute +## 💻 Cuir leis -### Translations +### Aistriúcháin -If you are a native speaker, help us translate Trilium by heading over to our -[Weblate page](https://hosted.weblate.org/engage/trilium/). +Más cainteoir dúchais thú, cabhraigh linn Trilium a aistriú trí dhul chuig ár +[leathanach Weblate](https://hosted.weblate.org/engage/trilium/). -Here's the language coverage we have so far: +Seo an clúdach teanga atá againn go dtí seo: -[![Translation -status](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/) +[![Stádas +aistriúcháin](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/) -### Code +### Cód -Download the repository, install dependencies using `pnpm` and then run the -server (available at http://localhost:8080): +Íoslódáil an stórlann, suiteáil spleáchais ag baint úsáide as `pnpm` agus ansin +rith an freastalaí (ar fáil ag http://localhost:8080): ```shell git clone https://github.com/TriliumNext/Trilium.git cd Trilium @@ -246,10 +262,10 @@ pnpm install pnpm run server:start ``` -### Documentation +### Doiciméadú -Download the repository, install dependencies using `pnpm` and then run the -environment required to edit the documentation: +Íoslódáil an stórlann, suiteáil spleáchais ag baint úsáide as `pnpm` agus ansin +rith an timpeallacht atá riachtanach chun an doiciméadú a chur in eagar: ```shell git clone https://github.com/TriliumNext/Trilium.git cd Trilium @@ -257,9 +273,9 @@ pnpm install pnpm edit-docs:edit-docs ``` -### Building the Executable -Download the repository, install dependencies using `pnpm` and then build the -desktop app for Windows: +### Ag Tógáil an Inrite +Íoslódáil an stórlann, suiteáil spleáchais ag baint úsáide as `pnpm` agus ansin +tóg an aip deisce do Windows: ```shell git clone https://github.com/TriliumNext/Trilium.git cd Trilium @@ -267,71 +283,73 @@ pnpm install pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32 ``` -For more details, see the [development -docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide). +Le haghaidh tuilleadh sonraí, féach ar na [doiciméid +forbartha](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide). -### Developer Documentation +### Doiciméadacht Forbróra -Please view the [documentation -guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) -for details. If you have more questions, feel free to reach out via the links -described in the "Discuss with us" section above. +Féach ar an [treoir +dhoiciméadúcháin](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) +le haghaidh tuilleadh sonraí. Má tá tuilleadh ceisteanna agat, bíodh leisce ort +teagmháil a dhéanamh linn trí na naisc a bhfuil cur síos orthu sa chuid "Pléigh +Linn" thuas. -## 👏 Shoutouts +## 👏 Glaonna amach -* [zadam](https://github.com/zadam) for the original concept and implementation - of the application. -* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the - application icon. -* [nriver](https://github.com/nriver) for his work on internationalization. -* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas. -* [antoniotejada](https://github.com/nriver) for the original syntax highlight - widget. -* [Dosu](https://dosu.dev/) for providing us with the automated responses to - GitHub issues and discussions. -* [Tabler Icons](https://tabler.io/icons) for the system tray icons. +* [zadam](https://github.com/zadam) as an gcoincheap bunaidh agus cur i bhfeidhm + an fheidhmchláir. +* [Sarah Hussein](https://github.com/Sarah-Hussein) as dearadh dheilbhín an + fheidhmchláir. +* [nriver](https://github.com/nriver) as a chuid oibre ar an idirnáisiúnú. +* [Thomas Frei](https://github.com/thfrei) as a shaothar bunaidh ar an Chanbhás. +* [antoniotejada](https://github.com/nriver) don ghiuirléid aibhsithe comhréire + bunaidh. +* [Dosu](https://dosu.dev/) as na freagraí uathoibrithe a sholáthar dúinn ar + shaincheisteanna agus ar phlé GitHub. +* [Deilbhíní Tábla](https://tabler.io/icons) do na deilbhíní sa tráidire córais. -Trilium would not be possible without the technologies behind it: +Ní bheadh Trilium indéanta gan na teicneolaíochtaí atá taobh thiar de: -* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - the visual editor behind - text notes. We are grateful for being offered a set of the premium features. -* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with - support for huge amount of languages. -* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite - whiteboard used in Canvas notes. -* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the - mind map functionality. -* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical - maps. -* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive - table used in collections. -* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library - without real competition. -* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library. - Used in [relation - maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and - [link - maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map) +* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - an t-eagarthóir amhairc + atá taobh thiar de nótaí téacs. Táimid buíoch as sraith de na gnéithe préimhe + a bheith curtha ar fáil dúinn. +* [CodeMirror](https://github.com/codemirror/CodeMirror) - eagarthóir cóid le + tacaíocht do líon ollmhór teangacha. +* [Excalidraw](https://github.com/excalidraw/excalidraw) - an clár bán gan + teorainn a úsáidtear i nótaí Canvas. +* [Intinn Elixir](https://github.com/SSShooter/mind-elixir-core) - ag soláthar + feidhmiúlacht léarscáil intinne. +* [Bileog](https://github.com/Leaflet/Leaflet) - le haghaidh léarscáileanna + geografacha a léiriú. +* [Tábla](https://github.com/olifolkerd/tabulator) - don tábla idirghníomhach a + úsáidtear i mbailiúcháin. +* [FancyTree](https://github.com/mar10/fancytree) - leabharlann crann lán + gnéithe gan iomaíocht cheart. +* [jsPlumb](https://github.com/jsplumb/jsplumb) - leabharlann nascachta amhairc. + Úsáidte i [léarscáileanna + caidrimh](https://docs.triliumnotes.org/user-guide/note-types/relation-map) + agus [léarscáileanna + nasc](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map) -## 🤝 Support +## 🤝 Tacaíocht -Trilium is built and maintained with [hundreds of hours of -work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your -support keeps it open-source, improves features, and covers costs such as -hosting. +Tógtar agus cothaítear Trilium le [na céadta uair an chloig +oibre](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Coinníonn +do thacaíocht é foinse oscailte, feabhsaíonn sé gnéithe, agus clúdaíonn sé +costais amhail óstáil. -Consider supporting the main developer -([eliandoran](https://github.com/eliandoran)) of the application via: +Smaoinigh ar thacaíocht a thabhairt don phríomhfhorbróir +([eliantoran](https://github.com/eliandoran)) den fheidhmchlár trí: -- [GitHub Sponsors](https://github.com/sponsors/eliandoran) +- [Urraitheoirí GitHub](https://github.com/sponsors/eliandoran) - [PayPal](https://paypal.me/eliandoran) -- [Buy Me a Coffee](https://buymeacoffee.com/eliandoran) +- [Ceannaigh Caife Dom](https://buymeacoffee.com/eliandoran) -## 🔑 License +## 🔑 Ceadúnas -Copyright 2017-2025 zadam, Elian Doran, and other contributors +Cóipcheart 2017-2025 zadam, Elian Doran, agus rannpháirtithe eile -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU Affero General Public License as published by the Free -Software Foundation, either version 3 of the License, or (at your option) any -later version. +Is bogearraí saor in aisce an clár seo: is féidir leat é a athdháileadh agus/nó +a mhodhnú faoi théarmaí Cheadúnas Poiblí Ginearálta GNU Affero mar atá foilsithe +ag an bhFondúireacht Bogearraí Saor in Aisce, cibé acu leagan 3 den Cheadúnas, +nó (de réir do rogha féin) aon leagan níos déanaí. From b453589077910d5a686e87ce6aad08711e9b5a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Sun, 1 Feb 2026 20:22:32 +0100 Subject: [PATCH 133/154] Translated using Weblate (Irish) Currently translated at 100.0% (152 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ga/ --- .../src/translations/ga/translation.json | 201 +++++++++++++++++- 1 file changed, 200 insertions(+), 1 deletion(-) diff --git a/apps/website/src/translations/ga/translation.json b/apps/website/src/translations/ga/translation.json index 0967ef424b..90e8808389 100644 --- a/apps/website/src/translations/ga/translation.json +++ b/apps/website/src/translations/ga/translation.json @@ -1 +1,200 @@ -{} +{ + "get-started": { + "title": "Tosaigh", + "desktop_title": "Íoslódáil an feidhmchlár deisce (v{{version}})", + "architecture": "Ailtireacht:", + "older_releases": "Féach ar eisiúintí níos sine", + "server_title": "Socraigh freastalaí le haghaidh rochtana ar ilghléasanna" + }, + "hero_section": { + "title": "Eagraigh do chuid smaointe. Tóg do bhunachar eolais pearsanta.", + "subtitle": "Is réiteach foinse oscailte é Trilium chun nótaí a thógáil agus bunachar eolais pearsanta a eagrú. Bain úsáid as go háitiúil ar do dheasc, nó sioncrónaigh é le do fhreastalaí féinóstáilte chun do nótaí a choinneáil cibé áit a théann tú.", + "get_started": "Tosaigh", + "github": "GitHub", + "dockerhub": "Docker Hub", + "screenshot_alt": "Scáileán den fheidhmchlár deisce Trilium Notes" + }, + "organization_benefits": { + "title": "Eagraíocht", + "note_structure_title": "Struchtúr nótaí", + "note_structure_description": "Is féidir nótaí a shocrú go hiarlathach. Níl aon ghá le fillteáin, ós rud é gur féidir fo-nótaí a bheith i ngach nóta. Is féidir nóta aonair a chur leis i roinnt áiteanna san ordlathas.", + "attributes_title": "Lipéid nótaí agus caidrimh", + "attributes_description": "Bain úsáid as caidrimh idir nótaí nó cuir lipéid leis le haghaidh catagóiriú éasca. Bain úsáid as tréithe ardaithe chun faisnéis struchtúrtha a iontráil ar féidir a úsáid i dtáblaí agus i gcláir.", + "hoisting_title": "Spásanna oibre agus ardaitheoir", + "hoisting_description": "Deighil do nótaí pearsanta agus oibre go héasca trí iad a ghrúpáil faoi spás oibre, rud a dhíríonn ar do chrann nótaí chun sraith nótaí ar leith amháin a thaispeáint." + }, + "productivity_benefits": { + "title": "Táirgiúlacht agus sábháilteacht", + "revisions_title": "Athbhreithnithe nóta", + "revisions_content": "Sábháiltear nótaí go tréimhsiúil sa chúlra agus is féidir athbhreithnithe a úsáid le haghaidh athbhreithnithe nó chun athruithe de thaisme a chealú. Is féidir athbhreithnithe a chruthú ar éileamh freisin.", + "sync_title": "Sioncrónú", + "sync_content": "Bain úsáid as cás féinóstáilte nó scamall chun do nótaí a shioncrónú go héasca ar fud ilghléasanna, agus chun rochtain a fháil orthu ó do ghuthán póca ag baint úsáide as PWA.", + "protected_notes_title": "Nótaí faoi chosaint", + "protected_notes_content": "Cosain faisnéis phearsanta íogair trí na nótaí a chriptiú agus iad a ghlasáil taobh thiar de sheisiún atá cosanta ag pasfhocal.", + "jump_to_title": "Cuardach tapa agus orduithe", + "jump_to_content": "Léim go tapa chuig nótaí nó orduithe UI ar fud an ordlathais trí chuardach a dhéanamh ar a dteideal, le meaitseáil doiléir chun clóscríobh nó difríochtaí beaga a chur san áireamh.", + "search_title": "Cuardach cumhachtach", + "search_content": "Nó déan cuardach ar théacs laistigh de nótaí agus caolaigh an cuardach trí scagadh a dhéanamh de réir an nóta tuismitheora, nó de réir doimhneachta.", + "web_clipper_title": "Gearrthóir gréasáin", + "web_clipper_content": "Gabh leathanaigh ghréasáin (nó scáileáin) agus cuir iad go díreach i Trilium ag baint úsáide as síneadh brabhsálaí an ghearrthóra gréasáin." + }, + "note_types": { + "title": "Ilbhealaí chun d’fhaisnéis a léiriú", + "text_title": "Nótaí téacs", + "text_description": "Déantar na nótaí a chur in eagar ag baint úsáide as eagarthóir amhairc (WYSIWYG), a thacaíonn le táblaí, íomhánna, nathanna matamaitice, bloic chóid le haibhsiú comhréire. Formáidigh an téacs go tapa ag baint úsáide as comhréir cosúil le Markdown nó ag baint úsáide as orduithe slaise.", + "code_title": "Nótaí cóid", + "code_description": "Úsáideann samplaí móra de chód foinse nó scripteanna eagarthóir tiomnaithe, le haibhsiú comhréire do go leor teangacha ríomhchlárúcháin agus le téamaí dathanna éagsúla.", + "file_title": "Nótaí comhaid", + "file_description": "Cuir comhaid ilmheán ar nós PDFanna, íomhánna, físeáin le chéile le réamhamharc san fheidhmchlár.", + "canvas_title": "Canbhás", + "canvas_description": "Socraigh cruthanna, íomhánna agus téacs ar chanbhás gan teorainn, ag baint úsáide as an teicneolaíocht chéanna atá taobh thiar de excalidraw.com. Oiriúnach do léaráidí, sceitsí agus pleanáil amhairc.", + "mermaid_title": "Léaráidí maighdeana mara", + "mermaid_description": "Cruthaigh léaráidí ar nós cairteacha sreafa, léaráidí ranga agus seicheamhacha, cairteacha Gantt agus go leor eile, ag baint úsáide as comhréir Mermaid.", + "mindmap_title": "Léarscáil intinne", + "mindmap_description": "Eagraigh do chuid smaointe go hamhairc nó déan seisiún smaointeoireachta.", + "others_list": "agus cinn eile: <0>léarscáil nótaí, <1>léarscáil gaoil, <2>cuardaigh shábháilte, <3>nóta rindreála, agus <4>radhairc ghréasáin." + }, + "extensibility_benefits": { + "title": "Comhroinnt & inleathnú", + "import_export_title": "Iompórtáil/onnmhairiú", + "import_export_description": "Idirghníomhaigh go héasca le feidhmchláir eile ag baint úsáide as formáidí Markdown, ENEX, OML.", + "share_title": "Comhroinn nótaí ar an ngréasán", + "share_description": "Má tá freastalaí agat, is féidir é a úsáid chun fo-thacar de do nótaí a roinnt le daoine eile.", + "scripting_title": "Scriptiú ardleibhéil", + "scripting_description": "Tóg do chomhtháthú féin laistigh de Trilium le giuirléidí saincheaptha, nó loighic taobh an fhreastalaí.", + "api_title": "REST API", + "api_description": "Idirghníomhaigh le Trilium go ríomhchláraitheach ag baint úsáide as a REST API ionsuite." + }, + "collections": { + "title": "Bailiúcháin", + "calendar_title": "Féilire", + "calendar_description": "Eagraigh d’imeachtaí pearsanta nó gairmiúla ag baint úsáide as féilire, le tacaíocht d’imeachtaí uile-lae agus il-lae. Féach ar d’imeachtaí go tapa leis na radhairc seachtaine, míosa agus bliana. Idirghníomhaíocht éasca chun imeachtaí a chur leis nó a tharraingt.", + "table_title": "Tábla", + "table_description": "Taispeáin agus cuir in eagar faisnéis faoi nótaí i struchtúr táblach, le cineálacha éagsúla colún amhail téacs, uimhir, boscaí seiceála, dáta & am, naisc agus dathanna agus tacaíocht do chaidrimh. De rogha air sin, taispeáin na nótaí laistigh de ordlathas crainn taobh istigh den tábla.", + "board_title": "Bord Kanban", + "board_description": "Eagraigh stádas do thascanna nó do thionscadail i mbord Kanban le bealach éasca chun míreanna agus colúin nua a chruthú agus a stádas a athrú go simplí trí tharraingt trasna an chláir.", + "geomap_title": "Geo-léarscáil", + "geomap_description": "Pleanáil do laethanta saoire nó marcáil do phointí spéise go díreach ar léarscáil gheografach ag baint úsáide as marcóirí saincheaptha. Taispeáin rianta GPX taifeadta chun bealaí taistil a rianú.", + "presentation_title": "Cur i Láthair", + "presentation_description": "Eagraigh faisnéis i sleamhnáin agus cuir i láthair iad i lánscáileán le haistrithe réidhe. Is féidir na sleamhnáin a onnmhairiú go PDF freisin le go mbeidh sé éasca iad a roinnt." + }, + "faq": { + "title": "Ceisteanna Coitianta", + "mobile_question": "An bhfuil feidhmchlár soghluaiste ann?", + "mobile_answer": "Faoi láthair níl aon aip shoghluaiste oifigiúil ann. Mar sin féin, má tá freastalaí agat is féidir leat rochtain a fháil air trí bhrabhsálaí gréasáin a úsáid agus fiú é a shuiteáil mar PWA. I gcás Android, tá aip neamhoifigiúil ann ar a dtugtar TriliumDroid a oibríonn as líne fiú (cosúil le cliant deisce).", + "database_question": "Cá bhfuil na sonraí stóráilte?", + "database_answer": "Stórálfar do nótaí go léir i mbunachar sonraí SQLite i bhfillteán feidhmchláir. Is é an chúis a n-úsáideann Trilium bunachar sonraí in ionad comhaid téacs simplí ná feidhmíocht agus go mbeadh roinnt gnéithe i bhfad níos deacra a chur i bhfeidhm amhail clóin (an nóta céanna in áiteanna éagsúla sa chrann). Chun an fillteán feidhmchláir a aimsiú, téigh go dtí an fhuinneog Maidir Linn.", + "server_question": "An bhfuil freastalaí ag teastáil uaim le Trilium a úsáid?", + "server_answer": "Ní hea, ceadaíonn an freastalaí rochtain trí bhrabhsálaí gréasáin agus bainistíonn sé an sioncrónú má tá ilghléasanna agat. Chun tús a chur leis, is leor an feidhmchlár deisce a íoslódáil agus tosú ag baint úsáide as.", + "scaling_question": "Cé chomh maith agus a scálaíonn an feidhmchlár le líon mór nótaí?", + "scaling_answer": "Ag brath ar úsáid, ba cheart go mbeadh an feidhmchlár in ann 100,000 nóta ar a laghad a láimhseáil gan fadhb. Tabhair faoi deara go bhféadfadh teip a bheith ar an bpróiseas sioncrónaithe uaireanta má tá go leor comhad mór á uaslódáil (1 GB in aghaidh an chomhaid) ós rud é go bhfuil Trilium beartaithe níos mó mar fheidhmchlár bonn eolais seachas stór comhad (cosúil le NextCloud, mar shampla).", + "network_share_question": "An féidir liom mo bhunachar sonraí a roinnt thar thiomántán líonra?", + "network_share_answer": "Ní hea, ní smaoineamh maith é bunachar sonraí SQLite a roinnt thar thiomántán líonra i gcoitinne. Cé go bhféadfadh sé oibriú uaireanta, tá seans ann go ndéanfar an bunachar sonraí a thruailliú mar gheall ar ghlasanna comhad neamhfhoirfe thar líonra.", + "security_question": "Conas a chosnaítear mo chuid sonraí?", + "security_answer": "De réir réamhshocraithe, ní chriptítear nótaí agus is féidir iad a léamh go díreach ón mbunachar sonraí. Nuair a mharcáiltear nóta mar chriptithe, déantar an nóta a chriptiú ag baint úsáide as AES-128-CBC." + }, + "final_cta": { + "title": "Réidh le tosú le Trilium Notes?", + "description": "Tóg do bhunachar eolais pearsanta le gnéithe cumhachtacha agus príobháideacht iomlán.", + "get_started": "Tosaigh" + }, + "components": { + "link_learn_more": "Foghlaim níos mó..." + }, + "download_now": { + "text": "Íoslódáil anois ", + "platform_big": "v{{version}} do {{platform}}", + "platform_small": "do {{platform}}", + "linux_big": "v{{version}} do Linux", + "linux_small": "do Linux", + "more_platforms": "Tuilleadh ardán & socrú freastalaí" + }, + "header": { + "get-started": "Tosaigh", + "documentation": "Doiciméadú", + "support-us": "Tacaigh linn" + }, + "footer": { + "copyright_and_the": " agus an ", + "copyright_community": "pobal" + }, + "social_buttons": { + "github": "GitHub", + "github_discussions": "Pléanna GitHub", + "matrix": "Maitrís", + "reddit": "Reddit" + }, + "support_us": { + "title": "Tacaigh linn", + "financial_donations_title": "Síntiúis airgeadais", + "financial_donations_description": "Tógtar agus cothaítear Trilium le na céadta uair an chloig oibre. Coinníonn do thacaíocht é foinse oscailte, feabhsaíonn sé gnéithe, agus clúdaíonn sé costais amhail óstáil.", + "financial_donations_cta": "Smaoinigh ar thacaíocht a thabhairt don phríomhfhorbróir (eliandoran) den fheidhmchlár trí:", + "github_sponsors": "Urraitheoirí GitHub", + "paypal": "PayPal", + "buy_me_a_coffee": "Ceannaigh Caife Dom" + }, + "contribute": { + "title": "Bealaí eile chun ranníocaíocht a dhéanamh", + "way_translate": "Aistrigh an feidhmchlár go do theanga dhúchais trí Weblate.", + "way_community": "Déan idirghníomhú leis an bpobal ar GitHub Discussions nó ar Matrix.", + "way_reports": "Tuairiscigh fabhtanna trí Fadhbanna GitHub.", + "way_document": "Feabhas a chur ar an doiciméadacht trí eolas a thabhairt dúinn faoi bhearnaí sa doiciméadacht nó trí threoracha, Ceisteanna Coitianta nó ranganna teagaisc a chur ar fáil.", + "way_market": "Scaip an scéal: Roinn Nótaí Trilium le cairde, nó ar bhlaganna agus ar na meáin shóisialta." + }, + "404": { + "title": "404: Níor aimsíodh", + "description": "Níorbh fhéidir an leathanach a bhí á lorg agat a aimsiú. B’fhéidir gur scriosadh é nó go bhfuil an URL mícheart." + }, + "download_helper_desktop_windows": { + "title_x64": "Windows 64-bit", + "title_arm64": "Windows ar ARM", + "description_x64": "Ag luí le gléasanna Intel nó AMD a bhfuil Windows 10 agus 11 á rith acu.", + "description_arm64": "Ag luí le gléasanna ARM (m.sh. le Qualcomm Snapdragon).", + "quick_start": "Chun a shuiteáil trí Winget:", + "download_exe": "Íoslódáil an Suiteálaí (.exe)", + "download_zip": "Iniompartha (.zip)", + "download_scoop": "Scúp" + }, + "download_helper_desktop_linux": { + "title_x64": "Linux 64-bit", + "title_arm64": "Linux ar ARM", + "description_x64": "Don chuid is mó de na dáiltí Linux, comhoiriúnach le hailtireacht x86_64.", + "description_arm64": "I gcás dáiltí Linux bunaithe ar ARM, comhoiriúnach le hailtireacht aarch64.", + "quick_start": "Roghnaigh formáid phacáiste chuí, ag brath ar do dháileadh:", + "download_deb": ".deb", + "download_rpm": ".rpm", + "download_flatpak": ".flatpak", + "download_zip": "Iniompartha (.zip)", + "download_nixpkgs": "nixpkgs", + "download_aur": "AUR" + }, + "download_helper_desktop_macos": { + "title_x64": "macOS do Intel", + "title_arm64": "macOS do Apple Silicon", + "description_x64": "Do Macs bunaithe ar Intel a bhfuil macOS Monterey nó níos déanaí á rith acu.", + "description_arm64": "Do ríomhairí Mac Apple Silicon ar nós iad siúd a bhfuil sceallóga M1 agus M2 acu.", + "quick_start": "Chun a shuiteáil trí Homebrew:", + "download_dmg": "Íoslódáil an Suiteálaí (.dmg)", + "download_homebrew_cask": "Homebrew Cask", + "download_zip": "Iniompartha (.zip)" + }, + "download_helper_server_docker": { + "title": "Féinóstáilte ag baint úsáide as Docker", + "description": "Imscaradh go héasca ar Windows, Linux nó macOS ag baint úsáide as coimeádán Docker.", + "download_dockerhub": "Docker Hub", + "download_ghcr": "ghcr.io" + }, + "download_helper_server_linux": { + "title": "Féinóstáilte ar Linux", + "description": "Imscar Trilium Notes ar do fhreastalaí nó VPS féin, atá comhoiriúnach leis an gcuid is mó de na dáileacháin.", + "download_tar_x64": "x64 (.tar.xz)", + "download_tar_arm64": "ARM (.tar.xz)", + "download_nixos": "Modúl NixOS" + }, + "download_helper_server_hosted": { + "title": "Óstáil íoctha", + "description": "Nótaí Trilium atá á n-óstáil ar PikaPods, seirbhís íoctha le haghaidh rochtana agus bainistíochta éasca. Níl baint dhíreach aige le foireann Trilium.", + "download_pikapod": "Socraigh ar PikaPods", + "download_triliumcc": "Nó féach ar trilium.cc" + } +} From 3e8376609964f2f449c2e2585557151d179ab858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Sun, 1 Feb 2026 20:53:16 +0100 Subject: [PATCH 134/154] Translated using Weblate (Irish) Currently translated at 0.1% (1 of 1767 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/ --- apps/client/src/translations/ga/translation.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ga/translation.json b/apps/client/src/translations/ga/translation.json index 0967ef424b..ca517b61ea 100644 --- a/apps/client/src/translations/ga/translation.json +++ b/apps/client/src/translations/ga/translation.json @@ -1 +1,5 @@ -{} +{ + "global_menu": { + "about": "Maidir le Trilium Notes" + } +} From 673cbc97e1fd1ffe044749c37c6f9d007fb0fb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Sun, 1 Feb 2026 20:52:56 +0100 Subject: [PATCH 135/154] Translated using Weblate (Irish) Currently translated at 100.0% (389 of 389 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ga/ --- .../src/assets/translations/ga/server.json | 444 +++++++++++++++++- 1 file changed, 443 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ga/server.json b/apps/server/src/assets/translations/ga/server.json index 0967ef424b..d4cbaa006c 100644 --- a/apps/server/src/assets/translations/ga/server.json +++ b/apps/server/src/assets/translations/ga/server.json @@ -1 +1,443 @@ -{} +{ + "keyboard_actions": { + "back-in-note-history": "Téigh go dtí an nóta roimhe seo sa stair", + "forward-in-note-history": "Téigh go dtí an chéad nóta eile sa stair", + "open-jump-to-note-dialog": "Oscail an dialóg \"Léim go dtí an nóta\"", + "open-command-palette": "Oscail pailéad orduithe", + "scroll-to-active-note": "Scrollaigh crann na nótaí go dtí an nóta gníomhach", + "quick-search": "Gníomhachtaigh an barra cuardaigh thapa", + "search-in-subtree": "Cuardaigh nótaí i bhfo-chrann an nóta ghníomhaigh", + "expand-subtree": "Leathnaigh fo-chrann an nóta reatha", + "collapse-tree": "Laghdaíonn sé an crann nótaí iomlán", + "collapse-subtree": "Laghdaíonn sé fo-chrann an nóta reatha", + "sort-child-notes": "Sórtáil nótaí leanaí", + "creating-and-moving-notes": "Nótaí a chruthú agus a bhogadh", + "create-note-after": "Cruthaigh nóta i ndiaidh nóta gníomhach", + "create-note-into": "Cruthaigh nóta mar leanbh den nóta gníomhach", + "create-note-into-inbox": "Cruthaigh nóta sa bhosca isteach (más sainithe) nó nóta lae", + "delete-note": "Scrios nóta", + "move-note-up": "Bog nóta suas", + "move-note-down": "Bog nóta síos", + "move-note-up-in-hierarchy": "Bog nóta suas san ordlathas", + "move-note-down-in-hierarchy": "Bog nóta síos san ordlathas", + "edit-note-title": "Léim ón gcrann go dtí sonraí an nóta agus cuir an teideal in eagar", + "edit-branch-prefix": "Taispeáin an dialóg \"Cuir réimír na brainse in eagar\"", + "clone-notes-to": "Clónáil nótaí roghnaithe", + "move-notes-to": "Bog nótaí roghnaithe", + "note-clipboard": "Gearrthaisce nótaí", + "copy-notes-to-clipboard": "Cóipeáil na nótaí roghnaithe chuig an ghearrthaisce", + "paste-notes-from-clipboard": "Greamaigh nótaí ón ghearrthaisce isteach sa nóta gníomhach", + "cut-notes-to-clipboard": "Gearr nótaí roghnaithe chuig an ghearrthaisce", + "select-all-notes-in-parent": "Roghnaigh na nótaí go léir ón leibhéal nóta reatha", + "add-note-above-to-the-selection": "Cuir nóta thuas leis an rogha", + "add-note-below-to-selection": "Cuir nóta leis an rogha thíos", + "duplicate-subtree": "Fo-chrann dúblach", + "tabs-and-windows": "Cluaisíní agus Fuinneoga", + "open-new-tab": "Oscail cluaisín nua", + "close-active-tab": "Dún an cluaisín gníomhach", + "reopen-last-tab": "Athoscail an cluaisín deireanach a dúnadh", + "activate-next-tab": "Gníomhachtaigh an cluaisín ar dheis", + "activate-previous-tab": "Gníomhachtaigh an cluaisín ar chlé", + "open-new-window": "Oscail fuinneog nua folamh", + "toggle-tray": "Taispeáin/folaigh an feidhmchlár ón tráidire córais", + "first-tab": "Gníomhachtaigh an chéad chluaisín sa liosta", + "second-tab": "Gníomhachtaigh an dara cluaisín sa liosta", + "third-tab": "Gníomhachtaigh an tríú cluaisín sa liosta", + "fourth-tab": "Gníomhachtaigh an ceathrú cluaisín sa liosta", + "fifth-tab": "Gníomhachtaigh an cúigiú cluaisín sa liosta", + "sixth-tab": "Gníomhachtaigh an séú cluaisín sa liosta", + "seventh-tab": "Gníomhachtaigh an seachtú cluaisín sa liosta", + "eight-tab": "Gníomhachtaigh an t-ochtú cluaisín sa liosta", + "ninth-tab": "Gníomhachtaigh an naoú cluaisín sa liosta", + "last-tab": "Gníomhachtaigh an cluaisín deireanach sa liosta", + "dialogs": "Dialóga", + "show-note-source": "Taispeáin an dialóg \"Foinse an Nóta\"", + "show-options": "Oscail an leathanach \"Roghanna\"", + "show-revisions": "Taispeáin an dialóg \"Athbhreithnithe Nóta\"", + "show-recent-changes": "Taispeáin an dialóg \"Athruithe Le Déanaí\"", + "show-sql-console": "Oscail an leathanach \"Consól SQL\"", + "show-backend-log": "Oscail an leathanach \"Log Backend\"", + "show-help": "Oscail an Treoir Úsáideora ionsuite", + "show-cheatsheet": "Taispeáin modal le hoibríochtaí coitianta méarchláir", + "text-note-operations": "Oibríochtaí nótaí téacs", + "add-link-to-text": "Oscail an dialóg chun nasc a chur leis an téacs", + "follow-link-under-cursor": "Lean an nasc ina bhfuil an caret curtha", + "insert-date-and-time-to-text": "Cuir an dáta agus an t-am reatha isteach sa téacs", + "paste-markdown-into-text": "Greamaigh Markdown ón ghearrthaisce isteach i nóta téacs", + "cut-into-note": "Gearrann sé an rogha ón nóta reatha agus cruthaíonn sé fo-nóta leis an téacs roghnaithe", + "add-include-note-to-text": "Osclaíonn an dialóg chun nóta a chur san áireamh", + "edit-readonly-note": "Cuir nóta inléite amháin in eagar", + "attributes-labels-and-relations": "Tréithe (lipéid & caidrimh)", + "add-new-label": "Cruthaigh lipéad nua", + "create-new-relation": "Cruthaigh caidreamh nua", + "ribbon-tabs": "Cluaisíní ribín", + "toggle-basic-properties": "Airíonna Bunúsacha a Athrú", + "toggle-file-properties": "Airíonna Comhaid a Athrú", + "toggle-image-properties": "Airíonna Íomhá a Athrú", + "toggle-owned-attributes": "Tréithe faoi Úinéireacht a Athrú", + "toggle-inherited-attributes": "Tréithe Oidhreachta a Athrú", + "toggle-promoted-attributes": "Tréithe Curtha Chun Cinn a Athrú", + "toggle-link-map": "Léarscáil Nasc a Athsholáthar", + "toggle-note-info": "Eolas Nóta a Athrú", + "toggle-note-paths": "Cosáin Nótaí a Athrú", + "toggle-similar-notes": "Nótaí Cosúla a Athsholáthar", + "other": "Eile", + "toggle-right-pane": "Athraigh taispeáint an phainéil dheis, lena n-áirítear Clár Ábhair agus Buaicphointí", + "print-active-note": "Priontáil nóta gníomhach", + "open-note-externally": "Oscail nóta mar chomhad leis an bhfeidhmchlár réamhshocraithe", + "render-active-note": "Rindreáil (ath-rindreáil) nóta gníomhach", + "run-active-note": "Rith nóta cóid JavaScript gníomhach (frontend/backend)", + "toggle-note-hoisting": "Scoránaigh ardú nóta an nóta ghníomhaigh", + "unhoist": "Dí-ardaigh ó áit ar bith", + "reload-frontend-app": "Athlódáil an tosaigh", + "open-dev-tools": "Uirlisí forbróra oscailte", + "find-in-text": "Painéal cuardaigh a scoránaigh", + "toggle-left-note-tree-panel": "Scoránaigh an painéal ar chlé (crann nótaí)", + "toggle-full-screen": "Scoraigh an scáileán iomlán", + "zoom-out": "Zúmáil Amach", + "zoom-in": "Zúmáil Isteach", + "note-navigation": "Nascleanúint nótaí", + "reset-zoom-level": "Athshocraigh leibhéal súmála", + "copy-without-formatting": "Cóipeáil téacs roghnaithe gan fhormáidiú", + "force-save-revision": "Cruthú/sábháil nóta nua den nóta gníomhach i bhfeidhm", + "toggle-book-properties": "Airíonna an Bhailiúcháin a Athrú", + "toggle-classic-editor-toolbar": "Athraigh an cluaisín Formáidithe don eagarthóir leis an mbarra uirlisí socraithe", + "export-as-pdf": "Easpórtáil an nóta reatha mar PDF", + "toggle-zen-mode": "Cumasaíonn/díchumasaíonn sé an mód zen (comhad úsáideora íosta le haghaidh eagarthóireacht níos dírithe)" + }, + "keyboard_action_names": { + "back-in-note-history": "Ar ais i Stair na Nótaí", + "forward-in-note-history": "Ar Aghaidh i Stair na Nótaí", + "jump-to-note": "Léim go...", + "command-palette": "Pailéad Ordú", + "scroll-to-active-note": "Scrollaigh go dtí an Nóta Gníomhach", + "quick-search": "Cuardach Tapa", + "search-in-subtree": "Cuardaigh i bhFo-chrann", + "expand-subtree": "Leathnaigh an Fo-Chrann", + "collapse-tree": "Laghdaigh Crann", + "collapse-subtree": "Laghdaigh Fo-chrann", + "sort-child-notes": "Sórtáil Nótaí Leanaí", + "create-note-after": "Cruthaigh Nóta Tar éis", + "create-note-into": "Cruthaigh Nóta Isteach", + "create-note-into-inbox": "Cruthaigh Nóta sa Bhosca Isteach", + "delete-notes": "Scrios Nótaí", + "move-note-up": "Bog Nóta Suas", + "move-note-down": "Bog Nóta Síos", + "move-note-up-in-hierarchy": "Bog Nóta Suas san Ordlathas", + "move-note-down-in-hierarchy": "Bog Nóta Síos san Ordlathas", + "edit-note-title": "Cuir Teideal an Nóta in Eagar", + "edit-branch-prefix": "Cuir Réimír na Brainse in Eagar", + "clone-notes-to": "Nótaí Clónála Chuig", + "move-notes-to": "Bog Nótaí Chuig", + "copy-notes-to-clipboard": "Cóipeáil Nótaí chuig an nGearrthaisce", + "paste-notes-from-clipboard": "Greamaigh Nótaí ón nGearrthaisce", + "cut-notes-to-clipboard": "Gearr Nótaí chuig an nGearrthaisce", + "select-all-notes-in-parent": "Roghnaigh Gach Nóta sa Tuismitheoir", + "add-note-above-to-selection": "Cuir Nóta Thuas leis an Roghnú", + "add-note-below-to-selection": "Cuir Nóta Thíos leis an Roghnú", + "duplicate-subtree": "Fo-chrann Dúblach", + "open-new-tab": "Oscail Cluaisín Nua", + "close-active-tab": "Dún an Cluaisín Gníomhach", + "reopen-last-tab": "Athoscail an Cluaisín Deireanach", + "activate-next-tab": "Gníomhachtaigh an Chluaisín Eile", + "activate-previous-tab": "Gníomhachtaigh an Cluaisín Roimhe Seo", + "open-new-window": "Oscail Fuinneog Nua", + "toggle-system-tray-icon": "Deilbhín Tráidire an Chórais a Athrú", + "toggle-zen-mode": "Mód Zen a athrú", + "switch-to-first-tab": "Athraigh go dtí an Chéad Chluaisín", + "switch-to-second-tab": "Athraigh go dtí an Dara Cluaisín", + "switch-to-third-tab": "Athraigh go dtí an Tríú Cluaisín", + "switch-to-fourth-tab": "Athraigh go dtí an Ceathrú Cluaisín", + "switch-to-fifth-tab": "Athraigh go dtí an Cúigiú Cluaisín", + "switch-to-sixth-tab": "Athraigh go dtí an Séú Cluaisín", + "switch-to-seventh-tab": "Athraigh go dtí an Seachtú Cluaisín", + "switch-to-eighth-tab": "Athraigh go dtí an tOchtú Cluaisín", + "switch-to-ninth-tab": "Athraigh go dtí an Naoú Cluaisín", + "switch-to-last-tab": "Athraigh go dtí an Cluaisín Deireanach", + "show-note-source": "Taispeáin Foinse an Nóta", + "show-options": "Taispeáin Roghanna", + "show-revisions": "Taispeáin Athbhreithnithe", + "show-recent-changes": "Taispeáin Athruithe Le Déanaí", + "show-sql-console": "Taispeáin Consól SQL", + "show-backend-log": "Taispeáin Logáil an Chúil", + "show-help": "Taispeáin Cabhair", + "show-cheatsheet": "Taispeáin Bileog Leideanna", + "add-link-to-text": "Cuir Nasc leis an Téacs", + "follow-link-under-cursor": "Lean an Nasc Faoin gCúrsóir", + "insert-date-and-time-to-text": "Cuir Dáta agus Am isteach sa Téacs", + "paste-markdown-into-text": "Greamaigh Markdown isteach sa Téacs", + "cut-into-note": "Gearr isteach i Nóta", + "add-include-note-to-text": "Cuir Nóta le Téacs", + "edit-read-only-note": "Cuir Nóta Léite Amháin in Eagar", + "add-new-label": "Cuir Lipéad Nua leis", + "add-new-relation": "Cuir Gaol Nua leis", + "toggle-ribbon-tab-classic-editor": "Eagarthóir Clasaiceach Cluaisín Ribín a Athrú", + "toggle-ribbon-tab-basic-properties": "Airíonna Bunúsacha an Chluaisín Ribín a Athrú", + "toggle-ribbon-tab-book-properties": "Airíonna Leabhar an Chluaisín Ribín a Athrú", + "toggle-ribbon-tab-file-properties": "Airíonna Comhaid Tab Ribín a Athrú", + "toggle-ribbon-tab-image-properties": "Airíonna Íomhá an Chluaisín Ribín a Athrú", + "toggle-ribbon-tab-owned-attributes": "Tréithe atá faoi úinéireacht ag an gcluaisín ribín", + "toggle-ribbon-tab-inherited-attributes": "Tréithe Oidhreachta Cluaisín Ribín a Scor", + "toggle-ribbon-tab-promoted-attributes": "Tréithe Curtha Chun Cinn sa Chluaisín Ribín", + "toggle-ribbon-tab-note-map": "Léarscáil Nótaí Tab Ribín a Athrú", + "toggle-ribbon-tab-note-info": "Eolas Nóta Cluaisín Ribín a Athrú", + "toggle-ribbon-tab-note-paths": "Cosáin Nóta Cluaisín Ribín a Athrú", + "toggle-ribbon-tab-similar-notes": "Nótaí Cosúla a Athraigh an Cluaisín Ribín", + "toggle-right-pane": "Scoránaigh an Phána Ar Dheis", + "print-active-note": "Priontáil Nóta Gníomhach", + "export-active-note-as-pdf": "Easpórtáil Nóta Gníomhach mar PDF", + "open-note-externally": "Oscail Nóta go Seachtrach", + "render-active-note": "Rindreáil Nóta Gníomhach", + "run-active-note": "Rith Nóta Gníomhach", + "toggle-note-hoisting": "Ardú Nótaí a Athrú", + "unhoist-note": "Nóta Dí-Ardaithe", + "reload-frontend-app": "Athlódáil an Aip Tosaigh", + "open-developer-tools": "Oscail Uirlisí Forbróra", + "find-in-text": "Aimsigh sa Téacs", + "toggle-left-pane": "Scoránaigh an Phána Chlé", + "toggle-full-screen": "Athraigh an Scáileán Lán", + "zoom-out": "Zúmáil Amach", + "zoom-in": "Zúmáil Isteach", + "reset-zoom-level": "Athshocraigh Leibhéal Súmála", + "copy-without-formatting": "Cóipeáil Gan Formáidiú", + "force-save-revision": "Athbhreithniú Sábháilte Fórsála" + }, + "login": { + "title": "Logáil Isteach", + "heading": "Logáil Isteach Trilium", + "incorrect-totp": "Tá an TOTP mícheart. Déan iarracht arís.", + "incorrect-password": "Tá an focal faire mícheart. Déan iarracht arís.", + "password": "Pasfhocal", + "remember-me": "Cuimhnigh orm", + "button": "Logáil Isteach", + "sign_in_with_sso": "Sínigh isteach le {{ ssoIssuerName }}" + }, + "set_password": { + "title": "Socraigh Pasfhocal", + "heading": "Socraigh pasfhocal", + "description": "Sula dtosaíonn tú ag úsáid Trilium ón ngréasán, ní mór duit pasfhocal a shocrú ar dtús. Úsáidfidh tú an pasfhocal seo ansin chun logáil isteach.", + "password": "Pasfhocal", + "password-confirmation": "Deimhniú pasfhocail", + "button": "Socraigh pasfhocal" + }, + "setup": { + "heading": "Socrú Trilium Notes", + "new-document": "Is úsáideoir nua mé, agus ba mhaith liom doiciméad Trilium nua a chruthú do mo nótaí", + "sync-from-desktop": "Tá cás deisce agam cheana féin, agus ba mhaith liom sioncrónú a shocrú leis", + "sync-from-server": "Tá sampla freastalaí agam cheana féin, agus ba mhaith liom sioncrónú a shocrú leis", + "next": "Ar Aghaidh", + "init-in-progress": "Túsú doiciméad ar siúl", + "redirecting": "Atreorófar chuig an bhfeidhmchlár thú go luath.", + "title": "Socrú" + }, + "setup_sync-from-desktop": { + "heading": "Sioncrónaigh ón Deasc", + "description": "Ní mór an socrú seo a thionscnamh ón deasc:", + "step1": "Oscail sampla de Trilium Notes ar do dheasc.", + "step2": "Ón Roghchlár Trilium, cliceáil Roghanna.", + "step3": "Cliceáil ar an gcatagóir Sioncrónaigh.", + "step4": "Athraigh seoladh an fhreastalaí go: {{- host}} agus cliceáil Sábháil.", + "step5": "Cliceáil an cnaipe \"Tástáil sioncrónaithe\" chun a fhíorú go bhfuil an nasc rathúil.", + "step6": "Nuair a bheidh na céimeanna seo críochnaithe agat, cliceáil {{- link}}.", + "step6-here": "anseo" + }, + "setup_sync-from-server": { + "heading": "Sioncrónaigh ón bhFreastalaí", + "instructions": "Cuir isteach seoladh agus dintiúir freastalaí Trilium thíos le do thoil. Íoslódálfaidh sé seo an doiciméad Trilium iomlán ón bhfreastalaí agus socróidh sé sioncrónú leis. Ag brath ar mhéid an doiciméid agus luas do nasc, d'fhéadfadh sé seo tamall a thógáil.", + "server-host": "Seoladh freastalaí Trilium", + "server-host-placeholder": "https://:", + "proxy-server": "Freastalaí seachfhreastalaí (roghnach)", + "proxy-server-placeholder": "https://:", + "note": "Nóta:", + "proxy-instruction": "Má fhágann tú an socrú seachfhreastalaí bán, úsáidfear seachfhreastalaí an chórais (baineann sé leis an bhfeidhmchlár deisce amháin)", + "password": "Pasfhocal", + "password-placeholder": "Pasfhocal", + "back": "Ar ais", + "finish-setup": "Críochnaigh an socrú" + }, + "setup_sync-in-progress": { + "heading": "Sioncrónú ar siúl", + "successful": "Tá an sioncrónú socraithe i gceart. Tógfaidh sé tamall go mbeidh an sioncrónú tosaigh críochnaithe. Nuair a bheidh sé déanta, atreorófar chuig an leathanach logála isteach thú.", + "outstanding-items": "Míreanna sioncrónaithe gan réiteach:", + "outstanding-items-default": "N/B" + }, + "share_404": { + "title": "Níor aimsíodh", + "heading": "Níor aimsíodh" + }, + "share_page": { + "parent": "tuismitheoir:", + "clipped-from": "Gearradh an nóta seo ó {{- url}} ar dtús", + "child-notes": "Nótaí leanaí:", + "no-content": "Níl aon ábhar sa nóta seo." + }, + "weekdays": { + "monday": "Dé Luain", + "tuesday": "Dé Máirt", + "wednesday": "Dé Céadaoin", + "thursday": "Déardaoin", + "friday": "Dé hAoine", + "saturday": "Dé Sathairn", + "sunday": "Dé Domhnaigh" + }, + "weekdayNumber": "Seachtain {weekNumber}", + "months": { + "january": "Eanáir", + "february": "Feabhra", + "march": "Márta", + "april": "Aibreán", + "may": "Bealtaine", + "june": "Meitheamh", + "july": "Iúil", + "august": "Lúnasa", + "september": "Meán Fómhair", + "october": "Deireadh Fómhair", + "november": "Samhain", + "december": "Nollaig" + }, + "quarterNumber": "Ráithe {quarterNumber}", + "special_notes": { + "search_prefix": "Cuardaigh:" + }, + "test_sync": { + "not-configured": "Níl an freastalaí sioncrónaithe cumraithe. Cumraigh an sioncrónú ar dtús.", + "successful": "Tá croitheadh láimhe an fhreastalaí sioncrónaithe tar éis a bheith rathúil, tá tús curtha leis an sioncrónú." + }, + "hidden-subtree": { + "root-title": "Nótaí Folaithe", + "search-history-title": "Stair Chuardaigh", + "note-map-title": "Léarscáil Nótaí", + "sql-console-history-title": "Stair Chonsól SQL", + "shared-notes-title": "Nótaí Comhroinnte", + "bulk-action-title": "Gníomh Bulc", + "backend-log-title": "Logáil Cúil", + "user-hidden-title": "Úsáideoir i bhfolach", + "launch-bar-templates-title": "Teimpléid Barra Seolta", + "base-abstract-launcher-title": "Tosaitheoir Bunúsach Teibí", + "command-launcher-title": "Tosaitheoir Ordú", + "note-launcher-title": "Tosaitheoir Nótaí", + "script-launcher-title": "Tosaitheoir Scripte", + "built-in-widget-title": "Giuirléid Tógtha isteach", + "spacer-title": "Spásaire", + "custom-widget-title": "Giuirléid Saincheaptha", + "launch-bar-title": "Barra Lainseáil", + "available-launchers-title": "Lainseálaithe atá ar Fáil", + "go-to-previous-note-title": "Téigh go dtí an Nóta Roimhe Seo", + "go-to-next-note-title": "Téigh go dtí an chéad Nóta Eile", + "new-note-title": "Nóta Nua", + "search-notes-title": "Cuardaigh Nótaí", + "jump-to-note-title": "Léim go...", + "calendar-title": "Féilire", + "recent-changes-title": "Athruithe Le Déanaí", + "bookmarks-title": "Leabharmharcanna", + "command-palette": "Oscail an Pailéad Ordaithe", + "zen-mode": "Mód Zen", + "open-today-journal-note-title": "Oscail Nóta Dialainne an Lae Inniu", + "quick-search-title": "Cuardach Tapa", + "protected-session-title": "Seisiún faoi Chosaint", + "sync-status-title": "Stádas Sioncrónaithe", + "settings-title": "Socruithe", + "llm-chat-title": "Comhrá le Nótaí", + "options-title": "Roghanna", + "appearance-title": "Dealramh", + "shortcuts-title": "Aicearraí", + "text-notes": "Nótaí Téacs", + "code-notes-title": "Nótaí Cód", + "images-title": "Íomhánna", + "spellcheck-title": "Seiceáil litrithe", + "password-title": "Pasfhocal", + "multi-factor-authentication-title": "MFA", + "etapi-title": "ETAPI", + "backup-title": "Cúltaca", + "sync-title": "Sioncrónaigh", + "ai-llm-title": "AI/LLM", + "other": "Eile", + "advanced-title": "Ardleibhéil", + "visible-launchers-title": "Lainseálaithe Infheicthe", + "user-guide": "Treoir Úsáideora", + "localization": "Teanga & Réigiún", + "inbox-title": "Bosca isteach", + "tab-switcher-title": "Athraitheoir Cluaisíní" + }, + "notes": { + "new-note": "Nóta nua", + "duplicate-note-suffix": "(dúpáil)", + "duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}" + }, + "backend_log": { + "log-does-not-exist": "Níl an comhad loga cúil '{{ fileName }}' ann (go fóill).", + "reading-log-failed": "Theip ar an gcomhad loga cúil '{{ fileName }}' a léamh." + }, + "content_renderer": { + "note-cannot-be-displayed": "Ní féidir an cineál nóta seo a thaispeáint." + }, + "pdf": { + "export_filter": "Doiciméad PDF (*.pdf)", + "unable-to-export-message": "Níorbh fhéidir an nóta reatha a easpórtáil mar PDF.", + "unable-to-export-title": "Ní féidir a onnmhairiú mar PDF", + "unable-to-save-message": "Níorbh fhéidir scríobh chuig an gcomhad roghnaithe. Déan iarracht eile nó roghnaigh ceann scríbe eile.", + "unable-to-print": "Ní féidir an nóta a phriontáil" + }, + "tray": { + "close": "Scoir de Trilium", + "recents": "Nótaí le déanaí", + "bookmarks": "Leabharmharcanna", + "today": "Oscail nóta dialainne an lae inniu", + "new-note": "Nóta nua", + "show-windows": "Taispeáin fuinneoga", + "open_new_window": "Oscail fuinneog nua", + "tooltip": "Trilium Notes" + }, + "migration": { + "old_version": "Ní thacaítear le haistriú díreach ó do leagan reatha. Uasghrádaigh go dtí an leagan is déanaí v0.60.4 ar dtús agus ansin go dtí an leagan seo amháin.", + "error_message": "Earráid le linn imirce go leagan {{version}}: {{stack}}", + "wrong_db_version": "Tá leagan an bhunachair shonraí ({{version}}) níos nuaí ná mar a bhfuil súil ag an bhfeidhmchlár leis ({{targetVersion}}), rud a chiallaíonn gur cruthaíodh é le leagan níos nuaí agus neamh-chomhoiriúnach de Trilium. Uasghrádaigh go dtí an leagan is déanaí de Trilium chun an fhadhb seo a réiteach." + }, + "modals": { + "error_title": "Earráid" + }, + "share_theme": { + "site-theme": "Téama an tSuímh", + "search_placeholder": "Cuardaigh...", + "image_alt": "Íomhá an Airteagail", + "last-updated": "Nuashonraithe go deireanach ar {{- date}}", + "subpages": "Fo-leathanaigh:", + "on-this-page": "Ar an Leathanach seo", + "expand": "Leathnaigh" + }, + "hidden_subtree_templates": { + "text-snippet": "Sleachta Téacs", + "description": "Cur síos", + "list-view": "Amharc Liosta", + "grid-view": "Radharc Eangaí", + "calendar": "Féilire", + "table": "Tábla", + "geo-map": "Léarscáil Gheografach", + "start-date": "Dáta Tosaigh", + "end-date": "Dáta Deiridh", + "start-time": "Am Tosaigh", + "end-time": "Am Deiridh", + "geolocation": "Geoshuíomh", + "built-in-templates": "Teimpléid ionsuite", + "board": "Bord Kanban", + "status": "Stádas", + "board_note_first": "An chéad nóta", + "board_note_second": "An dara nóta", + "board_note_third": "An tríú nóta", + "board_status_todo": "Le Déanamh", + "board_status_progress": "Ar Siúl", + "board_status_done": "Déanta", + "presentation": "Cur i Láthair", + "presentation_slide": "Sleamhnán cur i láthair", + "presentation_slide_first": "An chéad sleamhnán", + "presentation_slide_second": "An dara sleamhnán", + "background": "Cúlra" + }, + "sql_init": { + "db_not_initialized_desktop": "Níl an bunachar sonraí tosaithe, lean na treoracha ar an scáileán le do thoil.", + "db_not_initialized_server": "Níl an bunachar sonraí tosaithe, tabhair cuairt ar an leathanach socraithe - http://[your-server-host]:{{port}} le treoracha a fheiceáil maidir le conas Trilium a thosú." + }, + "desktop": { + "instance_already_running": "Tá sampla ag rith cheana féin, agus tá fócas á chur ar an sampla sin ina ionad." + } +} From 6c50664046e181845579db4ee76a1d2409b25df3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:41:33 +0100 Subject: [PATCH 136/154] Translated using Weblate (Romanian) Currently translated at 99.9% (1766 of 1767 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ro/ --- .../src/translations/ro/translation.json | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json index 4823db67d3..843f90a03e 100644 --- a/apps/client/src/translations/ro/translation.json +++ b/apps/client/src/translations/ro/translation.json @@ -1761,8 +1761,8 @@ "show-recent-notes": "Afișează notițele recente" }, "electron_integration": { - "background-effects": "Activează efectele de fundal (doar pentru Windows 11)", - "background-effects-description": "Efectul Mica adaugă un fundal estompat și elegant ferestrelor aplicațiilor, creând profunzime și un aspect modern. Opțiunea „Bară de titlu nativă” trebuie să fie dezactivată.", + "background-effects": "Activează efectele de fundal", + "background-effects-description": "Adaugă un fundal estompat și elegant ferestrelor aplicațiilor, creând profunzime și un aspect modern. Opțiunea „Bară de titlu nativă” trebuie să fie dezactivată.", "desktop-application": "Aplicația desktop", "native-title-bar": "Bară de titlu nativă", "native-title-bar-description": "Pentru Windows și macOS, dezactivarea bării de titlu native face aplicația să pară mai compactă. Pe Linux, păstrarea bării integrează mai bine aplicația cu restul sistemului de operare.", @@ -1781,7 +1781,8 @@ "geo-map": { "create-child-note-title": "Crează o notiță nouă și adaug-o pe hartă", "unable-to-load-map": "Nu s-a putut încărca harta.", - "create-child-note-instruction": "Click pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a anula." + "create-child-note-instruction": "Click pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a anula.", + "create-child-note-text": "Adaugă marcaj" }, "duration": { "days": "zile", @@ -2127,7 +2128,7 @@ }, "call_to_action": { "background_effects_title": "Efectele de fundal sunt acum stabile", - "background_effects_message": "Pe dispozitive cu Windows, efectele de fundal sunt complet stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei. Această tehnică este folosită și în alte aplicații precum Windows Explorer.", + "background_effects_message": "Pe dispozitive cu Windows și macOS, efectele de fundal sunt stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei.", "background_effects_button": "Activează efectele de fundal", "next_theme_title": "Încercați noua temă Trilium", "next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?", @@ -2281,5 +2282,14 @@ "pages_other": "{{count}} de pagini", "pages_alt": "Pagina {{pageNumber}}", "pages_loading": "Încărcare..." + }, + "platform_indicator": { + "available_on": "Disponibil pe {{platform}}" + }, + "mobile_tab_switcher": { + "title_one": "{{count}} tab", + "title_few": "{{count}} taburi", + "title_other": "{{count}} de taburi", + "more_options": "Mai multe opțiuni" } } From 703fe9a71b330608a9216901041a5777a1b53d55 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:39:07 +0100 Subject: [PATCH 137/154] Translated using Weblate (Romanian) Currently translated at 100.0% (389 of 389 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ro/ --- apps/server/src/assets/translations/ro/server.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ro/server.json b/apps/server/src/assets/translations/ro/server.json index e831c6f533..df2f73fc7c 100644 --- a/apps/server/src/assets/translations/ro/server.json +++ b/apps/server/src/assets/translations/ro/server.json @@ -257,7 +257,8 @@ "localization": "Limbă și regiune", "inbox-title": "Inbox", "command-palette": "Deschide paleta de comenzi", - "zen-mode": "Mod zen" + "zen-mode": "Mod zen", + "tab-switcher-title": "Schimbător de taburi" }, "notes": { "new-note": "Notiță nouă", From bc2915adb949eab9f7a076af8dede1adf9178577 Mon Sep 17 00:00:00 2001 From: ibs-allaow <255127489+ibs-allaow@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:27:22 +0100 Subject: [PATCH 138/154] Translated using Weblate (Arabic) Currently translated at 42.2% (49 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ar/ --- docs/README-ar.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README-ar.md b/docs/README-ar.md index c92fc73ec2..7f850ba445 100644 --- a/docs/README-ar.md +++ b/docs/README-ar.md @@ -88,11 +88,11 @@ script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md) النصية](https://docs.triliumnotes.org/user-guide/scripts) المتقدمة * UI available in English, German, Spanish, French, Romanian, and Chinese (simplified and traditional) -* Direct [OpenID and TOTP - integration](https://docs.triliumnotes.org/user-guide/setup/server/mfa) for - more secure login -* [Synchronization](https://docs.triliumnotes.org/user-guide/setup/synchronization) - with self-hosted sync server +* تكامل مباشر مع [أنظمة الهوية المفتوحة OpenID وكلمات المرور المؤقتة + TOTP](https://docs.triliumnotes.org/user-guide/setup/server/mfa) لتسجيل دخول + أكثر أماناً +* [المزامنة](https://docs.triliumnotes.org/user-guide/setup/synchronization) مع + خادم مزامنة مُستضاف ذاتيًا * there are [3rd party services for hosting synchronisation server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) * [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) From 331a56277c91eee335d6093fc144d58f41e4aafb Mon Sep 17 00:00:00 2001 From: ibs-allaow <255127489+ibs-allaow@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:19:00 +0100 Subject: [PATCH 139/154] Translated using Weblate (Arabic) Currently translated at 59.7% (1055 of 1767 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ar/ --- apps/client/src/translations/ar/translation.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ar/translation.json b/apps/client/src/translations/ar/translation.json index 39c9bb8415..91fbba92ce 100644 --- a/apps/client/src/translations/ar/translation.json +++ b/apps/client/src/translations/ar/translation.json @@ -39,7 +39,9 @@ "search_note": "البحث عن الملاحظة بالاسم", "link_title": "عنوان الرابط", "button_add_link": "اضافة رابط", - "help_on_links": "مساعدة حول الارتباطات التشعبية" + "help_on_links": "مساعدة حول الارتباطات التشعبية", + "link_title_mirrors": "عنوان الرابط يعكس العنوان الحالي للملاحظة", + "link_title_arbitrary": "يمكن تغيير عنوان الرابط حسب الرغبة" }, "branch_prefix": { "edit_branch_prefix": "تعديل بادئة الفرع", From 84cc4194aa7b860f27280f4ddb94dfac9bc8a214 Mon Sep 17 00:00:00 2001 From: ibs-allaow <255127489+ibs-allaow@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:30:14 +0100 Subject: [PATCH 140/154] Translated using Weblate (Arabic) Currently translated at 61.1% (93 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ar/ --- apps/website/src/translations/ar/translation.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/website/src/translations/ar/translation.json b/apps/website/src/translations/ar/translation.json index 497ea0559d..ff246039ff 100644 --- a/apps/website/src/translations/ar/translation.json +++ b/apps/website/src/translations/ar/translation.json @@ -43,7 +43,10 @@ "title": "الانتاجية والسلامة", "jump_to_title": "الاوامر والبحث السريع", "revisions_content": "تُحفظ الملاحظات دوريًا في الخلفية، ويمكن استخدام التعديلات للمراجعة أو للتراجع عن التغييرات غير المقصودة. كما يمكن إنشاء التعديلات عند الطلب.", - "sync_content": "استخدم نسخة مستضافة ذاتيًا أو نسخة سحابية لمزامنة ملاحظاتك بسهولة عبر أجهزة متعددة، وللوصول إليها من هاتفك المحمول باستخدام تطبيق ويب تقدمي (PWA)." + "sync_content": "استخدم نسخة مستضافة ذاتيًا أو نسخة سحابية لمزامنة ملاحظاتك بسهولة عبر أجهزة متعددة، وللوصول إليها من هاتفك المحمول باستخدام تطبيق ويب تقدمي (PWA).", + "protected_notes_content": "احمِ معلوماتك الشخصية الحساسة عبر تشفير الملاحظات وقفلها خلف جلسة محمية بكلمة مرور.", + "jump_to_content": "انتقل بسرعة إلى الملاحظات أو أوامر واجهة المستخدم عبر التسلسل الهرمي من خلال البحث عن عناوينها، مع ميزة المطابقة التقريبية لتجاوز الأخطاء الإملائية أو الاختلافات البسيطة.", + "search_content": "أو ابحث عن نص داخل الملاحظات وضيّق نطاق البحث عبر التصفية حسب الملاحظة الرئيسية، أو حسب مستوى التفرع." }, "note_types": { "canvas_title": "مساحة العمل", From d25e7915d937fc2d8494d814434fd3611ef3a263 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 00:41:41 +0200 Subject: [PATCH 141/154] style/tree/context menu: use the proper color for menu item icons --- apps/client/src/menus/context_menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/menus/context_menu.ts b/apps/client/src/menus/context_menu.ts index 986ea94c48..415c0a2c6a 100644 --- a/apps/client/src/menus/context_menu.ts +++ b/apps/client/src/menus/context_menu.ts @@ -248,7 +248,7 @@ class ContextMenu { if ("uiIcon" in item || "checked" in item) { const icon = (item.checked ? "bx bx-check" : item.uiIcon); if (icon) { - $icon.addClass(icon); + $icon.addClass([icon, "tn-icon"]); } else { $icon.append(" "); } From a05691fd076e33e26840cadefecbd9dc4985a023 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 00:52:36 +0200 Subject: [PATCH 142/154] style/tree: add a CSS variable to customize the color of tree item icons --- apps/client/src/stylesheets/theme-next-dark.css | 1 + apps/client/src/stylesheets/theme-next-light.css | 1 + apps/client/src/stylesheets/theme-next/shell.css | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 80acfe2e0a..e5c79afaf0 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -134,6 +134,7 @@ --left-pane-collapsed-border-color: #0009; --left-pane-background-color: #1f1f1f; --left-pane-text-color: #aaaaaa; + --left-pane-icon-color: currentColor; --left-pane-item-hover-background: #ffffff0d; --left-pane-item-selected-background: #ffffff25; --left-pane-item-selected-color: #dfdfdf; diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 1e50200d9a..2d7862ae00 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -127,6 +127,7 @@ --left-pane-collapsed-border-color: #0000000d; --left-pane-background-color: #f2f2f2; --left-pane-text-color: #383838; + --left-pane-icon-color: currentColor; --left-pane-item-hover-background: rgba(0, 0, 0, 0.032); --left-pane-item-selected-background: white; --left-pane-item-selected-color: black; diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 5a45ddf436..dd42c148be 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -769,9 +769,9 @@ body.mobile .fancytree-node > span { #left-pane .fancytree-custom-icon { margin-top: 0; /* Use this to align the icon with the tree view item's caption */ + color: var(--left-pane-icon-color); } - #left-pane span.fancytree-active .fancytree-title { font-weight: normal; } From 51d8b13a81524bbde547ef1e8b5dcaf4ac2b5eda Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 01:19:47 +0200 Subject: [PATCH 143/154] style/tree: tweak the color of tree item icons for the dark color scheme --- apps/client/src/stylesheets/theme-next-dark.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index e5c79afaf0..f8fb305726 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -134,7 +134,7 @@ --left-pane-collapsed-border-color: #0009; --left-pane-background-color: #1f1f1f; --left-pane-text-color: #aaaaaa; - --left-pane-icon-color: currentColor; + --left-pane-icon-color: #c5c5c5; --left-pane-item-hover-background: #ffffff0d; --left-pane-item-selected-background: #ffffff25; --left-pane-item-selected-color: #dfdfdf; From 6e792f9735425fb3e5795f0d5bac45d48cbcf709 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 01:25:28 +0200 Subject: [PATCH 144/154] style/tree/tree item icons: fix color for tinted tree items --- apps/client/src/stylesheets/theme-next/shell.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index dd42c148be..18ab495907 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -769,7 +769,7 @@ body.mobile .fancytree-node > span { #left-pane .fancytree-custom-icon { margin-top: 0; /* Use this to align the icon with the tree view item's caption */ - color: var(--left-pane-icon-color); + color: var(--custom-color, var(--left-pane-icon-color)); } #left-pane span.fancytree-active .fancytree-title { From 110e9200b952a4586ffee5c12996b9de5c56af23 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 02:11:57 +0200 Subject: [PATCH 145/154] style/scrolling container: improve alignment when content centering is turned on, refactor --- .../src/widgets/containers/scrolling_container.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/containers/scrolling_container.css b/apps/client/src/widgets/containers/scrolling_container.css index a4be33cf29..0e590801c4 100644 --- a/apps/client/src/widgets/containers/scrolling_container.css +++ b/apps/client/src/widgets/containers/scrolling_container.css @@ -1,12 +1,17 @@ .scrolling-container { + --content-margin-inline: 24px; + overflow: auto; scroll-behavior: smooth; position: relative; - > .inline-title, > .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor, > .note-list-widget:not(.full-height) .note-list-wrapper { - margin-inline: 24px; + margin-inline: var(--content-margin-inline); + } + + > .inline-title { + padding-inline: var(--content-margin-inline); } > .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor { From 2c74697fb11b2bcd59550be548a1ffb9dc36ac49 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 02:22:04 +0200 Subject: [PATCH 146/154] style/text editor: tweak the content placeholder --- apps/client/src/stylesheets/theme-next/notes/text.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/stylesheets/theme-next/notes/text.css b/apps/client/src/stylesheets/theme-next/notes/text.css index 91fe7f9b1b..e025b23fb2 100644 --- a/apps/client/src/stylesheets/theme-next/notes/text.css +++ b/apps/client/src/stylesheets/theme-next/notes/text.css @@ -522,6 +522,10 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck * EDITOR'S CONTENT */ +.note-detail-editable-text-editor > .ck-placeholder { + opacity: .5; +} + /* * Code Blocks */ From ff2a3d6a28c2f8364238a974e52d0553996ae7fb Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 02:56:46 +0200 Subject: [PATCH 147/154] client/search: fix menu dropdowns getting clipped --- apps/client/src/widgets/react/Collapsible.css | 4 ++++ apps/client/src/widgets/react/Collapsible.tsx | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/react/Collapsible.css b/apps/client/src/widgets/react/Collapsible.css index 7dca2f5651..a3222d20a8 100644 --- a/apps/client/src/widgets/react/Collapsible.css +++ b/apps/client/src/widgets/react/Collapsible.css @@ -17,6 +17,10 @@ .collapsible-body { height: 0; overflow: hidden; + + &.fully-expanded { + overflow: visible; + } } .collapsible-inner-body { diff --git a/apps/client/src/widgets/react/Collapsible.tsx b/apps/client/src/widgets/react/Collapsible.tsx index a6e1957b34..212de203dc 100644 --- a/apps/client/src/widgets/react/Collapsible.tsx +++ b/apps/client/src/widgets/react/Collapsible.tsx @@ -27,6 +27,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex const { height } = useElementSize(innerRef) ?? {}; const contentId = useUniqueName(); const [ transitionEnabled, setTransitionEnabled ] = useState(false); + const [ fullyExpanded, setFullyExpanded ] = useState(false); useEffect(() => { const timeout = setTimeout(() => { @@ -35,6 +36,21 @@ export function ExternallyControlledCollapsible({ title, children, className, ex return () => clearTimeout(timeout); }, []); + useEffect(() => { + if (expanded) { + if (transitionEnabled) { + const timeout = setTimeout(() => { + setFullyExpanded(true); + }, 250); + return () => clearTimeout(timeout); + } else { + setFullyExpanded(true); + } + } else { + setFullyExpanded(false); + } + }, [expanded, transitionEnabled]) + return (
    From 38489dbfeb16fdec127f8aea0264772aad0164f4 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 03:03:50 +0200 Subject: [PATCH 148/154] style/collapsible widget: fade content when expanding or collapsing --- apps/client/src/widgets/react/Collapsible.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/client/src/widgets/react/Collapsible.css b/apps/client/src/widgets/react/Collapsible.css index a3222d20a8..7acf628f71 100644 --- a/apps/client/src/widgets/react/Collapsible.css +++ b/apps/client/src/widgets/react/Collapsible.css @@ -25,17 +25,26 @@ .collapsible-inner-body { padding-top: 0.5em; + opacity: 0; } &.expanded { .collapsible-title .arrow { transform: rotate(90deg); } + + .collapsible-inner-body { + opacity: 1; + } } &.with-transition { .collapsible-body { transition: height 250ms ease-in; } + + .collapsible-inner-body { + transition: opacity 250ms ease-in; + } } } From 3f6f3d2565bbeffdc8a8e835a3a4265c913679bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:32:42 +0000 Subject: [PATCH 149/154] Update dependency @redocly/cli to v2.15.1 --- apps/build-docs/package.json | 2 +- pnpm-lock.yaml | 112 +++++++---------------------------- 2 files changed, 24 insertions(+), 90 deletions(-) diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index ac5be7e9be..56fff334a8 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -11,7 +11,7 @@ "license": "AGPL-3.0-only", "packageManager": "pnpm@10.28.2", "devDependencies": { - "@redocly/cli": "2.15.0", + "@redocly/cli": "2.15.1", "archiver": "7.0.1", "fs-extra": "11.3.3", "react": "19.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68404cf8df..c35cdf37f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,8 +156,8 @@ importers: apps/build-docs: devDependencies: '@redocly/cli': - specifier: 2.15.0 - version: 2.15.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) + specifier: 2.15.1 + version: 2.15.1(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) archiver: specifier: 7.0.1 version: 7.0.1 @@ -3562,10 +3562,6 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -4245,10 +4241,6 @@ packages: '@phosphor-icons/web@2.1.2': resolution: {integrity: sha512-rPAR9o/bEcp4Cw4DEeZHXf+nlGCMNGkNDRizYHM47NLxz9vvEHp/Tt6FMK1NcWadzw/pFDPnRBGi/ofRya958A==} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@playwright/test@1.58.1': resolution: {integrity: sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==} engines: {node: '>=18'} @@ -4629,27 +4621,27 @@ packages: '@redocly/ajv@8.17.2': resolution: {integrity: sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==} - '@redocly/cli@2.15.0': - resolution: {integrity: sha512-PjqCE1HDSHgHOrNxixeJSrumy8an2O4rcjXCIfdiHAjB1HOlMEtdgrz5OCx9f0lzcOXrgi61ThFNvO5jG56tUw==} + '@redocly/cli@2.15.1': + resolution: {integrity: sha512-wnzIxQxqhcg9HQUbjxD++cnRpCI8pUUFw2YV9XlFcvDp1EpPy6QokSnZZL3TbC1PLqzTE2XnUycawoTPv9hBuA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.41.2': - resolution: {integrity: sha512-G6muhdTKcEV2TECBFzuT905p4a27OgUtwBqRVnMx1JebO6i8zlm6bPB2H3fD1Hl+MiUpk7Jx2kwGmLVgpz5nIg==} + '@redocly/config@0.41.4': + resolution: {integrity: sha512-LaRKXpHyK5GxmgG2n2e8xp2NyUa8qZls3WMAVg5hKO4b2d7X5uU5F2jvH6JUTVdRW/VfStQqan5DQ1uQz7MVzg==} '@redocly/openapi-core@1.34.5': resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.15.0': - resolution: {integrity: sha512-q/2UM5tA9mgk4zaIDUkKOG9KBUqIdSIhp5DugHmFOszOE+WMiL23BIV40K79jNF9aSU3zF1p1c/Zu0UoBmBsZg==} + '@redocly/openapi-core@2.15.1': + resolution: {integrity: sha512-6OKY+ZgAnU2B1OoPQHXXWCXwNQQUn+8N7WfGMrpRZxorQsB9EcQs0rYofO4mquFzvmIgU2owf8cLaD771pv3BQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.15.0': - resolution: {integrity: sha512-0LFtQXNUE+e9OdGDLS+yt4RBx0JTGhM7UAaBC4tzLqPvhtbdWo/WB/WdasGIt4vMM9K47KivxPHmX13sSLEO2Q==} + '@redocly/respect-core@2.15.1': + resolution: {integrity: sha512-Qrbhv/TE0TkxNaoDv1hPtNcpMORQiSrU9R95ZtI6KevgUcuEK2GgmoXHTrtVdWdxnpqm8bESTxffcz7+etMjOQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@replit/codemirror-indentation-markers@6.5.3': @@ -8803,10 +8795,6 @@ packages: foreach@2.0.6: resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - form-data-encoder@4.1.0: resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} engines: {node: '>= 18'} @@ -9050,10 +9038,6 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true - glob@13.0.0: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} @@ -9961,9 +9945,6 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jake@10.9.2: resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} engines: {node: '>=10'} @@ -11688,10 +11669,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} @@ -16011,8 +15988,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -16078,6 +16053,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-watchdog': 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-dev-build-tools@54.3.2(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -16230,8 +16207,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.4.0': dependencies: @@ -16254,8 +16229,6 @@ snapshots: '@ckeditor/ckeditor5-table': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-emoji@47.4.0': dependencies: @@ -16281,8 +16254,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-engine': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-essentials@47.4.0': dependencies: @@ -16440,8 +16411,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-icons@47.4.0': {} @@ -18561,15 +18530,6 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -19379,9 +19339,6 @@ snapshots: '@phosphor-icons/web@2.1.2': {} - '@pkgjs/parseargs@0.11.0': - optional: true - '@playwright/test@1.58.1': dependencies: playwright: 1.58.1 @@ -19780,14 +19737,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.15.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': + '@redocly/cli@2.15.1(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.15.0 - '@redocly/respect-core': 2.15.0 + '@redocly/openapi-core': 2.15.1 + '@redocly/respect-core': 2.15.1 abort-controller: 3.0.0 ajv: '@redocly/ajv@8.17.1' ajv-formats: 3.0.1(@redocly/ajv@8.17.1) @@ -19798,6 +19755,7 @@ snapshots: handlebars: 4.7.8 https-proxy-agent: 7.0.6 mobx: 6.15.0 + picomatch: 4.0.3 pluralize: 8.0.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -19820,7 +19778,7 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.41.2': + '@redocly/config@0.41.4': dependencies: json-schema-to-ts: 2.7.2 @@ -19838,10 +19796,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.15.0': + '@redocly/openapi-core@2.15.1': dependencies: '@redocly/ajv': 8.17.2 - '@redocly/config': 0.41.2 + '@redocly/config': 0.41.4 ajv: '@redocly/ajv@8.17.2' ajv-formats: 3.0.1(@redocly/ajv@8.17.2) colorette: 1.4.0 @@ -19851,12 +19809,12 @@ snapshots: pluralize: 8.0.0 yaml-ast-parser: 0.0.43 - '@redocly/respect-core@2.15.0': + '@redocly/respect-core@2.15.1': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.15.0 + '@redocly/openapi-core': 2.15.1 ajv: '@redocly/ajv@8.17.1' better-ajv-errors: 1.2.0(@redocly/ajv@8.17.1) colorette: 2.0.20 @@ -19864,6 +19822,7 @@ snapshots: jsonpath-rfc9535: 1.3.0 openapi-sampler: 1.6.2 outdent: 0.8.0 + picomatch: 4.0.3 '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.12)': dependencies: @@ -22020,7 +21979,7 @@ snapshots: archiver-utils@5.0.2: dependencies: - glob: 10.5.0 + glob: 13.0.0 graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 @@ -25124,11 +25083,6 @@ snapshots: foreach@2.0.6: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - form-data-encoder@4.1.0: {} form-data@4.0.5: @@ -25402,15 +25356,6 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@13.0.0: dependencies: minimatch: 10.1.1 @@ -26390,12 +26335,6 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jake@10.9.2: dependencies: async: 3.2.6 @@ -28589,11 +28528,6 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-scurry@2.0.0: dependencies: lru-cache: 11.2.4 From 177aedeaaedf84acd3c54afdf4bdfbc650d45d91 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:33:28 +0000 Subject: [PATCH 150/154] Update dependency @smithy/middleware-retry to v4.4.30 --- packages/ckeditor5/package.json | 2 +- pnpm-lock.yaml | 153 ++++++++++++++++++-------------- 2 files changed, 88 insertions(+), 67 deletions(-) diff --git a/packages/ckeditor5/package.json b/packages/ckeditor5/package.json index d31e3a5ff1..af4b31a38a 100644 --- a/packages/ckeditor5/package.json +++ b/packages/ckeditor5/package.json @@ -16,7 +16,7 @@ "ckeditor5-premium-features": "47.4.0" }, "devDependencies": { - "@smithy/middleware-retry": "4.4.29", + "@smithy/middleware-retry": "4.4.30", "@types/jquery": "3.5.33" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68404cf8df..1a69c50d4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -917,8 +917,8 @@ importers: version: 47.4.0(bufferutil@4.0.9)(ckeditor5@47.4.0)(utf-8-validate@6.0.5) devDependencies: '@smithy/middleware-retry': - specifier: 4.4.29 - version: 4.4.29 + specifier: 4.4.30 + version: 4.4.30 '@types/jquery': specifier: 3.5.33 version: 3.5.33 @@ -5047,14 +5047,14 @@ packages: resolution: {integrity: sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==} engines: {node: '>=18.0.0'} - '@smithy/core@3.21.1': - resolution: {integrity: sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA==} - engines: {node: '>=18.0.0'} - '@smithy/core@3.22.0': resolution: {integrity: sha512-6vjCHD6vaY8KubeNw2Fg3EK0KLGQYdldG4fYgQmA0xSW0dJ8G2xFhSOdrlUakWVoP5JuWHtFODg3PNd/DN3FDA==} engines: {node: '>=18.0.0'} + '@smithy/core@3.22.1': + resolution: {integrity: sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.0.6': resolution: {integrity: sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==} engines: {node: '>=18.0.0'} @@ -5103,16 +5103,16 @@ packages: resolution: {integrity: sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.4.11': - resolution: {integrity: sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.4.12': resolution: {integrity: sha512-9JMKHVJtW9RysTNjcBZQHDwB0p3iTP6B1IfQV4m+uCevkVd/VuLgwfqk5cnI4RHcp4cPwoIvxQqN4B1sxeHo8Q==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.29': - resolution: {integrity: sha512-bmTn75a4tmKRkC5w61yYQLb3DmxNzB8qSVu9SbTYqW6GAL0WXO2bDZuMAn/GJSbOdHEdjZvWxe+9Kk015bw6Cg==} + '@smithy/middleware-endpoint@4.4.13': + resolution: {integrity: sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.30': + resolution: {integrity: sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==} engines: {node: '>=18.0.0'} '@smithy/middleware-serde@4.2.9': @@ -5131,6 +5131,10 @@ packages: resolution: {integrity: sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.4.9': + resolution: {integrity: sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.8': resolution: {integrity: sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==} engines: {node: '>=18.0.0'} @@ -5159,14 +5163,14 @@ packages: resolution: {integrity: sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.10.12': - resolution: {integrity: sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA==} - engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.11.1': resolution: {integrity: sha512-SERgNg5Z1U+jfR6/2xPYjSEHY1t3pyTHC/Ma3YQl6qWtmiL42bvNId3W/oMUWIwu7ekL2FMPdqAmwbQegM7HeQ==} engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.11.2': + resolution: {integrity: sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==} + engines: {node: '>=18.0.0'} + '@smithy/types@4.12.0': resolution: {integrity: sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==} engines: {node: '>=18.0.0'} @@ -5235,6 +5239,10 @@ packages: resolution: {integrity: sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==} engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.11': + resolution: {integrity: sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==} + engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.2.0': resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} @@ -15293,7 +15301,7 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.821.0 '@aws-sdk/util-user-agent-node': 3.823.0 '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.21.1 + '@smithy/core': 3.22.0 '@smithy/eventstream-serde-browser': 4.0.4 '@smithy/eventstream-serde-config-resolver': 4.1.2 '@smithy/eventstream-serde-node': 4.0.4 @@ -15301,14 +15309,14 @@ snapshots: '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.4.11 - '@smithy/middleware-retry': 4.4.29 + '@smithy/middleware-endpoint': 4.4.12 + '@smithy/middleware-retry': 4.4.30 '@smithy/middleware-serde': 4.2.9 '@smithy/middleware-stack': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/node-http-handler': 4.4.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 '@smithy/url-parser': 4.2.8 '@smithy/util-base64': 4.3.0 @@ -15342,19 +15350,19 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.821.0 '@aws-sdk/util-user-agent-node': 3.823.0 '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.21.1 + '@smithy/core': 3.22.0 '@smithy/fetch-http-handler': 5.3.9 '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.4.11 - '@smithy/middleware-retry': 4.4.29 + '@smithy/middleware-endpoint': 4.4.12 + '@smithy/middleware-retry': 4.4.30 '@smithy/middleware-serde': 4.2.9 '@smithy/middleware-stack': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/node-http-handler': 4.4.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 '@smithy/url-parser': 4.2.8 '@smithy/util-base64': 4.3.0 @@ -15374,12 +15382,12 @@ snapshots: dependencies: '@aws-sdk/types': 3.821.0 '@aws-sdk/xml-builder': 3.821.0 - '@smithy/core': 3.21.1 + '@smithy/core': 3.22.0 '@smithy/node-config-provider': 4.3.8 '@smithy/property-provider': 4.2.8 '@smithy/protocol-http': 5.3.8 '@smithy/signature-v4': 5.1.2 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 @@ -15404,7 +15412,7 @@ snapshots: '@smithy/node-http-handler': 4.4.8 '@smithy/property-provider': 4.2.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 '@smithy/util-stream': 4.5.10 tslib: 2.8.1 @@ -15516,7 +15524,7 @@ snapshots: '@aws-sdk/core': 3.823.0 '@aws-sdk/types': 3.821.0 '@aws-sdk/util-endpoints': 3.821.0 - '@smithy/core': 3.21.1 + '@smithy/core': 3.22.0 '@smithy/protocol-http': 5.3.8 '@smithy/types': 4.12.0 tslib: 2.8.1 @@ -15536,19 +15544,19 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.821.0 '@aws-sdk/util-user-agent-node': 3.823.0 '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.21.1 + '@smithy/core': 3.22.0 '@smithy/fetch-http-handler': 5.3.9 '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.4.11 - '@smithy/middleware-retry': 4.4.29 + '@smithy/middleware-endpoint': 4.4.12 + '@smithy/middleware-retry': 4.4.30 '@smithy/middleware-serde': 4.2.9 '@smithy/middleware-stack': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/node-http-handler': 4.4.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 '@smithy/url-parser': 4.2.8 '@smithy/util-base64': 4.3.0 @@ -16011,8 +16019,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -16281,8 +16287,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-engine': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-essentials@47.4.0': dependencies: @@ -16743,8 +16747,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.4.0': dependencies: @@ -20193,7 +20195,7 @@ snapshots: '@smithy/util-middleware': 4.2.8 tslib: 2.8.1 - '@smithy/core@3.21.1': + '@smithy/core@3.22.0': dependencies: '@smithy/middleware-serde': 4.2.9 '@smithy/protocol-http': 5.3.8 @@ -20206,7 +20208,7 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/core@3.22.0': + '@smithy/core@3.22.1': dependencies: '@smithy/middleware-serde': 4.2.9 '@smithy/protocol-http': 5.3.8 @@ -20214,7 +20216,7 @@ snapshots: '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-middleware': 4.2.8 - '@smithy/util-stream': 4.5.10 + '@smithy/util-stream': 4.5.11 '@smithy/util-utf8': 4.2.0 '@smithy/uuid': 1.1.0 tslib: 2.8.1 @@ -20291,17 +20293,6 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.4.11': - dependencies: - '@smithy/core': 3.21.1 - '@smithy/middleware-serde': 4.2.9 - '@smithy/node-config-provider': 4.3.8 - '@smithy/shared-ini-file-loader': 4.4.3 - '@smithy/types': 4.12.0 - '@smithy/url-parser': 4.2.8 - '@smithy/util-middleware': 4.2.8 - tslib: 2.8.1 - '@smithy/middleware-endpoint@4.4.12': dependencies: '@smithy/core': 3.22.0 @@ -20313,12 +20304,23 @@ snapshots: '@smithy/util-middleware': 4.2.8 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.29': + '@smithy/middleware-endpoint@4.4.13': + dependencies: + '@smithy/core': 3.22.1 + '@smithy/middleware-serde': 4.2.9 + '@smithy/node-config-provider': 4.3.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + '@smithy/util-middleware': 4.2.8 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.30': dependencies: '@smithy/node-config-provider': 4.3.8 '@smithy/protocol-http': 5.3.8 '@smithy/service-error-classification': 4.2.8 - '@smithy/smithy-client': 4.11.1 + '@smithy/smithy-client': 4.11.2 '@smithy/types': 4.12.0 '@smithy/util-middleware': 4.2.8 '@smithy/util-retry': 4.2.8 @@ -20351,6 +20353,14 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 + '@smithy/node-http-handler@4.4.9': + dependencies: + '@smithy/abort-controller': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/querystring-builder': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.8.1 + '@smithy/property-provider@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -20392,16 +20402,6 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/smithy-client@4.10.12': - dependencies: - '@smithy/core': 3.21.1 - '@smithy/middleware-endpoint': 4.4.11 - '@smithy/middleware-stack': 4.2.8 - '@smithy/protocol-http': 5.3.8 - '@smithy/types': 4.12.0 - '@smithy/util-stream': 4.5.10 - tslib: 2.8.1 - '@smithy/smithy-client@4.11.1': dependencies: '@smithy/core': 3.22.0 @@ -20412,6 +20412,16 @@ snapshots: '@smithy/util-stream': 4.5.10 tslib: 2.8.1 + '@smithy/smithy-client@4.11.2': + dependencies: + '@smithy/core': 3.22.1 + '@smithy/middleware-endpoint': 4.4.13 + '@smithy/middleware-stack': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-stream': 4.5.11 + tslib: 2.8.1 + '@smithy/types@4.12.0': dependencies: tslib: 2.8.1 @@ -20457,7 +20467,7 @@ snapshots: '@smithy/util-defaults-mode-browser@4.0.22': dependencies: '@smithy/property-provider': 4.2.8 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 bowser: 2.11.0 tslib: 2.8.1 @@ -20468,7 +20478,7 @@ snapshots: '@smithy/credential-provider-imds': 4.0.6 '@smithy/node-config-provider': 4.3.8 '@smithy/property-provider': 4.2.8 - '@smithy/smithy-client': 4.10.12 + '@smithy/smithy-client': 4.11.1 '@smithy/types': 4.12.0 tslib: 2.8.1 @@ -20510,6 +20520,17 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@smithy/util-stream@4.5.11': + dependencies: + '@smithy/fetch-http-handler': 5.3.9 + '@smithy/node-http-handler': 4.4.9 + '@smithy/types': 4.12.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.0': dependencies: tslib: 2.8.1 From 1c260f58902462e44fd2e2e84b5f28d643663692 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:34:19 +0000 Subject: [PATCH 151/154] Update dependency happy-dom to v20.5.0 --- apps/client/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 50 ++++++++++++++++++---------------------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index ca31f70a70..dc210480a6 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -78,7 +78,7 @@ "@types/reveal.js": "5.2.2", "@types/tabulator-tables": "6.3.1", "copy-webpack-plugin": "13.0.1", - "happy-dom": "20.4.0", + "happy-dom": "20.5.0", "lightningcss": "1.31.1", "script-loader": "0.7.2", "vite-plugin-static-copy": "3.2.0" diff --git a/package.json b/package.json index 8015f90bf6..2d66b6b943 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "eslint-config-prettier": "10.1.8", "eslint-plugin-playwright": "2.5.1", "eslint-plugin-simple-import-sort": "12.1.1", - "happy-dom": "20.4.0", + "happy-dom": "20.5.0", "http-server": "14.1.1", "jiti": "2.6.1", "js-yaml": "4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68404cf8df..94d0c15c59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,8 +108,8 @@ importers: specifier: 12.1.1 version: 12.1.1(eslint@9.39.2(jiti@2.6.1)) happy-dom: - specifier: 20.4.0 - version: 20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 20.5.0 + version: 20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) http-server: specifier: 14.1.1 version: 14.1.1 @@ -151,7 +151,7 @@ importers: version: 4.5.4(@types/node@24.10.9)(rollup@4.52.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) apps/build-docs: devDependencies: @@ -361,8 +361,8 @@ importers: specifier: 13.0.1 version: 13.0.1(webpack@5.101.3(esbuild@0.27.2)) happy-dom: - specifier: 20.4.0 - version: 20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 20.5.0 + version: 20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) lightningcss: specifier: 1.31.1 version: 1.31.1 @@ -887,7 +887,7 @@ importers: version: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) packages/ckeditor5: dependencies: @@ -978,7 +978,7 @@ importers: version: 2.0.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: specifier: 9.23.3 version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1038,7 +1038,7 @@ importers: version: 2.0.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: specifier: 9.23.3 version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1098,7 +1098,7 @@ importers: version: 2.0.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: specifier: 9.23.3 version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1165,7 +1165,7 @@ importers: version: 2.0.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: specifier: 9.23.3 version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1232,7 +1232,7 @@ importers: version: 2.0.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: 4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: specifier: 9.23.3 version: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -9170,8 +9170,8 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - happy-dom@20.4.0: - resolution: {integrity: sha512-RDeQm3dT9n0A5f/TszjUmNCLEuPnMGv3Tv4BmNINebz/h17PA6LMBcxJ5FrcqltNBMh9jA/8ufgDdBYUdBt+eg==} + happy-dom@20.5.0: + resolution: {integrity: sha512-VQe+Q5CYiGOgcCERXhcfNsbnrN92FDEKciMH/x6LppU9dd0j4aTjCTlqONFOIMcAm/5JxS3+utowbXV1OoFr+g==} engines: {node: '>=20.0.0'} has-bigints@1.1.0: @@ -16011,8 +16011,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -16078,6 +16076,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-watchdog': 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-dev-build-tools@54.3.2(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -16281,8 +16281,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-engine': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-essentials@47.4.0': dependencies: @@ -16743,8 +16741,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.4.0': dependencies: @@ -21493,7 +21489,7 @@ snapshots: '@vitest/browser-webdriverio@4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: '@vitest/browser': 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18) - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: 9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -21510,7 +21506,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) ws: 8.19.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -21530,7 +21526,7 @@ snapshots: magicast: 0.5.1 obug: 2.1.1 tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -21546,7 +21542,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) optionalDependencies: '@vitest/browser': 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18) @@ -21594,7 +21590,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) '@vitest/utils@4.0.18': dependencies: @@ -25556,7 +25552,7 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): + happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 24.10.9 '@types/whatwg-mimetype': 3.0.2 @@ -32022,7 +32018,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.1 - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/browser-webdriverio@4.0.18)(@vitest/ui@4.0.18)(happy-dom@20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.31.1)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) @@ -32049,7 +32045,7 @@ snapshots: '@types/node': 24.10.9 '@vitest/browser-webdriverio': 4.0.18(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.9)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.18)(webdriverio@9.23.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/ui': 4.0.18(vitest@4.0.18) - happy-dom: 20.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + happy-dom: 20.5.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - jiti From e951d60800469298790ccb030e91f8570795edc7 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 20:54:56 +0200 Subject: [PATCH 152/154] style/options: hide collection properties --- apps/client/src/stylesheets/theme-next/pages.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/stylesheets/theme-next/pages.css b/apps/client/src/stylesheets/theme-next/pages.css index f323d00005..e6125e80e2 100644 --- a/apps/client/src/stylesheets/theme-next/pages.css +++ b/apps/client/src/stylesheets/theme-next/pages.css @@ -156,6 +156,10 @@ --preferred-max-content-width: var(--options-card-max-width); } +.note-split.options .collection-properties { + visibility: hidden; +} + /* Create a gap at the top of the option pages */ .note-detail-content-widget-content.options>*:first-child { margin-top: var(--options-first-item-top-margin, 1em); From c42c06d048e148534ba8a572252d8b09675a0aa7 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 3 Feb 2026 21:01:08 +0200 Subject: [PATCH 153/154] style/global menu: use proper heading for the development options section --- apps/client/src/widgets/buttons/global_menu.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index 95a3b8b946..0986c8ad29 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -105,8 +105,7 @@ function BrowserOnlyOptions() { function DevelopmentOptions({ dropStart }: { dropStart: boolean }) { return <> - - Development Options + {experimentalFeatures.map((feature) => ( From bb05aeeaf79a4504c0965f5bae56aecf66525753 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Wed, 4 Feb 2026 00:44:54 +0200 Subject: [PATCH 154/154] style/split: tweak transition for the current split indicator --- apps/client/src/stylesheets/theme-next/shell.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 18ab495907..fae4001f08 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -1272,7 +1272,7 @@ body.layout-horizontal #rest-pane > .classic-toolbar-widget { #center-pane .note-split { padding-top: 2px; background-color: var(--note-split-background-color, var(--main-background-color)); - transition: border-color 250ms ease-in; + transition: border-color 150ms ease-out; border: 2px solid transparent; }