DevBlog/_includes/bluesky.liquid
2025-03-24 18:12:44 +01:00

131 lines
7.7 KiB
Plaintext

{% if page.data.bluesky_article == blank and post.data.bluesky_comments_id == blank %}
<div class="article-content">
<h2>Comments</h2>
<p>
You can use your Bluesky account to
<a class="button-link" href="https://bsky.app/profile/{{ page.data.bluesky_article }}/post/{{ post.data.bluesky_comments_id }}"
target="_blank">reply</a>
to this post. Learn how this is implemented
<a class="link" href="https://www.menzel.it/post/2024/11/set-comments-experience-bluesky-posts/">here</a>.
</p>
<div id="bluesky-comments-list"></div>
<noscript>You need JavaScript to view the comments.</noscript>
<script src="/assets/js/purify.min.js"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
const commentList = document.getElementById('bluesky-comments-list');
commentList.innerHTML = "Loading comments...";
fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=at://{{ page.data.bluesky_article }}/app.bsky.feed.post/{{ post.data.bluesky_comments_id }}&depth=10`)
.then(response => response.json())
.then(data => {
const replies = data.thread.replies || [];
replies.sort((a, b) => new Date(a.post.record.createdAt) - new Date(b.post.record.createdAt)); // Sort by date
commentList.innerHTML = ""; // Clear loading text
const renderComments = (comments, parentElement) => {
comments.forEach(reply => {
const author = reply.post.author;
let content = reply.post.record.text;
const createdAt = new Date(reply.post.record.createdAt).toLocaleString();
// Validate counts, ensure they are numbers
const replyCount = Number(reply.post.replyCount) || 0;
const repostCount = Number(reply.post.repostCount) || 0;
const likeCount = Number(reply.post.likeCount) || 0;
// Process facets to embed links and mentions
const facets = reply.post.record.facets || [];
facets.sort((a, b) => a.index.byteStart - b.index.byteStart); // Ensure facets are in order
let offset = 0;
facets.forEach(facet => {
const start = facet.index.byteStart + offset;
const end = facet.index.byteEnd + offset;
const originalText = content.slice(start, end);
let replacementText = originalText;
facet.features.forEach(feature => {
if (feature.$type === 'app.bsky.richtext.facet#link') {
replacementText = `<a class="link" href="${feature.uri}" target="_blank" rel="noopener noreferrer">${originalText}</a>`;
} else if (feature.$type === 'app.bsky.richtext.facet#mention') {
replacementText = `<a class="link" href="https://bsky.app/profile/${feature.did}" target="_blank" rel="noopener noreferrer">${originalText}</a>`;
}
});
content = content.slice(0, start) + replacementText + content.slice(end);
offset += replacementText.length - originalText.length;
});
const safeContent = DOMPurify.sanitize(content); // Sanitize the content
const commentHtml = `
<div class="comment-container">
<img src="${author.avatar}" alt="${author.displayName}'s avatar" class="comment-avatar">
<div class="comment-details">
<div class="comment-header">
<a href="https://bsky.app/profile/${author.did}" target="_blank" class="username-link">${author.displayName}</a>
<span class="comment-handle">@${author.handle}</span>
</div>
<div class="comment-text">${safeContent}</div>
<div class="comment-timestamp">${createdAt}</div>
<div class="comment-meta">
<!-- Comment Icon and Count -->
<span class="meta-item">
<svg class="icon icon-comment" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M12 2C6.48 2 2 5.58 2 10c0 2.5 1.64 4.71 4.11 6.13L5 21l5.11-2.11c.61.08 1.24.11 1.89.11 5.52 0 10-3.58 10-8s-4.48-8-10-8zm0 14c-.55 0-1.1-.05-1.64-.14l-.36-.07-3.09 1.27.64-2.73-.24-.14C5.14 13.88 4 12.03 4 10c0-3.31 3.58-6 8-6s8 2.69 8 6-3.58 6-8 6z"/>
</svg>
<span class="icon-text">${replyCount}</span>
</span>
<!-- Reshare Icon and Count -->
<span class="meta-item">
<svg class="icon icon-reshare" viewBox="0 0 24 24" width="21" height="21" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m13.5 13.5 3 3 3-3"/>
<path d="M9.5 4.5h3a4 4 0 0 1 4 4v8m-9-9-3-3-3 3"/>
<path d="M11.5 16.5h-3a4 4 0 0 1-4-4v-8"/>
</svg>
<span class="icon-text">${repostCount}</span>
</span>
<!-- Like Icon and Count -->
<span class="meta-item">
<svg class="icon icon-like" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3 9.24 3 10.91 3.81 12 5.09 13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
<span class="icon-text">${likeCount}</span>
</span>
</div>
</div>
</div>
`;
const commentElement = document.createElement("div");
commentElement.classList.add("comment");
commentElement.innerHTML = commentHtml;
parentElement.appendChild(commentElement);
// Render child comments recursively
if (reply.replies && reply.replies.length > 0) {
const childContainer = document.createElement("div");
childContainer.classList.add("child-comments");
commentElement.appendChild(childContainer);
renderComments(reply.replies, childContainer);
}
});
};
renderComments(replies, commentList);
})
.catch(error => {
console.error("Error fetching comments:", error);
commentList.innerHTML = "<p>Error loading comments.</p>";
});
});
</script>
</div>
{% endif %}