Initial import of Hugo site to Forgejo
This commit is contained in:
commit
cb1ba0317f
1259 changed files with 236349 additions and 0 deletions
3
layouts/_default/_markup/render-codeblock-mermaid.html
Normal file
3
layouts/_default/_markup/render-codeblock-mermaid.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{{ .Page.Store.Set "hasMermaid" true }}
|
||||
<div class="mermaid">{{ .Inner }}</div>
|
||||
|
||||
117
layouts/partials/comments.html
Normal file
117
layouts/partials/comments.html
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<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>
|
||||
|
||||
|
||||
|
||||
<!-- Giscus panel (lazy-loaded) -->
|
||||
<div id="panel-giscus" style="display:none">
|
||||
<section id="giscus-thread"></section>
|
||||
</div>
|
||||
|
||||
<!-- Isso panel (default) -->
|
||||
<div id="panel-isso">
|
||||
<section id="isso-thread"
|
||||
data-isso-id="{{ .RelPermalink }}"
|
||||
data-title="{{ .Title | htmlEscape }}">
|
||||
<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'); // PaperMod uses this
|
||||
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'); });
|
||||
|
||||
// default view
|
||||
show('isso');
|
||||
})();
|
||||
</script>
|
||||
|
||||
17
layouts/partials/extend_footer.html
Normal file
17
layouts/partials/extend_footer.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{{/* Only load Isso's count script on list-like pages */}}
|
||||
{{ $k := .Kind }}
|
||||
{{ if or (eq $k "home") (eq $k "section") (eq $k "taxonomy") (eq $k "term") }}
|
||||
<script src="/isso/js/count.min.js" data-isso="/isso" async></script>
|
||||
{{ end }}
|
||||
|
||||
<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;">
|
||||
{{ partial "svg.html" (dict "name" "rss") }}
|
||||
<span>RSS</span>
|
||||
</a>
|
||||
|
||||
<span aria-hidden="true">·</span>
|
||||
|
||||
<span>Visitors this week: <span id="gc_site_visitors_week">—</span></span>
|
||||
</div>
|
||||
102
layouts/partials/extend_head.html
Normal file
102
layouts/partials/extend_head.html
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<script>
|
||||
(function () {
|
||||
// Same-origin on clearnet and onion (nginx proxies both to GoatCounter).
|
||||
// Sessions are server-side (IP+UA → ephemeral UUID); no tracking cookies.
|
||||
window.goatcounter = { endpoint: "/count" };
|
||||
|
||||
const s = document.createElement("script");
|
||||
s.async = true;
|
||||
s.src = "/js/count.js";
|
||||
document.head.appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
// Tor + clearnet share one GoatCounter site → one TOTAL.
|
||||
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();
|
||||
// GoatCounter "count" is visits (session-deduped), not raw pageviews.
|
||||
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>
|
||||
33
layouts/partials/post_meta.html
Normal file
33
layouts/partials/post_meta.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{{- $scratch := newScratch }}
|
||||
|
||||
{{- if not .Date.IsZero -}}
|
||||
{{- $scratch.Add "meta" (slice (printf "<span title='%s'>%s</span>" (.Date) (.Date | time.Format (default "January 2, 2006" site.Params.DateFormat)))) }}
|
||||
{{- end }}
|
||||
|
||||
{{- if (.Param "ShowReadingTime") -}}
|
||||
{{- $scratch.Add "meta" (slice (i18n "read_time" .ReadingTime | default (printf "%d min" .ReadingTime))) }}
|
||||
{{- end }}
|
||||
|
||||
{{- if (.Param "ShowWordCount") -}}
|
||||
{{- $scratch.Add "meta" (slice (i18n "words" .WordCount | default (printf "%d words" .WordCount))) }}
|
||||
{{- end }}
|
||||
|
||||
{{- if not (.Param "hideAuthor") -}}
|
||||
{{- with (partial "author.html" .) }}
|
||||
{{- $scratch.Add "meta" (slice .) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- with ($scratch.Get "meta") }}
|
||||
{{- delimit . " · " | safeHTML -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Isso comment count on list views */}}
|
||||
{{ if or .Site.Home.IsHome .IsSection .IsTaxonomy .IsTerm }}
|
||||
<span class="post-meta-item">
|
||||
<a href="{{ .Permalink }}#isso-thread" class="isso-comments-link">Comments</a>
|
||||
</span>
|
||||
|
||||
|
||||
{{ end }}
|
||||
|
||||
8
layouts/shortcodes/mermaid.html
Normal file
8
layouts/shortcodes/mermaid.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{{ if not (.Page.Store.Get "mermaidLoaded") }}
|
||||
<script src="{{ "js/mermaid.min.js" | relURL }}" defer></script>
|
||||
<script src="{{ "js/init-mermaid-umd.js" | relURL }}" defer></script>
|
||||
{{ .Page.Store.Set "mermaidLoaded" true }}
|
||||
{{ end }}
|
||||
|
||||
<div class="mermaid">{{- .Inner -}}</div>
|
||||
|
||||
7
layouts/shortcodes/pgpkey.html
Normal file
7
layouts/shortcodes/pgpkey.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{{- /* pgpkey — show your ASCII-armored public key from static/keys/iman-alipour-pgp.asc */ -}}
|
||||
{{- $path := .Get 0 | default "static/keys/iman-alipour-pgp.asc" -}}
|
||||
{{- $key := readFile $path | htmlEscape -}}
|
||||
<details>
|
||||
<summary>Show ASCII-armored key</summary>
|
||||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">{{$key}}</pre>
|
||||
</details>
|
||||
9
layouts/shortcodes/sig.html.bak
Normal file
9
layouts/shortcodes/sig.html.bak
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{{- /* Usage: {{< sig >}} or {{< sig "name.asc" >}}. Reads from .Page.File.Dir */ -}}
|
||||
{{- $name := .Get 0 | default "signature.asc" -}}
|
||||
{{- $dir := .Page.File.Dir -}}
|
||||
{{- $sig := readFile (print $dir $name) | htmlEscape -}}
|
||||
<details class="signature">
|
||||
<summary>OpenPGP signature (.asc)</summary>
|
||||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">{{$sig}}</pre>
|
||||
</details>
|
||||
<p><em>Verify:</em> <code>gpg --verify {{ $name }} {{ .Page.File.Path }}</code></p>
|
||||
63
layouts/shortcodes/sigdl.html
Normal file
63
layouts/shortcodes/sigdl.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{{- /* sigdl — Download & Verify block for each post.
|
||||
Usage: {{< sigdl >}}
|
||||
Normalizes index.md/_index.md so links become /sources/<dir>.md
|
||||
*/ -}}
|
||||
|
||||
{{- /* Compute a normalized relative path for the source */ -}}
|
||||
{{- $rel := replace .Page.File.Path "content/" "" -}}
|
||||
{{- $rel = replace $rel "/index.md" ".md" -}}
|
||||
{{- $rel = replace $rel "/_index.md" ".md" -}}
|
||||
|
||||
{{- $download := printf "/sources/%s" $rel -}}
|
||||
{{- $asc := printf "%s.asc" $download -}}
|
||||
{{- $staticAscPath := printf "static%s.asc" $download -}}
|
||||
|
||||
{{- $sig := "" -}}
|
||||
{{- if (fileExists $staticAscPath) -}}
|
||||
{{- $sig = (readFile $staticAscPath) | htmlEscape -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Robust onion detection:
|
||||
1) prefer --environment onion
|
||||
2) else parse .Site.Params.isOnion (string-safe)
|
||||
3) else check baseURL contains ".onion"
|
||||
*/}}
|
||||
{{- $isOnion := eq hugo.Environment "onion" -}}
|
||||
{{- if not $isOnion -}}
|
||||
{{- with .Site.Params.isOnion -}}
|
||||
{{- $v := (printf "%v" . | lower) -}}
|
||||
{{- if or (eq $v "true") (eq $v "1") (eq $v "yes") (eq $v "on") -}}
|
||||
{{- $isOnion = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if and (not $isOnion) (in (lower (site.BaseURL)) ".onion") -}}
|
||||
{{- $isOnion = true -}}
|
||||
{{- end -}}
|
||||
|
||||
<div class="signature-block" style="margin-top:1rem">
|
||||
<p><strong>Downloads:</strong>
|
||||
<a href="{{ $download | relURL }}">Markdown</a> ·
|
||||
<a href="{{ $asc | relURL }}">Signature (.asc)</a>
|
||||
</p>
|
||||
|
||||
{{- if $sig -}}
|
||||
<details class="signature">
|
||||
<summary>View OpenPGP signature</summary>
|
||||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">{{ $sig }}</pre>
|
||||
</details>
|
||||
{{- end -}}
|
||||
|
||||
<p><em>Verify locally:</em></p>
|
||||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">
|
||||
{{- if $isOnion -}}
|
||||
torsocks curl -fSLO {{ $download | absURL }}
|
||||
torsocks curl -fSLO {{ $asc | absURL }}
|
||||
{{- else -}}
|
||||
curl -fSLO {{ $download | absURL }}
|
||||
curl -fSLO {{ $asc | absURL }}
|
||||
{{- end }}
|
||||
gpg --verify {{ path.Base $asc }} {{ path.Base $download }}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue