Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/client/src/translations/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,8 @@
"share_index": "note with this label will list all roots of shared notes",
"display_relations": "comma delimited names of relations which should be displayed. All other ones will be hidden.",
"hide_relations": "comma delimited names of relations which should be hidden. All other ones will be displayed.",
"title_template": "default title of notes created as children of this note. The value is evaluated as JavaScript string \n and thus can be enriched with dynamic content via the injected <code>now</code> and <code>parentNote</code> variables. Examples:\n \n <ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n See <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki with details</a>, API docs for <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> and <a href=\"https://day.js.org/docs/en/display/format\">now</a> for details.",
"title_template": "default title of notes created from this template note. The value is evaluated as JavaScript string \n and thus can be enriched with dynamic content via the injected <code>now</code> and <code>parentNote</code> variables. Examples:\n \n <ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n See <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki with details</a>, API docs for <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> and <a href=\"https://day.js.org/docs/en/display/format\">now</a> for details.",
"child_title_template": "default title of notes created as children of this note. The value is evaluated as JavaScript string \n and thus can be enriched with dynamic content via the injected <code>now</code> and <code>parentNote</code> variables. Examples:\n \n <ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n See <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki with details</a>, API docs for <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> and <a href=\"https://day.js.org/docs/en/display/format\">now</a> for details.",
"template": "This note will appear in the selection of available template when creating new note",
"toc": "<code>#toc</code> or <code>#toc=show</code> will force the Table of Contents to be shown, <code>#toc=hide</code> will force hiding it. If the label doesn't exist, the global setting is observed",
"color": "defines color of the note in note tree, links etc. Use any valid CSS color value like 'red' or #a13d5f",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ const ATTR_HELP: Record<string, Record<string, string>> = {
displayRelations: t("attribute_detail.display_relations"),
hideRelations: t("attribute_detail.hide_relations"),
titleTemplate: t("attribute_detail.title_template"),
childTitleTemplate: t("attribute_detail.child_title_template"),
template: t("attribute_detail.template"),
toc: t("attribute_detail.toc"),
color: t("attribute_detail.color"),
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 121 additions & 0 deletions apps/server/src/services/notes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { beforeAll, describe, expect, it } from "vitest";
import sql_init from "./sql_init";
import cls from "./cls";
import {newEntityId} from "./utils";
import notes from "./notes";
import becca from "../becca/becca";
import {buildNote} from "../test/becca_easy_mocking";

describe("Notes Title", () => {
beforeAll(async () => {
sql_init.initializeDb();
await sql_init.dbReady;
});

describe("setting titleTemplate label should set the notes title when created from template", () => {
it("uses #titleTemplate from the selected template note when creating a note from template", () => {
const parentNote = buildNote({
title: "randomNote"
});

cls.init(() => {
const templateNoteId = newEntityId();

const templateNote = notes.createNewNote({
parentNoteId: parentNote.noteId,
noteId: templateNoteId,
title: "Test template",
type: "text",
content: ""
}).note;

templateNote.setLabel("titleTemplate", "Hello from template");
templateNote.setLabel("template");
templateNote.invalidateThisCache(); // ensures getLabelValue sees the new label

const created = notes.createNewNote({
parentNoteId: parentNote.noteId,
templateNoteId: templateNote.noteId,
title: null as never, // let notes service derive the title from the template label
type: "text",
content: ""
}).note;

expect(created.title).toBe("Hello from template");

// sanity: ensure the template note is actually the one we set up
expect(becca.getNote(templateNoteId)?.getLabelValue("titleTemplate")).toBe("Hello from template");
});
});

it("uses #childTitleTemplate from the parent note when creating a note under a parent with label set", () => {
const parentNote = buildNote({
title: "randomNote",
"#childTitleTemplate": "my child title",
children: [
{
title: "childNote",
}
]
});

cls.init(() => {

const created = notes.createNewNote({
parentNoteId: parentNote.noteId,
title: null as never, // let notes service derive the title from the template label
type: "text",
content: ""
}).note;

expect(created.title).toBe("my child title");
});
});

it("uses #titleTemplate from the selected template note even if the parent note has #childTitleTemplate set", () => {
const templateParentNote = buildNote({
title: "randomNote"
});

const parentNote = buildNote({
title: "randomNote",
"#childTitleTemplate": "my child title",
children: [
{
title: "childNote",
}
]
});

cls.init(() => {
const templateNoteId = newEntityId();

const templateNote = notes.createNewNote({
parentNoteId: templateParentNote.noteId,
noteId: templateNoteId,
title: "Test template",
type: "text",
content: ""
}).note;

templateNote.setLabel("titleTemplate", "Hello from template");
templateNote.setLabel("template");
templateNote.invalidateThisCache(); // ensures getLabelValue sees the new label

const created = notes.createNewNote({
parentNoteId: parentNote.noteId,
templateNoteId: templateNote.noteId,
title: null as never, // let notes service derive the title from the template label
type: "text",
content: ""
}).note;

expect(created.title).toBe("Hello from template");

// sanity: ensure the template note is actually the one we set up
expect(becca.getNote(templateNoteId)?.getLabelValue("titleTemplate")).toBe("Hello from template");
});
});
});

});
15 changes: 12 additions & 3 deletions apps/server/src/services/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ function copyAttachments(origNote: BNote, newNote: BNote) {
}
}

function getNewNoteTitle(parentNote: BNote) {
function getNewNoteTitle(parentNote: BNote, params: NoteParams): string {
let title = t("notes.new-note");

const titleTemplate = parentNote.getLabelValue("titleTemplate");
const titleTemplate = resolveTitleTemplate(parentNote, params);

if (titleTemplate !== null) {
try {
Expand All @@ -140,6 +140,15 @@ function getNewNoteTitle(parentNote: BNote) {
return title;
}

function resolveTitleTemplate(parentNote: BNote, params: NoteParams): string | null {
if (params.templateNoteId != null) {
const templateNote = becca.getNote(params.templateNoteId);
return templateNote ? templateNote.getLabelValue("titleTemplate") : null;
}

return parentNote.getLabelValue("childTitleTemplate");
}
Comment on lines +143 to +150
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The resolveTitleTemplate function should be improved to handle two important cases:

  1. Backward Compatibility: Currently, it ignores titleTemplate on the parent note. Since this was the previous behavior, removing it without a fallback will break existing user setups. Adding a fallback ensures that existing folders with titleTemplate continue to work until users migrate to childTitleTemplate.
  2. Inherited Templates: If a note is created in a folder that has a child:template relation, it will eventually inherit that template. The title should be resolved from that template's titleTemplate if no explicit childTitleTemplate is defined on the parent. The current logic only checks params.templateNoteId, which is typically only set when a template is explicitly chosen in the UI.
function resolveTitleTemplate(parentNote: BNote, params: NoteParams): string | null {
    if (params.templateNoteId != null) {
        const templateNote = becca.getNote(params.templateNoteId);
        return templateNote ? templateNote.getLabelValue("titleTemplate") : null;
    }

    const childTitleTemplate = parentNote.getLabelValue("childTitleTemplate");
    if (childTitleTemplate !== null) {
        return childTitleTemplate;
    }

    const inheritedTemplateId = parentNote.getRelationValue("child:template");
    if (inheritedTemplateId) {
        const templateNote = becca.getNote(inheritedTemplateId);
        const templateTitleTemplate = templateNote?.getLabelValue("titleTemplate");
        if (templateTitleTemplate != null) {
            return templateTitleTemplate;
        }
    }

    // Fallback to titleTemplate on parent for backward compatibility
    return parentNote.getLabelValue("titleTemplate");
}


interface GetValidateParams {
parentNoteId: string;
type: string;
Expand Down Expand Up @@ -180,7 +189,7 @@ function createNewNote(params: NoteParams): {
const parentNote = getAndValidateParent(params);

if (params.title === null || params.title === undefined) {
params.title = getNewNoteTitle(parentNote);
params.title = getNewNoteTitle(parentNote, params);
}

if (params.content === null || params.content === undefined) {
Expand Down
2 changes: 1 addition & 1 deletion docs/Developer Guide/Developer Guide/Documentation.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Documentation
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/7hmsAGuPacge/Documentation_image.png" width="205" height="162">
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/0b0JHK3ILDvN/Documentation_image.png" width="205" height="162">

* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
Expand Down
Loading