Switched comment system to comtodon.
This commit is contained in:
parent
45488a2c62
commit
d3bfd509b9
|
@ -22,6 +22,9 @@ enableGitInfo = true
|
||||||
opengraph = true
|
opengraph = true
|
||||||
schema = true
|
schema = true
|
||||||
|
|
||||||
|
[params.comtodon]
|
||||||
|
domain = "likeable.space"
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = "tags"
|
tags = "tags"
|
||||||
|
|
||||||
|
|
|
@ -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"
|
date: "2019-05-08T15:59:00+02:00"
|
||||||
draft: false
|
draft: false
|
||||||
tags: ["emacs", "ssh"]
|
tags: ["emacs", "ssh"]
|
||||||
|
comtodon: 9ibPpjSsYeNmHrbBDc
|
||||||
---
|
---
|
||||||
|
|
||||||
It took me a long time to collect all the bits and pieces I needed to make
|
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
|
* 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).
|
open files the user can't read (for use with emacsremote --sudo).
|
||||||
* Updated 2019-10-17: Added Zsh-hack to prevent hanging TRAMP-connections.
|
* Updated 2019-10-17: Added Zsh-hack to prevent hanging TRAMP-connections.
|
||||||
|
|
||||||
== Comments
|
|
||||||
|
|
||||||
{{% mastodon-api-comments instance="likeable.space" status_id="9ibPpjSsYeNmHrbBDc" %}}
|
|
||||||
|
|
|
@ -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"
|
date: "2019-09-26T06:10:07+02:00"
|
||||||
draft: false
|
draft: false
|
||||||
tags: ["remwharead", "bookmarks", "archive", "Tooting my own horn"]
|
tags: ["remwharead", "bookmarks", "archive", "Tooting my own horn"]
|
||||||
|
comtodon: 9nIqtmAXvu4harUb7Q
|
||||||
---
|
---
|
||||||
|
|
||||||
:wp-asciidoc: https://en.wikipedia.org/wiki/AsciiDoc
|
: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.
|
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" %}}
|
|
||||||
|
|
|
@ -109,7 +109,3 @@ In your Gitea directory, create `custom/templates/custom/header.tmpl`.
|
||||||
}
|
}
|
||||||
</style>{{< / highlight >}}
|
</style>{{< / highlight >}}
|
||||||
----
|
----
|
||||||
|
|
||||||
== Comments
|
|
||||||
|
|
||||||
{{% mastodon-api-comments instance="soc.ialis.me" status_id="101483651770835303" %}}
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ draft: false
|
||||||
tags:
|
tags:
|
||||||
- wireguard
|
- wireguard
|
||||||
- vpn
|
- vpn
|
||||||
|
comtodon: 9fqBQKGZa6qMf98wIC
|
||||||
---
|
---
|
||||||
|
|
||||||
I wanted to create a https://en.wikipedia.org/wiki/WireGuard[WireGuard] VPN with
|
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 to include IP forwarding.
|
||||||
* Updated 2019-02-16 with information on how to turn SLAAC back on.
|
* 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
|
@ -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', ' ');
|
|
||||||
}
|
|
|
@ -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; }
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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'))
|
||||||
|
}
|
||||||
|
})()
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
@ -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/$/.right{float:right}/' tastytea/static/assets/${file}
|
||||||
sed -i 's/$/.left{float:left}/' 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
|
# Make CSS more readable
|
||||||
sed -i 's/}/}\n/g' tastytea/static/assets/${file}
|
sed -i 's/}/}\n/g' tastytea/static/assets/${file}
|
||||||
sed -i 's/{/ {/g' tastytea/static/assets/${file}
|
sed -i 's/{/ {/g' tastytea/static/assets/${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">
|
||||||
|
© {{ now.Format "2006" }} — {{ . }}
|
||||||
|
</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>
|
|
@ -301,6 +301,3 @@ div.tip td.icon {color:var(--blue)}
|
||||||
div.important td.icon {color:var(--yellow)}
|
div.important td.icon {color:var(--yellow)}
|
||||||
.right {float:right}
|
.right {float:right}
|
||||||
.left {float:left}
|
.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}
|
|
||||||
|
|
|
@ -334,6 +334,3 @@ div.tip td.icon {color:var(--blue)}
|
||||||
div.important td.icon {color:var(--yellow)}
|
div.important td.icon {color:var(--yellow)}
|
||||||
.right {float:right}
|
.right {float:right}
|
||||||
.left {float:left}
|
.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}
|
|
||||||
|
|
Loading…
Reference in New Issue