Add support for Fediverse comments

How to add Fediverse comments
=============================

In a post where to allow Fediverse comments:
```yaml
comments:
  fediverse:
    url: https://example.com/@handle/123456789012345678
```

All replies to the Fediverse post will appear as comments.

Moderation
==========

By default, the moderation system is opt-out. All replies will be
published unless they are individually hidden.

To hide one specific Fediverse post:
```yaml
comments:
  fediverse:
    url: https://example.com/@handle/123456789012345678
    hidden:
    - https://example.com/@troll/123456789012345679
```

To switch to opt-in moderation, where only accepted posts are shown:
```yaml
comments:
  fediverse:
    url: https://example.com/@handle/123456789012345678
    moderation: opt-in
    shown:
    - https://example.com/@friend/123456789012345680
```

The JavaScript for the comments is based on [yidhra's
work](https://yidhra.farm/tech/jekyll/2022/01/03/mastodon-comments-for-jekyll.html),
combined with [Daniel Pecos Martinez's
work](https://danielpecos.com/2022/12/25/mastodon-as-comment-system-for-your-static-blog/)
for the Hugo version, as pupularized by [Jan
Wildeboer](https://codeberg.org/jwildeboer/cayman-fedi). It also
includes changes by me.
This commit is contained in:
Pierre Prinetti 2023-03-13 10:44:43 +01:00
parent a2eb47bb4b
commit c2a3179a73
No known key found for this signature in database
GPG Key ID: F8360C8220AA1958
4 changed files with 212 additions and 0 deletions

View File

@ -407,3 +407,73 @@ img.in-text {
display: inline; display: inline;
margin: auto; margin: auto;
} }
.comment-reply-link {
box-shadow: 0 1px 0;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
.fediverse-comment {
background-color: var(--code-bg);
border-radius: var(--radius);
border: 1px var(--border) solid;
padding: 20px;
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
color: var(--content);
}
.fediverse-comment p {
margin-bottom: 0px;
}
.fediverse-comment .author {
padding-top:0;
display:flex;
}
.fediverse-comment .author a {
text-decoration: none;
}
.fediverse-comment .author .avatar img {
margin-right:1rem;
min-width:60px;
border-radius: 5px;
}
.fediverse-comment .author .details {
display: flex;
flex-direction: column;
}
.fediverse-comment .author .details .name {
font-weight: bold;
}
.fediverse-comment .author .details .user {
color: #5d686f;
font-size: medium;
}
.fediverse-comment .author .date {
margin-left: auto;
font-size: small;
}
.fediverse-comment .content {
margin: 15px 20px;
}
.fediverse-comment .content p:first-child {
margin-top:0;
margin-bottom:0;
}
.fediverse-comment .status > div {
display: inline-block;
margin-right: 15px;
}
.fediverse-comment .status a {
color: #5d686f;
text-decoration: none;
}
.fediverse-comment .status .replies.active a {
color: #003eaa;
}
.fediverse-comment .status .reblogs.active a {
color: #8c8dff;
}
.fediverse-comment .status .favourites.active a {
color: #ca8f04;
}

View File

@ -15,6 +15,7 @@
--code-block-bg: rgb(28, 29, 33); --code-block-bg: rgb(28, 29, 33);
--code-bg: rgb(245, 245, 245); --code-bg: rgb(245, 245, 245);
--border: rgb(238, 238, 238); --border: rgb(238, 238, 238);
--fediverse-comment-indent: 10px
} }
.dark { .dark {

View File

@ -0,0 +1,140 @@
{{- /* Fediverse comments area start */ -}}
<h2>Comments</h2>
<noscript>
<div id="error">
Enable JavaScript to view comments from the Fediverse.
</div>
</noscript>
<p>Use your Fediverse account (e.g. Mastodon) to <a class="comment-reply-link"
href="{{ .Params.comments.fediverse.url }}">reply</a>.
</p>
<p id="fediverse-comments-list"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.1.6/purify.min.js" integrity="sha512-jB0TkTBeQC9ZSkBqDhdmfTv1qdfbWpGE72yJ/01Srq6hEzZIz2xkz1e57p9ai7IeHMwEG7HpzG6NdptChif5Pg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript">
var [host, fedipostUrlPath] = '{{ .Params.comments.fediverse.url }}'.split('@', 2).map(x => { return x.endsWith('/') ? x.slice(0, -1) : x });
var [user, id] = fedipostUrlPath.split('/', 2);
// * always hide hidden
// * if moderation is not the default 'opt-out', only show accepted
var moderation = {{ .Params.comments.fediverse.moderation }} || 'opt-out';
var acceptedReplies = {{ .Params.comments.fediverse.shown }} || [];
var hiddenReplies = {{ .Params.comments.fediverse.hidden }} || [];
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
var commentsLoaded = false;
function toot_active(toot, what) {
var count = toot[what+'_count'];
return count > 0 ? 'active' : '';
}
function toot_count(toot, what) {
var count = toot[what+'_count'];
return count > 0 ? count : '0';
}
function user_account(account) {
var result =`@${account.acct}`;
if (account.acct.indexOf('@') === -1) {
var domain = new URL(account.url);
result += `@${domain.hostname}`;
}
return result;
}
function render_toots(toots, in_reply_to, depth) {
var tootsToRender = toots
.filter(toot => toot.in_reply_to_id === in_reply_to)
.filter(toot => {
if (hiddenReplies.includes(toot.url)) { return false };
if (acceptedReplies.includes(toot.url)) { return true };
return moderation == 'opt-out';
});
tootsToRender.forEach(toot => render_toot(toots, toot, depth));
}
function render_toot(toots, toot, depth) {
toot.account.display_name = escapeHtml(toot.account.display_name);
toot.account.emojis.forEach(emoji => {
toot.account.display_name = toot.account.display_name.replace(`:${emoji.shortcode}:`, `<img src="${escapeHtml(emoji.static_url)}" alt="Emoji ${emoji.shortcode}" height="20" width="20" />`);
});
fediverseComment =
`<div class="fediverse-comment" style="margin-left: calc(var(--fediverse-comment-indent) * ${depth})">
<div class="author">
<div class="avatar">
<img src="${escapeHtml(toot.account.avatar_static)}" height=60 width=60 alt="">
</div>
<div class="details">
<a class="name" href="${toot.account.url}" rel="nofollow">${toot.account.display_name}</a>
<a class="user" href="${toot.account.url}" rel="nofollow">${user_account(toot.account)}</a>
</div>
<a class="date" href="${toot.url}" rel="nofollow">${toot.created_at.substr(0, 10)} ${toot.created_at.substr(11, 8)}</a>
</div>
<div class="content">${toot.content}</div>
<div class="status">
<div class="replies ${toot_active(toot, 'replies')}">
<a href="${toot.url}" rel="nofollow">↩️ ${toot_count(toot, 'replies')}</a>
</div>
<div class="reblogs ${toot_active(toot, 'reblogs')}">
<a href="${toot.url}" rel="nofollow">🔁 ${toot_count(toot, 'reblogs')}</a>
</div>
<div class="favourites ${toot_active(toot, 'favourites')}">
<a href="${toot.url}" rel="nofollow">⭐ ${toot_count(toot, 'favourites')}</a>
</div>
</div>
</div>`;
document.getElementById('fediverse-comments-list').appendChild(DOMPurify.sanitize(fediverseComment, {'RETURN_DOM_FRAGMENT': true}));
render_toots(toots, toot.id, depth + 1);
}
function loadComments() {
if (commentsLoaded) return;
document.getElementById("fediverse-comments-list").innerHTML = "Loading comments from the Fediverse...";
fetch(host + '/api/v1/statuses/' + id + '/context')
.then(function(response) {
return response.json();
})
.then(function(data) {
if(data['descendants'] && Array.isArray(data['descendants']) && data['descendants'].length > 0) {
document.getElementById('fediverse-comments-list').innerHTML = "";
render_toots(data['descendants'], id, 0);
}
commentsLoaded = true;
});
}
function respondToVisibility(element, callback) {
var options = {
root: null,
};
var observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.intersectionRatio > 0) {
callback();
}
});
}, options);
observer.observe(element);
}
var comments = document.getElementById("fediverse-comments-list");
respondToVisibility(comments, loadComments);
</script>
{{- /* Fediverse comments area end */ -}}

View File

@ -1,3 +1,4 @@
{{- /* Comments area start */ -}} {{- /* Comments area start */ -}}
{{- /* to add comments read => https://gohugo.io/content-management/comments/ */ -}} {{- /* to add comments read => https://gohugo.io/content-management/comments/ */ -}}
{{- if (.Params.comments.fediverse) }} {{- partial "comments-fediverse.html" . }} {{- end }}
{{- /* Comments area end */ -}} {{- /* Comments area end */ -}}