1235 lines
115 KiB
HTML
1235 lines
115 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" dir="auto">
|
||
|
||
<head><meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||
<meta name="robots" content="noindex, nofollow">
|
||
<title>New features for my blog! Adding Comments + RSS to Hugo PaperMod (Giscus and Isso FTW) | AlipourIm journeys</title>
|
||
<meta name="keywords" content="hugo, papermod, giscus, rss, comments">
|
||
<meta name="description" content="A friendly, copy-pasteable guide to wiring up Giscus comments (GitHub Discussions) and Isso comments + RSS in Hugo PaperMod.">
|
||
<meta name="author" content="Iman Alipour">
|
||
<link rel="canonical" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/comments-rss-papermod/">
|
||
<link crossorigin="anonymous" href="/assets/css/stylesheet.31b150d909186c48d6dbbf653acc2489945fa9ac2c8f8cd3fe8448e89e40fccf.css" integrity="sha256-MbFQ2QkYbEjW279lOswkiZRfqawsj4zT/oRI6J5A/M8=" rel="preload stylesheet" as="style">
|
||
<link rel="icon" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/favicon.ico">
|
||
<link rel="icon" type="image/png" sizes="16x16" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/favicon-16x16.png">
|
||
<link rel="icon" type="image/png" sizes="32x32" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/favicon-32x32.png">
|
||
<link rel="apple-touch-icon" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/apple-touch-icon.png">
|
||
<link rel="mask-icon" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/safari-pinned-tab.svg">
|
||
<meta name="theme-color" content="#2e2e33">
|
||
<meta name="msapplication-TileColor" content="#2e2e33">
|
||
<link rel="alternate" hreflang="en" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/comments-rss-papermod/">
|
||
<noscript>
|
||
<style>
|
||
#theme-toggle,
|
||
.top-link {
|
||
display: none;
|
||
}
|
||
|
||
</style>
|
||
<style>
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--theme: rgb(29, 30, 32);
|
||
--entry: rgb(46, 46, 51);
|
||
--primary: rgb(218, 218, 219);
|
||
--secondary: rgb(155, 156, 157);
|
||
--tertiary: rgb(65, 66, 68);
|
||
--content: rgb(196, 196, 197);
|
||
--code-block-bg: rgb(46, 46, 51);
|
||
--code-bg: rgb(55, 56, 62);
|
||
--border: rgb(51, 51, 51);
|
||
}
|
||
|
||
.list {
|
||
background: var(--theme);
|
||
}
|
||
|
||
.list:not(.dark)::-webkit-scrollbar-track {
|
||
background: 0 0;
|
||
}
|
||
|
||
.list:not(.dark)::-webkit-scrollbar-thumb {
|
||
border-color: var(--theme);
|
||
}
|
||
}
|
||
|
||
</style>
|
||
</noscript><script>
|
||
(function () {
|
||
|
||
|
||
window.goatcounter = { endpoint: "/count" };
|
||
|
||
const s = document.createElement("script");
|
||
s.async = true;
|
||
s.src = "/js/count.js";
|
||
document.head.appendChild(s);
|
||
})();
|
||
</script>
|
||
|
||
<script>
|
||
(function () {
|
||
|
||
const GC = "";
|
||
const nf = new Intl.NumberFormat();
|
||
const SING = "visit";
|
||
const PLUR = "visits";
|
||
|
||
function ready(fn) {
|
||
if (document.readyState === "complete" || document.readyState === "interactive") setTimeout(fn, 0);
|
||
else document.addEventListener("DOMContentLoaded", fn);
|
||
}
|
||
|
||
function gcPath() {
|
||
try {
|
||
return window.goatcounter && window.goatcounter.get_data
|
||
? window.goatcounter.get_data().p
|
||
: location.pathname;
|
||
} catch (_) {
|
||
return location.pathname;
|
||
}
|
||
}
|
||
|
||
async function fetchCount(path, period) {
|
||
try {
|
||
const q = new URLSearchParams({ t: String(Date.now()) });
|
||
if (period) q.set("start", period);
|
||
const url = `${GC}/counter/${encodeURIComponent(path)}.json?${q}`;
|
||
const r = await fetch(url, { credentials: "omit" });
|
||
if (r.status === 404) return 0;
|
||
if (!r.ok) throw 0;
|
||
const j = await r.json();
|
||
|
||
const n = Number(String(j.count).replace(/,/g, ""));
|
||
return Number.isFinite(n) ? n : 0;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
const labelVisits = (n) =>
|
||
n === null ? "—" : `${nf.format(n)} ${n === 1 ? SING : PLUR}`;
|
||
|
||
ready(async function () {
|
||
const post = document.querySelector(".post-single");
|
||
if (post) {
|
||
const meta = post.querySelector(
|
||
".post-header .post-meta, .post-header .entry-meta, .post-meta"
|
||
);
|
||
const visits = await fetchCount(gcPath());
|
||
if (meta) {
|
||
let span = meta.querySelector(".post-views");
|
||
if (!span) {
|
||
span = document.createElement("span");
|
||
span.className = "post-views";
|
||
meta.appendChild(span);
|
||
}
|
||
span.textContent = labelVisits(visits ?? 0);
|
||
}
|
||
}
|
||
|
||
const cards = document.querySelectorAll("article.post-entry");
|
||
await Promise.all(
|
||
Array.from(cards).map(async (card) => {
|
||
const meta = card.querySelector(".entry-footer, .post-meta");
|
||
const link = card.querySelector("a.entry-link, h2 a, .entry-title a");
|
||
if (!meta || !link) return;
|
||
try {
|
||
const u = new URL(link.getAttribute("href"), location.origin);
|
||
if (u.origin !== location.origin) return;
|
||
let span = meta.querySelector(".post-views");
|
||
if (!span) {
|
||
span = document.createElement("span");
|
||
span.className = "post-views";
|
||
meta.appendChild(span);
|
||
}
|
||
span.textContent = labelVisits((await fetchCount(u.pathname)) ?? 0);
|
||
} catch {}
|
||
})
|
||
);
|
||
|
||
const uvEl = document.getElementById("gc_site_visitors_week");
|
||
if (uvEl) {
|
||
const n = await fetchCount("TOTAL", "week");
|
||
uvEl.textContent = n === null ? "—" : nf.format(n);
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
</head>
|
||
|
||
<body class="" id="top">
|
||
<script>
|
||
if (localStorage.getItem("pref-theme") === "dark") {
|
||
document.body.classList.add('dark');
|
||
} else if (localStorage.getItem("pref-theme") === "light") {
|
||
document.body.classList.remove('dark')
|
||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||
document.body.classList.add('dark');
|
||
}
|
||
|
||
</script>
|
||
|
||
<header class="header">
|
||
<nav class="nav">
|
||
<div class="logo">
|
||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/" accesskey="h" title="AlipourIm journeys (Alt + H)">AlipourIm journeys</a>
|
||
<div class="logo-switches">
|
||
<button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
|
||
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||
</svg>
|
||
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<ul id="menu">
|
||
<li>
|
||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/" title="Posts">
|
||
<span>Posts</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/phd_journey/" title="PhD_journey">
|
||
<span>PhD_journey</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/about/" title="About">
|
||
<span>About</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
</header>
|
||
<main class="main">
|
||
|
||
<article class="post-single">
|
||
<header class="post-header">
|
||
<div class="breadcrumbs"><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/">Home</a> » <a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/">Posts</a></div>
|
||
<h1 class="post-title entry-hint-parent">
|
||
New features for my blog! Adding Comments + RSS to Hugo PaperMod (Giscus and Isso FTW)
|
||
</h1>
|
||
<div class="post-description">
|
||
A friendly, copy-pasteable guide to wiring up Giscus comments (GitHub Discussions) and Isso comments + RSS in Hugo PaperMod.
|
||
</div>
|
||
<div class="post-meta"><span title='2025-10-23 19:31:12 +0200 +0200'>October 23, 2025</span> · 15 min · Iman Alipour
|
||
|
||
<span class="post-meta-item">
|
||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/comments-rss-papermod/#isso-thread" class="isso-comments-link">Comments</a>
|
||
</span>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</div>
|
||
</header> <div class="toc">
|
||
<details open>
|
||
<summary accesskey="c" title="(Alt + C)">
|
||
<span class="details">Table of Contents</span>
|
||
</summary>
|
||
|
||
<div class="inner"><ul><ul>
|
||
<li>
|
||
<a href="#why-i-chose-giscus-instead-of-disqus" aria-label="Why I chose Giscus (instead of Disqus)">Why I chose Giscus (instead of Disqus)</a></li>
|
||
<li>
|
||
<a href="#what-you-need-for-your-site-not-per-user" aria-label="What you need (for your site, not per user)">What you need (for your site, not per user)</a></li>
|
||
<li>
|
||
<a href="#1-turn-on-comments-with-giscus" aria-label="1) Turn on comments with Giscus">1) Turn on comments with Giscus</a><ul>
|
||
|
||
<li>
|
||
<a href="#11-enable-comments-globally" aria-label="1.1 Enable comments globally">1.1 Enable comments globally</a></li>
|
||
<li>
|
||
<a href="#12-create-the-comments-partial" aria-label="1.2 Create the comments partial">1.2 Create the comments partial</a></li></ul>
|
||
</li>
|
||
<li>
|
||
<a href="#2-enable-rss-home--sections" aria-label="2) Enable RSS (home + sections)">2) Enable RSS (home + sections)</a></li>
|
||
<li>
|
||
<a href="#3-show-an-rss-button-on-the-home-page" aria-label="3) Show an RSS button on the home page">3) Show an RSS button on the home page</a><ul>
|
||
|
||
<li>
|
||
<a href="#option-a--use-the-home-social-icons-row" aria-label="Option A — Use the home “social icons” row">Option A — Use the home “social icons” row</a></li>
|
||
<li>
|
||
<a href="#option-b--add-an-icon-in-the-footer-theme-safe" aria-label="Option B — Add an icon in the footer (theme-safe)">Option B — Add an icon in the footer (theme-safe)</a></li></ul>
|
||
</li>
|
||
<li>
|
||
<a href="#4-verify-everything" aria-label="4) Verify everything">4) Verify everything</a></li>
|
||
<li>
|
||
<a href="#5-per-post-controls-handy-later" aria-label="5) Per-post controls (handy later)">5) Per-post controls (handy later)</a></li>
|
||
<li>
|
||
<a href="#6-troubleshooting-aka-what-i-bumped-into" aria-label="6) Troubleshooting (aka “what I bumped into”)">6) Troubleshooting (aka “what I bumped into”)</a></li>
|
||
<li>
|
||
<a href="#7-bonus-fast-setup-via-github-cli-optional-i-didnt-use-it-but-i-shouldve-perhaps" aria-label="7) Bonus: fast setup via GitHub CLI (optional, I didn’t use it, but I should’ve perhaps)">7) Bonus: fast setup via GitHub CLI (optional, I didn’t use it, but I should’ve perhaps)</a><ul>
|
||
|
||
<li>
|
||
<a href="#final-config-snapshot-condensed" aria-label="Final config snapshot (condensed)">Final config snapshot (condensed)</a></li></ul>
|
||
</li></ul>
|
||
|
||
<li>
|
||
<a href="#appendix--why-i-added-isso-how-i-wired-it-and-what-i-fixed" aria-label="Appendix — Why I added Isso, how I wired it, and what I fixed">Appendix — Why I added Isso, how I wired it, and what I fixed</a><ul>
|
||
|
||
<li>
|
||
<a href="#the-story-why-isso--why-i-still-keep-giscus" aria-label="The story (why Isso + why I still keep Giscus)">The story (why Isso + why I still keep Giscus)</a></li>
|
||
<li>
|
||
<a href="#how-i-run-it-high-level" aria-label="How I run it (high-level)">How I run it (high-level)</a></li>
|
||
<li>
|
||
<a href="#hugo-partial-switcher--embeds" aria-label="Hugo partial: switcher + embeds">Hugo partial: switcher + embeds</a></li>
|
||
<li>
|
||
<a href="#nginx-proxy-isso-on-both-sites-and-avoid-the-404-onion-trap" aria-label="Nginx: proxy Isso on both sites (and avoid the 404-onion trap)">Nginx: proxy Isso on both sites (and avoid the 404-onion trap)</a></li>
|
||
<li>
|
||
<a href="#small-css-tweaks-one-color-across-lightdark--spacing" aria-label="Small CSS tweaks (one color across light/dark + spacing)">Small CSS tweaks (one color across light/dark + spacing)</a></li>
|
||
<li>
|
||
<a href="#what-changed-the-fixes-i-actually-made" aria-label="What changed (the fixes I actually made)">What changed (the fixes I actually made)</a></li>
|
||
<li>
|
||
<a href="#sanity-check" aria-label="Sanity check">Sanity check</a></li>
|
||
<li>
|
||
<a href="#8-final-notes" aria-label="8) Final notes!">8) Final notes!</a></li></ul>
|
||
</li>
|
||
<li>
|
||
<a href="#admin-note--delete-all-isso-comments-for-a-single-post" aria-label="Admin note — Delete all Isso comments for a single post">Admin note — Delete all Isso comments for a single post</a><ul>
|
||
|
||
<li>
|
||
<a href="#tldr-host-with-sqlite" aria-label="TL;DR (host with SQLite)">TL;DR (host with SQLite)</a></li>
|
||
<li>
|
||
<a href="#if-running-isso-in-docker" aria-label="If running Isso in Docker">If running Isso in Docker</a></li>
|
||
<li>
|
||
<a href="#notes--gotchas" aria-label="Notes & gotchas">Notes & gotchas</a></li>
|
||
<li>
|
||
<a href="#one-liners-for-the-impatient" aria-label="One-liners for the impatient">One-liners for the impatient</a>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
|
||
<div class="post-content"><blockquote>
|
||
<p>Short story: I wanted comments and a proper RSS feed on my Hugo + PaperMod site.<br>
|
||
Longer story: I spent 30 minutes poking around, and now you don’t have to. Here’s exactly what I did.</p></blockquote>
|
||
<hr>
|
||
<p>I’ve been wanting to add comment section to my blog for a while.
|
||
There are a few problems.</p>
|
||
<ul>
|
||
<li>I don’t want to get attacked by bots or random people for no reason.</li>
|
||
<li>I don’t want to allow weird things to happen.</li>
|
||
<li>I don’t want to have a database for it.</li>
|
||
</ul>
|
||
<p>So… I decided to take a path which is not necesserially the best.
|
||
It requires a third party to act on your behalf, but it is not as scary as it sounds.
|
||
According to Github:</p>
|
||
<blockquote>
|
||
<p><strong>What the app can do</strong>
|
||
The app’s rights are limited to the repo(s) where the site owner installed it and to the permissions it requested (basically: Discussions read/write + metadata). It can’t touch a commenter’s private repos or do things outside those scopes. GitHub’s docs also say a GitHub App can only do what both you and the app have permission to do.</p></blockquote>
|
||
<blockquote>
|
||
<p><strong>What “Act on your behalf” means</strong>
|
||
That scary wording is GitHub’s. The giscus maintainer explains it means “post comments under your username,” and shows that the app requests Discussions read/write + metadata when installed.</p></blockquote>
|
||
<blockquote>
|
||
<p><strong>Do commenters have alternatives?</strong>
|
||
Yes. Anyone can open the linked GitHub Discussion and comment directly on GitHub, bypassing the app’s OAuth popup. The README explicitly mentions this.</p></blockquote>
|
||
<blockquote>
|
||
<p><strong>Privacy & storage</strong>
|
||
No tracking/ads; comments live in GitHub Discussions. giscus is open-source and has a published privacy policy. You can also revoke the app any time in your GitHub settings.</p></blockquote>
|
||
<p>With the notice out of the way let’s dive right in!</p>
|
||
<hr>
|
||
<h2 id="why-i-chose-giscus-instead-of-disqus">Why I chose Giscus (instead of Disqus)<a hidden class="anchor" aria-hidden="true" href="#why-i-chose-giscus-instead-of-disqus">#</a></h2>
|
||
<ul>
|
||
<li><strong>No ads or trackers</strong> (my page weight thanks me).</li>
|
||
<li><strong>Uses GitHub Discussions</strong> (portable, transparent, easy to search).</li>
|
||
<li><strong>Readers log in with GitHub</strong> (no mystery auth popups).</li>
|
||
<li>In theory you can directly go to the repo’s discussion section and comment there!</li>
|
||
</ul>
|
||
<hr>
|
||
<h2 id="what-you-need-for-your-site-not-per-user">What you need (for your site, not per user)<a hidden class="anchor" aria-hidden="true" href="#what-you-need-for-your-site-not-per-user">#</a></h2>
|
||
<ul>
|
||
<li>A Hugo site using <strong>PaperMod</strong> ✅</li>
|
||
<li>A GitHub account ✅</li>
|
||
<li>A <strong>public</strong> repository for the discussion threads (I called mine <code>blog-comments</code>) with <strong>Discussions</strong> enabled ✅</li>
|
||
<li>Giscus app installed on that repo ✅</li>
|
||
</ul>
|
||
<p>If you like to keep your main site repo clean, use a separate one, e.g. <code>github-username/blog-comments</code>.</p>
|
||
<hr>
|
||
<h2 id="1-turn-on-comments-with-giscus">1) Turn on comments with Giscus<a hidden class="anchor" aria-hidden="true" href="#1-turn-on-comments-with-giscus">#</a></h2>
|
||
<p>PaperMod renders a <code>comments.html</code> partial when <code>params.comments = true</code>.<br>
|
||
So we enable it in config and create the partial.</p>
|
||
<h3 id="11-enable-comments-globally">1.1 Enable comments globally<a hidden class="anchor" aria-hidden="true" href="#11-enable-comments-globally">#</a></h3>
|
||
<p><code>hugo.toml</code> (snippet)</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">comments</span> = <span style="color:#66d9ef">true</span>
|
||
</span></span></code></pre></div><p>You can still opt out per post with <code>comments = false</code> in front matter.</p>
|
||
<h3 id="12-create-the-comments-partial">1.2 Create the comments partial<a hidden class="anchor" aria-hidden="true" href="#12-create-the-comments-partial">#</a></h3>
|
||
<p>Create this file <strong>in your site</strong> (not inside the theme):</p>
|
||
|
||
<div class="mermaid">layouts/partials/comments.html</div>
|
||
|
||
<p>Paste the Giscus embed (swap the placeholders with yours from giscus.app):</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><<span style="color:#f92672">section</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"comments"</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"giscus"</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">script</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">"https://giscus.app/client.js"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-repo</span><span style="color:#f92672">=</span><span style="color:#e6db74">"yourname/blog-comments"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-repo-id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"YOUR_REPO_ID"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-category</span><span style="color:#f92672">=</span><span style="color:#e6db74">"Comments"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-category-id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"YOUR_CATEGORY_ID"</span> <span style="color:#960050;background-color:#1e0010"><!</span><span style="color:#a6e22e">--</span> <span style="color:#a6e22e">Get</span> <span style="color:#a6e22e">them</span> <span style="color:#a6e22e">from</span> <span style="color:#a6e22e">Giscus</span><span style="color:#960050;background-color:#1e0010">'</span><span style="color:#a6e22e">s</span> <span style="color:#a6e22e">page</span><span style="color:#960050;background-color:#1e0010">!</span> <span style="color:#a6e22e">--</span>>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">mapping</span><span style="color:#f92672">=</span><span style="color:#e6db74">"pathname"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">strict</span><span style="color:#f92672">=</span><span style="color:#e6db74">"0"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">reactions</span><span style="color:#f92672">-</span><span style="color:#a6e22e">enabled</span><span style="color:#f92672">=</span><span style="color:#e6db74">"1"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">emit</span><span style="color:#f92672">-</span><span style="color:#a6e22e">metadata</span><span style="color:#f92672">=</span><span style="color:#e6db74">"0"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">input</span><span style="color:#f92672">-</span><span style="color:#a6e22e">position</span><span style="color:#f92672">=</span><span style="color:#e6db74">"bottom"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">theme</span><span style="color:#f92672">=</span><span style="color:#e6db74">"preferred_color_scheme"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">"en"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data</span><span style="color:#f92672">-</span><span style="color:#a6e22e">loading</span><span style="color:#f92672">=</span><span style="color:#e6db74">"lazy"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">crossorigin</span><span style="color:#f92672">=</span><span style="color:#e6db74">"anonymous"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">async</span><span style="color:#f92672">></span>
|
||
</span></span><span style="display:flex;"><span> </<span style="color:#f92672">script</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">noscript</span>>Enable JavaScript to view comments powered by Giscus.</<span style="color:#f92672">noscript</span>>
|
||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">section</span>>
|
||
</span></span></code></pre></div><p><strong>Notes</strong></p>
|
||
<ul>
|
||
<li>You’ll get <code>repo-id</code> and <code>category-id</code> from <strong>giscus.app</strong> after picking your repo + category.</li>
|
||
<li><code>data-mapping="pathname"</code> works great for static sites.</li>
|
||
<li>Prefer a <strong>public</strong> repo so visitors can read discussions without friction.</li>
|
||
<li>Want a different language? Change <code>data-lang</code> (e.g., <code>de</code>, <code>fr</code>).</li>
|
||
</ul>
|
||
<hr>
|
||
<h2 id="2-enable-rss-home--sections">2) Enable RSS (home + sections)<a hidden class="anchor" aria-hidden="true" href="#2-enable-rss-home--sections">#</a></h2>
|
||
<p>Hugo already knows how to generate feeds; we just tell it where.</p>
|
||
<p><code>hugo.toml</code></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">"https://example.com/"</span> <span style="color:#75715e"># set your production URL</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">outputs</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">home</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>, <span style="color:#e6db74">"JSON"</span>] <span style="color:#75715e"># JSON is optional (e.g., for on-site search)</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">section</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">taxonomy</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">term</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>]
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">services</span>.<span style="color:#a6e22e">rss</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">limit</span> = <span style="color:#ae81ff">-1</span> <span style="color:#75715e"># -1 = no cap; or set e.g. 20</span>
|
||
</span></span></code></pre></div><p><strong>Feed URLs</strong></p>
|
||
<ul>
|
||
<li>Home feed: <code>/index.xml</code> → <code>https://example.com/index.xml</code></li>
|
||
<li>Section feeds: <code>/posts/index.xml</code>, <code>/notes/index.xml</code>, etc. (folders are lowercase)</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>If you’re using a <code>.onion</code> address or staging domain, just set that in <code>baseURL</code>.<br>
|
||
Locally, Hugo serves feeds at <code>http://localhost:1313/index.xml</code>.</p></blockquote>
|
||
<hr>
|
||
<h2 id="3-show-an-rss-button-on-the-home-page">3) Show an RSS button on the home page<a hidden class="anchor" aria-hidden="true" href="#3-show-an-rss-button-on-the-home-page">#</a></h2>
|
||
<p>PaperMod shows a nice RSS icon on <strong>section/taxonomy</strong> lists by default (when enabled), but not on the home page. Two options:</p>
|
||
<h3 id="option-a--use-the-home-social-icons-row">Option A — Use the home “social icons” row<a hidden class="anchor" aria-hidden="true" href="#option-a--use-the-home-social-icons-row">#</a></h3>
|
||
<p>This appears if you enable <strong>Home-Info</strong> or <strong>Profile</strong> mode:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">homeInfoParams</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Title</span> = <span style="color:#e6db74">"Hello, internet!"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Content</span> = <span style="color:#e6db74">"Notes, experiments, and occasional rabbit holes."</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>[[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">socialIcons</span>]]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">"rss"</span> <span style="color:#75715e"># lowercase matters</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">url</span> = <span style="color:#e6db74">"/index.xml"</span>
|
||
</span></span></code></pre></div><h3 id="option-b--add-an-icon-in-the-footer-theme-safe">Option B — Add an icon in the footer (theme-safe)<a hidden class="anchor" aria-hidden="true" href="#option-b--add-an-icon-in-the-footer-theme-safe">#</a></h3>
|
||
<p>Create:</p>
|
||
|
||
<div class="mermaid">layouts/partials/extend_footer.html</div>
|
||
|
||
<p>Paste:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">"/index.xml"</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">"alternate"</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"application/rss+xml"</span> <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">"RSS"</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"rss-link"</span>>
|
||
</span></span><span style="display:flex;"><span> {{ partial "svg.html" (dict "name" "rss") }}
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span>>RSS</<span style="color:#f92672">span</span>>
|
||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">a</span>>
|
||
</span></span></code></pre></div><p>Make it a sensible size:</p>
|
||
<p>Create:</p>
|
||
|
||
<div class="mermaid">assets/css/extended/rss.css</div>
|
||
|
||
<p>Paste:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* Small & aligned RSS icon in the footer */</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">rss-link</span> { <span style="color:#66d9ef">display</span>: <span style="color:#66d9ef">inline-flex</span>; <span style="color:#66d9ef">align-items</span>: <span style="color:#66d9ef">center</span>; <span style="color:#66d9ef">gap</span>: <span style="color:#ae81ff">.35</span><span style="color:#66d9ef">rem</span>; }
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">rss-link</span> <span style="color:#f92672">svg</span> { <span style="color:#66d9ef">margin-left</span>: <span style="color:#ae81ff">10</span><span style="color:#66d9ef">px</span>; <span style="color:#66d9ef">width</span>: <span style="color:#ae81ff">18</span><span style="color:#66d9ef">px</span>; <span style="color:#66d9ef">height</span>: <span style="color:#ae81ff">18</span><span style="color:#66d9ef">px</span>; } <span style="color:#75715e">/* a bit of left margin for good measure!*/</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">rss-link</span> <span style="color:#f92672">span</span> { <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">.95</span><span style="color:#66d9ef">rem</span>; }
|
||
</span></span></code></pre></div><p>PaperMod auto-bundles anything under <code>assets/css/extended/</code>, so no extra imports needed.</p>
|
||
<hr>
|
||
<h2 id="4-verify-everything">4) Verify everything<a hidden class="anchor" aria-hidden="true" href="#4-verify-everything">#</a></h2>
|
||
<p>Build or run dev:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>hugo <span style="color:#75715e"># outputs to ./public</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or</span>
|
||
</span></span><span style="display:flex;"><span>hugo server <span style="color:#75715e"># serves at http://localhost:1313</span>
|
||
</span></span></code></pre></div><p>Check feeds:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Home feed:</span>
|
||
</span></span><span style="display:flex;"><span>curl -s http://localhost:1313/index.xml | head
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># A section feed (adjust path to your section name):</span>
|
||
</span></span><span style="display:flex;"><span>curl -s http://localhost:1313/posts/index.xml | head
|
||
</span></span></code></pre></div><p>Check comments:</p>
|
||
<ol>
|
||
<li>Open any post locally.</li>
|
||
<li>Scroll to the bottom — Giscus should appear.</li>
|
||
<li>Try a reaction or comment (you’ll be prompted to sign in with GitHub).</li>
|
||
</ol>
|
||
<hr>
|
||
<h2 id="5-per-post-controls-handy-later">5) Per-post controls (handy later)<a hidden class="anchor" aria-hidden="true" href="#5-per-post-controls-handy-later">#</a></h2>
|
||
<p>Turn comments off for a single post:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#75715e"># in that post’s front matter</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">comments</span> = <span style="color:#66d9ef">false</span>
|
||
</span></span></code></pre></div><p>Full content in RSS (global):</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ShowFullTextinRSS</span> = <span style="color:#66d9ef">true</span>
|
||
</span></span></code></pre></div><p>Show RSS buttons on section/taxonomy lists:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ShowRssButtonInSectionTermList</span> = <span style="color:#66d9ef">true</span>
|
||
</span></span></code></pre></div><hr>
|
||
<h2 id="6-troubleshooting-aka-what-i-bumped-into">6) Troubleshooting (aka “what I bumped into”)<a hidden class="anchor" aria-hidden="true" href="#6-troubleshooting-aka-what-i-bumped-into">#</a></h2>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Home page has no RSS icon</strong><br>
|
||
That’s expected; either enable Home-Info/Profile mode + <code>socialIcons</code>, or use the footer partial.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Section feed 404</strong><br>
|
||
Folder names are lowercase (<code>content/posts/</code>, not <code>content/Posts/</code>).<br>
|
||
The URL mirrors that: <code>/posts/index.xml</code>.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Giscus doesn’t create a discussion</strong><br>
|
||
Double-check the four attributes: <code>data-repo</code>, <code>data-repo-id</code>, <code>data-category</code>, <code>data-category-id</code>.<br>
|
||
Make sure Discussions is enabled and the Giscus app is installed for the repo.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Icon is comically large</strong><br>
|
||
Keep the CSS above; 16–20px usually looks right.</p>
|
||
</li>
|
||
</ul>
|
||
<hr>
|
||
<h2 id="7-bonus-fast-setup-via-github-cli-optional-i-didnt-use-it-but-i-shouldve-perhaps">7) Bonus: fast setup via GitHub CLI (optional, I didn’t use it, but I should’ve perhaps)<a hidden class="anchor" aria-hidden="true" href="#7-bonus-fast-setup-via-github-cli-optional-i-didnt-use-it-but-i-shouldve-perhaps">#</a></h2>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Create the public comments repo (adjust names)</span>
|
||
</span></span><span style="display:flex;"><span>gh repo create yourname/blog-comments --public -d <span style="color:#e6db74">"Blog comment threads"</span> --confirm
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Enable Discussions for it</span>
|
||
</span></span><span style="display:flex;"><span>gh repo edit yourname/blog-comments --enable-discussions
|
||
</span></span></code></pre></div><p>Then visit <strong>giscus.app</strong>, select the repo + category (e.g. “Comments”), and copy the generated IDs into <code>layouts/partials/comments.html</code>.</p>
|
||
<hr>
|
||
<h3 id="final-config-snapshot-condensed">Final config snapshot (condensed)<a hidden class="anchor" aria-hidden="true" href="#final-config-snapshot-condensed">#</a></h3>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">"https://example.com/"</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">theme</span> = <span style="color:#e6db74">"PaperMod"</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">outputs</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">home</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>, <span style="color:#e6db74">"JSON"</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">section</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">taxonomy</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">term</span> = [<span style="color:#e6db74">"HTML"</span>, <span style="color:#e6db74">"RSS"</span>]
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">services</span>.<span style="color:#a6e22e">rss</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">limit</span> = <span style="color:#ae81ff">-1</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">comments</span> = <span style="color:#66d9ef">true</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ShowRssButtonInSectionTermList</span> = <span style="color:#66d9ef">true</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ShowFullTextinRSS</span> = <span style="color:#66d9ef">true</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Optional if using Home-Info/Profile mode:</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># [[params.socialIcons]]</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># name = "rss"</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># url = "/index.xml"</span>
|
||
</span></span></code></pre></div><hr>
|
||
<h1 id="appendix--why-i-added-isso-how-i-wired-it-and-what-i-fixed">Appendix — Why I added Isso, how I wired it, and what I fixed<a hidden class="anchor" aria-hidden="true" href="#appendix--why-i-added-isso-how-i-wired-it-and-what-i-fixed">#</a></h1>
|
||
<h2 id="the-story-why-isso--why-i-still-keep-giscus">The story (why Isso + why I still keep Giscus)<a hidden class="anchor" aria-hidden="true" href="#the-story-why-isso--why-i-still-keep-giscus">#</a></h2>
|
||
<p>I wanted comments that:</p>
|
||
<ul>
|
||
<li>allow <strong>anonymous</strong> replies (no GitHub account required),</li>
|
||
<li>are <strong>lightweight</strong> and <strong>self-hosted</strong>, and</li>
|
||
<li>can work on my <strong>Tor <code>.onion</code> mirror</strong>.</li>
|
||
</ul>
|
||
<p><strong>Isso</strong> checks those boxes: it’s a tiny Python app with SQLite storage, no trackers, and a simple embed. At the same time, I like the developer-friendly workflow of <strong>Giscus</strong> (comments are GitHub Discussions), so I kept both and added a little switcher UI: <strong>“Anonymous (Isso)”</strong> or <strong>“GitHub (Giscus)”</strong>.</p>
|
||
<p>Result: readers can pick privacy/anon (Isso) or identity/notifications (Giscus).</p>
|
||
<h2 id="how-i-run-it-high-level">How I run it (high-level)<a hidden class="anchor" aria-hidden="true" href="#how-i-run-it-high-level">#</a></h2>
|
||
<ul>
|
||
<li><strong>Site:</strong> Hugo + PaperMod</li>
|
||
<li><strong>Isso:</strong> listening on <code>127.0.0.1:8080</code>, stored locally (SQLite)</li>
|
||
<li><strong>Nginx:</strong> reverse-proxies <strong><code>/isso/</code></strong> to Isso on both the clearnet and the <code>.onion</code> vhost</li>
|
||
<li><strong>Hugo partial:</strong> renders the two panels (Isso & Giscus) and a switcher; Giscus is <strong>lazy-loaded</strong> and <strong>auto-rethemed</strong> with the site theme</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>I’m deliberately keeping Isso’s own config tiny in this appendix; the important part for my setup is the <strong>Nginx proxying</strong> and the <strong>Hugo partial</strong>.</p></blockquote>
|
||
<h2 id="hugo-partial-switcher--embeds">Hugo partial: switcher + embeds<a hidden class="anchor" aria-hidden="true" href="#hugo-partial-switcher--embeds">#</a></h2>
|
||
<p>Save as <code>layouts/partials/comments.html</code> and include it in your single layout (e.g. <code>layouts/_default/single.html</code>) with <code>{{ partial "comments.html" . }}</code>.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e"><!-- comments.html --></span>
|
||
</span></span><span style="display:flex;"><span><<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"comments-switch"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:flex;gap:.5rem;margin:.5rem 0 1rem;"</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">button</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"btn-isso"</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"button"</span> <span style="color:#a6e22e">aria-pressed</span><span style="color:#f92672">=</span><span style="color:#e6db74">"true"</span>>Anonymous (Isso)</<span style="color:#f92672">button</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">button</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"btn-giscus"</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"button"</span> <span style="color:#a6e22e">aria-pressed</span><span style="color:#f92672">=</span><span style="color:#e6db74">"false"</span>>GitHub (Giscus)</<span style="color:#f92672">button</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">"https://github.com/AlipourIm/blog-comments/discussions/categories/comments"</span> <span style="color:#a6e22e">target</span><span style="color:#f92672">=</span><span style="color:#e6db74">"_blank"</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">"noopener"</span>>Open on GitHub ↗</<span style="color:#f92672">a</span>>
|
||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">div</span>>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"><!-- Isso panel (default) --></span>
|
||
</span></span><span style="display:flex;"><span><<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"panel-isso"</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">section</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"isso-thread"</span>></<span style="color:#f92672">section</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">script</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">"/isso/js/embed.min.js"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-isso</span><span style="color:#f92672">=</span><span style="color:#e6db74">"/isso"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-isso-css</span><span style="color:#f92672">=</span><span style="color:#e6db74">"true"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-isso-lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">"en"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-isso-max-comments-nested</span><span style="color:#f92672">=</span><span style="color:#e6db74">"5"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">data-isso-sorting</span><span style="color:#f92672">=</span><span style="color:#e6db74">"newest"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">async</span>>
|
||
</span></span><span style="display:flex;"><span> </<span style="color:#f92672">script</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">small</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"isso-powered"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:block;margin:.5rem 0;color:var(--secondary,#888)"</span>>
|
||
</span></span><span style="display:flex;"><span> Comments powered by <<span style="color:#f92672">a</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">"https://isso-comments.de"</span> <span style="color:#a6e22e">target</span><span style="color:#f92672">=</span><span style="color:#e6db74">"_blank"</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">"noopener"</span>>Isso</<span style="color:#f92672">a</span>>
|
||
</span></span><span style="display:flex;"><span> </<span style="color:#f92672">small</span>>
|
||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">div</span>>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"><!-- Giscus panel (lazy-loaded on click) --></span>
|
||
</span></span><span style="display:flex;"><span><<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"panel-giscus"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:none"</span>>
|
||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">section</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"giscus-thread"</span>></<span style="color:#f92672">section</span>>
|
||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">div</span>>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><<span style="color:#f92672">script</span>>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#66d9ef">function</span> () {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">panelIsso</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'panel-isso'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">panelGisc</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'panel-giscus'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">issoBtn</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'btn-isso'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">giscBtn</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'btn-giscus'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">gLoaded</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">isDark</span>() {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 1) explicit PaperMod preference
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">pref</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">localStorage</span>.<span style="color:#a6e22e">getItem</span>(<span style="color:#e6db74">'pref-theme'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pref</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">'dark'</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pref</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">'light'</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 2) page state (classes / data-theme)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">H</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">documentElement</span>, <span style="color:#a6e22e">B</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">body</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">hasDark</span> <span style="color:#f92672">=</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">H</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">'dark'</span>) <span style="color:#f92672">||</span> <span style="color:#a6e22e">B</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">'dark'</span>) <span style="color:#f92672">||</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">H</span>.<span style="color:#a6e22e">dataset</span>.<span style="color:#a6e22e">theme</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">'dark'</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">B</span>.<span style="color:#a6e22e">dataset</span>.<span style="color:#a6e22e">theme</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">'dark'</span> <span style="color:#f92672">||</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">H</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">'theme-dark'</span>) <span style="color:#f92672">||</span> <span style="color:#a6e22e">B</span>.<span style="color:#a6e22e">classList</span>.<span style="color:#a6e22e">contains</span>(<span style="color:#e6db74">'theme-dark'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">hasDark</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 3) OS preference
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span> window.<span style="color:#a6e22e">matchMedia</span> <span style="color:#f92672">&&</span> window.<span style="color:#a6e22e">matchMedia</span>(<span style="color:#e6db74">'(prefers-color-scheme: dark)'</span>).<span style="color:#a6e22e">matches</span>;
|
||
</span></span><span style="display:flex;"><span> }
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">gTheme</span> <span style="color:#f92672">=</span> () => (<span style="color:#a6e22e">isDark</span>() <span style="color:#f92672">?</span> <span style="color:#e6db74">'dark_dimmed'</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">'noborder_light'</span>);
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">loadGiscus</span>() {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">gLoaded</span>) <span style="color:#66d9ef">return</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">'script'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">src</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">'https://giscus.app/client.js'</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#66d9ef">async</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">crossOrigin</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">'anonymous'</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-repo'</span>,<span style="color:#e6db74">'AlipourIm/blog-comments'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-repo-id'</span>,<span style="color:#e6db74">'R_kgDOQGARyA'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-category'</span>,<span style="color:#e6db74">'Comments'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-category-id'</span>,<span style="color:#e6db74">'DIC_kwDOQGARyM4Cw3x-'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-mapping'</span>,<span style="color:#e6db74">'pathname'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-strict'</span>,<span style="color:#e6db74">'0'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-reactions-enabled'</span>,<span style="color:#e6db74">'1'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-emit-metadata'</span>,<span style="color:#e6db74">'0'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-input-position'</span>,<span style="color:#e6db74">'bottom'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-lang'</span>,<span style="color:#e6db74">'en'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'data-theme'</span>, <span style="color:#a6e22e">gTheme</span>()); <span style="color:#75715e">// initial theme
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'giscus-thread'</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">s</span>);
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// Retheme once iframe exists
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">reTheme</span> <span style="color:#f92672">=</span> () => {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">iframe</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'iframe.giscus-frame'</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">iframe</span>) <span style="color:#66d9ef">return</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">iframe</span>.<span style="color:#a6e22e">contentWindow</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">postMessage</span>(
|
||
</span></span><span style="display:flex;"><span> { <span style="color:#a6e22e">giscus</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">setConfig</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">theme</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">gTheme</span>() } } },
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">'https://giscus.app'</span>
|
||
</span></span><span style="display:flex;"><span> );
|
||
</span></span><span style="display:flex;"><span> };
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// PaperMod toggle button
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'theme-toggle'</span>)<span style="color:#f92672">?</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'click'</span>, () =>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">reTheme</span>, <span style="color:#ae81ff">0</span>)
|
||
</span></span><span style="display:flex;"><span> );
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// Also react to attribute changes
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">mo</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MutationObserver</span>(<span style="color:#a6e22e">reTheme</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">mo</span>.<span style="color:#a6e22e">observe</span>(document.<span style="color:#a6e22e">documentElement</span>, { <span style="color:#a6e22e">attributes</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">attributeFilter</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">'class'</span>,<span style="color:#e6db74">'data-theme'</span>] });
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">mo</span>.<span style="color:#a6e22e">observe</span>(document.<span style="color:#a6e22e">body</span>, { <span style="color:#a6e22e">attributes</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">attributeFilter</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">'class'</span>,<span style="color:#e6db74">'data-theme'</span>] });
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">gLoaded</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
|
||
</span></span><span style="display:flex;"><span> }
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">show</span>(<span style="color:#a6e22e">which</span>) {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">showIsso</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">which</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">'isso'</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">panelIsso</span>.<span style="color:#a6e22e">style</span>.<span style="color:#a6e22e">display</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">showIsso</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">'block'</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">'none'</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">panelGisc</span>.<span style="color:#a6e22e">style</span>.<span style="color:#a6e22e">display</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">showIsso</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">'none'</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">'block'</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">issoBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'aria-pressed'</span>, <span style="color:#a6e22e">showIsso</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">giscBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">'aria-pressed'</span>, <span style="color:#f92672">!</span><span style="color:#a6e22e">showIsso</span>);
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">showIsso</span>) <span style="color:#a6e22e">loadGiscus</span>();
|
||
</span></span><span style="display:flex;"><span> }
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">issoBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'click'</span>, <span style="color:#a6e22e">e</span> => { <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">preventDefault</span>(); <span style="color:#a6e22e">show</span>(<span style="color:#e6db74">'isso'</span>); });
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">giscBtn</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'click'</span>, <span style="color:#a6e22e">e</span> => { <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">preventDefault</span>(); <span style="color:#a6e22e">show</span>(<span style="color:#e6db74">'giscus'</span>); });
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// default
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">show</span>(<span style="color:#e6db74">'isso'</span>);
|
||
</span></span><span style="display:flex;"><span>})();
|
||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">script</span>>
|
||
</span></span></code></pre></div><h2 id="nginx-proxy-isso-on-both-sites-and-avoid-the-404-onion-trap">Nginx: proxy Isso on <strong>both</strong> sites (and avoid the 404-onion trap)<a hidden class="anchor" aria-hidden="true" href="#nginx-proxy-isso-on-both-sites-and-avoid-the-404-onion-trap">#</a></h2>
|
||
<p>The bug I hit: my <code>.onion</code> server had a regex static block that <strong>won over</strong> the plain <code>/isso/</code> location, so <code>/isso/js/embed.min.js</code> returned <strong>404</strong>.</p>
|
||
<p><strong>Fix:</strong> make <code>/isso/</code> a <code>^~</code> prefix location so it beats regex, and place it <strong>above</strong> the static block.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># /etc/nginx/sites-enabled/onion-blog (same idea for the clearnet vhost)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">server</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">listen</span> 127.0.0.1:<span style="color:#ae81ff">3301</span> <span style="color:#e6db74">default_server</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">server_name</span> <span style="color:#e6db74">fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion</span> 127.0.0.1 <span style="color:#e6db74">localhost</span>;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Referrer-Policy</span> <span style="color:#e6db74">no-referrer</span> <span style="color:#e6db74">always</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">add_header</span> <span style="color:#e6db74">X-Content-Type-Options</span> <span style="color:#e6db74">nosniff</span> <span style="color:#e6db74">always</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">add_header</span> <span style="color:#e6db74">X-Frame-Options</span> <span style="color:#e6db74">SAMEORIGIN</span> <span style="color:#e6db74">always</span>;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">root</span> <span style="color:#e6db74">/srv/hugo/mysite/public-onion</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">index</span> <span style="color:#e6db74">index.html</span>;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> { <span style="color:#f92672">try_files</span> $uri $uri/ <span style="color:#e6db74">/index.html</span>; }
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># /isso -> /isso/
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#f92672">location</span> = <span style="color:#e6db74">/isso</span> { <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">/isso/</span>; }
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># IMPORTANT: this must beat regex locations below
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#f92672">location</span> <span style="color:#e6db74">^~</span> <span style="color:#e6db74">/isso/</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8080/</span>; <span style="color:#75715e"># Isso container/port
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#f92672">proxy_http_version</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">.1</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> $host;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Host</span> $host;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> $scheme;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span> $proxy_add_x_forwarded_for;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_redirect</span> <span style="color:#66d9ef">off</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_read_timeout</span> <span style="color:#e6db74">60s</span>;
|
||
</span></span><span style="display:flex;"><span> }
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># static
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#f92672">location</span> ~<span style="color:#e6db74">*</span> <span style="color:#e6db74">\\.(css|js|ico|png|jpg|jpeg|gif|svg|webp|txt|xml)</span>$ {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">access_log</span> <span style="color:#66d9ef">off</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">add_header</span> <span style="color:#e6db74">Cache-Control</span> <span style="color:#e6db74">"public,</span> <span style="color:#e6db74">max-age=31536000,</span> <span style="color:#e6db74">immutable"</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">try_files</span> $uri =<span style="color:#ae81ff">404</span>;
|
||
</span></span><span style="display:flex;"><span> }
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span></code></pre></div><p>Reload and smoke test:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo nginx -t <span style="color:#f92672">&&</span> sudo systemctl reload nginx
|
||
</span></span><span style="display:flex;"><span>curl -I http://<your-onion-host>/isso/js/embed.min.js <span style="color:#75715e"># expect 200</span>
|
||
</span></span></code></pre></div><blockquote>
|
||
<p>If you ever point <code>data-isso</code> to a <strong>different origin</strong>, add CORS headers on that vhost. For same-origin (<code>data-isso="/isso"</code>), you don’t need CORS.</p></blockquote>
|
||
<h2 id="small-css-tweaks-one-color-across-lightdark--spacing">Small CSS tweaks (one color across light/dark + spacing)<a hidden class="anchor" aria-hidden="true" href="#small-css-tweaks-one-color-across-lightdark--spacing">#</a></h2>
|
||
<p>I wanted the same blue for <strong>reply / edit / delete</strong> links and for <strong>Submit / Preview</strong> buttons, in both themes, and a little spacing:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* put this in your site CSS */</span>
|
||
</span></span><span style="display:flex;"><span>:<span style="color:#a6e22e">root</span> { --btn-blue: <span style="color:#ae81ff">#2563eb</span>; } <span style="color:#75715e">/* adjust to taste */</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* Isso form buttons */</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-post-action</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"submit"</span><span style="color:#f92672">],</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-post-action</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">"preview"</span><span style="color:#f92672">]</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">background</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>btn<span style="color:#f92672">-</span><span style="color:#66d9ef">blue</span>) <span style="color:#75715e">!important</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">color</span>: <span style="color:#ae81ff">#fff</span> <span style="color:#75715e">!important</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">border</span>: <span style="color:#66d9ef">none</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">padding</span>: <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">.75</span><span style="color:#66d9ef">rem</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">border-radius</span>: <span style="color:#ae81ff">.4</span><span style="color:#66d9ef">rem</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">cursor</span>: <span style="color:#66d9ef">pointer</span>;
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* link actions under comments */</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-comment-footer</span> <span style="color:#f92672">a</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">color</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>btn<span style="color:#f92672">-</span><span style="color:#66d9ef">blue</span>) <span style="color:#75715e">!important</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">margin-right</span>: <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span>; <span style="color:#75715e">/* gives "edit" and "delete" some breathing room */</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">text-decoration</span>: <span style="color:#66d9ef">none</span>;
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-comment-footer</span> <span style="color:#f92672">a</span>:<span style="color:#a6e22e">hover</span> { <span style="color:#66d9ef">text-decoration</span>: <span style="color:#66d9ef">underline</span>; }
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* keep name/email/website stacked, slightly larger */</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> .<span style="color:#a6e22e">isso-input-wrapper</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">display</span>: <span style="color:#66d9ef">block</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">margin</span>: <span style="color:#ae81ff">.35</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">0</span>;
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">label</span> { <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">.95</span><span style="color:#66d9ef">rem</span>; }
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"text"</span><span style="color:#f92672">],</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"email"</span><span style="color:#f92672">],</span>
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">isso-auth-section</span> <span style="color:#f92672">input</span><span style="color:#f92672">[</span><span style="color:#f92672">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">"url"</span><span style="color:#f92672">]</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">width</span>: <span style="color:#ae81ff">100</span><span style="color:#66d9ef">%</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">1</span><span style="color:#66d9ef">rem</span>;
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">padding</span>: <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">.6</span><span style="color:#66d9ef">rem</span>;
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span></code></pre></div><h2 id="what-changed-the-fixes-i-actually-made">What changed (the fixes I actually made)<a hidden class="anchor" aria-hidden="true" href="#what-changed-the-fixes-i-actually-made">#</a></h2>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Theme correctness for Giscus.</strong><br>
|
||
PaperMod doesn’t add <code>.light</code>—it only toggles <code>.dark</code> and stores <code>pref-theme</code>. I now:</p>
|
||
<ul>
|
||
<li>read <code>localStorage.pref-theme</code> first,</li>
|
||
<li>fall back to class/data-theme, then</li>
|
||
<li>fall back to OS preference.<br>
|
||
I also re-theme the Giscus iframe on every toggle via <code>postMessage</code>.</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p><strong>Isso over Tor.</strong><br>
|
||
On the <code>.onion</code> vhost, I moved the proxy to a <code>^~ /isso/</code> block so it beats the static regex. That fixed the <code>404</code> on <code>/isso/js/embed.min.js</code>.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>UI polish.</strong></p>
|
||
<ul>
|
||
<li>Unified button/link color across themes,</li>
|
||
<li>ensured “edit”/“delete” aren’t touching,</li>
|
||
<li>kept the author/email/website inputs <strong>stacked</strong>, and</li>
|
||
<li>left <strong>Submit</strong>/<strong>Preview</strong> styled and grouped.</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<h2 id="sanity-check">Sanity check<a hidden class="anchor" aria-hidden="true" href="#sanity-check">#</a></h2>
|
||
<ul>
|
||
<li>Toggle the theme: Giscus switches between <code>noborder_light</code> and <code>dark_dimmed</code> without reload.</li>
|
||
<li>Open DevTools → Network on onion: <code>/isso/js/embed.min.js</code> loads with <strong>200</strong>.</li>
|
||
<li>Switcher buttons swap panels instantly; Giscus only loads when clicked.</li>
|
||
</ul>
|
||
<h2 id="8-final-notes">8) Final notes!<a hidden class="anchor" aria-hidden="true" href="#8-final-notes">#</a></h2>
|
||
<p>I could not fully fix the style of Isso.
|
||
CSS is not easy… drat!</p>
|
||
<p>I don’t think anyone actually reads the blog, let alone comment on it.
|
||
But it felt cool to have a comment section and also RSS for it.
|
||
I had a lot of not so fun debugging experience, and will probably work on the styling to make it look right, but for now I’m happy with it! ^^</p>
|
||
<h1 id="admin-note--delete-all-isso-comments-for-a-single-post">Admin note — Delete all <strong>Isso</strong> comments for a single post<a hidden class="anchor" aria-hidden="true" href="#admin-note--delete-all-isso-comments-for-a-single-post">#</a></h1>
|
||
<p>This note is for future me: how to remove every comment tied to one post in <strong>Isso</strong>.<br>
|
||
Tested with SQLite-backed Isso (default). Works whether you serve via clearnet or <code>.onion</code> — because this operates directly on the database.</p>
|
||
<hr>
|
||
<h2 id="tldr-host-with-sqlite">TL;DR (host with SQLite)<a hidden class="anchor" aria-hidden="true" href="#tldr-host-with-sqlite">#</a></h2>
|
||
<blockquote>
|
||
<p>Replace the placeholders for <code>DB</code> and <code>POST_URI</code> first.</p></blockquote>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 0) Variables — edit these two lines for your setup</span>
|
||
</span></span><span style="display:flex;"><span>DB<span style="color:#f92672">=</span><span style="color:#e6db74">"/var/lib/isso/comments.db"</span> <span style="color:#75715e"># path to your Isso SQLite DB (check isso.conf: [general] dbpath)</span>
|
||
</span></span><span style="display:flex;"><span>POST_URI<span style="color:#f92672">=</span><span style="color:#e6db74">"/posts/skin_routine/"</span> <span style="color:#75715e"># the post's URI as stored by Isso (often just the path)</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1) Backup (always do this!)</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">".backup '</span><span style="color:#66d9ef">$(</span>dirname <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">/comments.</span><span style="color:#66d9ef">$(</span>date +%F-%H%M%S<span style="color:#66d9ef">)</span><span style="color:#e6db74">.backup.sqlite3'"</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2) Inspect: find the thread row and preview the comments</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">.headers on
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">.mode column
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">SELECT id, uri FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%';
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">SELECT id, tid, created, author, text
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">FROM comments
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">WHERE tid IN (SELECT id FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%')
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">ORDER BY created DESC
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">LIMIT 25;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">"</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3) Delete all comments for that post (thread)</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">DELETE FROM comments
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">WHERE tid IN (SELECT id FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%');
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">"</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 4) (Optional) Remove the now-empty thread row too</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">DELETE FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%';
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">VACUUM;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">"</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 5) Done. Isso usually picks this up automatically.</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># If you're containerized and want to be safe:</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># docker restart <isso_container_name></span>
|
||
</span></span></code></pre></div><hr>
|
||
<h2 id="if-running-isso-in-docker">If running Isso in Docker<a hidden class="anchor" aria-hidden="true" href="#if-running-isso-in-docker">#</a></h2>
|
||
<p>Open a shell in the container, then run the same steps:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -it <isso_container_name> /bin/sh
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Inside the container:</span>
|
||
</span></span><span style="display:flex;"><span>DB<span style="color:#f92672">=</span><span style="color:#e6db74">"/db/comments.db"</span> <span style="color:#75715e"># or /data/comments.db, check your image/volume mapping</span>
|
||
</span></span><span style="display:flex;"><span>POST_URI<span style="color:#f92672">=</span><span style="color:#e6db74">"/posts/skin_routine/"</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">".backup '/db/comments.</span><span style="color:#66d9ef">$(</span>date +%F-%H%M%S<span style="color:#66d9ef">)</span><span style="color:#e6db74">.backup.sqlite3'"</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"SELECT id, uri FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%';"</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"DELETE FROM comments WHERE tid IN (SELECT id FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%');"</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"DELETE FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%'; VACUUM;"</span>
|
||
</span></span><span style="display:flex;"><span>exit
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Optionally restart the container:</span>
|
||
</span></span><span style="display:flex;"><span>docker restart <isso_container_name>
|
||
</span></span></code></pre></div><hr>
|
||
<h2 id="notes--gotchas">Notes & gotchas<a hidden class="anchor" aria-hidden="true" href="#notes--gotchas">#</a></h2>
|
||
<ul>
|
||
<li><strong>Find the real DB path</strong>: In <code>isso.conf</code> (often <code>/etc/isso/isso.conf</code> or within your container at <code>/config/isso.conf</code>), look for:
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[general]</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dbpath</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/var/lib/isso/comments.db</span>
|
||
</span></span></code></pre></div></li>
|
||
<li><strong>What goes in <code>POST_URI</code>?</strong> Isso stores a <code>uri</code> string per thread. With the default embed config, it’s usually the <strong>path</strong> (<code>/posts/<slug>/</code>). If you’re unsure, list recent threads:
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">".headers on"</span> <span style="color:#e6db74">".mode column"</span> <span style="color:#e6db74">"SELECT id, uri FROM threads ORDER BY id DESC LIMIT 50;"</span>
|
||
</span></span></code></pre></div></li>
|
||
<li><strong>Clearnet vs <code>.onion</code></strong>: If you load the same post under multiple origins <em>and</em> your embed setup stores full origins, you may have <strong>multiple thread rows</strong>. The <code>LIKE</code> query above deliberately matches any uri containing your <code>POST_URI</code> to catch those.</li>
|
||
<li><strong>Rollback</strong>: To undo, stop Isso (optional), replace the DB with your backup (<code>comments.YYYY-MM-DD-HHMMSS.backup.sqlite3</code>), and start Isso again.</li>
|
||
<li><strong>HTTP API option</strong>: Isso supports <code>DELETE /<comment_id></code> if admin endpoints/auth are enabled. For bulk removal it’s simpler and safer to use SQLite directly.</li>
|
||
<li><strong>Safety</strong>: Always run a backup first, and <strong>preview with the SELECT</strong> before you run the DELETE.</li>
|
||
</ul>
|
||
<hr>
|
||
<h2 id="one-liners-for-the-impatient">One-liners for the impatient<a hidden class="anchor" aria-hidden="true" href="#one-liners-for-the-impatient">#</a></h2>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>DB<span style="color:#f92672">=</span><span style="color:#e6db74">"/var/lib/isso/comments.db"</span>; POST_URI<span style="color:#f92672">=</span><span style="color:#e6db74">"/posts/skin_routine/"</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">".backup '</span><span style="color:#66d9ef">$(</span>dirname <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">/comments.</span><span style="color:#66d9ef">$(</span>date +%F-%H%M%S<span style="color:#66d9ef">)</span><span style="color:#e6db74">.backup.sqlite3'"</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"DELETE FROM comments WHERE tid IN (SELECT id FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%');"</span>
|
||
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">"</span>$DB<span style="color:#e6db74">"</span> <span style="color:#e6db74">"DELETE FROM threads WHERE uri LIKE '%' || '</span>$POST_URI<span style="color:#e6db74">' || '%'; VACUUM;"</span>
|
||
</span></span></code></pre></div><hr>
|
||
<p><strong>Isso container: quick checks & fixes for future me! (thank past me later ^^)</strong></p>
|
||
<p><strong>Find the container</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker ps -a --format <span style="color:#e6db74">'table {{.ID}} {{.Names}} {{.Status}} {{.Ports}}'</span> | grep -i isso
|
||
</span></span></code></pre></div><p><strong>Inspect basic state</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker inspect -f <span style="color:#e6db74">'Name={{.Name}} Running={{.State.Running}} Status={{.State.Status}} StartedAt={{.State.StartedAt}} FinishedAt={{.State.FinishedAt}}'</span> isso
|
||
</span></span></code></pre></div><p><strong>Logs</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker logs --tail<span style="color:#f92672">=</span><span style="color:#ae81ff">200</span> isso
|
||
</span></span><span style="display:flex;"><span>docker logs -f isso
|
||
</span></span></code></pre></div><p><strong>Ports & reachability</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker port isso
|
||
</span></span><span style="display:flex;"><span>sudo ss -ltnp | grep <span style="color:#e6db74">':8080 '</span>
|
||
</span></span><span style="display:flex;"><span>curl -i http://127.0.0.1:8080/
|
||
</span></span></code></pre></div><p><strong>Restart policy (keep it running)</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker inspect -f <span style="color:#e6db74">'{{.HostConfig.RestartPolicy.Name}}'</span> isso
|
||
</span></span><span style="display:flex;"><span>docker update --restart unless-stopped isso
|
||
</span></span></code></pre></div><p><strong>Start/stop the container</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker start isso
|
||
</span></span><span style="display:flex;"><span>docker stop isso
|
||
</span></span></code></pre></div><p><strong>Who/what stopped it (events)</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker events --since 24h --filter container<span style="color:#f92672">=</span>isso
|
||
</span></span></code></pre></div><p><strong>Common config issue: duplicate options/sections</strong>
|
||
If logs show DuplicateOptionError or DuplicateSectionError, fix <code>/config/isso.cfg</code> (remove duplicate <code>[server]</code> blocks or duplicate <code>public-endpoint</code>).</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nl -ba /config/isso.cfg | sed -n <span style="color:#e6db74">'1,120p'</span>
|
||
</span></span><span style="display:flex;"><span>grep -n <span style="color:#e6db74">'^\[server\]'</span> /config/isso.cfg
|
||
</span></span><span style="display:flex;"><span>grep -n <span style="color:#e6db74">'^public-endpoint'</span> /config/isso.cfg
|
||
</span></span></code></pre></div><p><strong>Using Docker Compose</strong> <em>(run these in the directory with your compose file)</em></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose ps
|
||
</span></span><span style="display:flex;"><span>docker compose logs --tail<span style="color:#f92672">=</span><span style="color:#ae81ff">200</span> isso
|
||
</span></span><span style="display:flex;"><span>docker compose up -d isso
|
||
</span></span></code></pre></div><p><strong>If the container doesn’t exist (create/recreate)</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker image ls | grep -i isso
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>docker run -d --name isso -p 8080:8080 -v /srv/isso/config:/config -v /srv/isso/db:/db --restart unless-stopped ghcr.io/posativ/isso:latest
|
||
</span></span></code></pre></div><p><strong>Optional: exclude from auto-updaters (like Watchtower)</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker update --label-add com.centurylinklabs.watchtower.enable<span style="color:#f92672">=</span>false isso
|
||
</span></span></code></pre></div><p><em>This note is intentionally self-contained so I can paste it at the end of the blog post as a maintainer-only appendix.</em></p>
|
||
<hr>
|
||
<div class="signature-block" style="margin-top:1rem">
|
||
<p><strong>Downloads:</strong>
|
||
<a href="/sources/posts/new_features.md">Markdown</a> ·
|
||
<a href="/sources/posts/new_features.md.asc">Signature (.asc)</a>
|
||
</p><details class="signature">
|
||
<summary>View OpenPGP signature</summary>
|
||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">-----BEGIN PGP SIGNATURE-----
|
||
|
||
iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbuMACgkQtYgoUOBM
|
||
jSr2VxAAgdHpf+4EIx2ASmSB+MeAHp7s1+2EPhmn96QuhiQO9Dr1e9250LbF/R8S
|
||
A0zcN8mmyOtKv2rediU6HsGy6ddwdTAtpQDRvMw2Xuk2pBUZaG5LuvlNgSse9zVB
|
||
dcG3GVLuMSRgNmyolhFoSWn46aa+3wZGrzYZQVUt8op+2fVp36agq2YoB4zeGKSm
|
||
Ri43rO/kTingD0bclOA+SMbHpKQOcLyHwDlVrqIjVXcJ/kKLcm1G3Z5owo+gB6jY
|
||
EMjxYiqyf5Zcbt4q/WzojbHLffjXoMzOgZ1sDOIObVQni9atgV49X4oIZ3+xSXSH
|
||
W7ZbH6sg9LlrU8djddBXUKB0i72IVZ/4F5icVFUcK+szOmASy8fFP7gCOcCRATtb
|
||
eNIFtiAlRXI0CqJdeq5fE9b+LX8lRpNnX229tg7GFgddzYUmFkKAsoJ9EUzUvUHm
|
||
Xzqlm9DKy9LG/CeOxo473fIo4YCT2fcmMnt9nCZW4iDKOVl1nCupkTn5qsfNdpQM
|
||
KycaNwsLBHPZWV+jDon8NEzYQk07n1Q9rlEWdL0egvn2S+pYnvLZbfi5dRhe+ciC
|
||
qcEW/I1NZZRdU7MUEzhWvhbWsZtTkm6OevXjnqACv91DIQv7tNrKwZuASnpFeDRa
|
||
GE0QYRrsaI3vCLR3cUmBAFsvZdwgzH0RQLb8w21XBW/BoFvixbA=
|
||
=9k+S
|
||
-----END PGP SIGNATURE-----
|
||
</pre>
|
||
</details><p><em>Verify locally:</em></p>
|
||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/new_features.md
|
||
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/new_features.md.asc
|
||
gpg --verify new_features.md.asc new_features.md
|
||
</pre>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
</div>
|
||
|
||
<footer class="post-footer">
|
||
<ul class="post-tags">
|
||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/hugo/">Hugo</a></li>
|
||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/papermod/">PaperMod</a></li>
|
||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/giscus/">Giscus</a></li>
|
||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/rss/">Rss</a></li>
|
||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/comments/">Comments</a></li>
|
||
</ul>
|
||
<nav class="paginav">
|
||
<a class="prev" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/sadness/">
|
||
<span class="title">« Prev</span>
|
||
<br>
|
||
<span>Sadness</span>
|
||
</a>
|
||
<a class="next" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/danya/">
|
||
<span class="title">Next »</span>
|
||
<br>
|
||
<span>Danya</span>
|
||
</a>
|
||
</nav>
|
||
|
||
</footer><div class="comments-switch" style="display:flex;gap:.5rem;margin:.5rem 0 1rem;">
|
||
<button id="btn-isso" type="button" aria-pressed="true">Anonymous (Isso)</button>
|
||
<button id="btn-giscus" type="button" aria-pressed="false">GitHub (Giscus)</button>
|
||
<a href="https://github.com/AlipourIm/blog-comments/discussions/categories/comments" target="_blank" rel="noopener">Open on GitHub ↗</a>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<div id="panel-giscus" style="display:none">
|
||
<section id="giscus-thread"></section>
|
||
</div>
|
||
|
||
|
||
<div id="panel-isso">
|
||
<section id="isso-thread"
|
||
data-isso-id="/posts/comments-rss-papermod/"
|
||
data-title="New features for my blog! Adding Comments + RSS to Hugo PaperMod (Giscus and Isso FTW)">
|
||
<noscript>Javascript needs to be activated to view comments.</noscript>
|
||
</section>
|
||
<script
|
||
src="/isso/js/embed.min.js"
|
||
data-isso="/isso/"
|
||
data-isso-css="false"
|
||
data-isso-lang="en"
|
||
data-isso-max-comments-nested="5"
|
||
data-isso-sorting="newest"
|
||
async>
|
||
</script>
|
||
<small class="isso-powered" style="display:block;margin:.5rem 0;color:var(--secondary,#888)">
|
||
Comments powered by <a href="https://isso-comments.de" target="_blank" rel="noopener">Isso</a>
|
||
</small>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
var panelIsso = document.getElementById('panel-isso');
|
||
var panelGisc = document.getElementById('panel-giscus');
|
||
var issoBtn = document.getElementById('btn-isso');
|
||
var giscBtn = document.getElementById('btn-giscus');
|
||
var gLoaded = false;
|
||
|
||
function isDark() {
|
||
var pref = localStorage.getItem('pref-theme');
|
||
if (pref === 'dark') return true;
|
||
if (pref === 'light') return false;
|
||
|
||
var html = document.documentElement, body = document.body;
|
||
if (
|
||
(html.classList && html.classList.contains('dark')) ||
|
||
(body.classList && body.classList.contains('dark')) ||
|
||
html.getAttribute('data-theme') === 'dark' ||
|
||
body.getAttribute('data-theme') === 'dark' ||
|
||
(html.classList && html.classList.contains('theme-dark')) ||
|
||
(body.classList && body.classList.contains('theme-dark'))
|
||
) return true;
|
||
|
||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
}
|
||
function gTheme(){ return isDark() ? 'dark_dimmed' : 'noborder_light'; }
|
||
|
||
function loadGiscus() {
|
||
if (gLoaded) return;
|
||
var s = document.createElement('script');
|
||
s.src = 'https://giscus.app/client.js';
|
||
s.async = true;
|
||
s.crossOrigin = 'anonymous';
|
||
s.setAttribute('data-repo','AlipourIm/blog-comments');
|
||
s.setAttribute('data-repo-id','R_kgDOQGARyA');
|
||
s.setAttribute('data-category','Comments');
|
||
s.setAttribute('data-category-id','DIC_kwDOQGARyM4Cw3x-');
|
||
s.setAttribute('data-mapping','pathname');
|
||
s.setAttribute('data-strict','0');
|
||
s.setAttribute('data-reactions-enabled','1');
|
||
s.setAttribute('data-emit-metadata','0');
|
||
s.setAttribute('data-input-position','bottom');
|
||
s.setAttribute('data-lang','en');
|
||
s.setAttribute('data-theme', gTheme());
|
||
document.getElementById('giscus-thread').appendChild(s);
|
||
|
||
function reTheme(){
|
||
var iframe = document.querySelector('iframe.giscus-frame');
|
||
if (iframe && iframe.contentWindow) {
|
||
iframe.contentWindow.postMessage(
|
||
{ giscus: { setConfig: { theme: gTheme() } } },
|
||
'https://giscus.app'
|
||
);
|
||
}
|
||
}
|
||
|
||
var tbtn = document.getElementById('theme-toggle');
|
||
if (tbtn) tbtn.addEventListener('click', function(){ setTimeout(reTheme, 0); });
|
||
|
||
var mo = new MutationObserver(function(){ reTheme(); });
|
||
mo.observe(document.documentElement, { attributes:true, attributeFilter:['class','data-theme'] });
|
||
mo.observe(document.body, { attributes:true, attributeFilter:['class','data-theme'] });
|
||
|
||
gLoaded = true;
|
||
}
|
||
|
||
function show(which) {
|
||
var showIsso = which === 'isso';
|
||
panelIsso.style.display = showIsso ? 'block' : 'none';
|
||
panelGisc.style.display = showIsso ? 'none' : 'block';
|
||
if (issoBtn) issoBtn.setAttribute('aria-pressed', showIsso ? 'true' : 'false');
|
||
if (giscBtn) giscBtn.setAttribute('aria-pressed', showIsso ? 'false' : 'true');
|
||
if (!showIsso) loadGiscus();
|
||
}
|
||
|
||
if (issoBtn) issoBtn.addEventListener('click', function(e){ e.preventDefault(); show('isso'); });
|
||
if (giscBtn) giscBtn.addEventListener('click', function(e){ e.preventDefault(); show('giscus'); });
|
||
|
||
|
||
show('isso');
|
||
})();
|
||
</script>
|
||
|
||
|
||
</article>
|
||
</main>
|
||
|
||
<footer class="footer">
|
||
<span>© 2026 <a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/">AlipourIm journeys</a></span> ·
|
||
|
||
<span>
|
||
Powered by
|
||
<a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
|
||
<a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
|
||
</span>
|
||
</footer>
|
||
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
|
||
<path d="M12 6H0l6-6z" />
|
||
</svg>
|
||
</a>
|
||
|
||
|
||
|
||
<div class="site-meta-row" style="display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;">
|
||
<a href="/index.xml" rel="alternate" type="application/rss+xml" title="RSS"
|
||
class="rss-link" style="display:inline-flex;align-items:center;gap:.35rem;">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M4 11a9 9 0 0 1 9 9" />
|
||
<path d="M4 4a16 16 0 0 1 16 16" />
|
||
<circle cx="5" cy="19" r="1" />
|
||
</svg>
|
||
<span>RSS</span>
|
||
</a>
|
||
|
||
<span aria-hidden="true">·</span>
|
||
|
||
<span>Visitors this week: <span id="gc_site_visitors_week">—</span></span>
|
||
</div>
|
||
|
||
|
||
<script>
|
||
let menu = document.getElementById('menu')
|
||
if (menu) {
|
||
menu.scrollLeft = localStorage.getItem("menu-scroll-position");
|
||
menu.onscroll = function () {
|
||
localStorage.setItem("menu-scroll-position", menu.scrollLeft);
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||
anchor.addEventListener("click", function (e) {
|
||
e.preventDefault();
|
||
var id = this.getAttribute("href").substr(1);
|
||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
|
||
behavior: "smooth"
|
||
});
|
||
} else {
|
||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
|
||
}
|
||
if (id === "top") {
|
||
history.replaceState(null, null, " ");
|
||
} else {
|
||
history.pushState(null, null, `#${id}`);
|
||
}
|
||
});
|
||
});
|
||
|
||
</script>
|
||
<script>
|
||
var mybutton = document.getElementById("top-link");
|
||
window.onscroll = function () {
|
||
if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
|
||
mybutton.style.visibility = "visible";
|
||
mybutton.style.opacity = "1";
|
||
} else {
|
||
mybutton.style.visibility = "hidden";
|
||
mybutton.style.opacity = "0";
|
||
}
|
||
};
|
||
|
||
</script>
|
||
<script>
|
||
document.getElementById("theme-toggle").addEventListener("click", () => {
|
||
if (document.body.className.includes("dark")) {
|
||
document.body.classList.remove('dark');
|
||
localStorage.setItem("pref-theme", 'light');
|
||
} else {
|
||
document.body.classList.add('dark');
|
||
localStorage.setItem("pref-theme", 'dark');
|
||
}
|
||
})
|
||
|
||
</script>
|
||
<script>
|
||
document.querySelectorAll('pre > code').forEach((codeblock) => {
|
||
const container = codeblock.parentNode.parentNode;
|
||
|
||
const copybutton = document.createElement('button');
|
||
copybutton.classList.add('copy-code');
|
||
copybutton.innerHTML = 'copy';
|
||
|
||
function copyingDone() {
|
||
copybutton.innerHTML = 'copied!';
|
||
setTimeout(() => {
|
||
copybutton.innerHTML = 'copy';
|
||
}, 2000);
|
||
}
|
||
|
||
copybutton.addEventListener('click', (cb) => {
|
||
if ('clipboard' in navigator) {
|
||
navigator.clipboard.writeText(codeblock.textContent);
|
||
copyingDone();
|
||
return;
|
||
}
|
||
|
||
const range = document.createRange();
|
||
range.selectNodeContents(codeblock);
|
||
const selection = window.getSelection();
|
||
selection.removeAllRanges();
|
||
selection.addRange(range);
|
||
try {
|
||
document.execCommand('copy');
|
||
copyingDone();
|
||
} catch (e) { };
|
||
selection.removeRange(range);
|
||
});
|
||
|
||
if (container.classList.contains("highlight")) {
|
||
container.appendChild(copybutton);
|
||
} else if (container.parentNode.firstChild == container) {
|
||
|
||
} else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
|
||
|
||
codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
|
||
} else {
|
||
|
||
codeblock.parentNode.appendChild(copybutton);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|