Enable webmention comments
This commit is contained in:
parent
855a896c19
commit
c976a26dc5
|
@ -0,0 +1,342 @@
|
||||||
|
/* webmention.js
|
||||||
|
|
||||||
|
Simple thing for embedding webmentions from webmention.io into a page, client-side.
|
||||||
|
|
||||||
|
(c)2018-2020 fluffy (http://beesbuzz.biz)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
GitHub repo (for latest released versions, issue tracking, etc.):
|
||||||
|
|
||||||
|
https://github.com/PlaidWeb/webmention.js
|
||||||
|
|
||||||
|
Basic usage:
|
||||||
|
|
||||||
|
<script src="/path/to/webmention.js" data-param="val" ... async />
|
||||||
|
<div id="webmentions"></div>
|
||||||
|
|
||||||
|
Allowed parameters:
|
||||||
|
|
||||||
|
page-url:
|
||||||
|
|
||||||
|
The base URL to use for this page. Defaults to window.location
|
||||||
|
|
||||||
|
add-urls:
|
||||||
|
|
||||||
|
Additional URLs to check, separated by |s
|
||||||
|
|
||||||
|
id:
|
||||||
|
|
||||||
|
The HTML ID for the object to fill in with the webmention data.
|
||||||
|
Defaults to "webmentions"
|
||||||
|
|
||||||
|
wordcount:
|
||||||
|
|
||||||
|
The maximum number of words to render in reply mentions.
|
||||||
|
|
||||||
|
max-webmentions:
|
||||||
|
|
||||||
|
The maximum number of mentions to retrieve. Defaults to 30.
|
||||||
|
|
||||||
|
prevent-spoofing:
|
||||||
|
|
||||||
|
By default, Webmentions render using the mf2 'url' element, which plays
|
||||||
|
nicely with webmention bridges (such as brid.gy and telegraph)
|
||||||
|
but allows certain spoofing attacks. If you would like to prevent
|
||||||
|
spoofing, set this to a non-empty string (e.g. "true").
|
||||||
|
|
||||||
|
sort-by:
|
||||||
|
|
||||||
|
What to order the responses by; defaults to 'published'. See
|
||||||
|
https://github.com/aaronpk/webmention.io#api
|
||||||
|
|
||||||
|
sort-dir:
|
||||||
|
|
||||||
|
The order to sort the responses by; defaults to 'up' (i.e. oldest
|
||||||
|
first). See https://github.com/aaronpk/webmention.io#api
|
||||||
|
|
||||||
|
comments-are-reactions:
|
||||||
|
|
||||||
|
If set to a non-empty string (e.g. "true"), will display comment-type responses
|
||||||
|
(replies/mentions/etc.) as being part of the reactions
|
||||||
|
(favorites/bookmarks/etc.) instead of in a separate comment list.
|
||||||
|
|
||||||
|
A more detailed example:
|
||||||
|
|
||||||
|
<script src="/path/to/webmention.min.js"
|
||||||
|
data-id="webmentionContainer"
|
||||||
|
data-wordcount="30"
|
||||||
|
data-prevent-spoofing="true"
|
||||||
|
data-comments-are-reactions="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function getCfg(key, dfl) {
|
||||||
|
return document.currentScript.getAttribute("data-" + key) || dfl;
|
||||||
|
}
|
||||||
|
|
||||||
|
var refurl = getCfg('page-url',
|
||||||
|
window.location.href.replace(/#.*$/, ''));
|
||||||
|
var addurls = getCfg('add-urls', undefined);
|
||||||
|
var containerID = getCfg('id', "webmentions");
|
||||||
|
var textMaxWords = getCfg('wordcount');
|
||||||
|
var maxWebmentions = getCfg('max-webmentions', 30);
|
||||||
|
var mentionSource = getCfg('prevent-spoofing') ? 'wm-source' : 'url';
|
||||||
|
var sortBy = getCfg('sort-by', 'published');
|
||||||
|
var sortDir = getCfg('sort-dir', 'up');
|
||||||
|
var commentsAreReactions = getCfg('comments-are-reactions');
|
||||||
|
|
||||||
|
var reactTitle = {
|
||||||
|
'in-reply-to': 'replied',
|
||||||
|
'like-of': 'liked',
|
||||||
|
'repost-of': 'reposted',
|
||||||
|
'bookmark-of': 'bookmarked',
|
||||||
|
'mention-of': 'mentioned',
|
||||||
|
'rsvp': 'RSVPed',
|
||||||
|
'follow-of': 'followed'
|
||||||
|
};
|
||||||
|
|
||||||
|
var reactEmoji = {
|
||||||
|
'in-reply-to': '💬',
|
||||||
|
'like-of': '❤️',
|
||||||
|
'repost-of': '🔄',
|
||||||
|
'bookmark-of': '⭐️',
|
||||||
|
'mention-of': '💬',
|
||||||
|
'rsvp': '📅',
|
||||||
|
'follow-of': '🐜'
|
||||||
|
};
|
||||||
|
|
||||||
|
var rsvpEmoji = {
|
||||||
|
'yes': '✅',
|
||||||
|
'no': '❌',
|
||||||
|
'interested': '💡',
|
||||||
|
'maybe': '💭'
|
||||||
|
};
|
||||||
|
|
||||||
|
function entities(text) {
|
||||||
|
return text.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function reactImage(r, isComment) {
|
||||||
|
var who = entities((r.author && r.author.name)
|
||||||
|
? r.author.name
|
||||||
|
: r.url.split('/')[2]);
|
||||||
|
var response = reactTitle[r['wm-property']] || 'reacted';
|
||||||
|
if (!isComment && r.content && r.content.text) {
|
||||||
|
response += ": " + extractComment(r);
|
||||||
|
}
|
||||||
|
var html = '<a class="reaction" rel="nofollow ugc" title="' + who + ' ' +
|
||||||
|
response + '" href="' + r[mentionSource] + '">';
|
||||||
|
if (r.author && r.author.photo) {
|
||||||
|
html += '<img src="' + entities(r.author.photo) +
|
||||||
|
'" loading="lazy" decoding="async" alt="' + who + '">';
|
||||||
|
}
|
||||||
|
html += (reactEmoji[r['wm-property']] || '💥');
|
||||||
|
if (r.rsvp && rsvpEmoji[r.rsvp]) {
|
||||||
|
html += '<sub>' + rsvpEmoji[r.rsvp] + '</sub>';
|
||||||
|
}
|
||||||
|
html += '</a>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip the protocol off a URL
|
||||||
|
function stripurl(url) {
|
||||||
|
return url.substr(url.indexOf('//'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate multiple mentions from the same source URL
|
||||||
|
function dedupe(mentions) {
|
||||||
|
var filtered = [];
|
||||||
|
var seen = {};
|
||||||
|
|
||||||
|
mentions.forEach(function(r) {
|
||||||
|
// Strip off the protocol (i.e. treat http and https the same)
|
||||||
|
var source = stripurl(r.url);
|
||||||
|
if (!seen[source]) {
|
||||||
|
filtered.push(r);
|
||||||
|
seen[source] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractComment(c) {
|
||||||
|
var text = entities(c.content.text);
|
||||||
|
|
||||||
|
if (textMaxWords) {
|
||||||
|
var words = text.replace(/\s+/g,' ')
|
||||||
|
.split(' ', textMaxWords + 1);
|
||||||
|
if (words.length > textMaxWords) {
|
||||||
|
words[textMaxWords - 1] += '…';
|
||||||
|
words = words.slice(0, textMaxWords);
|
||||||
|
text = words.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatComments(comments) {
|
||||||
|
var html = '<h2>' + comments.length + ' Response' +
|
||||||
|
(comments.length > 1 ? 's' : '') +
|
||||||
|
'</h2><ul class="comments">';
|
||||||
|
comments.forEach(function(c) {
|
||||||
|
html += '<li>';
|
||||||
|
|
||||||
|
html += reactImage(c, true);
|
||||||
|
|
||||||
|
html += ' <a class="source" rel="nofollow ugc" href="' +
|
||||||
|
c[mentionSource] + '">';
|
||||||
|
if (c.author && c.author.name) {
|
||||||
|
html += entities(c.author.name);
|
||||||
|
} else {
|
||||||
|
html += entities(c.url.split('/')[2]);
|
||||||
|
}
|
||||||
|
html += '</a>: ';
|
||||||
|
|
||||||
|
var linkclass;
|
||||||
|
var linktext;
|
||||||
|
if (c.name) {
|
||||||
|
linkclass = "name";
|
||||||
|
linktext = c.name;
|
||||||
|
} else if (c.content && c.content.text) {
|
||||||
|
linkclass = "text";
|
||||||
|
linktext = extractComment(c);
|
||||||
|
} else {
|
||||||
|
linkclass = "name";
|
||||||
|
linktext = "(mention)";
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<span class="' + linkclass + '">' + linktext + '</span>';
|
||||||
|
|
||||||
|
html += '</li>';
|
||||||
|
});
|
||||||
|
html += '</ul>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatReactions(reacts) {
|
||||||
|
var html = '<h2>' + reacts.length + ' Reaction' +
|
||||||
|
(reacts.length > 1 ? 's' : '') +
|
||||||
|
'</h2><ul class="reacts">';
|
||||||
|
|
||||||
|
reacts.forEach(function(r) {
|
||||||
|
html += reactImage(r);
|
||||||
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(url, callback) {
|
||||||
|
if (window.fetch) {
|
||||||
|
window.fetch(url).then(function(response) {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return Promise.resolve(response);
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error(response.statusText));
|
||||||
|
}
|
||||||
|
}).then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
}).then(callback).catch(function(error) {
|
||||||
|
console.error("Request failed", error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var oReq = new XMLHttpRequest();
|
||||||
|
oReq.onload = function(data) {
|
||||||
|
callback(JSON.parse(data));
|
||||||
|
};
|
||||||
|
oReq.onerror = function(error) {
|
||||||
|
console.error("Request failed", error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", function () {
|
||||||
|
var container = document.getElementById(containerID);
|
||||||
|
if (!container) {
|
||||||
|
// no container, so do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pages = [stripurl(refurl)];
|
||||||
|
if (!!addurls) {
|
||||||
|
addurls.split('|').forEach(function (url) {
|
||||||
|
pages.push(stripurl(url));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiURL = 'https://webmention.io/api/mentions.jf2?per-page=' +
|
||||||
|
maxWebmentions + '&sort-by=' + sortBy + '&sort-dir=' + sortDir;
|
||||||
|
|
||||||
|
pages.forEach(function (path) {
|
||||||
|
apiURL += '&target[]=' + encodeURIComponent('http:' + path) +
|
||||||
|
'&target[]=' + encodeURIComponent('https:' + path);
|
||||||
|
});
|
||||||
|
|
||||||
|
getData(apiURL, function(json) {
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
var comments = [];
|
||||||
|
var collects = [];
|
||||||
|
if (commentsAreReactions) {
|
||||||
|
comments = collects;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapping = {
|
||||||
|
"in-reply-to": comments,
|
||||||
|
"like-of": collects,
|
||||||
|
"repost-of": collects,
|
||||||
|
"bookmark-of": collects,
|
||||||
|
"mention-of": comments,
|
||||||
|
"rsvp": comments
|
||||||
|
};
|
||||||
|
|
||||||
|
json.children.forEach(function(c) {
|
||||||
|
var store = mapping[c['wm-property']];
|
||||||
|
if (store) {
|
||||||
|
store.push(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// format the comment-type things
|
||||||
|
if (comments.length > 0 && comments !== collects) {
|
||||||
|
html += formatComments(dedupe(comments));
|
||||||
|
}
|
||||||
|
|
||||||
|
// format the other reactions
|
||||||
|
if (collects.length > 0) {
|
||||||
|
html += formatReactions(dedupe(collects));
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
|
@ -1,16 +1,12 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-8 is-offset-2">
|
<div class="column is-8 is-offset-2">
|
||||||
<div class="message is-info">
|
{{ partial "comments/webmention" . }}
|
||||||
<div class="message-body">
|
{{ with .Site.DisqusShortname }}
|
||||||
TODO: implement comments
|
<div>
|
||||||
|
{{ template "_internal/disqus.html" . }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ with .Site.DisqusShortname }}
|
|
||||||
<div>
|
|
||||||
{{ template "_internal/disqus.html" . }}
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{{ if .Site.Params.comments.webmention }}
|
||||||
|
<hr />
|
||||||
|
<div class="content">
|
||||||
|
<h2>Comments</h2>
|
||||||
|
<form method="get" action="https://quill.p3k.io/" target="_blank"><input type="hidden" name="dontask" value="1">
|
||||||
|
<input type="hidden" name="me" value="https://commentpara.de/">
|
||||||
|
<input type="hidden" name="reply" value="{{ .Permalink }}">
|
||||||
|
|
||||||
|
You can comment on this post, <a href="{{ .Permalink }}">"{{ .Title }}"</a>, by:
|
||||||
|
<ul>
|
||||||
|
<li>Replying to its tweet on Twitter or its toot on Mastodon</li>
|
||||||
|
<li>Sending a Webmention from your own site to <code>{{ .Permalink }}</code></li>
|
||||||
|
<li>Using this button: </li>
|
||||||
|
</ul>
|
||||||
|
<div class="control ml-6">
|
||||||
|
<input class="button is-primary is-small" type="submit" value="Write a comment">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="webmentions">
|
||||||
|
Comments & reactions haven't loaded yet. You might have JavaScript disabled but that's cool 😎.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ end }}
|
|
@ -1 +1,8 @@
|
||||||
<script type="text/javascript" src="/js/navbar.js"></script>
|
<script type="text/javascript" src="/js/navbar.js"></script>
|
||||||
|
|
||||||
|
{{ if .Site.Params.comments.webmention }}
|
||||||
|
{{ $webmention_js := resources.Get "js/webmention.js" | minify }}
|
||||||
|
<script src="{{ $webmention_js.RelPermalink }}"
|
||||||
|
data-page-url="{{ .Permalink }}"
|
||||||
|
async></script>
|
||||||
|
{{ end }}
|
||||||
|
|
Loading…
Reference in New Issue