diff --git a/apps/server/src/services/builtin_attributes.ts b/apps/server/src/services/builtin_attributes.ts index 2d35992aa..293bc6b5d 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/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 171cdf71f..00daf4537 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 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` +- **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. 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 diff --git a/packages/share-theme/src/templates/page.ejs b/packages/share-theme/src/templates/page.ejs index e4ac1f603..cc96cc4ca 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") %>