From 42346db8fb9e05575352ca7ad65f6faa81c9aea9 Mon Sep 17 00:00:00 2001 From: Hendrik Langer Date: Fri, 7 Jun 2024 16:11:43 +0200 Subject: [PATCH] add mastodon comments extension --- .../mastodon-comments/_extension.yml | 8 + .../mastodon-comments/mastodon-comments.js | 347 ++++++++++++++++++ .../mastodon-comments/mastodon-comments.lua | 46 +++ 3 files changed, 401 insertions(+) create mode 100644 _extensions/AndreasThinks/mastodon-comments/_extension.yml create mode 100644 _extensions/AndreasThinks/mastodon-comments/mastodon-comments.js create mode 100644 _extensions/AndreasThinks/mastodon-comments/mastodon-comments.lua diff --git a/_extensions/AndreasThinks/mastodon-comments/_extension.yml b/_extensions/AndreasThinks/mastodon-comments/_extension.yml new file mode 100644 index 0000000..7142a02 --- /dev/null +++ b/_extensions/AndreasThinks/mastodon-comments/_extension.yml @@ -0,0 +1,8 @@ +title: Mastodon-comments +author: Andreas Varotsis +version: 1.0.1 +quarto-required: ">=1.3.0" +contributes: + filters: + - mastodon-comments.lua + diff --git a/_extensions/AndreasThinks/mastodon-comments/mastodon-comments.js b/_extensions/AndreasThinks/mastodon-comments/mastodon-comments.js new file mode 100644 index 0000000..7f28256 --- /dev/null +++ b/_extensions/AndreasThinks/mastodon-comments/mastodon-comments.js @@ -0,0 +1,347 @@ +const styles = ` +:root { + --font-color: #5d686f; + --font-size: 1.0rem; + + --block-border-width: 1px; + --block-border-radius: 3px; + --block-border-color: #ededf0; + --block-background-color: #f7f8f8; + + --comment-indent: 40px; +} + +#mastodon-stats { + text-align: center; + font-size: calc(var(--font-size) * 2) +} + +#mastodon-comments-list { + margin: 0 auto; + margin-top: 1rem; +} + +.mastodon-comment { + background-color: var(--block-background-color); + border-radius: var(--block-border-radius); + border: var(--block-border-width) var(--block-border-color) solid; + padding: 20px; + margin-bottom: 1.5rem; + display: flex; + flex-direction: column; + color: var(--font-color); + font-size: var(--font-size); +} + +.mastodon-comment p { + margin-bottom: 0px; +} + +.mastodon-comment .author { + padding-top:0; + display:flex; +} + +.mastodon-comment .author a { + text-decoration: none; +} + +.mastodon-comment .author .avatar img { + margin-right:1rem; + min-width:60px; + border-radius: 5px; +} + +.mastodon-comment .author .details { + display: flex; + flex-direction: column; +} + +.mastodon-comment .author .details .name { + font-weight: bold; +} + +.mastodon-comment .author .details .user { + color: #5d686f; + font-size: medium; +} + +.mastodon-comment .author .date { + margin-left: auto; + font-size: small; +} + +.mastodon-comment .content { + margin: 15px 20px; +} + +.mastodon-comment .attachments { + margin: 0px 10px; +} + +.mastodon-comment .attachments > * { + margin: 0px 10px; +} + +.mastodon-comment .attachments img { + max-width: 100%; +} + +.mastodon-comment .content p:first-child { + margin-top:0; + margin-bottom:0; +} + +.mastodon-comment .status > div, #mastodon-stats > div { + display: inline-block; + margin-right: 15px; +} + +.mastodon-comment .status a, #mastodon-stats a { + color: #5d686f; + text-decoration: none; +} + +.mastodon-comment .status .replies.active a, #mastodon-stats .replies.active a { + color: #003eaa; +} + +.mastodon-comment .status .reblogs.active a, #mastodon-stats .reblogs.active a { + color: #8c8dff; +} + +.mastodon-comment .status .favourites.active a, #mastodon-stats .favourites.active a { + color: #ca8f04; +} +`; + +class MastodonComments extends HTMLElement { + constructor() { + super(); + + // Retrieve the host, user, and tootId from global variables + this.host = mastodonHost; // Previously this.getAttribute("host") + this.user = mastodonUser; // Previously this.getAttribute("user") + this.tootId = mastodonTootId; // Previously this.getAttribute("tootId") + + this.commentsLoaded = false; + + const styleElem = document.createElement("style"); + styleElem.innerHTML = styles; + document.head.appendChild(styleElem); + } + + + connectedCallback() { + this.innerHTML = ` + +

Comments

+ + +

You can use your Fediverse (i.e. Mastodon, among many others) account to reply to this post. +

+
+

+ `; + + const comments = document.getElementById("mastodon-comments-list"); + const rootStyle = this.getAttribute("style"); + if (rootStyle) { + comments.setAttribute("style", rootStyle); + } + this.respondToVisibility(comments, this.loadComments.bind(this)); + } + + escapeHtml(unsafe) { + return (unsafe || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + + toot_active(toot, what) { + var count = toot[what + "_count"]; + return count > 0 ? "active" : ""; + } + + toot_count(toot, what) { + var count = toot[what + "_count"]; + return count > 0 ? count : ""; + } + + toot_stats(toot) { + return ` +
+ ${this.toot_count( + toot, + "replies", + )} +
+
+ ${this.toot_count( + toot, + "reblogs", + )} +
+
+ ${this.toot_count( + toot, + "favourites", + )} +
+ `; + } + + user_account(account) { + var result = `@${account.acct}`; + if (account.acct.indexOf("@") === -1) { + var domain = new URL(account.url); + result += `@${domain.hostname}`; + } + return result; + } + + render_toots(toots, in_reply_to, depth) { + var 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) => this.render_toot(toots, toot, depth)); + } + + render_toot(toots, toot, depth) { + toot.account.display_name = this.escapeHtml(toot.account.display_name); + toot.account.emojis.forEach((emoji) => { + toot.account.display_name = toot.account.display_name.replace( + `:${emoji.shortcode}:`, + `Emoji ${
+          emoji.shortcode
+        }`, + ); + }); + + const mastodonComment = `
+ +
${toot.content}
+
+ ${toot.media_attachments + .map((attachment) => { + if (attachment.type === "image") { + return `${this.escapeHtml(attachment.description)}`; + } else if (attachment.type === "video") { + return ``; + } else if (attachment.type === "gifv") { + return ``; + } else if (attachment.type === "audio") { + return ``; + } else { + return `${attachment.type}`; + } + }) + .join("")} +
+
+ ${this.toot_stats(toot)} +
+
`; + + var div = document.createElement("div"); + div.innerHTML = + typeof DOMPurify !== "undefined" + ? DOMPurify.sanitize(mastodonComment.trim()) + : mastodonComment.trim(); + document + .getElementById("mastodon-comments-list") + .appendChild(div.firstChild); + + this.render_toots(toots, toot.id, depth + 1); + } + + loadComments() { + if (this.commentsLoaded) return; + + document.getElementById("mastodon-comments-list").innerHTML = + "Loading comments from the Fediverse..."; + + let _this = this; + + fetch("https://" + this.host + "/api/v1/statuses/" + this.tootId) + .then((response) => response.json()) + .then((toot) => { + document.getElementById("mastodon-stats").innerHTML = + this.toot_stats(toot); + }); + + fetch( + "https://" + this.host + "/api/v1/statuses/" + this.tootId + "/context", + ) + .then((response) => response.json()) + .then((data) => { + if ( + data["descendants"] && + Array.isArray(data["descendants"]) && + data["descendants"].length > 0 + ) { + document.getElementById("mastodon-comments-list").innerHTML = ""; + _this.render_toots(data["descendants"], _this.tootId, 0); + } else { + document.getElementById("mastodon-comments-list").innerHTML = + "

No comments found

"; + } + + _this.commentsLoaded = true; + }); + } + + respondToVisibility(element, callback) { + var options = { + root: null, + }; + + var observer = new IntersectionObserver((entries, observer) => { + entries.forEach((entry) => { + if (entry.intersectionRatio > 0) { + callback(); + } + }); + }, options); + + observer.observe(element); + } +} + +customElements.define("mastodon-comments", MastodonComments); \ No newline at end of file diff --git a/_extensions/AndreasThinks/mastodon-comments/mastodon-comments.lua b/_extensions/AndreasThinks/mastodon-comments/mastodon-comments.lua new file mode 100644 index 0000000..27c50d3 --- /dev/null +++ b/_extensions/AndreasThinks/mastodon-comments/mastodon-comments.lua @@ -0,0 +1,46 @@ +local function ensureHtmlDeps() + quarto.doc.addHtmlDependency({ + name = 'mastodon-comments', + version = '1.0.0', + scripts = {"mastodon-comments.js"}, + }) +end + +function Meta(m) + ensureHtmlDeps() + if m.mastodon_comments and m.mastodon_comments.user and m.mastodon_comments.toot_id and m.mastodon_comments.host then + local user = pandoc.utils.stringify(m.mastodon_comments.user) + local toot_id = pandoc.utils.stringify(m.mastodon_comments.toot_id) + local host = pandoc.utils.stringify(m.mastodon_comments.host) + local mastodon_html = '' + + -- JavaScript to inject Mastodon comments into a specific div + local inject_script = [[ + +]] + + -- Include external scripts and styles directly + local script_html = '' + local css_html = '' + + -- Insert these elements in the document's head + quarto.doc.includeText("in-header", script_html .. css_html .. inject_script) + + -- JavaScript variable definitions + local js_vars = '' + + -- Include JavaScript variables in the header + quarto.doc.includeText("in-header", js_vars) + end +end