Files
trilium_share_gruvbox/templates/share/page.ejs
T

264 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
<style>#site-header .theme-selection{display:none}</style>
<% if (pageUrl) { %><link rel="canonical" href="<%= pageUrl %>"><% } %>
<script>
// Theme is fixed to dark light/dark switching is disabled.
const theme = "dark";
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>