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="columns">
|
||||
<div class="column is-8 is-offset-2">
|
||||
<div class="message is-info">
|
||||
<div class="message-body">
|
||||
TODO: implement comments
|
||||
{{ partial "comments/webmention" . }}
|
||||
{{ with .Site.DisqusShortname }}
|
||||
<div>
|
||||
{{ template "_internal/disqus.html" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</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>
|
||||
|
||||
{{ 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