From af599da0bfa8001ef3b3d691bb6dd27bd1f8df17 Mon Sep 17 00:00:00 2001
From: yequari
Date: Sat, 12 Aug 2023 03:44:17 -0700
Subject: [PATCH] create new theme
---
config.yaml | 2 +-
layouts/partials/left-sidebar.html | 4 +
layouts/partials/right-sidebar.html | 3 +
layouts/transformers/transformers.html | 6 +-
themes/saturn/LICENSE | 20 +
themes/saturn/archetypes/default.md | 2 +
themes/saturn/layouts/404.html | 3 +
themes/saturn/layouts/_default/baseof.html | 13 +
themes/saturn/layouts/_default/list.html | 11 +
themes/saturn/layouts/_default/single.html | 5 +
themes/saturn/layouts/blog/list.html | 20 +
themes/saturn/layouts/blog/single.html | 15 +
themes/saturn/layouts/index.html | 6 +
themes/saturn/layouts/notes/single.html | 15 +
themes/saturn/layouts/partials/footer.html | 12 +
themes/saturn/layouts/partials/head.html | 13 +
themes/saturn/layouts/partials/header.html | 10 +
themes/saturn/layouts/partials/tags.html | 6 +
themes/saturn/static/css/main.css | 330 +++++++++++++++
themes/saturn/static/js/quotes.js | 19 +
themes/saturn/static/js/webmention.js | 458 +++++++++++++++++++++
themes/saturn/theme.toml | 21 +
22 files changed, 990 insertions(+), 4 deletions(-)
create mode 100644 layouts/partials/left-sidebar.html
create mode 100644 layouts/partials/right-sidebar.html
create mode 100644 themes/saturn/LICENSE
create mode 100644 themes/saturn/archetypes/default.md
create mode 100644 themes/saturn/layouts/404.html
create mode 100644 themes/saturn/layouts/_default/baseof.html
create mode 100644 themes/saturn/layouts/_default/list.html
create mode 100644 themes/saturn/layouts/_default/single.html
create mode 100644 themes/saturn/layouts/blog/list.html
create mode 100644 themes/saturn/layouts/blog/single.html
create mode 100644 themes/saturn/layouts/index.html
create mode 100644 themes/saturn/layouts/notes/single.html
create mode 100644 themes/saturn/layouts/partials/footer.html
create mode 100644 themes/saturn/layouts/partials/head.html
create mode 100644 themes/saturn/layouts/partials/header.html
create mode 100644 themes/saturn/layouts/partials/tags.html
create mode 100644 themes/saturn/static/css/main.css
create mode 100644 themes/saturn/static/js/quotes.js
create mode 100644 themes/saturn/static/js/webmention.js
create mode 100644 themes/saturn/theme.toml
diff --git a/config.yaml b/config.yaml
index ae2809a..77ecdaa 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,7 +1,7 @@
baseURL: http://yequari.com/
languageCode: en-us
title: yequari.com
-theme: genesis
+theme: saturn
menu:
main:
- name: HOME
diff --git a/layouts/partials/left-sidebar.html b/layouts/partials/left-sidebar.html
new file mode 100644
index 0000000..7b3f16c
--- /dev/null
+++ b/layouts/partials/left-sidebar.html
@@ -0,0 +1,4 @@
+Latest Blog Posts
+{{ range first 3 (where .Site.RegularPages.ByDate.Reverse "Section" "blog") }}
+{{ .Name }}
+{{ end }}
diff --git a/layouts/partials/right-sidebar.html b/layouts/partials/right-sidebar.html
new file mode 100644
index 0000000..e4d2cc9
--- /dev/null
+++ b/layouts/partials/right-sidebar.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/layouts/transformers/transformers.html b/layouts/transformers/transformers.html
index ea72159..638c286 100644
--- a/layouts/transformers/transformers.html
+++ b/layouts/transformers/transformers.html
@@ -1,5 +1,5 @@
{{ define "main" }}
-
-
+
+
{{ end }}
diff --git a/themes/saturn/LICENSE b/themes/saturn/LICENSE
new file mode 100644
index 0000000..17993f6
--- /dev/null
+++ b/themes/saturn/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 YOUR_NAME_HERE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/themes/saturn/archetypes/default.md b/themes/saturn/archetypes/default.md
new file mode 100644
index 0000000..ac36e06
--- /dev/null
+++ b/themes/saturn/archetypes/default.md
@@ -0,0 +1,2 @@
++++
++++
diff --git a/themes/saturn/layouts/404.html b/themes/saturn/layouts/404.html
new file mode 100644
index 0000000..9aecf45
--- /dev/null
+++ b/themes/saturn/layouts/404.html
@@ -0,0 +1,3 @@
+{{ define "main" }}
+Not found
+{{ end }}
diff --git a/themes/saturn/layouts/_default/baseof.html b/themes/saturn/layouts/_default/baseof.html
new file mode 100644
index 0000000..e5d7ead
--- /dev/null
+++ b/themes/saturn/layouts/_default/baseof.html
@@ -0,0 +1,13 @@
+
+
+ {{- partial "head.html" . -}}
+
+
+ {{- partial "header.html" . -}}
+
+ {{- block "main" . }}{{- end }}
+
+ {{- partial "footer.html" . -}}
+
+
+
diff --git a/themes/saturn/layouts/_default/list.html b/themes/saturn/layouts/_default/list.html
new file mode 100644
index 0000000..9444094
--- /dev/null
+++ b/themes/saturn/layouts/_default/list.html
@@ -0,0 +1,11 @@
+{{ define "main" }}
+
+
+ {{ .Title }}
+
+
+{{ end }}
diff --git a/themes/saturn/layouts/_default/single.html b/themes/saturn/layouts/_default/single.html
new file mode 100644
index 0000000..28749ec
--- /dev/null
+++ b/themes/saturn/layouts/_default/single.html
@@ -0,0 +1,5 @@
+{{ define "main" }}
+
+{{ end }}
diff --git a/themes/saturn/layouts/blog/list.html b/themes/saturn/layouts/blog/list.html
new file mode 100644
index 0000000..50bac0e
--- /dev/null
+++ b/themes/saturn/layouts/blog/list.html
@@ -0,0 +1,20 @@
+{{ define "main" }}
+
+ {{ range .Paginator.Pages.ByPublishDate.Reverse }}
+
+
+ {{ .PublishDate.Format "Mon, Jan 2, 2006" }}
+ {{ partial "tags.html" .}}
+
+ {{ .Summary }}
+
+ {{ if .Truncated }}
+
+ {{ end }}
+
+ {{ end }}
+ {{ template "_internal/pagination.html" . }}
+
+{{ end }}
diff --git a/themes/saturn/layouts/blog/single.html b/themes/saturn/layouts/blog/single.html
new file mode 100644
index 0000000..b600265
--- /dev/null
+++ b/themes/saturn/layouts/blog/single.html
@@ -0,0 +1,15 @@
+{{ define "main" }}
+
+
+ {{ .Title }}
+ yequari
+ {{ .PublishDate.Format "Mon, Jan 02, 2006" }}
+ Permalink
+ {{ partial "tags.html" .}}
+
+ {{ .Content }}
+
+
+
+
+{{ end }}
diff --git a/themes/saturn/layouts/index.html b/themes/saturn/layouts/index.html
new file mode 100644
index 0000000..f679bd7
--- /dev/null
+++ b/themes/saturn/layouts/index.html
@@ -0,0 +1,6 @@
+{{ define "main" }}
+
+{{ .Content }}
+ {{ partial "left-sidebar.html" . }}
+
+{{ end }}
diff --git a/themes/saturn/layouts/notes/single.html b/themes/saturn/layouts/notes/single.html
new file mode 100644
index 0000000..b600265
--- /dev/null
+++ b/themes/saturn/layouts/notes/single.html
@@ -0,0 +1,15 @@
+{{ define "main" }}
+
+
+ {{ .Title }}
+ yequari
+ {{ .PublishDate.Format "Mon, Jan 02, 2006" }}
+ Permalink
+ {{ partial "tags.html" .}}
+
+ {{ .Content }}
+
+
+
+
+{{ end }}
diff --git a/themes/saturn/layouts/partials/footer.html b/themes/saturn/layouts/partials/footer.html
new file mode 100644
index 0000000..1e1a86b
--- /dev/null
+++ b/themes/saturn/layouts/partials/footer.html
@@ -0,0 +1,12 @@
+
+
+
+
+ {{ range site.Menus.social }}
+ {{ .Name }}
+ {{ end }}
+
+
+
diff --git a/themes/saturn/layouts/partials/head.html b/themes/saturn/layouts/partials/head.html
new file mode 100644
index 0000000..e3b0459
--- /dev/null
+++ b/themes/saturn/layouts/partials/head.html
@@ -0,0 +1,13 @@
+
+
+
+ {{ .Page.Title }}
+
+
+
+
+
+
+
+
+
diff --git a/themes/saturn/layouts/partials/header.html b/themes/saturn/layouts/partials/header.html
new file mode 100644
index 0000000..70f229a
--- /dev/null
+++ b/themes/saturn/layouts/partials/header.html
@@ -0,0 +1,10 @@
+
diff --git a/themes/saturn/layouts/partials/tags.html b/themes/saturn/layouts/partials/tags.html
new file mode 100644
index 0000000..e193e49
--- /dev/null
+++ b/themes/saturn/layouts/partials/tags.html
@@ -0,0 +1,6 @@
+{{ $taxonomy := "tags" }} {{ with .Param $taxonomy }}
+ {{ range $index, $tag := . }} {{ with $.Site.GetPage (printf "/%s/%s"
+ $taxonomy $tag) -}}
+ #{{ $tag | urlize }}
+ {{- end -}} {{- end -}}
+{{ end }}
diff --git a/themes/saturn/static/css/main.css b/themes/saturn/static/css/main.css
new file mode 100644
index 0000000..f77ce26
--- /dev/null
+++ b/themes/saturn/static/css/main.css
@@ -0,0 +1,330 @@
+/* CSS HEX */
+:root {
+ --jet: #353535ff;
+ --chinese-red: #aa3322ff;
+ --pale-silver: #c4bbafff;
+ --space-cadet: #292640ff;
+ --xiketic: #0f101aff;
+ --blue-ryb: #4056f4ff;
+ --raisin-black: #241e1eff;
+ --dim-gray: #756b6bff;
+ --pewter-blue: #93A8ACff;
+
+ --celestial-blue: #5299D3;
+ --white-smoke: #F1EDEE;
+ --midnight-blue: #18206F;
+ --penn-blue: #17255A;
+ --eerie-black: #172121;
+ --vista-blue: #8EA4D2;
+
+
+ /* solarized dark colors */
+ --base00: #657b83;
+ --base01: #586e75;
+ --base02: #073642;
+ --base03: #002b36;
+ --base0: #839496;
+ --base1: #93a1a1;
+ --base2: #eee8d5;
+ --base3: #fdf6e3;
+
+ --yellow: #b58900;
+ --orange: #cb4b16;
+ --red: #dc322f;
+ --magenta: #d33682;
+ --violet: #6c71c4;
+ --blue: #268bd2;
+ --cyan: #2aa198;
+ --green: #859900;
+
+ --main-bg: var(--base02);
+ --content-bg: var(--base03);
+ --primary-text: var(--base0);
+ --secondary-text: var(--base01);
+ --primary-link: var(--magenta);
+ --primary-link-hover: var(--red);
+ --secondary-link: var(--blue);
+}
+
+@font-face {
+ font-family: "Genesis";
+ src: url("/fonts/NiseGenesis.TTF");
+}
+
+/*
+ * Page text: Krub
+ * Headings & Top nav: Lalezar
+ */
+
+body {
+ background-color: var(--main-bg);
+ color: var(--primary-text);
+ font-family: 'Krub', sans-serif;
+ scrollbar-width: none;
+}
+
+body::-webkit-scrollbar {
+ display: none;
+}
+
+p {
+ font-size: 1.1rem;
+ line-height: 2;
+}
+
+a:link, a:visited {
+ color: var(--primary-link);
+ text-decoration: none;
+}
+
+a:hover, a:active {
+ text-decoration: underline;
+}
+
+h1,h2,h3,h4,h5,h6 {
+ color: var(--primary-text);
+ font-family: 'Lalezar', display;
+
+}
+
+.hidden {
+ display: none;
+}
+
+.column {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ max-width: 1000px;
+ margin: 10px auto;
+ background-color: rgba(23,33,33,0.7);
+ padding: 0 20px;
+ min-height: 800px;
+}
+
+header {
+ display: flex;
+ flex-flow: row wrap;
+ flex-shrink: 0;
+ margin: 10px;
+}
+
+header h1 {
+ font-family: "Genesis", 'Courier New', Courier, monospace;
+ font-size: 1.5rem;
+ color: var(--pewter-blue);
+}
+
+nav {
+ flex: 1;
+}
+
+nav ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+}
+
+nav li {
+ display: inline;
+}
+
+nav li a {
+ display: inline-block;
+ padding: 15px 20px;
+ transition-property: background-color;
+ transition-duration: 0.5s;
+ transition-delay: 0.1s;
+ font-family: 'Nova Mono', monospace;
+ font-weight: bold;
+ font-size: 0.9em;
+}
+
+main {
+ flex: 1;
+ display: flex;
+ flex-flow: row wrap;
+ /* gap: 20px; */
+ z-index: 1;
+ margin: 0 0.1rem;
+}
+
+.sidebar {
+ flex: 3 1;
+ margin: 0 2rem;
+}
+
+.sidebar p {
+ line-height: 1;
+}
+
+section.content {
+ flex: 8 1;
+ padding: 0 1rem;
+}
+
+section p {
+ text-indent: 1rem;
+ text-align: justify;
+}
+
+footer {
+ display: flex;
+ flex-direction: column;
+ padding: 0 0 15px 0px;
+ text-align: center;
+ color: var(--secondary-text);
+ border-top: 1px solid var(--secondary-text);
+}
+
+footer div {
+ flex: 1;
+}
+
+a.sitetitle {
+ text-decoration: none;
+}
+
+footer ul.social {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+}
+
+footer li.social {
+ display: inline;
+}
+
+footer li.social a {
+ display: inline-block;
+ padding: 0 10px;
+ color: var(--secondary-link);
+}
+
+a.tag {
+ color: var(--secondary-link);
+}
+
+a.permalink {
+ padding: 0 5px;
+}
+
+a.summary {
+ color: var(--primary-text);
+}
+
+time.dt-published {
+ color: var(--secondary-text);
+}
+
+ul.posts {
+ list-style-type: none;
+}
+
+img {
+ max-width: 500px;
+ height: auto;
+}
+
+code {
+ background-color: black;
+ padding: 10px;
+ overflow-x: scroll;
+ color: white;
+}
+
+pre code{
+ display: block;
+}
+
+#webmentions img { max-height: 1.2em; margin-right: -1ex; }
+
+.center {
+ text-align: center;
+}
+
+ul.pagination {
+ list-style: none;
+ text-align: center;
+}
+
+li.page-item {
+ display: inline;
+ padding: 0.3rem;
+}
+
+/*
+ * Links page
+ */
+
+
+#links-main {
+ display: flex;
+ flex-flow: row wrap;
+}
+
+#links-main li {
+ padding: 7px 0;
+}
+
+.links-column {
+ flex: 1 1 33%;
+ overflow-wrap: break-word;
+}
+
+.hover-links {
+ position: relative;
+ border-bottom: 1px dotted var(--chinese-red);
+}
+
+.hover-links:before {
+ content: attr(data-hover);
+ visibility: hidden;
+ opacity: 0;
+ max-width: 340px;
+ min-width: 200px;
+ background-color: var(--xiketic);
+ color: var(--pewter-blue);
+ text-align: center;
+ border: 4px double var(--dim-gray);
+ border-radius: 5px;
+ padding: 5px;
+ transition: opacity 1s ease-in-out;
+ position: absolute;
+ z-index: 1;
+ left: -50px;
+ top: -250%;
+}
+
+.hover-links:hover:before {
+ opacity: 1;
+ visibility: visible;
+}
+
+@media only screen and (max-width: 450px) {
+ div.nav {
+ border: none;
+ }
+
+ body {
+ background-color: var(--content-bg);
+ }
+
+ .column {
+ border: none;
+ background: none;
+ max-width: unset;
+ margin: 0 auto;
+ padding: 0.2rem;
+ }
+
+ #content {
+ padding: 0;
+ }
+
+ div.titlex {
+ margin: 0 auto;
+ }
+}
diff --git a/themes/saturn/static/js/quotes.js b/themes/saturn/static/js/quotes.js
new file mode 100644
index 0000000..783246d
--- /dev/null
+++ b/themes/saturn/static/js/quotes.js
@@ -0,0 +1,19 @@
+var subtitles = [
+ "null",
+ "George likes his chicken spicy",
+ "Graphic design is my passion",
+ "They don\'t think it be like it is, but it do",
+ "Terminally online",
+ "Twitter is a fandom site for current events",
+ "Genesis does what Nintendon\'t",
+ "1000 JS libraries in your pocket",
+ "A large boulder the size of a small boulder",
+ "Smooth jazz for a smooth brain",
+ "Back at it again for the very first time",
+ "I have no spoons and I must cope"
+];
+var index = Math.floor(Math.random() * subtitles.length);
+console.log("setting subtitle to " + subtitles[index]);
+window.onload = function() {
+ document.getElementById("subtitle").textContent = subtitles[index];
+}
diff --git a/themes/saturn/static/js/webmention.js b/themes/saturn/static/js/webmention.js
new file mode 100644
index 0000000..023e792
--- /dev/null
+++ b/themes/saturn/static/js/webmention.js
@@ -0,0 +1,458 @@
+/* webmention.js
+
+Simple thing for embedding webmentions from webmention.io into a page, client-side.
+
+(c)2018-2022 fluffy (http://beesbuzz.biz)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+GitHub repo (for latest released versions, issue tracking, etc.):
+
+ https://github.com/PlaidWeb/webmention.js
+
+Basic usage:
+
+
+
+
+Allowed parameters:
+
+ page-url:
+
+ The base URL to use for this page. Defaults to window.location
+
+ add-urls:
+
+ Additional URLs to check, separated by |s
+
+ id:
+
+ The HTML ID for the object to fill in with the webmention data.
+ Defaults to "webmentions"
+
+ wordcount:
+
+ The maximum number of words to render in reply mentions.
+
+ max-webmentions:
+
+ The maximum number of mentions to retrieve. Defaults to 30.
+
+ prevent-spoofing:
+
+ By default, Webmentions render using the mf2 'url' element, which plays
+ nicely with webmention bridges (such as brid.gy and telegraph)
+ but allows certain spoofing attacks. If you would like to prevent
+ spoofing, set this to a non-empty string (e.g. "true").
+
+ sort-by:
+
+ What to order the responses by; defaults to 'published'. See
+ https://github.com/aaronpk/webmention.io#api
+
+ sort-dir:
+
+ The order to sort the responses by; defaults to 'up' (i.e. oldest
+ first). See https://github.com/aaronpk/webmention.io#api
+
+ comments-are-reactions:
+
+ If set to a non-empty string (e.g. "true"), will display comment-type responses
+ (replies/mentions/etc.) as being part of the reactions
+ (favorites/bookmarks/etc.) instead of in a separate comment list.
+
+A more detailed example:
+
+
+
+
+
+
+
+*/
+
+// Begin LibreJS code licensing
+// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt
+
+(function () {
+ "use strict";
+
+ // Shim i18next
+ window.i18next = window.i18next || {
+ t: function t(/** @type {string} */key) { return key; }
+ }
+ const t = window.i18next.t.bind(window.i18next);
+
+ /**
+ * Read the configuration value.
+ *
+ * @param {string} key The configuration key.
+ * @param {string} dfl The default value.
+ * @returns {string}
+ */
+ function getCfg(key, dfl) {
+ return document.currentScript.getAttribute("data-" + key) || dfl;
+ }
+
+ const refurl = getCfg("page-url", window.location.href.replace(/#.*$/, ""));
+ const addurls = getCfg("add-urls", undefined);
+ const containerID = getCfg("id", "webmentions");
+ /** @type {Number} */
+ const textMaxWords = getCfg("wordcount");
+ const maxWebmentions = getCfg("max-webmentions", 30);
+ const mentionSource = getCfg("prevent-spoofing") ? "wm-source" : "url";
+ const sortBy = getCfg("sort-by", "published");
+ const sortDir = getCfg("sort-dir", "up");
+ /** @type {boolean} */
+ const commentsAreReactions = getCfg("comments-are-reactions");
+
+ /**
+ * @typedef MentionType
+ * @type {"in-reply-to"|"like-of"|"repost-of"|"bookmark-of"|"mention-of"|"rsvp"|"follow-of"}
+ */
+
+ /**
+ * Maps a reaction to a hover title.
+ *
+ * @type {Record}
+ */
+ const reactTitle = {
+ "in-reply-to": t("replied"),
+ "like-of": t("liked"),
+ "repost-of": t("reposted"),
+ "bookmark-of": t("bookmarked"),
+ "mention-of": t("mentioned"),
+ "rsvp": t("RSVPed"),
+ "follow-of": t("followed")
+ };
+
+ /**
+ * Maps a reaction to an emoji.
+ *
+ * @type {Record}
+ */
+ const reactEmoji = {
+ "in-reply-to": "💬",
+ "like-of": "❤️",
+ "repost-of": "🔄",
+ "bookmark-of": "⭐️",
+ "mention-of": "💬",
+ "rsvp": "📅",
+ "follow-of": "🐜"
+ };
+
+ /**
+ * @typedef RSVPEmoji
+ * @type {"yes"|"no"|"interested"|"maybe"|null}
+ */
+
+ /**
+ * Maps a RSVP to an emoji.
+ *
+ * @type {Record}
+ */
+ const rsvpEmoji = {
+ "yes": "✅",
+ "no": "❌",
+ "interested": "💡",
+ "maybe": "💭"
+ };
+
+ /**
+ * HTML escapes the string.
+ *
+ * @param {string} text The string to be escaped.
+ * @returns {string}
+ */
+ function entities(text) {
+ return text.replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+ }
+
+ /**
+ * Creates the markup for an reaction image.
+ *
+ * @param {Reaction} r
+ * @param {boolean} isComment
+ * @returns {string}
+ */
+ function reactImage(r, isComment) {
+ const who = entities(
+ r.author?.name || r.url.split("/")[2]
+ );
+ /** @type {string} */
+ let response = reactTitle[r["wm-property"]] || t("reacted");
+ if (!isComment && r.content && r.content.text) {
+ response += ": " + extractComment(r);
+ }
+
+ let authorPhoto = '';
+ if (r.author && r.author.photo) {
+ authorPhoto = `
+
+ `;
+ }
+
+ let rsvp = '';
+ if (r.rsvp && rsvpEmoji[r.rsvp]) {
+ rsvp = `${rsvpEmoji[r.rsvp]} `;
+ }
+
+ return`
+
+ ${authorPhoto}
+ ${(reactEmoji[r['wm-property']] || '💥')}
+ ${rsvp}
+
+ `;
+ }
+
+ /**
+ * Strip the protocol off a URL.
+ *
+ * @param {string} url The URL to strip protocol off.
+ * @returns {string}
+ */
+ function stripurl(url) {
+ return url.substr(url.indexOf('//'));
+ }
+
+ /**
+ * Deduplicate multiple mentions from the same source URL.
+ *
+ * @param {Array} mentions Mentions of the source URL.
+ * @return {Array}
+ */
+ function dedupe(mentions) {
+ /** @type {Array} */
+ const filtered = [];
+ /** @type {Record} */
+ const seen = {};
+
+ mentions.forEach(function(r) {
+ // Strip off the protocol (i.e. treat http and https the same)
+ const source = stripurl(r.url);
+ if (!seen[source]) {
+ filtered.push(r);
+ seen[source] = true;
+ }
+ });
+
+ return filtered;
+ }
+
+ /**
+ * Extract comments from a reaction.
+ *
+ * @param {Reactions} c
+ * @returns string
+ */
+ function extractComment(c) {
+ let text = entities(c.content.text);
+
+ if (textMaxWords) {
+ let words = text.replace(/\s+/g,' ').split(' ', textMaxWords + 1);
+ if (words.length > textMaxWords) {
+ words[textMaxWords - 1] += '…';
+ words = words.slice(0, textMaxWords);
+ text = words.join(' ');
+ }
+ }
+
+ return text;
+ }
+
+ /**
+ * Format comments as HTML.
+ *
+ * @param {Array} comments The comments to format.
+ * @returns string
+ */
+ function formatComments(comments) {
+ const headline = `${t('Responses')} `;
+ const markup = comments
+ .map((c) => {
+ const image = reactImage(c, true);
+
+ let source = entities(c.url.split('/')[2]);
+ if (c.author && c.author.name) {
+ source = entities(c.author.name);
+ }
+ const link = `${source} `;
+
+ let linkclass = "name";
+ let linktext = `(${t("mention")})`;
+ if (c.name) {
+ linkclass = "name";
+ linktext = c.name;
+ } else if (c.content && c.content.text) {
+ linkclass = "text";
+ linktext = extractComment(c);
+ }
+
+ const type = `${linktext} `;
+
+ return `${image} ${link} ${type} `;
+ })
+ .join('');
+ return `
+ ${headline}
+
+ `;
+ }
+
+ /**
+ * @typedef {Object} Reaction
+ * @property {string} url
+ * @property {Object?} author
+ * @property {string?} author.name
+ * @property {string?} author.photo
+ * @property {Object?} content
+ * @property {string?} content.text
+ * @property {RSVPEmoji?} rsvp
+ * @property {MentionType?} wm-property
+ * @property {string?} wm-source
+ */
+
+ /**
+ * Formats a list of reactions as HTML.
+ *
+ * @param {Array} reacts List of reactions to format
+ * @returns string
+ */
+ function formatReactions(reacts) {
+ const headline = `${t('Reactions')} `;
+
+ const markup = reacts.map((r) => reactImage(r)).join('');
+
+ return `
+ ${headline}
+
+ `;
+ }
+
+ /**
+ * @typedef WebmentionResponse
+ * @type {Object}
+ * @property {Array} children
+ */
+
+ /**
+ * Register event listener.
+ */
+ window.addEventListener("load", async function () {
+ const container = document.getElementById(containerID);
+ if (!container) {
+ // no container, so do nothing
+ return;
+ }
+
+ const pages = [stripurl(refurl)];
+ if (!!addurls) {
+ addurls.split('|').forEach(function (url) {
+ pages.push(stripurl(url));
+ });
+ }
+
+ let apiURL = `https://webmention.io/api/mentions.jf2?per-page=${maxWebmentions}&sort-by=${sortBy}&sort-dir=${sortDir}`;
+
+ pages.forEach(function (path) {
+ apiURL += `&target[]=${encodeURIComponent('http:' + path)}&target[]=${encodeURIComponent('https:' + path)}`;
+ });
+
+ /** @type {WebmentionResponse} */
+ let json = {};
+ try {
+ const response = await window.fetch(apiURL);
+ if (response.status >= 200 && response.status < 300) {
+ json = await response.json();
+ } else {
+ console.error("Could not parse response");
+ new Error(response.statusText);
+ }
+ } catch(error) {
+ // Purposefully not escalate further, i.e. no UI update
+ console.error("Request failed", error);
+ }
+
+ /** @type {Array} */
+ let comments = [];
+ /** @type {Array} */
+ const collects = [];
+
+ if (commentsAreReactions) {
+ comments = collects;
+ }
+
+ /** @type {Record>} */
+ const mapping = {
+ "in-reply-to": comments,
+ "like-of": collects,
+ "repost-of": collects,
+ "bookmark-of": collects,
+ "follow-of": collects,
+ "mention-of": comments,
+ "rsvp": comments
+ };
+
+ json.children.forEach(function(child) {
+ // Map each mention into its respective container
+ const store = mapping[child['wm-property']];
+ if (store) {
+ store.push(child);
+ }
+ });
+
+ // format the comment-type things
+ let formattedComments = '';
+ if (comments.length > 0 && comments !== collects) {
+ formattedComments = formatComments(dedupe(comments));
+ }
+
+ // format the other reactions
+ let reactions = '';
+ if (collects.length > 0) {
+ reactions = formatReactions(dedupe(collects));
+ }
+
+ container.innerHTML = `${formattedComments}${reactions}`;
+ });
+}());
+
+// End-of-file marker for LibreJS
+// @license-end
diff --git a/themes/saturn/theme.toml b/themes/saturn/theme.toml
new file mode 100644
index 0000000..e8135e3
--- /dev/null
+++ b/themes/saturn/theme.toml
@@ -0,0 +1,21 @@
+# theme.toml template for a Hugo theme
+# See https://github.com/gohugoio/hugoThemes#themetoml for an example
+
+name = "Genesis"
+license = "MIT"
+licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
+description = ""
+homepage = "http://example.com/"
+tags = []
+features = []
+min_version = "0.41.0"
+
+[author]
+ name = ""
+ homepage = ""
+
+# If porting an existing theme
+[original]
+ name = ""
+ homepage = ""
+ repo = ""