From d483b6e8401cea9659b2388f5746b9a0e22b2a15 Mon Sep 17 00:00:00 2001 From: Kevin Leutzinger <6435727+kleutzinger@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:50:10 -0400 Subject: [PATCH 1/4] add shareHTML relation --- .../server/src/services/builtin_attributes.ts | 2 ++ packages/share-theme/src/templates/page.ejs | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/server/src/services/builtin_attributes.ts b/apps/server/src/services/builtin_attributes.ts index 2d35992aa..ca48f3df1 100644 --- a/apps/server/src/services/builtin_attributes.ts +++ b/apps/server/src/services/builtin_attributes.ts @@ -66,6 +66,7 @@ export default [ { type: "label", name: "shareDisallowRobotIndexing" }, { type: "label", name: "shareCredentials" }, { type: "label", name: "shareIndex" }, + { type: "label", name: "shareHTMLLocation" }, { type: "label", name: "displayRelations" }, { type: "label", name: "hideRelations" }, { type: "label", name: "titleTemplate", isDangerous: true }, @@ -105,6 +106,7 @@ export default [ { type: "relation", name: "renderNote", isDangerous: true }, { type: "relation", name: "shareCss" }, { type: "relation", name: "shareJs" }, + { type: "relation", name: "shareHTML" }, { type: "relation", name: "shareTemplate" }, { type: "relation", name: "shareFavicon" } ]; diff --git a/packages/share-theme/src/templates/page.ejs b/packages/share-theme/src/templates/page.ejs index e4ac1f603..eaa7dbe3b 100644 --- a/packages/share-theme/src/templates/page.ejs +++ b/packages/share-theme/src/templates/page.ejs @@ -1,7 +1,31 @@ - <% const hasTree = subRoot.note.hasVisibleChildren(); %> + <% + const hasTree = subRoot.note.hasVisibleChildren(); + + // Collect HTML snippets by location + const htmlSnippetsByLocation = {}; + for (const htmlRelation of note.getRelations("shareHTML")) { + const htmlNote = htmlRelation.targetNote; + if (htmlNote) { + let location = htmlNote.getLabelValue("shareHTMLLocation") || "content:end"; + // Default to :end if no position specified + if (!location.includes(":")) { + location = location + ":end"; + } + if (!htmlSnippetsByLocation[location]) { + htmlSnippetsByLocation[location] = []; + } + htmlSnippetsByLocation[location].push(htmlNote.getContent()); + } + } + const renderSnippets = (location) => { + const snippets = htmlSnippetsByLocation[location]; + return snippets ? snippets.join("\n") : ""; + }; + %> + <%- renderSnippets("head:start") %> @@ -53,6 +77,7 @@ + <%- renderSnippets("head:end") %> <% const customLogoId = subRoot.note.getRelation("shareLogo")?.value; @@ -72,6 +97,7 @@ content = content.replaceAll(headingRe, (...match) => { }); %> +<%- renderSnippets("body:start") %>
Logo @@ -121,8 +147,8 @@ content = content.replaceAll(headingRe, (...match) => {
-
ck-content<% } %><% if (isEmpty) { %> no-content<% } %>"> + <%- renderSnippets("content:start") %>

<%= note.title %>

<% if (isEmpty && (!note.hasVisibleChildren() && note.type !== "book")) { %>

This note has no content.

@@ -132,6 +158,7 @@ content = content.replaceAll(headingRe, (...match) => { %> <%- content %> <% } %> + <%- renderSnippets("content:end") %>
<% if (note.hasVisibleChildren() || note.type === "book") { %> @@ -164,7 +191,7 @@ content = content.replaceAll(headingRe, (...match) => {
<% } %> - <% if (hasTree) { %> + <% if (hasTree) { %> <%- include("prev_next", { note: note, subRoot: subRoot }) %> <% } %> @@ -205,5 +232,6 @@ content = content.replaceAll(headingRe, (...match) => { <% } %>
+<%- renderSnippets("body:end") %> From 0a25d4db0d5be2a50ef510549c6f8479f250fb9f Mon Sep 17 00:00:00 2001 From: Kevin Leutzinger <6435727+kleutzinger@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:30:16 -0400 Subject: [PATCH 2/4] shareHTML to shareHtml --- apps/server/src/services/builtin_attributes.ts | 4 ++-- packages/share-theme/src/templates/page.ejs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/src/services/builtin_attributes.ts b/apps/server/src/services/builtin_attributes.ts index ca48f3df1..293bc6b5d 100644 --- a/apps/server/src/services/builtin_attributes.ts +++ b/apps/server/src/services/builtin_attributes.ts @@ -66,7 +66,7 @@ export default [ { type: "label", name: "shareDisallowRobotIndexing" }, { type: "label", name: "shareCredentials" }, { type: "label", name: "shareIndex" }, - { type: "label", name: "shareHTMLLocation" }, + { type: "label", name: "shareHtmlLocation" }, { type: "label", name: "displayRelations" }, { type: "label", name: "hideRelations" }, { type: "label", name: "titleTemplate", isDangerous: true }, @@ -106,7 +106,7 @@ export default [ { type: "relation", name: "renderNote", isDangerous: true }, { type: "relation", name: "shareCss" }, { type: "relation", name: "shareJs" }, - { type: "relation", name: "shareHTML" }, + { type: "relation", name: "shareHtml" }, { type: "relation", name: "shareTemplate" }, { type: "relation", name: "shareFavicon" } ]; diff --git a/packages/share-theme/src/templates/page.ejs b/packages/share-theme/src/templates/page.ejs index eaa7dbe3b..cc96cc4ca 100644 --- a/packages/share-theme/src/templates/page.ejs +++ b/packages/share-theme/src/templates/page.ejs @@ -6,10 +6,10 @@ // Collect HTML snippets by location const htmlSnippetsByLocation = {}; - for (const htmlRelation of note.getRelations("shareHTML")) { + for (const htmlRelation of note.getRelations("shareHtml")) { const htmlNote = htmlRelation.targetNote; if (htmlNote) { - let location = htmlNote.getLabelValue("shareHTMLLocation") || "content:end"; + let location = htmlNote.getLabelValue("shareHtmlLocation") || "content:end"; // Default to :end if no position specified if (!location.includes(":")) { location = location + ":end"; From 888d0d10846d55c44c6b3f438f774303db53cfd1 Mon Sep 17 00:00:00 2001 From: Kevin Leutzinger <6435727+kleutzinger@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:46:08 -0400 Subject: [PATCH 3/4] add docs about shareHtml and shareHtmlLocation --- .../Advanced Usage/Attributes/Relations.md | 1 + .../User Guide/Advanced Usage/Sharing.md | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/User Guide/User Guide/Advanced Usage/Attributes/Relations.md b/docs/User Guide/User Guide/Advanced Usage/Attributes/Relations.md index 2916640ec..454e7d007 100644 --- a/docs/User Guide/User Guide/Advanced Usage/Attributes/Relations.md +++ b/docs/User Guide/User Guide/Advanced Usage/Attributes/Relations.md @@ -50,5 +50,6 @@ These relations are supported and used internally by Trilium. | `widget_relation` | target of this relation will be executed and rendered as a widget in the sidebar | | `shareCss` | CSS note which will be injected into the share page. CSS note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree` and `share_omit_default_css` as well. | | `shareJs` | JavaScript note which will be injected into the share page. JS note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree`. | +| `shareHtml` | HTML note which will be injected into the share page at locations specified by the `shareHtmlLocation` label. HTML note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree`. | | `shareTemplate` | Embedded JavaScript note that will be used as the template for displaying the shared note. Falls back to the default template. Consider using `share_hidden_from_tree`. | | `shareFavicon` | Favicon note to be set in the shared page. Typically you want to set it to share root and make it inheritable. Favicon note must be in the shared sub-tree as well. Consider using `share_hidden_from_tree`. | \ No newline at end of file diff --git a/docs/User Guide/User Guide/Advanced Usage/Sharing.md b/docs/User Guide/User Guide/Advanced Usage/Sharing.md index 37ce5fee8..793389ae2 100644 --- a/docs/User Guide/User Guide/Advanced Usage/Sharing.md +++ b/docs/User Guide/User Guide/Advanced Usage/Sharing.md @@ -67,6 +67,23 @@ The default design should be a good starting point, but you can customize it usi You can inject custom JavaScript into the shared note using the `~shareJs` relation. This allows you to access note attributes or traverse the note tree using the `fetchNote()` API, which retrieves note data based on its ID. +### Adding custom HTML + +You can inject custom HTML snippets into specific locations of the shared page using the `~shareHtml` relation. The HTML note should contain the raw HTML content you want to inject, and you can control where it appears using the `#shareHtmlLocation` label. + +The `#shareHtmlLocation` label accepts values in the format `location:position`: +- **Locations**: `head`, `body`, `content` +- **Positions**: `start`, `end` + +For example: +- `#shareHtmlLocation=head:start` - Injects HTML at the beginning of the `` section +- `#shareHtmlLocation=head:end` - Injects HTML at the end of the `` section (default) +- `#shareHtmlLocation=body:start` - Injects HTML at the beginning of the `` section +- `#shareHtmlLocation=content:start` - Injects HTML at the beginning of the content area +- `#shareHtmlLocation=content:end` - Injects HTML at the end of the content area + +If no location is specified, the HTML will be injected at `content:end` by default. + Example: ```javascript @@ -106,7 +123,7 @@ To do so, create a shared text note and apply the `shareIndex` label. When viewe ## Attribute reference -
AttributeDescription
shareHiddenFromTreethis note is hidden from left navigation tree, but still accessible with its URL
shareExternalLinknote will act as a link to an external website in the share tree
shareAliasdefine an alias using which the note will be available under https://your_trilium_host/share/[your_alias]
shareOmitDefaultCssdefault share page CSS will be omitted. Use when you make extensive styling changes.
shareRootmarks note which is served on /share root.
shareDescriptiondefine text to be added to the HTML meta tag for description
shareRawNote will be served in its raw format, without HTML wrapper. See also Serving directly the content of a note for an alternative method without setting an attribute.
shareDisallowRobotIndexing

Indicates to web crawlers that the page should not be indexed of this note by:

  • Setting the X-Robots-Tag: noindex HTTP header.
  • Setting the noindex, follow meta tag.
shareCredentialsrequire credentials to access this shared note. Value is expected to be in format username:password. Don't forget to make this inheritable to apply to child-notes/images.
shareIndexNote with this label will list all roots of shared notes.
+
AttributeDescription
shareHiddenFromTreethis note is hidden from left navigation tree, but still accessible with its URL
shareExternalLinknote will act as a link to an external website in the share tree
shareAliasdefine an alias using which the note will be available under https://your_trilium_host/share/[your_alias]
shareOmitDefaultCssdefault share page CSS will be omitted. Use when you make extensive styling changes.
shareRootmarks note which is served on /share root.
shareDescriptiondefine text to be added to the HTML meta tag for description
shareRawNote will be served in its raw format, without HTML wrapper. See also Serving directly the content of a note for an alternative method without setting an attribute.
shareDisallowRobotIndexing

Indicates to web crawlers that the page should not be indexed of this note by:

  • Setting the X-Robots-Tag: noindex HTTP header.
  • Setting the noindex, follow meta tag.
shareCredentialsrequire credentials to access this shared note. Value is expected to be in format username:password. Don't forget to make this inheritable to apply to child-notes/images.
shareIndexNote with this label will list all roots of shared notes.
shareHtmlLocationdefines where custom HTML injected via ~shareHtml relation should be placed. Format: location:position where location is head, body, or content and position is start or end. Defaults to content:end.
## Credits From b8851565ebcba216d5dc025a513edda9242e73ca Mon Sep 17 00:00:00 2001 From: Kevin Leutzinger <6435727+kleutzinger@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:53:29 -0400 Subject: [PATCH 4/4] docs: clarify shareHtmlLocation goes on snippet note --- docs/User Guide/User Guide/Advanced Usage/Sharing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/User Guide/User Guide/Advanced Usage/Sharing.md b/docs/User Guide/User Guide/Advanced Usage/Sharing.md index 793389ae2..caee9238e 100644 --- a/docs/User Guide/User Guide/Advanced Usage/Sharing.md +++ b/docs/User Guide/User Guide/Advanced Usage/Sharing.md @@ -69,7 +69,7 @@ You can inject custom JavaScript into the shared note using the `~shareJs` relat ### Adding custom HTML -You can inject custom HTML snippets into specific locations of the shared page using the `~shareHtml` relation. The HTML note should contain the raw HTML content you want to inject, and you can control where it appears using the `#shareHtmlLocation` label. +You can inject custom HTML snippets into specific locations of the shared page using the `~shareHtml` relation. The HTML note should contain the raw HTML content you want to inject, and you can control where it appears by adding the `#shareHtmlLocation` label to the HTML snippet note itself. The `#shareHtmlLocation` label accepts values in the format `location:position`: - **Locations**: `head`, `body`, `content` @@ -123,7 +123,7 @@ To do so, create a shared text note and apply the `shareIndex` label. When viewe ## Attribute reference -
AttributeDescription
shareHiddenFromTreethis note is hidden from left navigation tree, but still accessible with its URL
shareExternalLinknote will act as a link to an external website in the share tree
shareAliasdefine an alias using which the note will be available under https://your_trilium_host/share/[your_alias]
shareOmitDefaultCssdefault share page CSS will be omitted. Use when you make extensive styling changes.
shareRootmarks note which is served on /share root.
shareDescriptiondefine text to be added to the HTML meta tag for description
shareRawNote will be served in its raw format, without HTML wrapper. See also Serving directly the content of a note for an alternative method without setting an attribute.
shareDisallowRobotIndexing

Indicates to web crawlers that the page should not be indexed of this note by:

  • Setting the X-Robots-Tag: noindex HTTP header.
  • Setting the noindex, follow meta tag.
shareCredentialsrequire credentials to access this shared note. Value is expected to be in format username:password. Don't forget to make this inheritable to apply to child-notes/images.
shareIndexNote with this label will list all roots of shared notes.
shareHtmlLocationdefines where custom HTML injected via ~shareHtml relation should be placed. Format: location:position where location is head, body, or content and position is start or end. Defaults to content:end.
+
AttributeDescription
shareHiddenFromTreethis note is hidden from left navigation tree, but still accessible with its URL
shareExternalLinknote will act as a link to an external website in the share tree
shareAliasdefine an alias using which the note will be available under https://your_trilium_host/share/[your_alias]
shareOmitDefaultCssdefault share page CSS will be omitted. Use when you make extensive styling changes.
shareRootmarks note which is served on /share root.
shareDescriptiondefine text to be added to the HTML meta tag for description
shareRawNote will be served in its raw format, without HTML wrapper. See also Serving directly the content of a note for an alternative method without setting an attribute.
shareDisallowRobotIndexing

Indicates to web crawlers that the page should not be indexed of this note by:

  • Setting the X-Robots-Tag: noindex HTTP header.
  • Setting the noindex, follow meta tag.
shareCredentialsrequire credentials to access this shared note. Value is expected to be in format username:password. Don't forget to make this inheritable to apply to child-notes/images.
shareIndexNote with this label will list all roots of shared notes.
shareHtmlLocationdefines where custom HTML injected via ~shareHtml relation should be placed. Applied to the HTML snippet note itself. Format: location:position where location is head, body, or content and position is start or end. Defaults to content:end.
## Credits