ghost-theme-sd.ai/assets/js/mastodon.js

145 lines
6.0 KiB
JavaScript
Raw Normal View History

2023-10-17 10:17:37 +01:00
const MASTODON_ACCOUNT_ID = '109285376472065471'
const MASTODON_HOST = 'social.sd.ai'
2023-10-19 14:51:43 +01:00
// Copies the text from an element to the clipboard, and flashes the element to provide visual feedback
2023-10-17 10:17:37 +01:00
async function copyElementTextToClipboard(e)
{
const text = e.textContent
2023-10-17 14:18:00 +01:00
await navigator.clipboard.writeText(text)
2023-10-17 10:17:37 +01:00
e.classList.add('tootClick');
setTimeout(() => {
e.classList.remove('tootClick');
}, 600);
}
2023-10-19 14:51:43 +01:00
// sanitise text content for display
2023-10-17 10:17:37 +01:00
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
2023-10-19 14:51:43 +01:00
// renders the content - provides an array of toots, the element to add the content to and whether to show a copyable link for replies
2023-10-17 14:18:00 +01:00
function renderMastodonContent(toots, parentElement, showLink) {
2023-10-19 14:51:43 +01:00
// clear the parent element so that we can add the new content
2023-10-17 14:18:00 +01:00
parentElement.innerHTML = ''
2023-10-19 14:51:43 +01:00
// add a simple "no comments" message if there are no toots
2023-10-17 10:17:37 +01:00
if (!Array.isArray(toots) || toots.length === 0) {
document.getElementById('mastodon-comments-list').innerHTML = "<div class='mastodon-comment'>No comments (yet)!</div>"
return
}
2023-10-19 14:51:43 +01:00
// render each toot
2023-10-17 10:17:37 +01:00
for (const toot of toots) {
2023-10-17 14:18:00 +01:00
if (toot.sensitive) {
2023-10-19 14:51:43 +01:00
// don't display toots marked as sensitive
2023-10-17 14:18:00 +01:00
continue
}
2023-10-19 14:51:43 +01:00
// sanitise the toot content for display, including correctly rendering custom emojis
2023-10-17 10:17:37 +01:00
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}" class="mastodon-emoji" />`);
})
toot.emojis.forEach(emoji => {
toot.content = toot.content.replace(`:${emoji.shortcode}:`,
`<img src="${escapeHtml(emoji.static_url)}" alt="Emoji ${emoji.shortcode}" class="mastodon-emoji" />`);
})
2023-10-19 14:51:43 +01:00
// create a block of HTML content including the toot data
2023-10-17 10:17:37 +01:00
const comment =
`<div class="mastodon-comment">
<div class="mastodon-avatar">
<img src="${escapeHtml(toot.account.avatar_static)}" height=60 width=60 alt="${escapeHtml(toot.account.display_name)}'s avatar">
</div>
<div class="mastodon-body">
<div class="mastodon-meta">
<div class="mastodon-author">
<div class="mastodon-author-link">
2023-10-17 17:04:52 +01:00
<a href="${toot.account.url}" target="_blank" rel="nofollow">
2023-10-17 10:17:37 +01:00
<span>${toot.account.display_name}</span>
</a>
<br/>
<span class="mastodon-author-uid">(@${escapeHtml(toot.account.acct === 's' ? 's@sd.ai' : toot.account.acct)})</span>
</div>
</div>
<div class="toot-link">
2023-10-17 14:18:00 +01:00
<a class="date" href="${toot.uri}" rel="nofollow" target="_blank">
2023-10-17 10:17:37 +01:00
${toot.created_at.substring(0, 10)}
</a>
<br/>
</div>
</div>
<div class="mastodon-comment-content">
${toot.content}
2023-10-17 14:18:00 +01:00
<span class="tootlink" ${showLink ? '' : 'style="display: none;"'}>${toot.uri}</span>
2023-10-17 10:17:37 +01:00
</div>
</div>
</div>`
2023-10-19 14:51:43 +01:00
// Use DOMPurify to create a sanitised element for the toot
2023-10-17 10:17:37 +01:00
const child = DOMPurify.sanitize(comment, {'RETURN_DOM_FRAGMENT': true});
2023-10-19 14:51:43 +01:00
// make all toot links clickable
2023-10-17 10:17:37 +01:00
const links = child.querySelectorAll('.tootlink');
for (const link of links) {
2023-10-17 14:18:00 +01:00
link.onclick = function() { return copyElementTextToClipboard(this); }
2023-10-17 10:17:37 +01:00
}
2023-10-19 14:51:43 +01:00
// insert the toot into the DOM
2023-10-17 10:17:37 +01:00
parentElement.appendChild(child);
}
}
2023-10-19 14:51:43 +01:00
// We set this in the "code injection" footer for any page for which we want to enable comments
2023-10-17 14:18:00 +01:00
let MASTODON_POST_ID
2023-10-19 14:51:43 +01:00
// when the page has finished loading, send a request for the toots
2023-10-17 10:17:37 +01:00
document.addEventListener("DOMContentLoaded", async (event) => {
2023-10-17 14:18:00 +01:00
let url, isComments
2023-10-19 14:51:43 +01:00
// if we're being crawled, don't render comments - may help against spam
2023-10-17 14:18:00 +01:00
const isBot = /bot|google|baidu|bing|msn|teoma|slurp|yandex/i
.test(navigator.userAgent)
2023-10-19 14:51:43 +01:00
// if there is a sidebar, we're expecting to load the toots from the main account
2023-10-17 10:17:37 +01:00
if (document.getElementsByClassName('gh-sidebar').length > 0) {
2023-10-17 14:18:00 +01:00
url = `https://${MASTODON_HOST}/api/v1/accounts/${MASTODON_ACCOUNT_ID}/statuses?exclude_replies=true&exclude_reblogs=true`
}
2023-10-19 14:51:43 +01:00
// if there's a post ID and we're not a bot, we're expecting to load the replies from a specific toot
2023-10-17 14:18:00 +01:00
if (MASTODON_POST_ID && !isBot) {
url = `https://${MASTODON_HOST}/api/v1/statuses/${MASTODON_POST_ID}/context`
isComments = true
}
2023-10-19 14:51:43 +01:00
// find the element to append the content to - if there isn't one, we don't need to query
2023-10-17 14:18:00 +01:00
const element = document.getElementById('mastodon-comments-list')
if (url && element) {
2023-10-19 14:51:43 +01:00
// populate the link to the source toot, if necessary (for replies)
2023-10-17 14:18:00 +01:00
const linkElement = document.getElementById('toot-link-top')
const clipElement = document.getElementById('toot-link-clip')
const tootUrl = `https://${MASTODON_HOST}/@s/${MASTODON_POST_ID}`
if (linkElement) {
linkElement.href = tootUrl
}
if (clipElement) {
clipElement.innerText = tootUrl
}
2023-10-19 14:51:43 +01:00
// fetch the data from Mastodon
2023-10-17 14:18:00 +01:00
const response = await fetch(url)
let content = await response.json()
if (isComments) {
content = content.descendants
}
2023-10-19 14:51:43 +01:00
// render the content into the page
2023-10-17 14:18:00 +01:00
const header = document.getElementById('mastodon-comments-header')
if (header) {
header.style.display = ''
}
return renderMastodonContent(content, element, isComments)
2023-10-17 10:17:37 +01:00
}
})