Spaces:
Sleeping
Sleeping
| const id = getElement('metadata-post').innerText; | |
| if (id) { | |
| const host = getElement('metadata-instance').innerText; | |
| const i18nreplies = getElement('i18n--is-replies').innerHTML; | |
| const i18nreblogs = getElement('i18n--is-reblogs').innerHTML; | |
| const i18nfavourites = getElement('i18n--is-favourites').innerHTML; | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| #comments > * {width: var(--golden-ratio)} | |
| #comments noscript {margin: var(--medskip) 0} | |
| #discussion-starter {margin-bottom: var(--medskip)} | |
| #discussion-starter > footer {display: flex; align-items: center; justify-content: space-between} | |
| .mastodon-comment {margin: 1rem 0 1rem calc(var(--mul) * var(--indent)); border: 1pt solid #fff4; border-left: 2pt solid var(--ac); background: #80808008; padding: 1rem 1rem 1ex; box-shadow: 0 .5pt 1pt 0 var(--g18s); overflow: auto} | |
| .mastodon-comment .content {margin-left: 4rem; line-height: calc(var(--baselineStretch) * 1.272)} | |
| .mastodon-comment .par a {max-width: 100%; vertical-align: bottom; white-space: break-spaces} | |
| .mastodon-comment .attachments * {width: 100%; height: auto} | |
| .mastodon-comment > footer {margin-top: 1rem; margin-left: 3.5rem} | |
| .mastodon-comment > footer .stat {display: inline-flex; flex-shrink: 0; gap: 5pt} | |
| .stat a {display: inline-flex; align-items: center; padding: 2pt; color: var(--mid); gap: 2pt} | |
| .stat a::before {vertical-align: text-top} | |
| a.replies.active, a.reblogs.active {color: var(--ac)} | |
| a.favourites.active {color: var(--i3i)} | |
| .mastodon-comment .date {margin-left: auto; padding-left: 1rem; color: var(--mid); font-size: calc(10pt * var(--fontScale))} | |
| @media only screen and (max-width: 960px) { | |
| .mastodon-comment .content, .mastodon-comment > footer {margin-left: 0} | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| function escapeHtml(unsafe) { | |
| return unsafe | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| } | |
| let commentsLoaded = false; | |
| const toot_active = (toot, what) => { | |
| const count = toot[`${what}_count`]; | |
| return count > 0 ? 'active' : ''; | |
| }; | |
| const toot_count = (toot, what) => { | |
| const count = toot[`${what}_count`]; | |
| return count > 0 ? count : ''; | |
| }; | |
| const user_account = (account) => { | |
| let result = `@${account.acct}`; | |
| if (!account.acct.includes('@')) { | |
| const domain = new URL(account.url); | |
| result += `@${domain.hostname}`; | |
| } | |
| return result; | |
| }; | |
| const render_toots = (toots, in_reply_to, depth) => { | |
| const tootsToRender = toots | |
| .filter(toot => toot.in_reply_to_id === in_reply_to) | |
| .sort((a, b) => a.created_at.localeCompare(b.created_at)); | |
| tootsToRender.forEach(toot => render_toot(toots, toot, depth)); | |
| }; | |
| const 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" />` | |
| ); | |
| }); | |
| const renderAttachment = attachment => { | |
| const attachmentTypes = { | |
| image: () => `<a href="${attachment.url}" rel="nofollow"><img src="${attachment.preview_url}" alt="${attachment.description}" loading="lazy" /></a>`, | |
| video: () => `<video controls><source src="${attachment.url}" type="${attachment.mime_type}"></video>`, | |
| gifv: () => `<video autoplay loop muted playsinline><source src="${attachment.url}" type="${attachment.mime_type}"></video>`, | |
| audio: () => `<audio controls><source src="${attachment.url}" type="${attachment.mime_type}"></audio>`, | |
| default: () => `<a href="${attachment.url}" rel="nofollow">${attachment.type}</a>` | |
| }; | |
| return (attachmentTypes[attachment.type] || attachmentTypes.default)(); | |
| }; | |
| const mastodonComment = ` | |
| <article class="mastodon-comment" style="--mul: ${depth}"> | |
| <header class="author"> | |
| <img src="${escapeHtml(toot.account.avatar_static)}" height=48 width=48 alt="${user_account(toot.account)}" loading="lazy"/> | |
| <a class="has-aria-label" href="${toot.account.url}" rel="nofollow" title="${toot.account.display_name}" aria-label="${user_account(toot.account)}"> | |
| <span>${toot.account.display_name}</span> | |
| </a> | |
| </header> | |
| <div class="content"> | |
| <div class="par" data-bionRead-safe>${toot.content}</div> | |
| <div class="attachments"> | |
| ${toot.media_attachments.map(renderAttachment).join('')} | |
| </div> | |
| </div> | |
| <footer> | |
| <div class="stat"> | |
| <a class="replies ${toot_active(toot, 'replies')}" href="${toot.url}" rel="nofollow" aria-label="${i18nreplies}"> | |
| ${toot_count(toot, 'replies')} | |
| </a> | |
| <a class="reblogs ${toot_active(toot, 'reblogs')}" href="${toot.url}" rel="nofollow" aria-label="${i18nreblogs}"> | |
| ${toot_count(toot, 'reblogs')} | |
| </a> | |
| <a class="favourites ${toot_active(toot, 'favourites')}" href="${toot.url}" rel="nofollow" aria-label="${i18nfavourites}"> | |
| ${toot_count(toot, 'favourites')} | |
| </a> | |
| </div> | |
| <a class="date" href="${toot.url}" rel="nofollow"> | |
| <time>${toot.created_at.substr(0, 10)} ${toot.created_at.substr(11, 8)}</time> | |
| </a> | |
| </footer> | |
| </article>`; | |
| getElement('mastodon-comments-list') | |
| .appendChild(DOMPurify.sanitize(mastodonComment, {'RETURN_DOM_FRAGMENT': true})); | |
| render_toots(toots, toot.id, depth + 1); | |
| }; | |
| const toot_stats = toot => ` | |
| <a class="replies ${toot_active(toot, 'replies')}" href="${toot.url}" rel="nofollow" aria-label="${i18nreplies}"> | |
| ${toot_count(toot, 'replies')} | |
| </a> | |
| <a class="reblogs ${toot_active(toot, 'reblogs')}" href="${toot.url}" rel="nofollow" aria-label="${i18nreblogs}"> | |
| ${toot_count(toot, 'reblogs')} | |
| </a> | |
| <a class="favourites ${toot_active(toot, 'favourites')}" href="${toot.url}" rel="nofollow" aria-label="${i18nfavourites}"> | |
| ${toot_count(toot, 'favourites')} | |
| </a>`; | |
| const loadComments = async () => { | |
| if (commentsLoaded) return; | |
| const commentsList = getElement('mastodon-comments-list'); | |
| commentsList.innerHTML = getElement('i18n--is-loading').innerHTML; | |
| try { | |
| const [tootResponse, contextResponse] = await Promise.all([ | |
| fetch(`https://${host}/api/v1/statuses/${id}`), | |
| fetch(`https://${host}/api/v1/statuses/${id}/context`) | |
| ]); | |
| const [toot, data] = await Promise.all([ | |
| tootResponse.json(), | |
| contextResponse.json() | |
| ]); | |
| getElement("mastodon-stats").innerHTML = toot_stats(toot); | |
| if (data.descendants?.length > 0) { | |
| commentsList.innerHTML = ""; | |
| render_toots(data.descendants, id, 0); | |
| } else { | |
| commentsList.innerHTML = getElement('i18n--no-comment').innerHTML; | |
| } | |
| commentsLoaded = true; | |
| commentsList.setAttribute('aria-busy', 'false'); | |
| } catch (error) { | |
| console.error('Error loading comments:', error); | |
| commentsList.innerHTML = 'Error loading comments'; | |
| } | |
| }; | |
| const respondToVisibility = (element, callback) => { | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.intersectionRatio > 0) { | |
| callback(); | |
| } | |
| }); | |
| }, { root: null }); | |
| observer.observe(element); | |
| }; | |
| const comments = getElement("mastodon-comments-list"); | |
| respondToVisibility(comments, loadComments); | |
| } |