Switched comment system to comtodon.

This commit is contained in:
tastytea 2019-11-03 07:42:56 +01:00
parent 45488a2c62
commit d3bfd509b9
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
17 changed files with 278 additions and 374 deletions

View File

@ -22,6 +22,9 @@ enableGitInfo = true
opengraph = true
schema = true
[params.comtodon]
domain = "likeable.space"
[taxonomies]
tags = "tags"

View File

@ -4,6 +4,7 @@ description: "Edit remote files with Emacs using SSH and a wrapper for emacsclie
date: "2019-05-08T15:59:00+02:00"
draft: false
tags: ["emacs", "ssh"]
comtodon: 9ibPpjSsYeNmHrbBDc
---
It took me a long time to collect all the bits and pieces I needed to make
@ -234,7 +235,3 @@ echo 'Defaults env_keep += "SSH_CONNECTION"' >> /etc/sudoers.d/ssh_vars
* Updated 2019-10-06: Support files with spaces in emacsremote and allow to
open files the user can't read (for use with emacsremote --sudo).
* Updated 2019-10-17: Added Zsh-hack to prevent hanging TRAMP-connections.
== Comments
{{% mastodon-api-comments instance="likeable.space" status_id="9ibPpjSsYeNmHrbBDc" %}}

View File

@ -4,6 +4,7 @@ description: "How to archive articles you read online locally and how to find th
date: "2019-09-26T06:10:07+02:00"
draft: false
tags: ["remwharead", "bookmarks", "archive", "Tooting my own horn"]
comtodon: 9nIqtmAXvu4harUb7Q
---
:wp-asciidoc: https://en.wikipedia.org/wiki/AsciiDoc
@ -141,7 +142,3 @@ remwharead -e rss -T $(date -d "-1 week" -I),$(date -Iminutes) \
----
TIP: Put that script into `/etc/cron.hourly/` to update your feed once an hour.
== Comments
{{% mastodon-api-comments instance="likeable.space" status_id="9nIqtmAXvu4harUb7Q" %}}

View File

@ -109,7 +109,3 @@ In your Gitea directory, create `custom/templates/custom/header.tmpl`.
}
</style>{{< / highlight >}}
----
== Comments
{{% mastodon-api-comments instance="soc.ialis.me" status_id="101483651770835303" %}}

View File

@ -6,6 +6,7 @@ draft: false
tags:
- wireguard
- vpn
comtodon: 9fqBQKGZa6qMf98wIC
---
I wanted to create a https://en.wikipedia.org/wiki/WireGuard[WireGuard] VPN with
@ -170,7 +171,3 @@ by Stavros Korokithakis helped me a great deal in understanding WireGuard.
* Updated 2019-02-16 to include IP forwarding.
* Updated 2019-02-16 with information on how to turn SLAAC back on.
== Comments
{{% mastodon-api-comments instance="likeable.space" status_id="9fqBQKGZa6qMf98wIC" %}}

File diff suppressed because one or more lines are too long

View File

@ -1,110 +0,0 @@
/* globals MastodonAPI */
function fetch_mastodon_api_comments(instance, status_id)
{
const root = document.getElementById("mastodon-api-comments_" + status_id);
let api = new MastodonAPI(
{
instance: "https://" + instance,
api_user_token: ""
});
// Get the URL of the status and write the intro.
api.get("statuses/" + status_id, function(data)
{
root.appendChild(comments_intro(data.url));
});
// Get all the replies to the status and write them to the page.
api.get("statuses/" + status_id + "/context", function(data)
{
write_comments(root, data);
});
}
function comments_intro(url)
{
const p = document.createElement("p");
const a = document.createElement("a");
p.appendChild(document.createTextNode("You can "));
p.setAttribute("class", "mastodon-api-comment-intro");
a.setAttribute("href", url);
a.appendChild(
document.createTextNode("comment on this post in the Fediverse"));
p.appendChild(a);
p.appendChild(document.createTextNode("."));
return p;
}
function write_comments(root, data)
{
for (const status of data.descendants)
{
const content = add_emojis(status.content, status.emojis);
const div = document.createElement("div");
div.setAttribute("class", "mastodon-api-comment");
div.appendChild(author_html(status));
if (status.spoiler_text.length > 0)
{
div.appendChild(subject_html(status));
}
const content_p = document.createElement("p");
content_p.setAttribute("class", "mastodon-api-comment-content");
content_p.innerHTML = content;
div.appendChild(content_p);
root.appendChild(div);
}
}
function add_emojis(text, emojis)
{
for (const emoji of emojis)
{
text = text.replace(
':' + emoji.shortcode + ':',
'<img class="mastodon-api-comment-emoji" src="' + emoji.url + '">');
}
return text;
}
function author_html(status)
{
const p = document.createElement("p");
p.setAttribute("class", "mastodon-api-comment-author");
const img = document.createElement("img");
img.setAttribute("class", "mastodon-api-comment-avatar");
img.setAttribute("src", status.account.avatar);
p.appendChild(img);
p.innerHTML += " ";
const strong = document.createElement("strong");
strong.appendChild(document.createTextNode(status.account.display_name));
p.appendChild(strong);
p.appendChild(
document.createTextNode(" (" + status.account.acct + ") wrote on "
+ get_status_time(status) + ":"));
return p;
}
function subject_html(status)
{
const p = document.createElement("p");
p.setAttribute("class", "mastodon-api-comment-subject");
const subject = document.createElement("strong");
subject.innerHTML += add_emojis(status.spoiler_text, status.emojis);
p.appendChild(subject);
return p;
}
// Human readable time, YYYY-MM-DD HH:MM.
function get_status_time(status)
{
return status.created_at.substr(0, 16).replace('T', ' ');
}

View File

@ -1,223 +0,0 @@
// mastodon javascript lib
// by @kirschn@pleasehug.me 2017
// no fucking copyright
// do whatever you want with it
// but please don't hurt it (and keep this header)
var MastodonAPI = function (config) {
var apiBase = config.instance + "/api/v1/";
function checkArgs(args) {
var checkedArgs;
if (typeof args[1] === "function") {
checkedArgs = { data: {}, callback: args[1] };
} else {
checkedArgs = { data: args[1], callback: args[2] };
}
return checkedArgs;
}
function addAuthorizationHeader(headers, token) {
if (token) {
headers.Authorization = "Bearer " + token;
}
return headers;
}
function onAjaxSuccess(url, op, callback, logData) {
return function (data, textStatus, xhr) {
console.log("Successful " + op + " API request to " + url,
", status: " + textStatus,
", HTTP status: " + xhr.status,
", data: " + (logData ? JSON.stringify(data) : "<skipped>"));
if (typeof callback !== "undefined") {
callback(data, textStatus);
}
};
}
function onAjaxError(url, op) {
return function (xhr, textStatus, errorThrown) {
console.error("Failed " + op + " API request to " + url,
", status: " + textStatus,
", error: " + errorThrown,
", HTTP status: " + xhr.status,
", response JSON: " + JSON.stringify(xhr.responseJSON));
};
}
return {
setConfig: function (key, value) {
// modify initial config afterwards
config[key] = value;
},
getConfig: function (key) {
// get config key
return config[key];
},
get: function (endpoint) {
// for GET API calls
// can be called with two or three parameters
// endpoint, callback
// or
// endpoint, queryData, callback
// where queryData is an object { paramname1: "paramvalue1", paramname2: paramvalue2 }
var args = checkArgs(arguments);
var queryData = args.data;
var callback = args.callback;
var url = apiBase + endpoint;
// ajax function
return $.ajax({
url: url,
type: "GET",
data: queryData,
headers: addAuthorizationHeader({}, config.api_user_token),
success: onAjaxSuccess(url, "GET", callback, false),
error: onAjaxError(url, "GET")
});
},
patch: function (endpoint) {
// for PATCH API calls
var args = checkArgs(arguments);
var postData = args.data;
var callback = args.callback;
var url = apiBase + endpoint;
return $.ajax({
url: url,
type: "PATCH",
data: postData,
headers: addAuthorizationHeader({}, config.api_user_token),
success: onAjaxSuccess(url, "POST", callback, false),
error: onAjaxError(url, "POST")
});
},
post: function (endpoint) {
// for POST API calls
var args = checkArgs(arguments);
var postData = args.data;
var callback = args.callback;
var url = apiBase + endpoint;
return $.ajax({
url: url,
type: "POST",
data: postData,
headers: addAuthorizationHeader({}, config.api_user_token),
success: onAjaxSuccess(url, "POST", callback, false),
error: onAjaxError(url, "POST")
});
},
postMedia: function (endpoint) {
// for POST API calls
var args = checkArgs(arguments);
var postData = args.data;
var callback = args.callback;
var url = apiBase + endpoint;
return $.ajax({
url: url,
type: "POST",
data: postData,
contentType: false,
processData: false,
headers: addAuthorizationHeader({}, config.api_user_token),
success: onAjaxSuccess(url, "POST MEDIA", callback, false),
error: onAjaxError(url, "POST MEDIA")
});
},
delete: function (endpoint, callback) {
// for DELETE API calls.
var url = apiBase + endpoint;
return $.ajax({
url: url,
type: "DELETE",
headers: addAuthorizationHeader({}, config.api_user_token),
success: onAjaxSuccess(url, "DELETE", callback, false),
error: onAjaxError(url, "DELETE")
});
},
stream: function (streamType, onData) {
// Event Stream Support
// websocket streaming is undocumented. i had to reverse engineer the fucking web client.
// streamType is either
// user for your local home TL and notifications
// public for your federated TL
// public:local for your home TL
// hashtag&tag=fuckdonaldtrump for the stream of #fuckdonaldtrump
// callback gets called whenever new data ist recieved
// callback { event: (eventtype), payload: {mastodon object as described in the api docs} }
// eventtype could be notification (=notification) or update (= new toot in TL)
var es = new WebSocket("wss://" + apiBase.substr(8)
+ "streaming?access_token=" + config.api_user_token + "&stream=" + streamType);
var listener = function (event) {
console.log("Got Data from Stream " + streamType);
event = JSON.parse(event.data);
event.payload = JSON.parse(event.payload);
onData(event);
};
es.onmessage = listener;
},
registerApplication: function (client_name, redirect_uri, scopes, website, callback) {
// register a new application
// OAuth Auth flow:
// First register the application
// 2) get a access code from a user (using the link, generation function below!)
// 3) insert the data you got from the application and the code from the user into
// getAccessTokenFromAuthCode. Note: scopes has to be an array, every time!
// For example ["read", "write"]
// determine which parameters we got
if (website === null) {
website = "";
}
// build scope array to string for the api request
if (typeof scopes !== "string") {
scopes = scopes.join(" ");
}
var url = apiBase + "apps";
return $.ajax({
url: url,
type: "POST",
data: {
client_name: client_name,
redirect_uris: redirect_uri,
scopes: scopes,
website: website
},
success: onAjaxSuccess(url, "REGISTER", callback, true),
error: onAjaxError(url, "REGISTER")
});
},
generateAuthLink: function (client_id, redirect_uri, responseType, scopes) {
return config.instance + "/oauth/authorize?client_id=" + client_id + "&redirect_uri=" + redirect_uri +
"&response_type=" + responseType + "&scope=" + scopes.join("+");
},
getAccessTokenFromAuthCode: function (client_id, client_secret, redirect_uri, code, callback) {
var url = config.instance + "/oauth/token";
return $.ajax({
url: url,
type: "POST",
data: {
client_id: client_id,
client_secret: client_secret,
redirect_uri: redirect_uri,
grant_type: "authorization_code",
code: code
},
success: onAjaxSuccess(url, "TOKEN", callback, true),
error: onAjaxError(url, "TOKEN")
});
}
};
};
// node.js
if (typeof module !== "undefined") { module.exports = MastodonAPI; }

View File

@ -1,3 +0,0 @@
<script type="application/javascript" src="/scripts/jquery-3.4.1.min.js"></script>
<script type="application/javascript" src="/scripts/mastodon.js"></script>
<script type="application/javascript" src="/scripts/mastodon-api-comments.js"></script>

View File

@ -1,10 +0,0 @@
{{ $instance := .Get "instance" | default "" }}
{{ $status_id := .Get "status_id" | default "" }}
{{ partial "mastodon-api-comments-scripts.html" . }}
<style type="text/css">.mastodon-api-comment-avatar,.mastodon-api-comment-emoji{height:1em;}</style>
<div id="mastodon-api-comments_{{ $status_id }}" class="mastodon-api-comments"></div>
<noscript><p>Comments only work with JavaScript enabled.</p></noscript>
<script type="application/javascript">
fetch_mastodon_api_comments("{{ $instance }}", "{{ $status_id }}");
</script>

106
static/comtodon.css Normal file
View File

@ -0,0 +1,106 @@
@charset "UTF-8";
.comtodon .status {
position: relative;
padding: 0.5em;
margin-bottom: 0.5em;
margin-left: 1em;
border: 0.1em solid black;
}
.comtodon .status:nth-child(even) {
background-color:#0402;
}
.comtodon .status:nth-child(odd) {
background-color:#2402;
}
.comtodon .status-content, .comtodon .reply-main {
margin-bottom: .5em;
}
.comtodon .status-content {
padding-left: 0.1em;
}
.comtodon .status-content.sensitive {
content: 'Sensitive';
}
.comtodon .replies {
margin-left: 1em;
}
.comtodon p {
margin: .1em;
}
.comtodon .emoji {
height: 1em;
}
.comtodon .author {
display: -ms-grid;
display: grid;
-ms-grid-columns: 3.3em auto;
grid-template-columns: 3.3em auto;
-ms-grid-rows: (1em)[3];
grid-template-rows: repeat(3, 1em);
grid-gap: 0.1em;
}
.comtodon .author .avatar {
-ms-grid-row: 1;
-ms-grid-row-span: 3;
-ms-grid-column: 1;
grid-area: 1 / 1 / 4 / 1;
height: 100%;
}
.comtodon a.author {
text-decoration: none;
color: var(--blue);
}
.comtodon .author .name {
font-weight: bold;
}
.comtodon .author .acct:before {
content: '@';
}
.comtodon .date {
float: right;
}
.comtodon .spoiler {
font-weight: bold;
}
.comtodon .reply-main {
display: inline-block;
padding: .1em .3em;
}
.comtodon .reply-main:after {
content: ' in the Fediverse';
}
.comtodon .reply {
visibility: hidden;
position: absolute;
right: .1em;
top: 2em;
}
.comtodon .reply:after {
content: 'reply';
visibility: visible;
display: block;
position: absolute;
right: 0;
bottom: 0;
font-size: .8em;
padding: .1em .3em;
}

108
static/comtodon.js Normal file
View File

@ -0,0 +1,108 @@
(function () {
function fail(el, text) {
el.innerHTML = 'Loading fail =('
console.error(text, el)
}
function required(el, field) {
const val = el.dataset[field]
if (val) {
return val
}
fail(el, `Missing data-${field} attribut`)
}
function h(classes, content, tag = 'div') {
return `<${tag} class="${classes}">${content}</${tag}>`
}
const TIMES = new Map([
['second', 1000],
['minute', 60],
['hour', 60],
['day', 24],
['month', 30.5],
['year', 12],
['century', 100]
])
function ago(date) {
const now = Date.now()
const target = Number(new Date(date))
const prefix = target > now ? 'in ' : ''
const milliseconds = Math.floor(Math.abs(target - now))
let cur = 0
let divider = 1
let name = 'millisecond'
for (const time of TIMES) {
divider *= time[1]
const next = Math.floor(milliseconds / divider)
if (next <= 0) {
return `${prefix}${cur} ${name}${cur > 1 ? 's' : ''} ago`
}
name = time[0]
cur = next
}
return `${prefix}a long time ago`
}
function moji(text, emojis) {
for (const emoji of emojis) {
text = text.split(`:${emoji.shortcode}:`).join(
`<img class="emoji" alt="${emoji.shortcode}" title="${emoji.shortcode}" src="${emoji.static_url}">`
)
}
return text
}
function tree(statuses, parent, limit) {
const [replies, others] = statuses.reduce(([c, o], s) => (s.in_reply_to_id == parent.id ? [[...c, s], o] : [c, [...o, s]]), [[], []])
parent.replies = limit ? replies.map(r => tree(others, r, limit - 1)) : []
return parent
}
function html(statuses, domain) {
return statuses.map(({ account, created_at, content, id, emojis, sensitive, spoiler_text, replies }) =>
h('status', h('date', ago(created_at), 'p') +
h('author" target="_blank" target="_blank" href="' + account.url, `<img class="avatar" src="${account.avatar_static}" />` +
h('name', moji(account.display_name, account.emojis), 'span') +
h('acct', account.acct, 'span'), 'a') +
(spoiler_text || sensitive ? h('spoiler', spoiler_text || h('spoiler-empty', 'Sensitive', 'span')) : '') +
h('status-content' + (spoiler_text || sensitive ? ' sensitive' : ''), moji(content, emojis)) +
(replies ? h('replies', html(replies, domain)) : '') +
h(`reply" target="_blank" href="https://${domain}/interact/${id}?type=reply`, 'Reply', 'a'))).join('')
}
function moderate(statuses, id) {
if (!id) {
return statuses
}
const valids = statuses.filter(s => s.account.id == id).map(s => s.in_reply_to_id)
return statuses.filter(s => valids.includes(s.id))
}
for (const el of document.getElementsByClassName('comtodon')) {
el.innerHTML = '<div class="loading">Loading...</div>'
const domain = required(el, 'domain')
const status = required(el, 'status')
if (!domain || !status) {
return
}
fetch(`https://${domain}/api/v1/statuses/${status}/context`)
.then(res => res.json())
.then(res => {
el.innerHTML = h(`reply-main" target=\"_blank\" href="https://${domain}/interact/${status}?type=reply`, 'Comment', 'a')
const statuses = moderate(res.descendants, el.dataset.moderator)
if (statuses) {
el.innerHTML += html('deep' in el.dataset ? tree(statuses, { id: status }, el.dataset.deep || -1).replies : statuses, domain)
} else {
el.innerHTML += h('empty', 'Any comment')
}
})
.catch(() => fail(el, 'Request fail'))
}
})()

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -73,10 +73,6 @@ for file in style.css style-compat.css; do
sed -i 's/$/.right{float:right}/' tastytea/static/assets/${file}
sed -i 's/$/.left{float:left}/' tastytea/static/assets/${file}
sed -i 's/$/.mastodon-api-comment{margin-left:1em;margin-bottom:0.4em;padding:0 0.4em;border:0.1em solid black;}/' tastytea/static/assets/${file}
sed -i 's/$/.mastodon-api-comment:nth-child(even){background-color:#0402}/' tastytea/static/assets/${file}
sed -i 's/$/.mastodon-api-comment:nth-child(odd){background-color:#2402}/' tastytea/static/assets/${file}
# Make CSS more readable
sed -i 's/}/}\n/g' tastytea/static/assets/${file}
sed -i 's/{/ {/g' tastytea/static/assets/${file}

View File

@ -0,0 +1,58 @@
{{ if .Params.comtodon }}
<link rel="stylesheet" href="/comtodon.css"/>
<h2 id="comments">Comments</h2>
<noscript>Comments work only with JavaScript enabled.</noscript>
<div class="comtodon" data-domain="{{ .Site.Params.comtodon.domain }}" data-status="{{ .Params.comtodon }}" {{ with .Site.Params.comtodon.moderator }}data-moderator="{{ . }}"{{ end }}></div>
<script src="/comtodon.js" defer></script>
{{- end }}
<div class="footer-content">
<div class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list">
{{- range .Site.Menus.footer }}
<li class="pure-menu-item">
{{- if .URL }}
<a class="pure-menu-link" href="{{ .URL }}">{{ .Name }}</a>
{{- else }}
{{ .Name }}
{{- end }}
</li>
{{- end }}
{{- with .Site.RSSLink }}
<li class="pure-menu-item">
<a href="{{ . }}" class="pure-menu-link">RSS</a>
</li>
{{- end }}
</ul>
</div>
{{- with .Site.Copyright }}
<div class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list">
<li class="pure-menu-item pure-menu-disabled">
&copy; {{ now.Format "2006" }} &mdash; {{ . }}
</li>
</ul>
</div>
{{- end }}
</div>
<script>
function setElementsClass(selector, value) {
Array.prototype.forEach.call(
document.querySelectorAll(selector),
function(elem) { elem.className = value; }
);
}
setElementsClass('img', 'pure-img');
setElementsClass('table', 'pure-table');
function onResize() {
setElementsClass(
'.pure-menu', document.documentElement.clientWidth >= 568 ?
'pure-menu pure-menu-horizontal' : 'pure-menu'
);
}
onResize();
window.addEventListener('resize', onResize);
});
</script>

View File

@ -301,6 +301,3 @@ div.tip td.icon {color:var(--blue)}
div.important td.icon {color:var(--yellow)}
.right {float:right}
.left {float:left}
.mastodon-api-comment {margin-left:1em; margin-bottom:0.4em; padding:0 0.4em; border:0.1em solid black; }
.mastodon-api-comment:nth-child(even) {background-color:#0402}
.mastodon-api-comment:nth-child(odd) {background-color:#2402}

View File

@ -334,6 +334,3 @@ div.tip td.icon {color:var(--blue)}
div.important td.icon {color:var(--yellow)}
.right {float:right}
.left {float:left}
.mastodon-api-comment {margin-left:1em; margin-bottom:0.4em; padding:0 0.4em; border:0.1em solid black; }
.mastodon-api-comment:nth-child(even) {background-color:#0402}
.mastodon-api-comment:nth-child(odd) {background-color:#2402}