Compare commits

..
4 Commits
Author SHA1 Message Date
simon ebee3479b1 Load tags for the 'read more' section 2023-10-19 16:32:16 +01:00
simon ca4231f8b0 Add handlebars support for Prism 2023-10-19 16:30:19 +01:00
simon a28cc7cd39 Add comments 2023-10-19 14:51:43 +01:00
simon 9d6cc301e8 Add line before comments 2023-10-19 14:24:04 +01:00
9 changed files with 46 additions and 9 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -3,6 +3,9 @@
padding: 1em; padding: 1em;
padding-top: 0.3em; padding-top: 0.3em;
} }
.mastodon-comments-top {
padding-bottom: 1.5em;
}
.mastodon-comment { .mastodon-comment {
background-color: var(--background-color); background-color: var(--background-color);
border-radius: var(--card-border-radius); border-radius: var(--card-border-radius);
+1 -1
View File
@@ -1,5 +1,5 @@
/* PrismJS 1.29.0 /* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-twilight&languages=markup+css+clike+javascript+bash+go+go-module+json+ruby+typescript+yaml&plugins=line-highlight+line-numbers+autoloader+toolbar+copy-to-clipboard */ https://prismjs.com/download.html#themes=prism-twilight&languages=markup+css+clike+javascript+bash+go+go-module+handlebars+json+markup-templating+ruby+typescript+yaml&plugins=line-highlight+line-numbers+toolbar+copy-to-clipboard */
code[class*=language-],pre[class*=language-]{color:#fff;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;text-shadow:0 -.1em .2em #000;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}:not(pre)>code[class*=language-],pre[class*=language-]{background:#141414}pre[class*=language-]{border-radius:.5em;border:.3em solid #545454;box-shadow:1px 1px .5em #000 inset;margin:.5em 0;overflow:auto;padding:1em}pre[class*=language-]::-moz-selection{background:#27292a}pre[class*=language-]::selection{background:#27292a}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:hsla(0,0%,93%,.15)}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:hsla(0,0%,93%,.15)}:not(pre)>code[class*=language-]{border-radius:.3em;border:.13em solid #545454;box-shadow:1px 1px .3em -.1em #000 inset;padding:.15em .2em .05em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#777}.token.punctuation{opacity:.7}.token.namespace{opacity:.7}.token.boolean,.token.deleted,.token.number,.token.tag{color:#ce6849}.token.builtin,.token.constant,.token.keyword,.token.property,.token.selector,.token.symbol{color:#f9ed99}.language-css .token.string,.style .token.string,.token.attr-name,.token.attr-value,.token.char,.token.entity,.token.inserted,.token.operator,.token.string,.token.url,.token.variable{color:#909e6a}.token.atrule{color:#7385a5}.token.important,.token.regex{color:#e8c062}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.language-markup .token.attr-name,.language-markup .token.punctuation,.language-markup .token.tag{color:#ac885c}.token{position:relative;z-index:1}.line-highlight.line-highlight{background:hsla(0,0%,33%,.25);background:linear-gradient(to right,hsla(0,0%,33%,.1) 70%,hsla(0,0%,33%,0));border-bottom:1px dashed #545454;border-top:1px dashed #545454;margin-top:.75em;z-index:0}.line-highlight.line-highlight:before,.line-highlight.line-highlight[data-end]:after{background-color:#8693a6;color:#f4f1ef} code[class*=language-],pre[class*=language-]{color:#fff;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;text-shadow:0 -.1em .2em #000;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}:not(pre)>code[class*=language-],pre[class*=language-]{background:#141414}pre[class*=language-]{border-radius:.5em;border:.3em solid #545454;box-shadow:1px 1px .5em #000 inset;margin:.5em 0;overflow:auto;padding:1em}pre[class*=language-]::-moz-selection{background:#27292a}pre[class*=language-]::selection{background:#27292a}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:hsla(0,0%,93%,.15)}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:hsla(0,0%,93%,.15)}:not(pre)>code[class*=language-]{border-radius:.3em;border:.13em solid #545454;box-shadow:1px 1px .3em -.1em #000 inset;padding:.15em .2em .05em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#777}.token.punctuation{opacity:.7}.token.namespace{opacity:.7}.token.boolean,.token.deleted,.token.number,.token.tag{color:#ce6849}.token.builtin,.token.constant,.token.keyword,.token.property,.token.selector,.token.symbol{color:#f9ed99}.language-css .token.string,.style .token.string,.token.attr-name,.token.attr-value,.token.char,.token.entity,.token.inserted,.token.operator,.token.string,.token.url,.token.variable{color:#909e6a}.token.atrule{color:#7385a5}.token.important,.token.regex{color:#e8c062}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.language-markup .token.attr-name,.language-markup .token.punctuation,.language-markup .token.tag{color:#ac885c}.token{position:relative;z-index:1}.line-highlight.line-highlight{background:hsla(0,0%,33%,.25);background:linear-gradient(to right,hsla(0,0%,33%,.1) 70%,hsla(0,0%,33%,0));border-bottom:1px dashed #545454;border-top:1px dashed #545454;margin-top:.75em;z-index:0}.line-highlight.line-highlight:before,.line-highlight.line-highlight[data-end]:after{background-color:#8693a6;color:#f4f1ef}
pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)} pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)}
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
File diff suppressed because one or more lines are too long
+28
View File
@@ -1,6 +1,7 @@
const MASTODON_ACCOUNT_ID = '109285376472065471' const MASTODON_ACCOUNT_ID = '109285376472065471'
const MASTODON_HOST = 'social.sd.ai' const MASTODON_HOST = 'social.sd.ai'
// Copies the text from an element to the clipboard, and flashes the element to provide visual feedback
async function copyElementTextToClipboard(e) async function copyElementTextToClipboard(e)
{ {
const text = e.textContent const text = e.textContent
@@ -12,6 +13,7 @@ async function copyElementTextToClipboard(e)
}, 600); }, 600);
} }
// sanitise text content for display
function escapeHtml(unsafe) { function escapeHtml(unsafe) {
return unsafe return unsafe
.replace(/&/g, "&") .replace(/&/g, "&")
@@ -21,16 +23,24 @@ function escapeHtml(unsafe) {
.replace(/'/g, "'"); .replace(/'/g, "'");
} }
// renders the content - provides an array of toots, the element to add the content to and whether to show a copyable link for replies
function renderMastodonContent(toots, parentElement, showLink) { function renderMastodonContent(toots, parentElement, showLink) {
// clear the parent element so that we can add the new content
parentElement.innerHTML = '' parentElement.innerHTML = ''
// add a simple "no comments" message if there are no toots
if (!Array.isArray(toots) || toots.length === 0) { if (!Array.isArray(toots) || toots.length === 0) {
document.getElementById('mastodon-comments-list').innerHTML = "<div class='mastodon-comment'>No comments (yet)!</div>" document.getElementById('mastodon-comments-list').innerHTML = "<div class='mastodon-comment'>No comments (yet)!</div>"
return return
} }
// render each toot
for (const toot of toots) { for (const toot of toots) {
if (toot.sensitive) { if (toot.sensitive) {
// don't display toots marked as sensitive
continue continue
} }
// sanitise the toot content for display, including correctly rendering custom emojis
toot.account.display_name = escapeHtml(toot.account.display_name) toot.account.display_name = escapeHtml(toot.account.display_name)
toot.account.emojis.forEach(emoji => { toot.account.emojis.forEach(emoji => {
toot.account.display_name = toot.account.display_name.replace(`:${emoji.shortcode}:`, toot.account.display_name = toot.account.display_name.replace(`:${emoji.shortcode}:`,
@@ -40,6 +50,8 @@ function renderMastodonContent(toots, parentElement, showLink) {
toot.content = toot.content.replace(`:${emoji.shortcode}:`, toot.content = toot.content.replace(`:${emoji.shortcode}:`,
`<img src="${escapeHtml(emoji.static_url)}" alt="Emoji ${emoji.shortcode}" class="mastodon-emoji" />`); `<img src="${escapeHtml(emoji.static_url)}" alt="Emoji ${emoji.shortcode}" class="mastodon-emoji" />`);
}) })
// create a block of HTML content including the toot data
const comment = const comment =
`<div class="mastodon-comment"> `<div class="mastodon-comment">
<div class="mastodon-avatar"> <div class="mastodon-avatar">
@@ -69,30 +81,44 @@ function renderMastodonContent(toots, parentElement, showLink) {
</div> </div>
</div> </div>
</div>` </div>`
// Use DOMPurify to create a sanitised element for the toot
const child = DOMPurify.sanitize(comment, {'RETURN_DOM_FRAGMENT': true}); const child = DOMPurify.sanitize(comment, {'RETURN_DOM_FRAGMENT': true});
// make all toot links clickable
const links = child.querySelectorAll('.tootlink'); const links = child.querySelectorAll('.tootlink');
for (const link of links) { for (const link of links) {
link.onclick = function() { return copyElementTextToClipboard(this); } link.onclick = function() { return copyElementTextToClipboard(this); }
} }
// insert the toot into the DOM
parentElement.appendChild(child); parentElement.appendChild(child);
} }
} }
// We set this in the "code injection" footer for any page for which we want to enable comments
let MASTODON_POST_ID let MASTODON_POST_ID
// when the page has finished loading, send a request for the toots
document.addEventListener("DOMContentLoaded", async (event) => { document.addEventListener("DOMContentLoaded", async (event) => {
let url, isComments let url, isComments
// if we're being crawled, don't render comments - may help against spam
const isBot = /bot|google|baidu|bing|msn|teoma|slurp|yandex/i const isBot = /bot|google|baidu|bing|msn|teoma|slurp|yandex/i
.test(navigator.userAgent) .test(navigator.userAgent)
// if there is a sidebar, we're expecting to load the toots from the main account
if (document.getElementsByClassName('gh-sidebar').length > 0) { if (document.getElementsByClassName('gh-sidebar').length > 0) {
url = `https://${MASTODON_HOST}/api/v1/accounts/${MASTODON_ACCOUNT_ID}/statuses?exclude_replies=true&exclude_reblogs=true` url = `https://${MASTODON_HOST}/api/v1/accounts/${MASTODON_ACCOUNT_ID}/statuses?exclude_replies=true&exclude_reblogs=true`
} }
// if there's a post ID and we're not a bot, we're expecting to load the replies from a specific toot
if (MASTODON_POST_ID && !isBot) { if (MASTODON_POST_ID && !isBot) {
url = `https://${MASTODON_HOST}/api/v1/statuses/${MASTODON_POST_ID}/context` url = `https://${MASTODON_HOST}/api/v1/statuses/${MASTODON_POST_ID}/context`
isComments = true isComments = true
} }
// find the element to append the content to - if there isn't one, we don't need to query
const element = document.getElementById('mastodon-comments-list') const element = document.getElementById('mastodon-comments-list')
if (url && element) { if (url && element) {
// populate the link to the source toot, if necessary (for replies)
const linkElement = document.getElementById('toot-link-top') const linkElement = document.getElementById('toot-link-top')
const clipElement = document.getElementById('toot-link-clip') const clipElement = document.getElementById('toot-link-clip')
const tootUrl = `https://${MASTODON_HOST}/@s/${MASTODON_POST_ID}` const tootUrl = `https://${MASTODON_HOST}/@s/${MASTODON_POST_ID}`
@@ -102,11 +128,13 @@ document.addEventListener("DOMContentLoaded", async (event) => {
if (clipElement) { if (clipElement) {
clipElement.innerText = tootUrl clipElement.innerText = tootUrl
} }
// fetch the data from Mastodon
const response = await fetch(url) const response = await fetch(url)
let content = await response.json() let content = await response.json()
if (isComments) { if (isComments) {
content = content.descendants content = content.descendants
} }
// render the content into the page
const header = document.getElementById('mastodon-comments-header') const header = document.getElementById('mastodon-comments-header')
if (header) { if (header) {
header.style.display = '' header.style.display = ''
+7 -2
View File
@@ -49,9 +49,14 @@
</article> </article>
<section class="gh-comments gh-canvas"> <section class="gh-comments gh-canvas">
<div id="mastodon-comments-header" style="display: none;"> <div id="mastodon-comments-header" style="display: none;">
<div class="mastodon-comments-top">
<hr/>
</div>
<h2>Comments</h2> <h2>Comments</h2>
<p>Comments powered by Mastodon. Reply to <a id="toot-link-top">this status</a> to comment.</p> <p>Comments powered by Mastodon. Reply to <a id="toot-link-top">this status</a> to comment.</p>
<div class="tootlink-tip"> <div class="tootlink-tip">
@@ -74,7 +79,7 @@
{{/post}} {{/post}}
{{#get "posts" include="authors" filter="id:-{{post.id}}" limit="4" as |next|}} {{#get "posts" include="authors,tags" filter="id:-{{post.id}}" limit="4" as |next|}}
{{#if next}} {{#if next}}
<section class="gh-container is-grid gh-outer"> <section class="gh-container is-grid gh-outer">
<div class="gh-container-inner gh-inner"> <div class="gh-container-inner gh-inner">