share template

<!DOCTYPE html>
<html lang="en">
<head>
    <%
    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";
            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") : "";
    };

    // ---- SEO: per-page URL / canonical / OG image / JSON-LD ----
    const rootUrl = subRoot.note.getLabelValue("shareOpenGraphURL") || "";
    const perNoteUrl = note.getLabelValue("shareOpenGraphURL");
    const pageUrl = perNoteUrl
        || (rootUrl && note.noteId !== subRoot.note.noteId ? rootUrl + (note.shareId || note.noteId) : rootUrl);

    let ogImageRel = subRoot.note.getLabelValue("shareOpenGraphImage");
    if (subRoot.note.hasRelation("shareOpenGraphImage")) {
        ogImageRel = `api/images/${subRoot.note.getRelation("shareOpenGraphImage").value}/image.png`;
    }
    const ogImage = ogImageRel ? rootUrl + ogImageRel : "";

    const jsonLd = [];
    if (note.noteId === subRoot.note.noteId) {
        jsonLd.push({ "@context": "https://schema.org", "@type": "WebSite", name: subRoot.note.title, url: pageUrl, description: note.getLabelValue("shareDescription") || "" });
    } else if (note.getLabelValue("publish") === "true") {
        jsonLd.push({ "@context": "https://schema.org", "@type": "BlogPosting", headline: note.title, url: pageUrl, datePublished: (note.getLabelValue("date") || note.dateCreated).slice(0, 10), dateModified: (note.utcDateModified || "").slice(0, 10), description: note.getLabelValue("shareDescription") || "", mainEntityOfPage: pageUrl, author: { "@type": "Person", name: subRoot.note.title } });
    } else {
        jsonLd.push({ "@context": "https://schema.org", "@type": "CollectionPage", name: note.title, url: pageUrl, description: note.getLabelValue("shareDescription") || "" });
    }
    %>
    <%- renderSnippets("head:start") %>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="shortcut icon" href="<%= faviconUrl %>">
    <% for (const url of cssToLoad) { %>
        <link href="<%= url %>" rel="stylesheet">
    <% } %>
    <% for (const url of jsToLoad) { %>
        <script type="module" src="<%= url %>"></script>
    <% } %>
    <% if (note.hasLabel("shareDisallowRobotIndexing")) { %>
        <meta name="robots" content="noindex,follow" />
    <% } else { %>
        <meta name="robots" content="index,follow" />
    <% } %>

    <%
        const pageTitle = `${note.title}${note.noteId !== subRoot.note.noteId ? ` - ${subRoot.note.title}` : ""}`;

        const openGraphColor = subRoot.note.getLabelValue("shareOpenGraphColor");
        const openGraphDomain = subRoot.note.getLabelValue("shareOpenGraphDomain");
    %>
    <title><%= pageTitle %></title>
    <% if (pageUrl) { %><link rel="canonical" href="<%= pageUrl %>"><% } %>
    <script>
        // Load dark/light theme as soon as possible to avoid color flashes.
        let theme = localStorage.getItem("theme");
        if (!theme) {
            theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light";
        }
        document.documentElement.classList.add(`theme-${theme}`);

        window.glob = {
            isStatic: <%= !!isStatic %>,
            theme
        };

        (function() {
            const leftCollapsed = localStorage.getItem("left-pane-collapsed") === "true";
            const tocCollapsed = localStorage.getItem("toc-pane-collapsed") === "true";
            if (leftCollapsed) document.documentElement.classList.add("left-pane-collapsed");
            if (tocCollapsed) document.documentElement.classList.add("toc-pane-collapsed");
        })();
    </script>
    <!-- HTML Meta Tags -->
    <meta name="description" content="<%= note.getLabelValue("shareDescription") %>">
    <!-- Open Graph -->
    <meta property="og:url" content="<%= pageUrl %>">
    <meta property="og:type" content="<%= note.getLabelValue("publish") === "true" ? "article" : "website" %>">
    <meta property="og:site_name" content="<%= subRoot.note.title %>">
    <meta property="og:title" content="<%= pageTitle %>">
    <meta property="og:description" content="<%= note.getLabelValue("shareDescription") %>">
    <meta property="og:image" content="<%= ogImage %>">
    <meta property="og:locale" content="en_US">
    <!-- Twitter -->
    <meta name="twitter:card" content="summary_large_image">
    <meta property="twitter:domain" content="<%= openGraphDomain %>">
    <meta property="twitter:url" content="<%= pageUrl %>">
    <meta name="twitter:title" content="<%= pageTitle %>">
    <meta name="twitter:description" content="<%= note.getLabelValue("shareDescription") %>">
    <meta name="twitter:image" content="<%= ogImage %>">
    <meta name="theme-color" content="<%= openGraphColor %>">
    <script type="application/ld+json"><%= JSON.stringify(jsonLd) %></script>
    <style id="trilium-icon-packs">
      <%- iconPackCss %>
    </style>
    <%- renderSnippets("head:end") %>
</head>
<%
const logoWidth = subRoot.note.getLabelValue("shareLogoWidth") ?? 53;
const logoHeight = subRoot.note.getLabelValue("shareLogoHeight") ?? 40;
const mobileLogoHeight = logoHeight && logoWidth ? 32 / (logoWidth / logoHeight) : "";
const shareRootLink = subRoot.note.hasLabel("shareRootLink") ? sanitizeUrl(subRoot.note.getLabelValue("shareRootLink") ?? "") : `./${subRoot.note.noteId}`;
const headingRe = /(<h[1-6]>)(.+?)(<\/h[1-6]>)/g;
const headingMatches = [...content.matchAll(headingRe)];
const headingSlugs = utils.slugifyHeadings(headingMatches.map((m) => m[2]));
let headingIndex = 0;
content = content.replaceAll(headingRe, (...match) => {
    const slug = headingSlugs[headingIndex++];
    match[0] = match[0].replace(match[3], `<a id="${slug}" class="toc-anchor" name="${slug}" href="#${slug}">#</a>${match[3]}`);
    return match[0];
});
%>
<body data-note-id="<%= note.noteId %>" class="type-<%= note.type %>" data-ancestor-note-id="<%= subRoot.note.noteId %>">
<%- renderSnippets("body:start") %>
<div id="header">
    <button aria-label="<%= t("share_theme.toggle-navigation") %>" id="left-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
    <a href="<%= shareRootLink %>" id="header-logo">
            <img src="<%= logoUrl %>" width="32" height="<%= mobileLogoHeight %>" alt="<%= t("share_theme.logo-alt") %>" />
        <%= subRoot.note.title %>
    </a>
    <% if (headingMatches.length > 1) { %>
        <button aria-label="<%= t("share_theme.toggle-toc") %>" id="toc-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10V7h2v2h-2zm0 6h2v-2h-2v2zm0 6h2v-2h-2v2z"></path></svg></button>
    <% } else { %>
        <div class="header-button-placeholder"></div>
    <% } %>
</div>
<div id="split-pane">
    <div id="left-pane">
        <div id="navigation">
            <div id="site-header">
                <div class="theme-selection">
                    <span id="sitetheme"><%= t("share_theme.site-theme") %></span>
                    <label class="switch">
                      <input type="checkbox" aria-labelledby="sitetheme">
                      <span class="slider"></span>
                      <svg class="dark-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20.742 13.045a8.088 8.088 0 0 1-2.077.271c-2.135 0-4.14-.83-5.646-2.336a8.025 8.025 0 0 1-2.064-7.723A1 1 0 0 0 9.73 2.034a10.014 10.014 0 0 0-4.489 2.582c-3.898 3.898-3.898 10.243 0 14.143a9.937 9.937 0 0 0 7.072 2.93 9.93 9.93 0 0 0 7.07-2.929 10.007 10.007 0 0 0 2.583-4.491 1.001 1.001 0 0 0-1.224-1.224zm-2.772 4.301a7.947 7.947 0 0 1-5.656 2.343 7.953 7.953 0 0 1-5.658-2.344c-3.118-3.119-3.118-8.195 0-11.314a7.923 7.923 0 0 1 2.06-1.483 10.027 10.027 0 0 0 2.89 7.848 9.972 9.972 0 0 0 7.848 2.891 8.036 8.036 0 0 1-1.484 2.059z"></path></svg>
                      <svg class="light-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M6.993 12c0 2.761 2.246 5.007 5.007 5.007s5.007-2.246 5.007-5.007S14.761 6.993 12 6.993 6.993 9.239 6.993 12zM12 8.993c1.658 0 3.007 1.349 3.007 3.007S13.658 15.007 12 15.007 8.993 13.658 8.993 12 10.342 8.993 12 8.993zM10.998 19h2v3h-2zm0-17h2v3h-2zm-9 9h3v2h-3zm17 0h3v2h-3zM4.219 18.363l2.12-2.122 1.415 1.414-2.12 2.122zM16.24 6.344l2.122-2.122 1.414 1.414-2.122 2.122zM6.342 7.759 4.22 5.637l1.415-1.414 2.12 2.122zm13.434 10.605-1.414 1.414-2.122-2.122 1.414-1.414z"></path></svg>
                    </label>
                    <script>
                        const el = document.querySelector(".theme-selection input");
                        el.checked = (glob.theme === "dark");
                    </script>
                </div>
                <% if (hasTree) { %>
                    <div class="search-item">
                        <svg class="search-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M10 18a7.952 7.952 0 0 0 4.897-1.688l4.396 4.396 1.414-1.414-4.396-4.396A7.952 7.952 0 0 0 18 10c0-4.411-3.589-8-8-8s-8 3.589-8 8 3.589 8 8 8zm0-14c3.309 0 6 2.691 6 6s-2.691 6-6 6-6-2.691-6-6 2.691-6 6-6z"></path></svg>
                        <input type="text" class="search-input" placeholder="<%= t("share_theme.search_placeholder") %>">
                    </div>
                <% } %>
            </div>
        <% if (hasTree) { %>
            <nav id="menu">
                <%- include("tree_item", {note: subRoot.note, activeNote: note, subRoot: subRoot, ancestors}) %>
            </nav>
        <% } %>
        </div>
    </div>
    <div id="right-pane">
        <div id="main">
            <div id="content" class="type-<%= note.type %><% if (note.type === "text" || (note.type === "code" && note.mime === "text/x-markdown")) { %> ck-content<% } %><% if (isEmpty) { %> no-content<% } %>">
                <%- renderSnippets("content:start") %>
                    <h1 id="title"><%= note.title %></h1>
                <% if (isEmpty && (!note.hasVisibleChildren() && note.type !== "book")) { %>
                    <p><%= t("share_page.no-content") %></p>
                <% } else { %>
                    <%
                    content = content.replace(/<img /g, `<img alt="${t("share_theme.image_alt")}" loading="lazy" `);
                    %>
                    <%- content %>
                <% } %>
                <%- renderSnippets("content:end") %>
            </div>

            <% if (note.hasVisibleChildren() || note.type === "book") { %>
                <nav id="childLinks" class="<% if (isEmpty) { %>grid<% } else { %>list<% } %>">
                    <% if (!isEmpty) { %>
                        <span><%= t("share_theme.subpages") %></span>
                    <% } %>

                    <ul>
                        <%
                        const action = note.type === "book" ? "getChildNotes" : "getVisibleChildNotes";
                        for (const childNote of note[action]()) {
                            const isExternalLink = childNote.hasLabel("shareExternal") || childNote.hasLabel("shareExternalLink");
                            const rawHref = isExternalLink ? childNote.getLabelValue("shareExternal") ?? childNote.getLabelValue("shareExternalLink") : `./${childNote.shareId}`;
                            const linkHref = isExternalLink ? sanitizeUrl(rawHref ?? "") : rawHref;
                        %>
                            <li>
                                <a class="type-<%= childNote.type %>" href="<%= linkHref %>"<% if (isExternalLink) { %> target="_blank" rel="noopener noreferrer"<% } %>><%= childNote.title %></a>
                            </li>
                        <% } %>
                    </ul>
                </nav>
            <% } %>

            <footer id="content-footer">
                <% if (!isEmpty && !isStatic) { %>
                    <div class="updated">
                        <% const lastUpdated = new Date(note.utcDateModified); %>
                        <%- t("share_theme.last-updated", { date: `<time datetime="${lastUpdated.toISOString()}">${lastUpdated.toLocaleDateString("en-US")}</time>`}) %>
                    </div>
                <% } %>

                <% if (hasTree) { %>
                    <%- include("prev_next", { note: note, subRoot: subRoot }) %>
                <% } %>

                <% if (showLoginInShareTheme && !isStatic) { %>
                    <div class="login">
                        <a href="../login" class="login-link"><%= t("share_theme.login") %></a>
                    </div>
                <% } %>
            </footer>
        </div>
        <%
        if (headingMatches.length > 1) {
            const level = (m) => parseInt(m[1].replace(/[<h>]+/g, ""));

            const makeEntry = (i) => ({level: level(headingMatches[i]), name: headingMatches[i][2], slug: headingSlugs[i], children: []});
            const toc = [ makeEntry(0) ];
            const last = (arr = toc) => arr[arr.length - 1];
            const getLevelArr = (lvl, arr = toc) => {
                if (arr[0].level === lvl) return arr;
                const top = last(arr);
                return top.children.length ? getLevelArr(lvl, top.children) : top.children;
            };


            for (let m = 1; m < headingMatches.length; m++) {
                const target = getLevelArr(level(headingMatches[m]));
                target.push(makeEntry(m));
            }
        %>
            <div id="toc-pane">
                <h3><%= t("share_theme.on-this-page") %></h3>
                <ul id="toc">
                    <% for (const entry of toc) { %>
                        <%- include("toc_item", {entry}) %>
                    <% } %>
                </ul>
            </div>
        <% } %>
    </div>
</div>
<%- renderSnippets("body:end") %>
</body>
</html>