Initial import of Hugo site to Forgejo
This commit is contained in:
commit
cb1ba0317f
1259 changed files with 236349 additions and 0 deletions
863
public-onion/posts/papermod-views-debugging-story/index.html
Normal file
863
public-onion/posts/papermod-views-debugging-story/index.html
Normal file
|
|
@ -0,0 +1,863 @@
|
|||
<!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>A Tiny 'Views' Badge Sent Me Down a Rabbit Hole (PaperMod + Busuanzi + Debugging --> swapped with GoatCounter the GOAT) | AlipourIm journeys</title>
|
||||
<meta name="keywords" content="Hugo, PaperMod, Analytics, Debugging, Busuanzi, Vercount, GoatCounter">
|
||||
<meta name="description" content="I tried to add a simple ‘views’ counter to my PaperMod blog. It worked—until it didn’t. Here’s the story of blank numbers, a mysterious Chinese message, and the final fix.">
|
||||
<meta name="author" content="Iman Alipour">
|
||||
<link rel="canonical" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/papermod-views-debugging-story/">
|
||||
<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/papermod-views-debugging-story/">
|
||||
<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">
|
||||
A Tiny 'Views' Badge Sent Me Down a Rabbit Hole (PaperMod + Busuanzi + Debugging --> swapped with GoatCounter the GOAT)
|
||||
</h1>
|
||||
<div class="post-meta"><span title='2025-10-18 10:45:00 +0000 UTC'>October 18, 2025</span> · 9 min · Iman Alipour
|
||||
|
||||
<span class="post-meta-item">
|
||||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/papermod-views-debugging-story/#isso-thread" class="isso-comments-link">Comments</a>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div class="post-content"><p>I only wanted a tiny <strong>“views”</strong> badge next to my RSS link—just a friendly nudge that real humans were reading my words. PaperMod makes it easy to tuck little UI bits in the footer, so I dropped in Busuanzi (that neat, no-account page/site view counter) and called it a day.</p>
|
||||
<p>The thing is, I don’t think the counter even works correctly.
|
||||
Or maybe I’m undermining my blog popularity!
|
||||
But regardless, it was a small stiching task that I did on this lovely Friday evening.
|
||||
The day before my next term of German class starts.
|
||||
The day before playing deadface CTF that I’ve been counting days for.</p>
|
||||
<h3 id="the-neat-footer-row">The neat footer row<a hidden class="anchor" aria-hidden="true" href="#the-neat-footer-row">#</a></h3>
|
||||
<p>First I got the layout right. PaperMod supports extension partials, so I used a simple flex row to keep things side by side:</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">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"site-meta-row"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;"</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">"/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></span><span style="display:flex;"><span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"rss-link"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:inline-flex;align-items:center;gap:.35rem;"</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><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">"true"</span>>·</<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"busuanzi_container_site_pv"</span>>
|
||||
</span></span><span style="display:flex;"><span> Site views: <<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"busuanzi_value_site_pv"</span>>—</<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span> </<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">"true"</span>>·</<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"busuanzi_container_site_uv"</span>>
|
||||
</span></span><span style="display:flex;"><span> Visitors: <<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"busuanzi_value_site_uv"</span>>—</<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span> </<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">div</span>>
|
||||
</span></span></code></pre></div><p>So far, so good. The labels showed up. The numbers, though? <strong>Nothing.</strong> Just an em dash. I shrugged—maybe it was a caching thing.</p>
|
||||
<h3 id="the-first-round-of-checks">The first round of checks<a hidden class="anchor" aria-hidden="true" href="#the-first-round-of-checks">#</a></h3>
|
||||
<p>I added the script site‑wide in <code>layouts/partials/extend_head.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:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">"//cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.min.js"</span> <span style="color:#a6e22e">defer</span>></<span style="color:#f92672">script</span>>
|
||||
</span></span></code></pre></div><p>Refreshed. Still blank.</p>
|
||||
<p><strong>DevTools time.</strong> Network tab: the script <strong>does</strong> load:</p>
|
||||
<ul>
|
||||
<li><strong>Request URL:</strong> <code>https://cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.min.js</code></li>
|
||||
<li><strong>Request Method:</strong> GET</li>
|
||||
<li><strong>Status Code:</strong> 304 Not Modified</li>
|
||||
<li><strong>Referrer Policy:</strong> strict-origin-when-cross-origin</li>
|
||||
<li><strong>User-Agent:</strong> a normal Chromium build (nothing exotic)</li>
|
||||
</ul>
|
||||
<p>No red flags. Just a perfectly normal 304.</p>
|
||||
<p>Maybe my IDs were wrong? Busuanzi has a couple of flavors: <code>busuanzi_value_*</code> (with container wrappers) or shorthand like <code>busuanzi_site_pv</code>. I tried <strong>both</strong>, carefully, one at a time. Still… <strong>—</strong>.</p>
|
||||
<h3 id="the-odd-message">The odd message<a hidden class="anchor" aria-hidden="true" href="#the-odd-message">#</a></h3>
|
||||
<p>Then it flashed for a moment—the Chinese line that made me google twice to be sure I saw it right:</p>
|
||||
<blockquote>
|
||||
<p><strong>“域名过长,已被禁用 views”</strong></p></blockquote>
|
||||
<p>That’s “domain name too long, disabled.” Wait, what?</p>
|
||||
<p>I checked my hostname: <strong><code>blog.alipourimjourneys.ir</code></strong>. I counted: <strong>27 characters</strong>. Busuanzi’s limit? <strong>22.</strong> Suddenly, everything clicked. The script loads, but the service refuses to count for long domains, so the placeholders never fill.</p>
|
||||
<h3 id="two-ways-out">Two ways out<a hidden class="anchor" aria-hidden="true" href="#two-ways-out">#</a></h3>
|
||||
<p>At this point I had two clean options:</p>
|
||||
<ol>
|
||||
<li><strong>Keep Busuanzi</strong> and move the site to the <strong>apex domain</strong> (short enough).</li>
|
||||
<li>Keep my beloved <code>blog.</code> subdomain and swap in <strong>Vercount</strong>, a Busuanzi‑style drop‑in without the 22‑char limit.</li>
|
||||
</ol>
|
||||
<p>I liked the subdomain (habit, mostly), so I tried <strong>Vercount</strong>.</p>
|
||||
<h3 id="the-swap-five-minute-fix">The swap (five-minute fix)<a hidden class="anchor" aria-hidden="true" href="#the-swap-five-minute-fix">#</a></h3>
|
||||
<p>I removed the Busuanzi script and added Vercount instead:</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"><!-- extend_head.html --></span>
|
||||
</span></span><span style="display:flex;"><span><<span style="color:#f92672">script</span> <span style="color:#a6e22e">defer</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">"https://events.vercount.one/js"</span>></<span style="color:#f92672">script</span>>
|
||||
</span></span></code></pre></div><p>I kept the same tidy footer row, but switched the IDs to Vercount’s:</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">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"site-meta-row"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;"</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">"/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></span><span style="display:flex;"><span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">"rss-link"</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">"display:inline-flex;align-items:center;gap:.35rem;"</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><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">"true"</span>>·</<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span>>Site views: <<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"vercount_value_site_pv"</span>>—</<span style="color:#f92672">span</span>></<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span> <span style="color:#a6e22e">aria-hidden</span><span style="color:#f92672">=</span><span style="color:#e6db74">"true"</span>>·</<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span> <<span style="color:#f92672">span</span>>Visitors: <<span style="color:#f92672">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"vercount_value_site_uv"</span>>—</<span style="color:#f92672">span</span>></<span style="color:#f92672">span</span>>
|
||||
</span></span><span style="display:flex;"><span></<span style="color:#f92672">div</span>>
|
||||
</span></span></code></pre></div><p>For per‑post counts (in the post meta), I added:</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">span</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">"vercount_value_page_pv"</span>>—</<span style="color:#f92672">span</span>> views
|
||||
</span></span></code></pre></div><p>Deploy. Refresh. <strong>Numbers.</strong></p>
|
||||
<h3 id="but-if-you-want-to-stay-with-busuanzi">But if you want to stay with Busuanzi…<a hidden class="anchor" aria-hidden="true" href="#but-if-you-want-to-stay-with-busuanzi">#</a></h3>
|
||||
<p>If your apex domain is short enough, pointing the site there works fine. The gist:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Set <code>baseURL</code> in Hugo to the apex:</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://alipourimjourneys.ir/"</span>
|
||||
</span></span></code></pre></div></li>
|
||||
<li>
|
||||
<p>Add a 301 redirect from the long subdomain to the apex (Cloudflare, Netlify, Nginx—pick your tool).</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Keep your Busuanzi spans as you had them; once the host fits the limit, they’ll populate.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 id="postmortem-checklist-aka-things-i-learned">Post‑mortem checklist (a.k.a. things I learned)<a hidden class="anchor" aria-hidden="true" href="#postmortem-checklist-aka-things-i-learned">#</a></h3>
|
||||
<ul>
|
||||
<li><strong>If the script loads but you get blanks</strong>, it’s not always IDs or caching. Sometimes it’s a <strong>service rule</strong>.</li>
|
||||
<li><strong>Busuanzi refuses long hostnames</strong> (>22 chars) and ignores localhost. On those hosts, the placeholders stay empty or show the Chinese “disabled” note.</li>
|
||||
<li><strong>Don’t mix providers</strong> on the same page. Load exactly one script (Busuanzi <em>or</em> Vercount).</li>
|
||||
<li><strong>CSP and blockers matter.</strong> If you run a strict Content‑Security‑Policy or heavy ad blockers, they can block the script. Test in a clean profile and allow the script origin.</li>
|
||||
</ul>
|
||||
<h3 id="happy-ending">Happy ending<a hidden class="anchor" aria-hidden="true" href="#happy-ending">#</a></h3>
|
||||
<p>Now my footer shows RSS, site views, and visitors on a single neat line, and each post has a tiny “views” chip that quietly ticks upward. I got the tiny badge I wanted—and a reminder that the smallest features can hide the most interesting puzzles.</p>
|
||||
<p>If you hit the same wall, check the hostname length first. It might save you an afternoon—and lead you to a solution you’ll feel weirdly proud of.</p>
|
||||
<h3 id="final-notes">Final notes<a hidden class="anchor" aria-hidden="true" href="#final-notes">#</a></h3>
|
||||
<p>Yea… I just felt this feature was missing, and it’s good to have it there.
|
||||
I hope I stop adding things to the blog, and just keep writing in it.
|
||||
I’ve spent too much time on it, and it feels like wasted time.</p>
|
||||
<hr>
|
||||
<h2 id="change-of-plans-selfhosting-goatcounter-for-papermod-docker--cloudflare--onion">Change of plans: Self‑hosting GoatCounter for PaperMod (Docker + Cloudflare + Onion)<a hidden class="anchor" aria-hidden="true" href="#change-of-plans-selfhosting-goatcounter-for-papermod-docker--cloudflare--onion">#</a></h2>
|
||||
<p>This is the <strong>self‑host</strong> part I ended up with: Docker for GoatCounter, Cloudflare (orange‑cloud) in front, Nginx on the server as reverse proxy, plus my <code>.onion</code> mirror counting to the same GoatCounter site. Paste this whole section at the end of the post.</p>
|
||||
<hr>
|
||||
<h3 id="1-clean-up-old-counters-optional-once">1) Clean up old counters (optional, once)<a hidden class="anchor" aria-hidden="true" href="#1-clean-up-old-counters-optional-once">#</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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># From your Hugo site root</span>
|
||||
</span></span><span style="display:flex;"><span>grep -RinE <span style="color:#e6db74">"busuanzi|vercount|vercount_value_|busuanzi_value_"</span> .
|
||||
</span></span></code></pre></div><p>Remove any matching <code><script></code> and leftover <code>*_value_*</code> spans you no longer want.</p>
|
||||
<hr>
|
||||
<h3 id="2-run-goatcounter-with-docker-compose">2) Run GoatCounter with Docker Compose<a hidden class="anchor" aria-hidden="true" href="#2-run-goatcounter-with-docker-compose">#</a></h3>
|
||||
<p>Minimal <code>docker-compose.yml</code> in <code>~/goatcounter</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">goatcounter</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">image</span>: <span style="color:#ae81ff">arp242/goatcounter:2.6</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">container_name</span>: <span style="color:#ae81ff">goatcounter</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ports</span>:
|
||||
</span></span><span style="display:flex;"><span> - <span style="color:#e6db74">"127.0.0.1:8081:8080"</span> <span style="color:#75715e"># bind only to localhost; nginx will proxy</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumes</span>:
|
||||
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">goatcounter-data:/home/goatcounter/goatcounter-data</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">restart</span>: <span style="color:#ae81ff">unless-stopped</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">volumes</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">goatcounter-data</span>: {}
|
||||
</span></span></code></pre></div><p>Start:</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 pull <span style="color:#f92672">&&</span> docker compose up -d
|
||||
</span></span></code></pre></div><p>Initialize the site (replace email if needed):</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 exec goatcounter goatcounter db create site -vhost<span style="color:#f92672">=</span>statsblog.alipourimjourneys.ir -user.email<span style="color:#f92672">=</span>iman.alip2001@gmail.com
|
||||
</span></span></code></pre></div><blockquote>
|
||||
<p>If you ever see “no site at this domain” on localhost: use the exact <strong>vhost</strong> above.</p></blockquote>
|
||||
<hr>
|
||||
<h3 id="3-cloudflare--origin-certificate-cf--origin-tls">3) Cloudflare + Origin certificate (CF → origin TLS)<a hidden class="anchor" aria-hidden="true" href="#3-cloudflare--origin-certificate-cf--origin-tls">#</a></h3>
|
||||
<ul>
|
||||
<li>DNS: <code>statsblog.alipourimjourneys.ir</code> → server IP (<strong>proxied/orange</strong>).</li>
|
||||
<li>SSL/TLS mode: <strong>Full (strict)</strong>.</li>
|
||||
<li>Issue a <strong>Cloudflare Origin Certificate</strong> for <code>statsblog.alipourimjourneys.ir</code> and save on the server:</li>
|
||||
</ul>
|
||||
<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 mkdir -p /etc/ssl/cloudflare
|
||||
</span></span><span style="display:flex;"><span>sudo nano /etc/ssl/cloudflare/origin.crt <span style="color:#75715e"># paste PEM</span>
|
||||
</span></span><span style="display:flex;"><span>sudo nano /etc/ssl/cloudflare/origin.key <span style="color:#75715e"># paste key</span>
|
||||
</span></span><span style="display:flex;"><span>sudo chown root:root /etc/ssl/cloudflare/origin.<span style="color:#f92672">{</span>crt,key<span style="color:#f92672">}</span>
|
||||
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">644</span> /etc/ssl/cloudflare/origin.crt
|
||||
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">600</span> /etc/ssl/cloudflare/origin.key
|
||||
</span></span></code></pre></div><hr>
|
||||
<h3 id="4-nginx-vhost-for-the-stats-subdomain">4) Nginx vhost for the stats subdomain<a hidden class="anchor" aria-hidden="true" href="#4-nginx-vhost-for-the-stats-subdomain">#</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-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># /etc/nginx/sites-available/statsblog.alipourimjourneys.ir.conf
|
||||
</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> <span style="color:#ae81ff">80</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">server_name</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host$request_uri;
|
||||
</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">server</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">server_name</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ssl_certificate</span> <span style="color:#e6db74">/etc/ssl/cloudflare/origin.crt</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ssl_certificate_key</span> <span style="color:#e6db74">/etc/ssl/cloudflare/origin.key</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Proxy to the container on localhost
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></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-Real-IP</span> $remote_addr;
|
||||
</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_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> $scheme;
|
||||
</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">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8081</span>; }
|
||||
</span></span><span style="display:flex;"><span>}
|
||||
</span></span></code></pre></div><p>Enable & reload:</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 ln -s /etc/nginx/sites-available/statsblog.alipourimjourneys.ir.conf /etc/nginx/sites-enabled/
|
||||
</span></span><span style="display:flex;"><span>sudo nginx -t <span style="color:#f92672">&&</span> sudo systemctl reload nginx
|
||||
</span></span></code></pre></div><hr>
|
||||
<h3 id="5-hugo-integration-clearnet--onion-singularplural--0-views">5) Hugo integration (clearnet + onion, singular/plural + “0 views”)<a hidden class="anchor" aria-hidden="true" href="#5-hugo-integration-clearnet--onion-singularplural--0-views">#</a></h3>
|
||||
<p>Self‑host <code>count.js</code> so the onion mirror never fetches a third‑party script:</p>
|
||||
<ul>
|
||||
<li>Save the official <code>count.js</code> as <code>static/js/count.js</code> in your Hugo repo.</li>
|
||||
</ul>
|
||||
<p>Then add this to <code>layouts/partials/extend_head.html</code> <strong>(loader + helper)</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-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e"><!-- Loader: choose endpoint based on hostname --></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">var</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/\.onion$/i</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hostname</span>);
|
||||
</span></span><span style="display:flex;"><span> window.<span style="color:#a6e22e">goatcounter</span> <span style="color:#f92672">=</span> { <span style="color:#a6e22e">endpoint</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">'/count'</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">'https://statsblog.alipourimjourneys.ir/count'</span> };
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</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:#66d9ef">async</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>; <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">src</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">'/js/count.js'</span>;
|
||||
</span></span><span style="display:flex;"><span> document.<span style="color:#a6e22e">head</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:#f92672">script</span>>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#75715e"><!-- Helper: inline meta counts (post pages + lists) and totals --></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">ONION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/\.onion$/i</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hostname</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">GC_HOST</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">ONION</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">''</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">'https://statsblog.alipourimjourneys.ir'</span>; <span style="color:#75715e">// '' = same-origin on .onion
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">SING</span><span style="color:#f92672">=</span><span style="color:#e6db74">'view'</span>, <span style="color:#a6e22e">PLUR</span><span style="color:#f92672">=</span><span style="color:#e6db74">'views'</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">nf</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Intl</span>.<span style="color:#a6e22e">NumberFormat</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">ready</span>(<span style="color:#a6e22e">fn</span>){ <span style="color:#66d9ef">if</span> (document.<span style="color:#a6e22e">readyState</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">'loading'</span>) <span style="color:#a6e22e">fn</span>(); <span style="color:#66d9ef">else</span> document.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'DOMContentLoaded'</span>, <span style="color:#a6e22e">fn</span>); }
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">gcPath</span>(){ <span style="color:#66d9ef">try</span> { <span style="color:#66d9ef">return</span> (window.<span style="color:#a6e22e">goatcounter</span> <span style="color:#f92672">&&</span> window.<span style="color:#a6e22e">goatcounter</span>.<span style="color:#a6e22e">get_data</span>) <span style="color:#f92672">?</span> window.<span style="color:#a6e22e">goatcounter</span>.<span style="color:#a6e22e">get_data</span>().<span style="color:#a6e22e">p</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">pathname</span>; } <span style="color:#66d9ef">catch</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">pathname</span>; } }
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#a6e22e">path</span>){
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">try</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">GC_HOST</span><span style="color:#e6db74">}</span><span style="color:#e6db74">/counter/</span><span style="color:#e6db74">${</span>encodeURIComponent(<span style="color:#a6e22e">path</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">.json?t=</span><span style="color:#e6db74">${</span>Date.<span style="color:#a6e22e">now</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>, { <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span><span style="color:#e6db74">'omit'</span> });
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">status</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">404</span>) <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">ok</span>) <span style="color:#66d9ef">throw</span> <span style="color:#ae81ff">0</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">json</span>();
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> Number(String(<span style="color:#a6e22e">j</span>.<span style="color:#a6e22e">count</span>).<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">/,/g</span>,<span style="color:#e6db74">''</span>));
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> Number.isFinite(<span style="color:#a6e22e">n</span>) <span style="color:#f92672">?</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:</span> <span style="color:#ae81ff">0</span>;
|
||||
</span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">catch</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</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">label</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">n</span>) => (<span style="color:#a6e22e">n</span> <span style="color:#f92672">===</span> <span style="color:#66d9ef">null</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">'—'</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">nf</span>.<span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">n</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">n</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">SING</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">PLUR</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ready</span>(<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> () {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// A) Single post: add to existing meta row + small bottom
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">post</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.post-single'</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">post</span>) {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">meta</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.post-header .post-meta, .post-header .entry-meta, .post-meta'</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">gcPath</span>();
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#a6e22e">path</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">meta</span>) {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.post-views'</span>); <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">span</span>) { <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">'span'</span>); <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">'post-views'</span>; <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">span</span>); }
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">label</span>(<span style="color:#a6e22e">n</span> <span style="color:#f92672">??</span> <span style="color:#ae81ff">0</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">after</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.post-content'</span>) <span style="color:#f92672">||</span> <span style="color:#a6e22e">post</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">bottom</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.post-views-bottom'</span>); <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">bottom</span>) { <span style="color:#a6e22e">bottom</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">'div'</span>); <span style="color:#a6e22e">bottom</span>.<span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">'post-views-bottom'</span>; <span style="color:#a6e22e">after</span>.<span style="color:#a6e22e">insertAdjacentElement</span>(<span style="color:#e6db74">'afterend'</span>, <span style="color:#a6e22e">bottom</span>); }
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">bottom</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">label</span>(<span style="color:#a6e22e">n</span> <span style="color:#f92672">??</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">// B) Lists (/posts, home, sections): show “0 views” instead of hiding
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cards</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">querySelectorAll</span>(<span style="color:#e6db74">'article.post-entry'</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">await</span> Promise.<span style="color:#a6e22e">all</span>(Array.<span style="color:#a6e22e">from</span>(<span style="color:#a6e22e">cards</span>).<span style="color:#a6e22e">map</span>(<span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">card</span>) => {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">meta</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">card</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.entry-footer, .post-meta'</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">link</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">card</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'a.entry-link, h2 a, .entry-title a'</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">meta</span> <span style="color:#f92672">||</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">link</span>) <span style="color:#66d9ef">return</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">try</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">u</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URL</span>(<span style="color:#a6e22e">link</span>.<span style="color:#a6e22e">getAttribute</span>(<span style="color:#e6db74">'href'</span>), <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">origin</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">origin</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">origin</span>) <span style="color:#66d9ef">return</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">'.post-views'</span>); <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">span</span>) { <span style="color:#a6e22e">span</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">'span'</span>); <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">'post-views'</span>; <span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">span</span>); }
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">pathname</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">label</span>(<span style="color:#a6e22e">n</span> <span style="color:#f92672">??</span> <span style="color:#ae81ff">0</span>);
|
||||
</span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">catch</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">// C) Site totals (optional placeholders)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">pvEl</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">'vercount_value_site_pv'</span>);
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pvEl</span>) { <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchCount</span>(<span style="color:#e6db74">'TOTAL'</span>); <span style="color:#a6e22e">pvEl</span>.<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">n</span><span style="color:#f92672">===</span><span style="color:#66d9ef">null</span>) <span style="color:#f92672">?</span> <span style="color:#e6db74">'—'</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">nf</span>.<span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">n</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:#f92672">script</span>>
|
||||
</span></span></code></pre></div><p>Style so meta items stay inline with middle dots (PaperMod‑like). Put this in <code>assets/css/extended/goatcounter.css</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-css" data-lang="css"><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">post-meta</span><span style="color:#f92672">,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">entry-meta</span><span style="color:#f92672">,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">entry-footer</span><span style="color:#f92672">,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">post-meta</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">display</span>:<span style="color:#66d9ef">flex</span>; <span style="color:#66d9ef">align-items</span>:<span style="color:#66d9ef">center</span>; <span style="color:#66d9ef">flex-wrap</span>:<span style="color:#66d9ef">wrap</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></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">></span> <span style="color:#f92672">*,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">entry-meta</span> <span style="color:#f92672">></span> <span style="color:#f92672">*,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">entry-footer</span> <span style="color:#f92672">></span> <span style="color:#f92672">*,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">></span> <span style="color:#f92672">*</span> {
|
||||
</span></span><span style="display:flex;"><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">white-space</span>:<span style="color:#66d9ef">nowrap</span>;
|
||||
</span></span><span style="display:flex;"><span>}
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">></span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span><span style="color:#f92672">,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-single</span> .<span style="color:#a6e22e">post-header</span> .<span style="color:#a6e22e">entry-meta</span> <span style="color:#f92672">></span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span><span style="color:#f92672">,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">entry-footer</span> <span style="color:#f92672">></span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span><span style="color:#f92672">,</span>
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-entry</span> .<span style="color:#a6e22e">post-meta</span> <span style="color:#f92672">></span> <span style="color:#f92672">*</span> <span style="color:#f92672">+</span> <span style="color:#f92672">*</span>::<span style="color:#a6e22e">before</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">content</span>:<span style="color:#e6db74">"·"</span>; <span style="color:#66d9ef">margin</span>:<span style="color:#ae81ff">0</span> <span style="color:#ae81ff">.5</span><span style="color:#66d9ef">rem</span>; <span style="color:#66d9ef">opacity</span>:<span style="color:#ae81ff">.6</span>;
|
||||
</span></span><span style="display:flex;"><span>}
|
||||
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-views-bottom</span> { <span style="color:#66d9ef">margin</span>:<span style="color:#ae81ff">.6</span><span style="color:#66d9ef">rem</span> <span style="color:#ae81ff">0</span> <span style="color:#ae81ff">0</span>; <span style="color:#66d9ef">opacity</span>:<span style="color:#ae81ff">.75</span>; <span style="color:#66d9ef">font-size</span>:<span style="color:#ae81ff">.95</span><span style="color:#66d9ef">em</span>; }
|
||||
</span></span></code></pre></div><hr>
|
||||
<h3 id="6-count-tor-onion-visits-into-the-same-site">6) Count Tor (.onion) visits <strong>into the same site</strong><a hidden class="anchor" aria-hidden="true" href="#6-count-tor-onion-visits-into-the-same-site">#</a></h3>
|
||||
<p>In the <strong>.onion</strong> server block, proxy GoatCounter endpoints to the same site by forcing the Host header:</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"># inside: server { ... } for fjthpp2h3mj2r....onion
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">location</span> = <span style="color:#e6db74">/count</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8081/count</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
|
||||
</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_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> <span style="color:#e6db74">http</span>; <span style="color:#75715e"># onion is http
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">location</span> <span style="color:#e6db74">/counter/</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:8081/counter/</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> <span style="color:#e6db74">statsblog.alipourimjourneys.ir</span>;
|
||||
</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_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> <span style="color:#e6db74">http</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">"no-store"</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_no_cache</span> <span style="color:#ae81ff">1</span>; <span style="color:#f92672">proxy_cache_bypass</span> <span style="color:#ae81ff">1</span>;
|
||||
</span></span><span style="display:flex;"><span>}
|
||||
</span></span></code></pre></div><p>Because the loader above sets <code>window.goatcounter.endpoint</code> to <code>/count</code> on <code>.onion</code>, both <strong>hits</strong> and <strong>reads</strong> stay on the onion origin and are tallied into the same GoatCounter site.</p>
|
||||
<blockquote>
|
||||
<p><strong>Note on DNT/Tor:</strong> Tor Browser sends <code>DNT: 1</code>. GoatCounter respects DNT by default, so Tor users won’t be counted unless you disable “Respect Do Not Track” in GoatCounter settings. Your choice; I left the code compatible either way.</p></blockquote>
|
||||
<hr>
|
||||
<h3 id="7-troubleshooting-quick-notes">7) Troubleshooting quick notes<a hidden class="anchor" aria-hidden="true" href="#7-troubleshooting-quick-notes">#</a></h3>
|
||||
<ul>
|
||||
<li><strong>400 “no site at this domain”</strong> → create the site with <code>-vhost=statsblog.alipourimjourneys.ir</code> or fix your proxy <code>Host</code> header.</li>
|
||||
<li><strong>Counts don’t change on onion</strong> → verify Tor path via <code>torsocks</code>, watch logs:
|
||||
<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 logs -f goatcounter
|
||||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># look for “ignored: bot” (curl UA), or DNT being respected</span>
|
||||
</span></span></code></pre></div></li>
|
||||
<li><strong>DevTools sanity</strong> → on clearnet you should see <code>https://statsblog…/count</code> and <code>…/counter/*.json</code>; on onion you should see <code>/count</code> and <code>/counter/*.json</code> (same‑origin).</li>
|
||||
</ul>
|
||||
<p>That’s it. One dashboard; clearnet + onion both increment; tiny inline counters everywhere.</p>
|
||||
<p>Honestly, self-hosting GoatCounter wasn’t as straightforward as I thought it would be. It almost made me cry a few times. I definitly thought about my life choices a few times along the way. But oh well, now it’s done and dusted, and I’m a happy puppy! :D</p>
|
||||
<hr>
|
||||
<div class="signature-block" style="margin-top:1rem">
|
||||
<p><strong>Downloads:</strong>
|
||||
<a href="/sources/posts/view_count.md">Markdown</a> ·
|
||||
<a href="/sources/posts/view_count.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/tYgoUOBMjSoFAmpkbuoACgkQtYgoUOBM
|
||||
jSolRg//SwN9nMf9/Wgkr5h+dQowZMCHXXcKWgO8J08ITVDN1+weNNGANFrz63SQ
|
||||
DajHGeSogYW1+AAxJaAeQ7hLdOX9foV5pT+kWANIjRBXnFyW+WR7kt6tmN2rcSH8
|
||||
z4GKxqE3kdd3kCRhqT0pLiwnurqLgdCK+YldftO6GB8WP4oNDqOwHvoIE6o0ekdH
|
||||
sn7ZBIxMaWc5UqijjqgBHhdHz3uSmL+rN4UwLFM3WXXlwl3HdGyjHcNTG7k648s2
|
||||
X5+7a78OZAuDElpyp9y6WqiAFJQdrwBbTv3vvpU+f911voV7ZA+KbUqHsQrISTKz
|
||||
cd+tPw2lcayfBZRtHMrL3fygvT3AWktcSavZrU5EOX/u/P7eMWEYu0FYh6aHqRak
|
||||
+Ft2FR7EPlB2faS6wWYfoua6BYxFvevXX1fk+0HhZn9UJxJPaa/WTgVWDgpt++Ce
|
||||
fdaDmsR69LIj2QTjPMXdQiUKkWPG2ziadTwJEWUHzOuREifmuBgp9Mwedk6zYkdT
|
||||
m5Roi70IPtX3dDDHG5Votw3AKng3gaU7YcNzZVyi3iZkQkUHPUK47Wj21FajikMP
|
||||
gCcWsoYWBRqdhL5XDrodt28AuLpm0cslz79DZdbLnielcjOS+GaUynjLzEWveOib
|
||||
dxLcbIIap+nt315cjvkfatGv14Dres/K7vhQAhqGy5o0LM1D0qc=
|
||||
=o387
|
||||
-----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/view_count.md
|
||||
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/view_count.md.asc
|
||||
gpg --verify view_count.md.asc view_count.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/analytics/">Analytics</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/debugging/">Debugging</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/busuanzi/">Busuanzi</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/vercount/">Vercount</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/goatcounter/">GoatCounter</a></li>
|
||||
</ul>
|
||||
<nav class="paginav">
|
||||
<a class="prev" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/skin_routine/">
|
||||
<span class="title">« Prev</span>
|
||||
<br>
|
||||
<span>Skin routine</span>
|
||||
</a>
|
||||
<a class="next" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/inet_logo/">
|
||||
<span class="title">Next »</span>
|
||||
<br>
|
||||
<span>INET Logo That Breathes — From CAD to LEDs to a Calm Little Server</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/papermod-views-debugging-story/"
|
||||
data-title="A Tiny &#39;Views&#39; Badge Sent Me Down a Rabbit Hole (PaperMod + Busuanzi + Debugging --&gt; swapped with GoatCounter the GOAT)">
|
||||
<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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue