diff options
Diffstat (limited to 'assets/js/webmentions.js')
-rw-r--r-- | assets/js/webmentions.js | 216 |
1 files changed, 216 insertions, 0 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; + } + }; +} |