diff options
author | Leonardo Faria <leonardofaria@gmail.com> | 2020-11-29 06:47:25 +0300 |
---|---|---|
committer | Leonardo Faria <leonardofaria@gmail.com> | 2020-11-29 06:47:25 +0300 |
commit | b8e2fa37b0dcb7d7786471705ff6ae90c7eaef99 (patch) | |
tree | c610e71d664e20c90b12fb82bc09bdad6d80982c | |
parent | 8075a7db53ceadd32ebb6a6fdc3129112eb68a90 (diff) |
Add webmentions in posts
-rw-r--r-- | assets/js/webmentions.js | 216 | ||||
-rw-r--r-- | exampleSite/config.toml | 2 | ||||
-rw-r--r-- | layouts/_default/single.html | 18 | ||||
-rw-r--r-- | layouts/partials/head.html | 3 | ||||
-rw-r--r-- | layouts/partials/webmentions.html | 71 |
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> |