基于 Alpine.js + UnoCSS 的 Hugo Mastodon 评论系统 极简版
缘起
最近新主题研究了一下,应该适配什么评论系统。
静态博客可选的评论系统还挺多的。支持的平台也多。即使要使用一些没有受到支持的平台,使用非主流 DB,大概现在 AI 也能很快搓一个出来。
想了想最后还是随便搞了一个基于长毛象的,评论系统。或者说互动吧,其实称不上是评论系统。
以前搞过长毛象登录博客回复,也搞过发博后自动创建关联嘟嘟。不过这次倒是没有那么复杂了。功能就是传入嘟嘟链接,调用 API 获取点赞等数据,获取回复内容。
效果
见
实现
功能十分的简陋。也就比没有强一点。好处是轻量(简陋)。
---
title: Title
comments: true
fediverse: "https://mastodon.social/@name/xxxxxxxxx"
---yaml
{{$url:=.Params.fediverse}}
{{if $url}}
<div x-data="mastodonCommentComponent()" x-init="initMastodonCommentComponent('{{$url}}', '{{$id}}')"
class="relative flex flex-col gap-y-2 rounded-xl border px-3 sm:px-4 py-2 sm:py-3 mt-3">
<div class="font-medium text-foreground">Fediverse
</div>
<div x-text="info"></div>
{{/* <a class="text-foreground" x-text="`${status.account.display_name}`" :href="status.account.url" target="_blank" rel="noopener noreferrer"></a> */}}
<div x-html="sanitizeHTML(status.content)"></div>
<a class="flex gap-x-4 justify-end" :href="url" target="_blank" rel="noopener noreferrer">
<span class="mdi--reply"></span>
<span x-text="status.replies_count"></span>
<span class="mdi--star"></span>
<span x-text="status.favourites_count"></span>
<span class="mdi--twitter-retweet"></span>
<span x-text="status.reblogs_count"></span>
</a>
<div class="flex gap-x-4 justify-center flex-col">
<template x-for="reply in replies">
<span class="gap-y-2 rounded-xl border px-3 sm:px-4 py-2 sm:py-3 mt-3">
<a class="text-foreground" x-text="reply.account.display_name" :href="reply.account.url" target="_blank" rel="noopener noreferrer"></a>
<span x-html="sanitizeHTML(reply.content)"></span>
</span>
</template>
</div>
</div>
<script>
function mastodonCommentComponent() {
return {
info: "",
url: "",
status: {
content: "",
account: {
display_name: "",
url: "",
},
replies_count: 0,
reblogs_count: 0,
favourites_count: 0,
},
replies: [],
async initMastodonCommentComponent(url, id) {
this.url = url;
const api = `https://${url.split("/")[2]}/api/v1/statuses/${url.split("/")[4]}`;
this.fetchStatus(api);
},
async fetchStatus(url) {
fetch(url)
.then((response) => response.json())
.then((data) => {
this.status = data;
if (this.status.replies_count > 0) {
this.fetchReplies(url + "/context");
}
})
.catch((error) => this.info = error);
},
async fetchReplies(url) {
fetch(url)
.then((response) => response.json())
.then((data) => {
this.replies = data.descendants;
})
.catch((error) => this.info = error);
},
sanitizeHTML(html) {
// 移除不安全的标签
html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
html = html.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '');
// 移除不安全的属性
html = html.replace(/javascript:/gi, '');
return html;
}
}
}
</script>
<style>
.mdi--reply {
display: inline-block;
width: 1.3em;
height: 1.3em;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M10 9V5l-7 7l7 7v-4.1c5 0 8.5 1.6 11 5.1c-1-5-4-10-11-11'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.mdi--star {
display: inline-block;
width: 1.3em;
height: 1.3em;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21z'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.mdi--twitter-retweet {
display: inline-block;
width: 1.3em;
height: 1.3em;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M6 5.75L10.25 10H7v6h6.5l2 2H7a2 2 0 0 1-2-2v-6H1.75zm12 12.5L13.75 14H17V8h-6.5l-2-2H17a2 2 0 0 1 2 2v6h3.25z'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
</style>
{{else}}
{{/* <div class="relative flex flex-col gap-y-2 rounded-xl border px-3 sm:px-4 py-2 sm:py-3 mt-3">
<div class="font-medium text-foreground text-center">Fediverse Unready</div>
</div> */}}
{{end}}html