feat(website): add giscus blog comments

This commit is contained in:
4gray committed 2026-05-20 18:02:19 +02:00
1 parent 3096115e64
commit 738397c9ed
5 files changed
+101

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
# IPTVnator Website
The website is an Astro static site deployed to GitHub Pages at `https://4gray.github.io/iptvnator/`.
## Blog Comments
Blog posts render Giscus comments from `apps/website/src/components/GiscusComments.astro`.
Giscus stores comments in GitHub Discussions for `4gray/iptvnator` and maps each page to a discussion by `pathname`, including the GitHub Pages base path such as `/iptvnator/blog/why-external-players-help/`.
The embed is wired to the dedicated `Blog comments` discussion category:
- Repository id: `MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=`
- Category id: `DIC_kwDODLUX8s4C9eBJ`
- Mapping: `pathname`
- Theme: `transparent_dark`
If the category is recreated, query the new category id:
```bash
gh api graphql \
-f owner=4gray \
-f name=iptvnator \
-f query='query($owner:String!, $name:String!) { repository(owner:$owner, name:$name) { discussionCategories(first:25) { nodes { id name slug isAnswerable } } } }'
```
Then update `data-category-id` in `GiscusComments.astro`.
Moderation happens in GitHub Discussions. Maintainers can hide, delete, lock, or move discussions and comments from the repository Discussions UI.
+7
View File
@@ -14,6 +14,13 @@
"cwd": "apps/website"
}
},
"test": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"options": {
"command": "node --test tools/testing/website-giscus-comments.test.mjs"
}
},
"serve": {
"executor": "nx:run-commands",
"options": {
@@ -0,0 +1,27 @@
<section class="mt-16 border-t border-dashed border-surface-700/60 pt-10" aria-labelledby="blog-comments-heading">
<div class="mb-6">
<h2 id="blog-comments-heading" class="font-display text-2xl font-semibold tracking-tight text-surface-100">
Comments
</h2>
</div>
<div class="giscus min-h-40"></div>
<script
is:inline
src="https://giscus.app/client.js"
data-repo="4gray/iptvnator"
data-repo-id="MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg="
data-category="Blog comments"
data-category-id="DIC_kwDODLUX8s4C9eBJ"
data-mapping="pathname"
data-strict="1"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="transparent_dark"
data-lang="en"
crossorigin="anonymous"
loading="lazy"
async
></script>
</section>
+3
View File
@@ -1,5 +1,6 @@
---
import BaseLayout from './BaseLayout.astro';
import GiscusComments from '../components/GiscusComments.astro';
interface Props {
title: string;
@@ -150,6 +151,8 @@ const blogPostSchema = {
>
<slot />
</div>
<GiscusComments />
</div>
</article>
</BaseLayout>
@@ -0,0 +1,36 @@
import { readFile } from 'node:fs/promises';
import { test } from 'node:test';
import assert from 'node:assert/strict';
const postHtmlPath = new URL('../../dist/apps/website/blog/why-external-players-help/index.html', import.meta.url);
const expectedGiscusAttributes = {
src: 'https://giscus.app/client.js',
'data-repo': '4gray/iptvnator',
'data-repo-id': 'MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=',
'data-category': 'Blog comments',
'data-category-id': 'DIC_kwDODLUX8s4C9eBJ',
'data-mapping': 'pathname',
'data-strict': '1',
'data-reactions-enabled': '1',
'data-emit-metadata': '0',
'data-input-position': 'bottom',
'data-theme': 'transparent_dark',
'data-lang': 'en',
};
test('published blog posts include the Giscus comments embed configuration', async () => {
const html = await readFile(postHtmlPath, 'utf8');
const scriptMatch = html.match(/<script\b[^>]*giscus\.app\/client\.js[^>]*>/);
assert.ok(scriptMatch, 'Expected the blog post HTML to include the Giscus client script.');
const scriptTag = scriptMatch[0];
for (const [name, value] of Object.entries(expectedGiscusAttributes)) {
assert.match(scriptTag, new RegExp(`${name}="${value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`));
}
assert.match(scriptTag, /\bloading="lazy"/);
assert.match(scriptTag, /\bcrossorigin="anonymous"/);
assert.match(scriptTag, /\basync/);
});