diff --git a/_regroup/package.json b/_regroup/package.json
index fd13c78f11..ce0e70d452 100644
--- a/_regroup/package.json
+++ b/_regroup/package.json
@@ -41,7 +41,7 @@
"@types/node": "24.10.0",
"@types/yargs": "17.0.34",
"@vitest/coverage-v8": "3.2.4",
- "eslint": "9.39.0",
+ "eslint": "9.39.1",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.5",
diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json
index f22baec816..00196de82e 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.20.0",
"devDependencies": {
- "@redocly/cli": "2.10.0",
+ "@redocly/cli": "2.11.0",
"archiver": "7.0.1",
"fs-extra": "11.3.2",
"react": "19.2.0",
diff --git a/apps/build-docs/src/main.ts b/apps/build-docs/src/main.ts
index 19d533420b..d94ada167b 100644
--- a/apps/build-docs/src/main.ts
+++ b/apps/build-docs/src/main.ts
@@ -22,8 +22,9 @@ async function main() {
buildSwagger(context);
buildScriptApi(context);
- // Copy index file.
+ // Copy index and 404 files.
cpSync(join(__dirname, "index.html"), join(context.baseDir, "index.html"));
+ cpSync(join(context.baseDir, "user-guide/404.html"), join(context.baseDir, "404.html"));
}
main();
diff --git a/apps/client/package.json b/apps/client/package.json
index c080281ec5..5ecf18cac9 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -15,7 +15,7 @@
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
- "@eslint/js": "9.39.0",
+ "@eslint/js": "9.39.1",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
@@ -59,7 +59,7 @@
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
- "react-i18next": "16.2.3",
+ "react-i18next": "16.2.4",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts
index 6504b49eb5..7384573d85 100644
--- a/apps/client/src/menus/tree_context_menu.ts
+++ b/apps/client/src/menus/tree_context_menu.ts
@@ -137,7 +137,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener {
expect(matchesShortcut(event, "Shift+F1")).toBeTruthy();
// Special keys
- for (const keyCode of [ "Delete", "Enter" ]) {
+ for (const keyCode of [ "Delete", "Enter", "NumpadEnter" ]) {
event = createKeyboardEvent({ key: keyCode, code: keyCode });
expect(matchesShortcut(event, keyCode), `Key ${keyCode}`).toBeTruthy();
}
diff --git a/apps/client/src/services/shortcuts.ts b/apps/client/src/services/shortcuts.ts
index 94dd8893cb..7d6a1e956a 100644
--- a/apps/client/src/services/shortcuts.ts
+++ b/apps/client/src/services/shortcuts.ts
@@ -46,6 +46,7 @@ for (let i = 1; i <= 19; i++) {
const KEYCODES_WITH_NO_MODIFIER = new Set([
"Delete",
"Enter",
+ "NumpadEnter",
...functionKeyCodes
]);
diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json
index 1868619d4b..ff732a04be 100644
--- a/apps/client/src/translations/en/translation.json
+++ b/apps/client/src/translations/en/translation.json
@@ -36,10 +36,13 @@
},
"branch_prefix": {
"edit_branch_prefix": "Edit branch prefix",
+ "edit_branch_prefix_multiple": "Edit branch prefix for {{count}} branches",
"help_on_tree_prefix": "Help on Tree prefix",
"prefix": "Prefix: ",
"save": "Save",
- "branch_prefix_saved": "Branch prefix has been saved."
+ "branch_prefix_saved": "Branch prefix has been saved.",
+ "branch_prefix_saved_multiple": "Branch prefix has been saved for {{count}} branches.",
+ "affected_branches": "Affected branches ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Bulk actions",
diff --git a/apps/client/src/translations/it/translation.json b/apps/client/src/translations/it/translation.json
index d9b760dd8f..a06fe08f57 100644
--- a/apps/client/src/translations/it/translation.json
+++ b/apps/client/src/translations/it/translation.json
@@ -109,7 +109,8 @@
"export_type_single": "Solo questa nota, senza le sottostanti",
"format_opml": "OPML - formato per scambio informazioni outline. Formattazione, immagini e files non sono inclusi.",
"opml_version_1": "OPML v.1.0 - solo testo semplice",
- "opml_version_2": "OPML v2.0 - supporta anche HTML"
+ "opml_version_2": "OPML v2.0 - supporta anche HTML",
+ "share-format": "HTML per la pubblicazione sul web - utilizza lo stesso tema utilizzato per le note condivise, ma può essere pubblicato come sito web statico."
},
"password_not_set": {
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
diff --git a/apps/client/src/widgets/dialogs/branch_prefix.css b/apps/client/src/widgets/dialogs/branch_prefix.css
new file mode 100644
index 0000000000..3470f1018e
--- /dev/null
+++ b/apps/client/src/widgets/dialogs/branch_prefix.css
@@ -0,0 +1,13 @@
+.branch-prefix-dialog .branch-prefix-notes-list {
+ margin-top: 10px;
+}
+
+.branch-prefix-dialog .branch-prefix-notes-list ul {
+ max-height: 200px;
+ overflow: auto;
+ margin-top: 5px;
+}
+
+.branch-prefix-dialog .branch-prefix-current {
+ opacity: 0.6;
+}
diff --git a/apps/client/src/widgets/dialogs/branch_prefix.tsx b/apps/client/src/widgets/dialogs/branch_prefix.tsx
index 46888f0ab4..e715c894f6 100644
--- a/apps/client/src/widgets/dialogs/branch_prefix.tsx
+++ b/apps/client/src/widgets/dialogs/branch_prefix.tsx
@@ -10,53 +10,86 @@ import Button from "../react/Button.jsx";
import FormGroup from "../react/FormGroup.js";
import { useTriliumEvent } from "../react/hooks.jsx";
import FBranch from "../../entities/fbranch.js";
+import type { ContextMenuCommandData } from "../../components/app_context.js";
+import "./branch_prefix.css";
+
+// Virtual branches (e.g., from search results) start with this prefix
+const VIRTUAL_BRANCH_PREFIX = "virt-";
export default function BranchPrefixDialog() {
const [ shown, setShown ] = useState(false);
- const [ branch, setBranch ] = useState();
+ const [ branches, setBranches ] = useState([]);
const [ prefix, setPrefix ] = useState("");
const branchInput = useRef(null);
- useTriliumEvent("editBranchPrefix", async () => {
- const notePath = appContext.tabManager.getActiveContextNotePath();
- if (!notePath) {
+ useTriliumEvent("editBranchPrefix", async (data?: ContextMenuCommandData) => {
+ let branchIds: string[] = [];
+
+ if (data?.selectedOrActiveBranchIds && data.selectedOrActiveBranchIds.length > 0) {
+ // Multi-select mode from tree context menu
+ branchIds = data.selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith(VIRTUAL_BRANCH_PREFIX));
+ } else {
+ // Single branch mode from keyboard shortcut or when no selection
+ const notePath = appContext.tabManager.getActiveContextNotePath();
+ if (!notePath) {
+ return;
+ }
+
+ const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
+
+ if (!noteId || !parentNoteId) {
+ return;
+ }
+
+ const branchId = await froca.getBranchId(parentNoteId, noteId);
+ if (!branchId) {
+ return;
+ }
+ const parentNote = await froca.getNote(parentNoteId);
+ if (!parentNote || parentNote.type === "search") {
+ return;
+ }
+
+ branchIds = [branchId];
+ }
+
+ if (branchIds.length === 0) {
return;
}
- const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
+ const newBranches = branchIds
+ .map(id => froca.getBranch(id))
+ .filter((branch): branch is FBranch => branch !== null);
- if (!noteId || !parentNoteId) {
+ if (newBranches.length === 0) {
return;
}
- const newBranchId = await froca.getBranchId(parentNoteId, noteId);
- if (!newBranchId) {
- return;
- }
- const parentNote = await froca.getNote(parentNoteId);
- if (!parentNote || parentNote.type === "search") {
- return;
- }
-
- const newBranch = froca.getBranch(newBranchId);
- setBranch(newBranch);
- setPrefix(newBranch?.prefix ?? "");
+ setBranches(newBranches);
+ // Use the prefix of the first branch as the initial value
+ setPrefix(newBranches[0]?.prefix ?? "");
setShown(true);
});
async function onSubmit() {
- if (!branch) {
+ if (branches.length === 0) {
return;
}
- savePrefix(branch.branchId, prefix);
+ if (branches.length === 1) {
+ await savePrefix(branches[0].branchId, prefix);
+ } else {
+ await savePrefixBatch(branches.map(b => b.branchId), prefix);
+ }
setShown(false);
}
+ const isSingleBranch = branches.length === 1;
+
return (
branchInput.current?.focus()}
onHidden={() => setShown(false)}
@@ -69,9 +102,27 @@ export default function BranchPrefixDialog() {
+ {!isSingleBranch && (
+
+
{t("branch_prefix.affected_branches", { count: branches.length })}
+
+ {branches.map((branch) => {
+ const note = branch.getNoteFromCache();
+ return (
+
+ {branch.prefix && {branch.prefix} - }
+ {note.title}
+
+ );
+ })}
+
+
+ )}
);
}
@@ -80,3 +131,8 @@ async function savePrefix(branchId: string, prefix: string) {
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
toast.showMessage(t("branch_prefix.branch_prefix_saved"));
}
+
+async function savePrefixBatch(branchIds: string[], prefix: string) {
+ await server.put("branches/set-prefix-batch", { branchIds, prefix });
+ toast.showMessage(t("branch_prefix.branch_prefix_saved_multiple", { count: branchIds.length }));
+}
diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts
index f1c2ca7363..cb21206870 100644
--- a/apps/client/src/widgets/note_tree.ts
+++ b/apps/client/src/widgets/note_tree.ts
@@ -1591,6 +1591,20 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.clearSelectedNodes();
}
+ async editBranchPrefixCommand({ node }: CommandListenerData<"editBranchPrefix">) {
+ const branchIds = this.getSelectedOrActiveBranchIds(node).filter((branchId) => !branchId.startsWith("virt-"));
+
+ if (!branchIds.length) {
+ return;
+ }
+
+ // Trigger the event with the selected branch IDs
+ appContext.triggerEvent("editBranchPrefix", {
+ selectedOrActiveBranchIds: branchIds,
+ node: node
+ });
+ }
+
canBeMovedUpOrDown(node: Fancytree.FancytreeNode) {
if (node.data.noteId === "root") {
return false;
diff --git a/apps/client/src/widgets/ribbon/EditedNotesTab.tsx b/apps/client/src/widgets/ribbon/EditedNotesTab.tsx
index 5bab1c8169..4bdae4126b 100644
--- a/apps/client/src/widgets/ribbon/EditedNotesTab.tsx
+++ b/apps/client/src/widgets/ribbon/EditedNotesTab.tsx
@@ -13,8 +13,8 @@ export default function EditedNotesTab({ note }: TabContext) {
useEffect(() => {
if (!note) return;
server.get(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => {
- editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
- const noteIds = editedNotes.flatMap((n) => n.noteId);
+ editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
+ const noteIds = editedNotes.flatMap((n) => n.noteId);
await froca.getNotes(noteIds, true); // preload all at once
setEditedNotes(editedNotes);
});
@@ -41,11 +41,11 @@ export default function EditedNotesTab({ note }: TabContext) {
)}
)
- }))}
+ }), " ")}
) : (
{t("edited_notes.no_edited_notes_found")}
)}
- )
+ )
}
diff --git a/apps/client/src/widgets/type_widgets/options/text_notes.tsx b/apps/client/src/widgets/type_widgets/options/text_notes.tsx
index 4e24759227..0dd1021457 100644
--- a/apps/client/src/widgets/type_widgets/options/text_notes.tsx
+++ b/apps/client/src/widgets/type_widgets/options/text_notes.tsx
@@ -72,8 +72,8 @@ function EditorFeatures() {
return (
-
-
+
+
);
}
diff --git a/apps/edit-docs/demo/!!!meta.json b/apps/edit-docs/demo/!!!meta.json
index ce5046fb11..44b61171d5 100644
--- a/apps/edit-docs/demo/!!!meta.json
+++ b/apps/edit-docs/demo/!!!meta.json
@@ -1,6 +1,6 @@
{
"formatVersion": 2,
- "appVersion": "0.99.2",
+ "appVersion": "0.99.3",
"files": [
{
"isClone": false,
@@ -2700,6 +2700,7 @@
}
],
"format": "html",
+ "dataFileName": "Note Types.html",
"attachments": [],
"dirFileName": "Note Types",
"children": [
diff --git a/apps/edit-docs/demo/navigation.html b/apps/edit-docs/demo/navigation.html
index 1d4d5d57b8..4d1371ac2c 100644
--- a/apps/edit-docs/demo/navigation.html
+++ b/apps/edit-docs/demo/navigation.html
@@ -270,7 +270,7 @@
- Note Types
+ Note Types
Canvas
diff --git a/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html b/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html
index 8f2333bf02..a33b14490e 100644
--- a/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html
+++ b/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html
@@ -14,6 +14,7 @@
☑️ Tasks
+
diff --git a/apps/edit-docs/demo/root/Trilium Demo.html b/apps/edit-docs/demo/root/Trilium Demo.html
index 206054b925..b5b6672d6d 100644
--- a/apps/edit-docs/demo/root/Trilium Demo.html
+++ b/apps/edit-docs/demo/root/Trilium Demo.html
@@ -14,11 +14,10 @@
-
+
Welcome to Trilium Notes!
-
This is a "demo" document packaged with Trilium to showcase some of its
features and also give you some ideas on how you might structure your notes.
@@ -26,22 +25,17 @@
you wish.
If you need any help, visit triliumnotes.org or
our GitHub repository
-
Cleanup
-
Once you're finished with experimenting and want to cleanup these pages,
you can simply delete them all.
Formatting
-
Trilium supports classic formatting like italic , bold , bold and italic .
You can add links pointing to external pages or
Formatting examples .
Lists
-
Ordered:
-
First Item
@@ -56,7 +50,6 @@
Unordered:
-
Block quotes
-
Whereof one cannot speak, thereof one must be silent”
– Ludwig Wittgenstein
@@ -75,9 +67,9 @@
See also other examples like tables ,
checkbox lists, highlighting ,
+ href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists, highlighting , code blocks and
code blocks and math examples .
+ href="Trilium%20Demo/Formatting%20examples/Math.html">math examples.
+
+
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html b/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html
index 6827fa8af4..214ef212e7 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html
@@ -21,8 +21,12 @@
language, should that fail it is possible to manually adjust it. The color
scheme for the syntax highlighting is adjustable in settings.
For larger pieces of code it is better to use a code note, which uses
a fully-fledged code editor (CodeMirror). For an example of a code note,
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Note Types.html b/apps/edit-docs/demo/root/Trilium Demo/Note Types.html
new file mode 100644
index 0000000000..614d566bc6
--- /dev/null
+++ b/apps/edit-docs/demo/root/Trilium Demo/Note Types.html
@@ -0,0 +1,21 @@
+
+
+