diff --git a/_regroup/package.json b/_regroup/package.json
index fd13c78f1..ce0e70d45 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 f22baec81..00196de82 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 19d533420..d94ada167 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 c080281ec..5ecf18cac 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 6504b49eb..7384573d8 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 94dd8893c..7d6a1e956 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 1868619d4..ff732a04b 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 d9b760dd8..a06fe08f5 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 000000000..3470f1018
--- /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 46888f0ab..e715c894f 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 f1c2ca736..cb2120687 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 5bab1c816..4bdae4126 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 4e2475922..0dd102145 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 ce5046fb1..44b61171d 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 1d4d5d57b..4d1371ac2 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 8f2333bf0..a33b14490 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 206054b92..b5b6672d6 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 6827fa8af..214ef212e 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 000000000..614d566bc
--- /dev/null
+++ b/apps/edit-docs/demo/root/Trilium Demo/Note Types.html
@@ -0,0 +1,21 @@
+
+
+