From b159f4f6f7a349590c971b2dca392a3a78dad524 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:42:14 +0000 Subject: [PATCH 1/2] Initial plan From e0ebd3227a827a5e84d476f7f74855c4387898ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:45:14 +0000 Subject: [PATCH 2/2] Add Utterances comments integration using Ferretosan/web-blog-comments Co-authored-by: Ferretosan <129646624+Ferretosan@users.noreply.github.com> --- README.md | 14 ++++++++++- js/markdown-parser.js | 58 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c33504f..f997111 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,16 @@ Before contributing, please read the following: ### Contributors - [Ferretosan](https://github.com/ferretosan) -- [proplayer919](https://github.com/proplayer919) \ No newline at end of file +- [proplayer919](https://github.com/proplayer919) + +## Comments (Utterances) + +Blog post comments are powered by [Utterances](https://utteranc.es/), which stores threads as GitHub Issues in [`Ferretosan/web-blog-comments`](https://github.com/Ferretosan/web-blog-comments). + +### Setup steps + +1. **Install the Utterances GitHub App** on the `Ferretosan/web-blog-comments` repository: + - Visit and grant access to `Ferretosan/web-blog-comments`. +2. **Ensure the repo is public** – Utterances requires a public repository to create and read issues. +3. The embed is already wired up in `js/markdown-parser.js`. Each blog post gets its own GitHub Issue titled `blog/` (e.g. `blog/newyear26.md`), providing a stable, deterministic mapping regardless of URL changes. +4. To change the comment theme, update `UTTERANCES_THEME` at the top of `js/markdown-parser.js`. Available themes: `github-light`, `github-dark`, `preferred-color-scheme`, `github-dark-orange`, `icy-dark`, `dark-blue`, `photon-dark`, `boxy-light`. \ No newline at end of file diff --git a/js/markdown-parser.js b/js/markdown-parser.js index 9bed2f7..a699130 100644 --- a/js/markdown-parser.js +++ b/js/markdown-parser.js @@ -65,6 +65,49 @@ function parseMarkdown(markdown) { return html; } +// ---- Utterances comments config ---- +const UTTERANCES_REPO = "Ferretosan/web-blog-comments"; +const UTTERANCES_THEME = "github-light"; + +// Inject (or replace) the Utterances widget inside targetEl. +// postId is used as the issue title so each post gets its own stable thread. +function injectUtterances(targetEl, postId) { + if (!targetEl) return; + + // Remove any existing Utterances iframe/container when switching posts + const existing = targetEl.querySelector('.utterances'); + if (existing) existing.remove(); + + // Use document.title to pass the stable postId to Utterances (issue-term="title"). + // We set it briefly while the script loads, then restore the original title. + // This avoids any URL or og:title hacking and gives a clean, deterministic mapping. + const originalTitle = document.title; + document.title = postId; + + const s = document.createElement('script'); + s.src = 'https://utteranc.es/client.js'; + s.async = true; + s.crossOrigin = 'anonymous'; + s.setAttribute('repo', UTTERANCES_REPO); + s.setAttribute('issue-term', 'title'); + s.setAttribute('theme', UTTERANCES_THEME); + s.setAttribute('label', 'blog-comment'); + + // Restore the original page title after Utterances has had time to read it. + // The script reads document.title synchronously on execution, so the load event + // fires after the title has been captured. A setTimeout fallback guards against + // any async initialisation in the Utterances client. + s.addEventListener('load', function() { + setTimeout(function() { document.title = originalTitle; }, 0); + }); + // Restore on error too + s.addEventListener('error', function() { + document.title = originalTitle; + }); + + targetEl.appendChild(s); +} + // Blog post hash/file mappings const blogHashToFile = { '#blog-newyear26': 'newyear26.md', @@ -125,6 +168,21 @@ async function loadBlogPost(filename) { // Observe all animated elements in the popup const animatedElements = popupContent.querySelectorAll('.fade-in, .slide-in-left, .slide-in-right, .scale-in'); animatedElements.forEach(el => observer.observe(el)); + + // Append comments section and inject Utterances widget + const comments = document.createElement('div'); + comments.id = 'blog-comments'; + const hr = document.createElement('hr'); + const heading = document.createElement('h2'); + heading.className = 'slide-in-right'; + heading.textContent = 'Comments'; + comments.appendChild(hr); + comments.appendChild(heading); + popupContent.appendChild(comments); + + // Use "blog/" as the stable, deterministic issue title per post + const postId = `blog/${filename}`; + injectUtterances(comments, postId); } }, 100); // Small delay to ensure popup is fully rendered