Add mastodon post
This commit is contained in:
parent
6f4f515e3b
commit
d09635bf0e
4 changed files with 241 additions and 4 deletions
45
content/posts/2024-02-24-adding-mastodon-comments.md
Normal file
45
content/posts/2024-02-24-adding-mastodon-comments.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
date: "2024-02-24"
|
||||
title: On Mastodon, a social media curmudgeon's perspective
|
||||
comments:
|
||||
host: indieweb.social
|
||||
username: adriano
|
||||
id: 111988206938582030
|
||||
---
|
||||
|
||||
## Mastodon
|
||||
|
||||
While I'm in no way a Mastodon power user, I've really been enjoying the content that I get from my _very_ limited Mastodon feed. I recently stopped consuming news from -- hold on let's not call it news. I recently stopped consuming useless information from a site that I won't name. Everyone has those junk, just-for-fun sites they visit to blow off stream. You don't have to be proud of them, and it's fine that you visit them; every part of every day need not be productive. Anyway, I recently stopped consuming a lot of useless information that I plan to replace with an equally fun, but probably less useless Mastodon feed.
|
||||
|
||||
Feel free to visit [my feed](https://indieweb.social@adriano) and follow me, if that's the sort of thing you're into.
|
||||
|
||||
There are a few things about Mastodon that are surprisingly refreshing.
|
||||
|
||||
### The algorithm
|
||||
|
||||
The Mastodon feed algorithm is an absolute delight after years of being forced to consume linear timelines non-linearly, because linearity is apparently not profitable enough for Facebook. You don't use Facebook, you say? I'll remind you that Facebook is the reason that most timelines today are non-linear, algorithm-driven ad and vitriol machines designed to juice your serotonin levels. Stable feeds don't build addictions, and ad businesses require your addiction's constant pulls-to-refresh to keep revenue up and to the right.
|
||||
|
||||
Mastodon's algorithm is not what most people think of as an algorithm, but it is one nonetheless. It's a date sorter.
|
||||
|
||||
|
||||
### The anti-social-media
|
||||
|
||||
I'm a grumpy man and not one to suffer uninvited guests on my lawn. The typical social media fare are all uninvited lawn guests. The comments made with the express purpose of stirring up conflict, i.e. the "hot takes". The manufactured, smiling Instagram moments made by people who were bitching at their camera person seconds earlier. The sharing of news stories, without comment, because the headline says everything that one's audience wants to be confirmed about their existence.
|
||||
|
||||
I want no part of that. Mastodon seems to be full of people with heads on their shoulders. Weird, creative, fun, and not too serious. These are my people.
|
||||
|
||||
Read Jonathan Haidt's [WHY THE PAST 10 YEARS OF AMERICAN LIFE HAVE BEEN UNIQUELY STUPID](https://www.theatlantic.com/magazine/archive/2022/05/social-media-democracy-trust-babel/629369/) if you have an afternoon on your hands and want to understand where these feelings of mine are coming from.
|
||||
|
||||
### Open protocols
|
||||
|
||||
[Protocols, Not Platforms: A Technological Approach to Free Speech](https://knightcolumbia.org/content/protocols-not-platforms-a-technological-approach-to-free-speech) covers well the topic of protocols' superiority over platforms. Mastodon embraced [Activity-Pub](https://en.wikipedia.org/wiki/ActivityPub) early on, and in my opinion, that has been a great success. I don't think Activity-Pub is a perfect protocol, but Twitter, Facebook, Instagram, YouTube ... the list goes on -- these are not protocols at all. We don't _need_ a perfect protocol to improve upon the current platform landscape that is "social media", we only needed _a_ protocol.
|
||||
|
||||
Activity-Pub is largely responsible for the rise of the "Fediverse", which I won't try to define here. [Fediverse.party](https://fediverse.party/) has a nice little overview of some of the bigger players.
|
||||
|
||||
### I'm bought in
|
||||
|
||||
That's all to say that I'm bought in on Mastodon, the Fediverse, and open protocols. My minor contribution is adding Mastodon comments to this site. I doubt this will be a world-changing event, but it's a fun way to participate in the greater Mastodon community and get more non-commercial media in front of normal people.
|
||||
|
||||
Thanks to [Carl Schwan](https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/) for the code!
|
||||
|
||||
Cheers
|
||||
|
|
@ -22,7 +22,7 @@ paginate = 5
|
|||
missingBackButtonLabel = "Back to home page"
|
||||
minuteReadingTime = "min read"
|
||||
words = "words"
|
||||
|
||||
comments = true
|
||||
|
||||
# dir name of your main content (default is `content/posts`).
|
||||
# the list of set content will show up on your index page (baseurl).
|
||||
|
|
|
|||
|
|
@ -50,12 +50,13 @@
|
|||
{{- end -}}
|
||||
</div>
|
||||
|
||||
{{ if not (.Params.hideComments | default false) }}
|
||||
{{ partial "comments.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Type $.Site.Params.contentTypeName }}
|
||||
{{ partial "posts_pagination.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ if not (.Params.hideComments | default false) }}
|
||||
{{ partial "comments.html" . }}
|
||||
{{ end }}
|
||||
</article>
|
||||
{{ end }}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,194 @@ Hugo directory and insert:
|
|||
|
||||
or whatever comment engine you want -> https://gohugo.io/content-management/comments/#readout
|
||||
-->
|
||||
{{ with .Params.comments }}
|
||||
<section id="comments" class="article-content">
|
||||
<h2>Comments</h2>
|
||||
<p>With an account on the Fediverse or Mastodon, you can respond to this <a href="https://{{ .host }}/@{{ .username }}/{{ .id }}">post</a>. Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one. Known non-private replies are displayed below.</p>
|
||||
|
||||
<p id="mastodon-comments-list"><button id="load-comment">Load comments</button></p>
|
||||
<div id="comments-wrapper">
|
||||
<noscript><p>Loading comments relies on JavaScript. Try enabling JavaScript and reloading, or visit <a href="https://{{ .host }}/@{{ .username }}/{{ .id }}">the original post</a> on Mastodon.</p></noscript>
|
||||
</div>
|
||||
<noscript>You need JavaScript to view the comments.</noscript>
|
||||
<script src="/assets/js/purify.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
function emojify(input, emojis) {
|
||||
let output = input;
|
||||
|
||||
emojis.forEach(emoji => {
|
||||
let picture = document.createElement("picture");
|
||||
|
||||
let source = document.createElement("source");
|
||||
source.setAttribute("srcset", escapeHtml(emoji.url));
|
||||
source.setAttribute("media", "(prefers-reduced-motion: no-preference)");
|
||||
|
||||
let img = document.createElement("img");
|
||||
img.className = "emoji";
|
||||
img.setAttribute("src", escapeHtml(emoji.static_url));
|
||||
img.setAttribute("alt", `:${ emoji.shortcode }:`);
|
||||
img.setAttribute("title", `:${ emoji.shortcode }:`);
|
||||
img.setAttribute("width", "20");
|
||||
img.setAttribute("height", "20");
|
||||
|
||||
picture.appendChild(source);
|
||||
picture.appendChild(img);
|
||||
|
||||
output = output.replace(`:${ emoji.shortcode }:`, picture.outerHTML);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function loadComments() {
|
||||
let commentsWrapper = document.getElementById("comments-wrapper");
|
||||
document.getElementById("load-comment").innerHTML = "Loading";
|
||||
fetch('https://{{ .host }}/api/v1/statuses/{{ .id }}/context')
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
let descendants = data['descendants'];
|
||||
if(
|
||||
descendants &&
|
||||
Array.isArray(descendants) &&
|
||||
descendants.length > 0
|
||||
) {
|
||||
commentsWrapper.innerHTML = "";
|
||||
|
||||
descendants.forEach(function(status) {
|
||||
console.log(descendants)
|
||||
if( status.account.display_name.length > 0 ) {
|
||||
status.account.display_name = escapeHtml(status.account.display_name);
|
||||
status.account.display_name = emojify(status.account.display_name, status.account.emojis);
|
||||
} else {
|
||||
status.account.display_name = status.account.username;
|
||||
};
|
||||
|
||||
let instance = "";
|
||||
if( status.account.acct.includes("@") ) {
|
||||
instance = status.account.acct.split("@")[1];
|
||||
} else {
|
||||
instance = "{{ .host }}";
|
||||
}
|
||||
|
||||
const isReply = status.in_reply_to_id !== "{{ .id }}";
|
||||
|
||||
let op = false;
|
||||
if( status.account.acct == "{{ .username }}" ) {
|
||||
op = true;
|
||||
}
|
||||
|
||||
status.content = emojify(status.content, status.emojis);
|
||||
|
||||
let avatarSource = document.createElement("source");
|
||||
avatarSource.setAttribute("srcset", escapeHtml(status.account.avatar));
|
||||
avatarSource.setAttribute("media", "(prefers-reduced-motion: no-preference)");
|
||||
|
||||
let avatarImg = document.createElement("img");
|
||||
avatarImg.className = "avatar";
|
||||
avatarImg.setAttribute("src", escapeHtml(status.account.avatar_static));
|
||||
avatarImg.setAttribute("alt", `@${ status.account.username }@${ instance } avatar`);
|
||||
|
||||
let avatarPicture = document.createElement("picture");
|
||||
avatarPicture.appendChild(avatarSource);
|
||||
avatarPicture.appendChild(avatarImg);
|
||||
|
||||
let avatar = document.createElement("a");
|
||||
avatar.className = "avatar-link";
|
||||
avatar.setAttribute("href", status.account.url);
|
||||
avatar.setAttribute("rel", "external nofollow");
|
||||
avatar.setAttribute("title", `View profile at @${ status.account.username }@${ instance }`);
|
||||
avatar.appendChild(avatarPicture);
|
||||
|
||||
let instanceBadge = document.createElement("a");
|
||||
instanceBadge.className = "instance";
|
||||
instanceBadge.setAttribute("href", status.account.url);
|
||||
instanceBadge.setAttribute("title", `@${ status.account.username }@${ instance }`);
|
||||
instanceBadge.setAttribute("rel", "external nofollow");
|
||||
instanceBadge.textContent = instance;
|
||||
|
||||
let display = document.createElement("span");
|
||||
display.className = "display";
|
||||
display.setAttribute("itemprop", "author");
|
||||
display.setAttribute("itemtype", "http://schema.org/Person");
|
||||
display.innerHTML = status.account.display_name;
|
||||
|
||||
let header = document.createElement("header");
|
||||
header.className = "author";
|
||||
header.appendChild(display);
|
||||
header.appendChild(instanceBadge);
|
||||
|
||||
let permalink = document.createElement("a");
|
||||
permalink.setAttribute("href", status.url);
|
||||
permalink.setAttribute("itemprop", "url");
|
||||
permalink.setAttribute("title", `View comment at ${ instance }`);
|
||||
permalink.setAttribute("rel", "external nofollow");
|
||||
permalink.textContent = new Date( status.created_at ).toLocaleString('en-US', {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
let timestamp = document.createElement("time");
|
||||
timestamp.setAttribute("datetime", status.created_at);
|
||||
timestamp.appendChild(permalink);
|
||||
|
||||
let main = document.createElement("main");
|
||||
main.setAttribute("itemprop", "text");
|
||||
main.innerHTML = status.content;
|
||||
|
||||
let interactions = document.createElement("footer");
|
||||
if(status.favourites_count > 0) {
|
||||
let faves = document.createElement("a");
|
||||
faves.className = "faves";
|
||||
faves.setAttribute("href", `${ status.url }/favourites`);
|
||||
faves.setAttribute("title", `Favorites from ${ instance }`);
|
||||
faves.textContent = status.favourites_count;
|
||||
|
||||
interactions.appendChild(faves);
|
||||
}
|
||||
|
||||
let comment = document.createElement("article");
|
||||
comment.id = `comment-${ status.id }`;
|
||||
comment.className = isReply ? "comment comment-reply" : "comment";
|
||||
comment.setAttribute("itemprop", "comment");
|
||||
comment.setAttribute("itemtype", "http://schema.org/Comment");
|
||||
comment.appendChild(avatar);
|
||||
comment.appendChild(header);
|
||||
comment.appendChild(timestamp);
|
||||
comment.appendChild(main);
|
||||
comment.appendChild(interactions);
|
||||
|
||||
if(op === true) {
|
||||
comment.classList.add("op");
|
||||
|
||||
avatar.classList.add("op");
|
||||
avatar.setAttribute(
|
||||
"title",
|
||||
"Blog post author; " + avatar.getAttribute("title")
|
||||
);
|
||||
|
||||
instanceBadge.classList.add("op");
|
||||
instanceBadge.setAttribute(
|
||||
"title",
|
||||
"Blog post author: " + instanceBadge.getAttribute("title")
|
||||
);
|
||||
}
|
||||
|
||||
commentsWrapper.innerHTML += DOMPurify.sanitize(comment.outerHTML);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById("load-comment").addEventListener("click", loadComments);
|
||||
</script>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
|
|
|||
Loading…
Reference in a new issue