Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/leonardofaria/bento.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonardo Faria <leonardofaria@gmail.com>2020-11-29 06:47:25 +0300
committerLeonardo Faria <leonardofaria@gmail.com>2020-11-29 06:47:25 +0300
commitb8e2fa37b0dcb7d7786471705ff6ae90c7eaef99 (patch)
treec610e71d664e20c90b12fb82bc09bdad6d80982c
parent8075a7db53ceadd32ebb6a6fdc3129112eb68a90 (diff)
Add webmentions in posts
-rw-r--r--assets/js/webmentions.js216
-rw-r--r--exampleSite/config.toml2
-rw-r--r--layouts/_default/single.html18
-rw-r--r--layouts/partials/head.html3
-rw-r--r--layouts/partials/webmentions.html71
5 files changed, 307 insertions, 3 deletions
diff --git a/assets/js/webmentions.js b/assets/js/webmentions.js
new file mode 100644
index 0000000..0576560
--- /dev/null
+++ b/assets/js/webmentions.js
@@ -0,0 +1,216 @@
+// based on https://shindakun.dev/posts/adding-webmentions-to-microblog/
+
+// user-circle icon from heroicons.com encoded by https://yoksel.github.io/url-encoder/
+const ANON_AVATAR = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"%3E%3Cpath fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z" clip-rule="evenodd" /%3E%3C/svg%3E';
+
+const fetchWebmentions = (url, aliases, productionBaseUrl) => {
+ if (!document.getElementById('comments')) {
+ return;
+ }
+ if (!url) {
+ url = document.location.origin + document.location.pathname;
+ }
+ const targets = getUrlPermutations(url, aliases, productionBaseUrl);
+
+ const script = document.createElement('script');
+ let src =
+ 'https://webmention.io/api/mentions?perPage=500&jsonp=parseWebmentions';
+ targets.forEach((targetUrl) => {
+ src += `&target[]=${encodeURIComponent(targetUrl)}`;
+ });
+ src += `&_=${Math.random()}`;
+ script.src = src;
+ script.async = true;
+ document.body.appendChild(script);
+}
+
+const getUrlPermutations = (url, aliases, productionBaseUrl) => {
+ const urls = [];
+
+ // Replace the current base URL (ex.: http://localhost:1313) to the production one
+ const currentUrl = new URL(window.location);
+ url = url.replace(currentUrl.origin, productionBaseUrl);
+ urls.push(url);
+
+ // Add http variation
+ urls.push(url.replace('https://', 'http://'));
+
+ // Add variation with final /
+ if (url.substr(-1) === '/') {
+ var noslash = url.substr(0, url.length - 1);
+ urls.push(noslash);
+ urls.push(noslash.replace('https://', 'http://'));
+ }
+
+ // Add aliases variations
+ if (aliases) {
+ aliases.forEach((alias) => {
+ urls.push(`${productionBaseUrl}/${alias}`);
+ });
+ }
+
+ return urls;
+}
+
+const parseWebmentions = (data) => {
+ const links = data.links.sort(wmSort);
+ const likes = [];
+ const reposts = [];
+ const replies = [];
+ const avatarUrls = [];
+
+ links.map((l) => {
+ if (!l.activity || !l.activity.type) {
+ console.warning('unknown link type', l);
+ return;
+ }
+ if (!l.verified) {
+ return;
+ }
+ if (l.data.author) {
+ avatarUrls.push(l.data.author.photo);
+ }
+ switch (l.activity.type) {
+ case 'like':
+ likes.push(l);
+ break;
+ case 'link':
+ case 'repost':
+ reposts.push(l);
+ break;
+ default:
+ replies.push(l);
+ break;
+ }
+ });
+
+ resetContainers();
+ render(likes, 'like-template', 'likes');
+ render(reposts, 'reply-template', 'likes');
+ render(replies, 'reply-template', 'shares');
+ renderSummary(likes.length, (reposts.length + replies.length), avatarUrls);
+}
+window.parseWebmentions = parseWebmentions;
+
+// Reset containers due to the usage of Turbolinks
+const resetContainers = () => {
+ const likes = document.querySelector('#likes');
+ const shares = document.querySelector('#shares');
+ const replies = document.querySelector('#replies');
+ const avatars = document.querySelector('#webmentions-avatars');
+
+ while (likes.lastChild) { likes.removeChild(likes.lastChild); }
+ while (shares.lastChild) { shares.removeChild(shares.lastChild); }
+ while (replies.lastChild) { replies.removeChild(replies.lastChild); }
+ while (avatars.lastChild) { avatars.removeChild(avatars.lastChild); }
+}
+
+const wmSort = (a, b) => {
+ const dateA = getWmDate(a);
+ const dateB = getWmDate(b);
+ if (dateA < dateB) {
+ return -1;
+ } else if (dateB < dateA) {
+ return 1;
+ }
+ return 0;
+}
+
+const getWmDate = (webmention) => {
+ if (webmention.data.published) {
+ return new Date(webmention.data.published);
+ }
+ return new Date(webmention.verified_date);
+}
+
+const render = (items, template, destination) => {
+ const t = document.getElementById(template).content;
+ const list = document.getElementById(destination);
+
+ items.map((l) => {
+ const data = {
+ url: l.data.url,
+ date: new Date(l.data.published || l.verified_date),
+ content: l.data.content,
+ };
+
+ if (l.data.author) {
+ data.photo = l.data.author.photo || ANON_AVATAR;
+ data.name = l.data.author.name;
+ data.authorUrl = l.data.author.url;
+ } else {
+ data.photo = ANON_AVATAR;
+ data.name = getHostName(l.data.url) || 'inbound link';
+ data.authorUrl = l.data.url;
+ }
+
+ if (l.activity.sentence) {
+ data.sentence = l.activity.sentence;
+ }
+
+ fillTemplate(t, data);
+ const clone = document.importNode(t, true);
+ list.appendChild(clone);
+ });
+
+}
+
+const getHostName = (url) => {
+ var a = document.createElement('a');
+ a.href = url;
+ return (a.hostname || '').replace('www.', '');
+}
+
+const fillTemplate = (template, vals) => {
+ template.querySelector('.js-avatar').src = vals.photo;
+ template.querySelector('.js-author').href = vals.authorUrl;
+ template.querySelector('.js-author-name').innerHTML = vals.name;
+ template.querySelector('.js-author-name').href = vals.authorUrl;
+ template.querySelector('.js-source').href = vals.url;
+ if (vals.sentence && template.querySelector('.js-sentence')) {
+ template.querySelector('.js-sentence').innerText = vals.sentence;
+ }
+ const date = template.querySelector('.js-date');
+ if (date) {
+ date.innerHTML = formatDate(vals.date);
+ }
+ if (vals.content) {
+ template.querySelector('.js-content').innerHTML = vals.content;
+ }
+}
+
+const formatDate = (date) => {
+ if (!date) {
+ return '';
+ }
+ return date.toLocaleDateString('en-US', {
+ month: 'short',
+ day: '2-digit',
+ year: 'numeric'
+ });
+}
+
+const renderSummary = (totalLikes, totalInteractions, avatars) => {
+ document.querySelector('#webmentions-total-likes').innerText = totalLikes;
+ document.querySelector('#webmentions-total-interactions').innerText = totalInteractions;
+ const avatarsContainer = document.querySelector('#webmentions-avatars');
+ const uniqueAvatars = [...new Set(avatars)];
+
+ for (const [index, avatar] of uniqueAvatars.entries()) {
+ const image = document.createElement("img");
+ image.src = avatar;
+ image.classList = 'inline-block h-8 w-8 rounded-full ring-2 ring-white';
+ image.alt = '';
+
+ avatarsContainer.appendChild(image);
+
+ if (index > 9) {
+ const more = document.createElement("span");
+ more.classList = 'flex items-center justify-center text-xs bg-black text-white font-bold h-8 w-8 rounded-full ring-2 ring-white';
+ more.innerHTML = `+${uniqueAvatars.length - 9}`;
+ avatarsContainer.appendChild(more);
+
+ break;
+ }
+ };
+}
diff --git a/exampleSite/config.toml b/exampleSite/config.toml
index e5c4e07..956c3ea 100644
--- a/exampleSite/config.toml
+++ b/exampleSite/config.toml
@@ -49,6 +49,8 @@ disqusShortname = "bento-hugo-theme"
# Learn more about webmention
# https://sebastiandedeyne.com/adding-webmentions-to-my-blog/
# webmention = ""
+ # productionBaseUrl is need if you want to receive webmentions in local environment
+ # productionBaseUrl = "https://yourwebsite.com"
# Avatar (shown in the homepage)
avatar = "images/avatar.jpg"
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index 6765bd5..3c67c9f 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -59,9 +59,21 @@
{{ end }}
{{if not .Params.hideComments }}
- <section class="my-5 py-5 ">
- <h2>Comments</h2>
- {{ template "_internal/disqus.html" . }}
+ <section class="my-5 py-5 relative">
+ <h2>Interactions</h2>
+
+ {{if .Site.Params.webmention }}
+ <h3>Webmentions</h3>
+ {{ partial "webmentions.html" . }}
+
+ <h3>Comments</h3>
+ {{end}}
+
+ {{ if not .Site.IsServer }}
+ {{ template "_internal/disqus.html" . }}
+ {{ else }}
+ Disqus doesn't load in development mode
+ {{ end }}
</section>
{{ end }}
</article>
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
index fef91e6..f886603 100644
--- a/layouts/partials/head.html
+++ b/layouts/partials/head.html
@@ -122,5 +122,8 @@
{{with .Site.Params.webmention }}
<link rel="webmention" href="https://webmention.io/{{.}}/webmention">
<link rel="pingback" href="https://webmention.io/{{.}}/xmlrpc">
+
+ {{ $webmentions := resources.Get "js/webmentions.js" }}
+ <script src="{{ $webmentions.RelPermalink }}" type="text/javascript"></script>
{{end}}
</head>
diff --git a/layouts/partials/webmentions.html b/layouts/partials/webmentions.html
new file mode 100644
index 0000000..4062aa7
--- /dev/null
+++ b/layouts/partials/webmentions.html
@@ -0,0 +1,71 @@
+<div id="summary" class="flex items-center">
+ <div class="flex items-center mr-6 rounded-md">
+ <svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
+ <path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd" />
+ </svg>
+ <span id="webmentions-total-likes" class="mr-1"></span>
+ <!-- <span>(Show)</span> -->
+ </div>
+ <div class="flex items-center mr-6 rounded-md">
+ <svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
+ <path fill-rule="evenodd" d="M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z" clip-rule="evenodd" />
+ </svg>
+ <span id="webmentions-total-interactions" class="mr-1"></span>
+ <!-- <span>(Show)</span> -->
+ </div>
+ <div class="flex-grow">
+ <div id="webmentions-avatars" class="flex -space-x-2 overflow-hidden"></div>
+ </div>
+ <div>
+ <a href="https://indieweb.org/Webmention" title="What are Webmentions?">
+ <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
+ </svg>
+ </a>
+ </div>
+</div>
+
+<template id="reply-template" class="list-none">
+ <li class="h-entry flex mb-8 text-base">
+ <a class="js-author flex-shrink-0" href="">
+ <img class="comment-avatar u-photo js-avatar w-8 h-8 rounded-full mr-3" alt="" src=""/>
+ </a>
+ <div class="comment-note__body">
+ <div class="p-author">
+ <strong><a class="no-underline u-author js-author-name" href=""></a></strong>
+ <small><a class="no-underline u-url js-date js-source" href=""></a></small>
+ </div>
+ <div class="js-content e-entry"></div>
+ </div>
+ </li>
+</template>
+<template id="like-template" class="list-none">
+ <li class="h-entry flex mb-8 text-base">
+ <a class="reply__avatar u-author js-author flex-shrink-0" href="">
+ <img class="u-photo js-avatar w-8 h-8 rounded-full mr-3" alt="" src=""/>
+ </a>
+ <a class="no-underline u-url js-source" href="">
+ <span class="reply__author p-name js-sentence js-author-name" href=""></span>
+ <small class="reply__date js-date" href=""></small>
+ </a>
+ </li>
+</template>
+
+<div id="webmentions-interactions">
+ <ul class="replies" id="replies"></ul>
+ <ul class="shares" id="shares"></ul>
+</div>
+
+<div id="webmentions-likes">
+ <ul class="likes" id="likes"></ul>
+</div>
+
+<div id="comments"></div>
+
+<script type="text/javascript">
+ var webmentions = (permalink, aliases, productionBaseUrl) => {
+ fetchWebmentions(permalink, aliases, productionBaseUrl);
+ };
+
+ document.addEventListener('turbolinks:load', webmentions({{ .Permalink }}, {{ .Aliases }}, {{ .Site.Params.productionBaseUrl }}));
+</script>