Merge pull request #6 from mmai/webmentions

Update Webmentions template & style
This commit is contained in:
Jeremiah Russell 2025-05-10 08:54:09 +01:00 committed by GitHub
commit d3d5e2439e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 339 additions and 303 deletions

View file

@ -1,96 +1,132 @@
#webmentions {
margin: 0;
position: relative;
z-index: 100;
line-height: 1.2em;
color: var(--text-color);
background-color: var(--background-color);
}
margin: 0;
position: relative;
z-index: 100;
line-height: 1.2em;
color: var(--text-color);
background-color: var(--background-color);
#webmentions .comments {
max-height: 20em;
overflow-x: hidden;
overflow-y: scroll;
h2 {
font-size: 1.1em;
margin-bottom: 1.5em;
}
h3 {
font-size: 0.9em;
display: flex;
align-items: center;
.svg-icon, span {
margin-right: .375rem;
}
}
ol {
padding: 0;
}
li, p {
font-family: inherit;
}
.likes {
display: flex;
flex-wrap: wrap;
list-style: none;
padding-left: 1rem;
li {
margin-bottom: .375rem;
// margin-left: -1rem;
position: relative;
img {
border-radius: 50%;
height: 3rem;
-o-object-fit: cover;
object-fit: cover;
display: block;
height: auto;
max-width: 100%;
}
}
}
.comment {
font-size: 80%;
}
#webmentions h2 {
font-size: medium;
margin: 0;
margin-top: 1.4em;
margin-bottom: 1.2em;
padding: 2px;
background: var(--background-color);
}
#webmentions .reacts img {
margin: 3px -1ex 1px 0;
display: inline;
}
#webmentions img.missing {
background: white;
border: dashed black 1px;
}
#webmentions ul {
list-style-type: none;
margin: 0;
padding: 4px;
}
#webmentions li {
text-indent: -1em;
padding-left: 1em;
}
#webmentions a.reaction {
position: relative;
text-decoration: none;
text-shadow: 0px 0px 3px white;
margin-right: 0;
letter-spacing: -1ex;
margin-right: 1ex;
}
#webmentions a.source {
margin-right: 1ex;
text-decoration: none;
color: var(--primary-color);
background-color: var(--background-color);
}
#webmentions a.reaction img {
max-height: 1.3em;
display: inline;
width: auto;
margin-right: -1ex;
border-radius: 25%;
}
#webmentions a.reaction sub {
font-size: 50%;
}
#webmentions .comments li {
white-space: nowrap;
text-overflow: ellipsis;
box-shadow: rgba(50,50,93,.25) 0px 2px 5px -1px,rgba(0,0,0,.3) 0px 1px 3px -1px;
border-radius: 0.5rem;
padding: 0.5rem;
padding-bottom: 0;
background: var(--bg-1);
min-height: 100px;
overflow: hidden;
}
margin-bottom: 0.5em;
#webmentions .comments li .text {
color: var(--meta-color);
font-style: italic;
text-decoration: none;
text-wrap: wrap;
padding-left: 0.5em;
}
#webmentions .comments li .name {
color: var(--meta-color);
padding-left: 0.5em;
}
div {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
}
#webmentions .comments li .emoji {
display: none;
}
p {
line-height: 1.5em;
}
.p-author {
font-size: 1.3em;
font-style: bold;
}
.u-url {
font-style: italic;
text-decoration: underline;
}
.u-author {
display: flex;
align-items: center;
img {
height: 2rem;
margin-right: .625rem;
width: 2rem;
display: block;
max-width: 100%;
}
}
}
form {
input {
flex: 1;
border: 1px solid var(--divider-color);
border-radius: 20px 0px 0px 20px;
background-color: var(--input-background-color);
color: var(--text-color);
font-size: 1rem;
padding-inline: 1rem 1rem;
padding-block: .75rem;
width: calc(60% - 2rem);
}
button {
flex: 1;
border: 1px solid var(--divider-color);
border-radius: 0px 20px 20px 0px;
background-color: var(--input-background-color);
color: var(--text-color);
font-size: 1rem;
padding-inline: 0.7rem 0.7rem;
padding-block: .75rem;
width: 7rem;
}
button:hover {
background-color: var(--primary-color);
color: var(--hover-color);
cursor: pointer;
}
}
}

View file

@ -3,6 +3,7 @@
Simple thing for embedding webmentions from webmention.io into a page, client-side.
(c)2018-2022 fluffy (http://beesbuzz.biz)
2025 mmai (https://misc.rhumbs.fr)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -22,10 +23,6 @@ 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 />
@ -126,138 +123,6 @@ A more detailed example:
const mentionSource = getCfg("prevent-spoofing") ? "wm-source" : "url";
const sortBy = getCfg("sort-by", "published");
const sortDir = getCfg("sort-dir", "up");
/** @type {boolean} */
const commentsAreReactions = getCfg("comments-are-reactions", false);
/**
* @typedef MentionType
* @type {"in-reply-to"|"like-of"|"repost-of"|"bookmark-of"|"mention-of"|"rsvp"|"follow-of"}
*/
/**
* Maps a reaction to a hover title.
*
* @type {Record<MentionType, string>}
*/
const reactTitle = {
"in-reply-to": t("replied"),
"like-of": t("liked"),
"repost-of": t("reposted"),
"bookmark-of": t("bookmarked"),
"mention-of": t("mentioned"),
"rsvp": t("RSVPed"),
"follow-of": t("followed")
};
/**
* Maps a reaction to an emoji.
*
* @type {Record<MentionType, string>}
*/
const reactEmoji = {
"in-reply-to": "💬",
"like-of": "❤️",
"repost-of": "🔄",
"bookmark-of": "⭐️",
"mention-of": "💬",
"rsvp": "📅",
"follow-of": "🐜"
};
/**
* @typedef RSVPEmoji
* @type {"yes"|"no"|"interested"|"maybe"|null}
*/
/**
* Maps a RSVP to an emoji.
*
* @type {Record<RSVPEmoji, string>}
*/
const rsvpEmoji = {
"yes": "✅",
"no": "❌",
"interested": "💡",
"maybe": "💭"
};
/**
* HTML escapes the string.
*
* @param {string} text The string to be escaped.
* @returns {string}
*/
function entities(text) {
return text.replace(/[&<>"]/g, (tag) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
}[tag] || tag));
}
/**
* Creates the markup for an reaction image.
*
* @param {Reaction} r
* @param {boolean} isComment
* @returns {string}
*/
function reactImage(r, isComment) {
const who = entities(
r.author?.name || r.url.split("/")[2]
);
/** @type {string} */
let response = reactTitle[r["wm-property"]] || t("reacted");
if (!isComment && r.content && r.content.text) {
response += ": " + extractComment(r);
}
let authorPhoto = '';
if (r.author && r.author.photo) {
authorPhoto = `
<img
src="${entities(r.author.photo)}"
loading="lazy"
decoding="async"
alt="${who}"
>
`;
} else {
authorPhoto = `
<img
class="missing"
src="data:image/webp;base64,UklGRkoCAABXRUJQVlA4TD4CAAAvP8APAIV0WduUOLr/m/iqY6SokDJSMD5xYX23SQizRsVdZmIj/f6goYUbiOj/BED7MOPReuBNT3vBesSzIex+SeqMFFkjebFmzH3S7POxDSJ1yaCbCmMnS2R46cRMPyQLw4GBK4esdK60pYwsZakecUCl5zsHv/5cPH08nx9/7i6rEEVCg2hR8VSd30PxMZpVoJZQO6Dixgg6X5oKFCmlVHIDmmMFShWumAXgCuyqVN8hHff/k+9fj8+ei7BVjpxBmZCUJv+6DhWGZwWvs+UoLHFCKsPYpfJtIcEXBTopEEsKwedZUv4ku1FZErKULLyQwFGgnmTs2vBD5qu44xwnG9uyjgrFOd+KRVlXyQfwQlauydaU6AVI7OjKXLUEqNtxJBmQegNDZgV7lxxqYMOMrDyC1NdAGbdiH9Ij0skjG+oTyfO0lmjdgvoH8iIgreuBMRYLSH+R3sAztXgL+XfS7E2bmfo6gnS0TrpnzHT7kL+skj7PgHuBwv/zpN8LDLQg7zfJZLBubMKnyeh6ZGyfDEfc2LYpnlUtG7JqsSHq1WoASbUS4KVaLwB8be5mfsGMDwBcm5VxbuxWxx3nkFanB6lYqsqSkOGkKicoDvXsneR7BkKU7DtaEuT7+pxBGVwx+9gVyqf2pVA9sC2CsmjZ1RJqEJHS4Tj/pCcS0JoyBYOsB91Xjh3OFfQPQhvCAYyeLJlaOoFp0XNNuD0BC8exr8uPx7D1JWkwFdZIXmD3MOPReuDNzHjBesSzIbQD"
alt="${who}$"
>
`;
}
let emoji = '';
if (reactEmoji[r['wm-property']]) {
emoji = `<span class="emoji">${reactEmoji[r['wm-property']]}</span>`;
} else {
emoji = `<span class="emoji">💥</span>`;
}
let rsvp = '';
if (r.rsvp && rsvpEmoji[r.rsvp]) {
rsvp = `${rsvpEmoji[r.rsvp]}`;
}
return`
<a
class="reaction"
rel="nofollow ugc"
title="${who} ${response}"
href="${r[mentionSource]}"
>
${authorPhoto}
${emoji}
${rsvp}
</a>
`;
}
/**
* Strip the protocol off a URL.
@ -281,7 +146,7 @@ A more detailed example:
/** @type {Record<string, boolean>} */
const seen = {};
mentions.forEach(function(r) {
mentions.forEach(function (r) {
// Strip off the protocol (i.e. treat http and https the same)
const source = stripurl(r.url);
if (!seen[source]) {
@ -293,64 +158,72 @@ A more detailed example:
return filtered;
}
/**
* Extract comments from a reaction.
*
* @param {Reactions} c
* @returns string
*/
function extractComment(c) {
let text = entities(c.content.text);
if (textMaxWords) {
let words = text.replace(/\s+/g,' ').split(' ', textMaxWords + 1);
if (words.length > textMaxWords) {
words[textMaxWords - 1] += '&hellip;';
words = words.slice(0, textMaxWords);
text = words.join(' ');
}
}
return text;
}
/**
* Format comments as HTML.
*
* @param {Array<Reaction>} comments The comments to format.
* @returns string
*/
function formatComments(comments) {
const headline = `<h2>${t('Responses')}</h2>`;
const markup = comments
.map((c) => {
const image = reactImage(c, true);
function formatComments(type, comments) {
let source = entities(c.url.split('/')[2]);
if (c.author && c.author.name) {
source = entities(c.author.name);
let html = `
<div class="webcomments">
<h3 id="replies-header">` + getIcon('comment') + `&nbsp;<span>` + comments.length + `</span> ` + type + `s </h3>
<ol aria-labelledby="replies-header" role="list">`;
comments.forEach(function (comment) {
let content = '';
if (comment.hasOwnProperty('content')) {
if (comment.content.hasOwnProperty('html')) {
content = comment.content.html;
} else if (comment.content.hasOwnProperty('text')) {
content = comment.content.text;
}
const link = `<a class="source" rel="nofollow ugc" href="${c[mentionSource]}">${source}</a>`;
}
let linkclass = "name";
let linktext = `(${t("mention")})`;
if (c.name) {
linkclass = "name";
linktext = entities(c.name);
} else if (c.content && c.content.text) {
linkclass = "text";
linktext = extractComment(c);
}
html += `
<li class="comment h-entry">
<div>
<a class="comment_author u-author"
href="`+ comment.author.url + `"
target="_blank"
title="`+ comment.author.name + `"
rel="noreferrer">
<img
src="`+ comment.author.photo + `"
alt=""
class="u-photo"
loading="lazy"
decoding="async"
width="48"
height="48"
>
<span class="p-author">`+ comment.author.name + `</span>
</a>`;
if (comment.published) {
const published = new Date(comment.published);
html += `
<time class="dt-published" datetime="`+ comment.published + `">` + published.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) + `</time>`;
}
html += `
</div>
const type = `<span class="${linkclass}">${link} ${linktext}</span>`;
<p class="e-entry">`+ content + `
<a class="u-url"
href="`+ comment.url + `"
target="_blank"
rel="noreferrer">
source
</a>
</p>
</li>
`;
});
html += `
</ol >
</div >
`;
return `<li>${image} ${type}</li>`;
})
.join('');
return `
${headline}
<ul class="comments">${markup}</ul>
`;
return html;
}
/**
@ -366,21 +239,62 @@ A more detailed example:
* @property {string?} wm-source
*/
function getIcon(name) {
if (name == 'like') {
return `<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M95.997 41.986l-.026-.035C85.746 28.36 68.428 21.423 51.165 24.881 30.138 29.094 15.004 47.558 15 69.003c0 24.413 14.906 47.964 39.486 70.086 8.43 7.586 17.437 14.468 26.444 20.533.728.49 1.444.967 2.148 1.43l1.39.909 1.355.872 1.317.835.645.403 1.259.78 1.194.726 1.032.619 1.38.807.418.236a6 6 0 005.864 0l1.138-.654 1.154-.684 1.118-.675.614-.376 1.26-.779a212 212 0 00.644-.403l1.317-.835 1.355-.872 1.39-.909c.704-.463 1.42-.94 2.148-1.43 9.007-6.065 18.015-12.947 26.444-20.533C162.094 116.967 177 93.416 177 69.004c-.004-21.446-15.138-39.91-36.165-44.123-17.07-3.42-34.174 3.323-44.43 16.568l-.408.537zm42.48-5.338c15.421 3.09 26.52 16.63 26.523 32.357 0 19.607-12.438 39.847-33.532 59.357l-1.316 1.205c-.22.201-.443.402-.666.603-7.977 7.18-16.548 13.727-25.118 19.498l-.745.5c-.74.494-1.466.973-2.177 1.437l-1.402.906-1.359.864-.662.416-1.292.8-.732.446-.73-.446-1.292-.8-.662-.416-1.36-.864-1.4-.906a235.406 235.406 0 01-2.923-1.937c-8.57-5.77-17.14-12.319-25.118-19.498l-.666-.603-1.316-1.205C39.438 108.852 27 88.612 27 69.004c.003-15.726 11.102-29.267 26.523-32.356 15.253-3.056 30.565 4.954 36.756 19.208l.204.478c2.084 4.878 9.009 4.85 11.053-.045 6.062-14.511 21.52-22.73 36.941-19.641z" fill="currentColor" /></svg> `;
} else if (name == 'repost') {
return `<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M18.472 146.335l-.075-.184a5.968 5.968 0 01-.216-.684l-.014-.056a5.643 5.643 0 01-.082-.397l-.013-.083a5.886 5.886 0 01-.072-.96V144c0-.157.006-.313.018-.467l.006-.075c.012-.132.028-.261.048-.39l.016-.095c.008-.05.017-.1.027-.149.005-.019.008-.038.012-.058.028-.133.06-.264.096-.393l.026-.088a5.86 5.86 0 01.482-1.159l.043-.077a5.642 5.642 0 01.31-.49l.015-.022.076-.104.044-.059a3.856 3.856 0 01.165-.208l.052-.061c.102-.12.21-.236.321-.348l18-18a6 6 0 018.661 8.303l-.175.183L38.484 138H120c23.196 0 42-18.804 42-42a6 6 0 0112 0c0 29.525-23.696 53.516-53.107 53.993L120 150H38.486l7.757 7.757a6 6 0 01.175 8.303l-.175.183a6 6 0 01-8.303.175l-.183-.175-18-18-.145-.151a6.036 6.036 0 01-.829-1.125l-.058-.105a4.08 4.08 0 01-.06-.114l-.04-.077a4.409 4.409 0 01-.139-.3l-.014-.036zM154.06 25.582l.183.175 18 18a6.036 6.036 0 01.974 1.276l.058.105c.02.035.038.07.056.105l.043.086a4.411 4.411 0 01.14.3l.014.036a5.965 5.965 0 01.291.868l.014.056c.032.13.059.263.082.397l.013.083a5.886 5.886 0 01.067.692v.014a6.11 6.11 0 01-.013.692l-.006.075a5.856 5.856 0 01-.048.39l-.016.095c-.008.05-.017.1-.027.149-.005.019-.008.038-.012.058-.028.133-.06.264-.096.393l-.026.088a5.86 5.86 0 01-.482 1.159l-.043.077-.052.09-.029.048a6.006 6.006 0 01-.32.478l-.044.059a3.857 3.857 0 01-.165.208l-.052.061a6.34 6.34 0 01-.176.197l-.145.15-18 18a6 6 0 01-8.661-8.302l.175-.183L153.514 54H72c-23.196 0-42 18.804-42 42a6 6 0 11-12 0c0-29.525 23.696-53.516 53.107-53.993L72 42h81.516l-7.759-7.757a6 6 0 01-.175-8.303l.175-.183a6 6 0 018.303-.175z" fill="currentColor" /></svg> `;
} else if (name == 'comment') {
return `<svg width = "24" height = "24" viewBox = "0 0 150 150" xmlns = "http://www.w3.org/2000/svg" > <path d="M75-.006a75 75 0 0174.997 74.31l.003.69c0 41.422-33.579 75-75 75H11.75c-6.49 0-11.75-5.26-11.75-11.75v-63.25a75 75 0 0175-75zm0 12a63 63 0 00-63 63v63h63c34.446 0 62.435-27.645 62.992-61.93l.008-1.041-.003-.633A63 63 0 0075 11.994zm21 72a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45zm0-24a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45z" fill="currentColor" /></svg> `;
} else if (name == 'bookmark') {
return `<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24">
<path d="M 6.0097656 2 C 4.9143111 2 4.0097656 2.9025988 4.0097656 3.9980469 L 4 22 L 12 19 L 20 22 L 20 20.556641 L 20 4 C 20 2.9069372 19.093063 2 18 2 L 6.0097656 2 z M 6.0097656 4 L 18 4 L 18 19.113281 L 12 16.863281 L 6.0019531 19.113281 L 6.0097656 4 z" fill="currentColor"></path>
</svg>`
}
}
/**
* Formats a list of reactions as HTML.
*
* @param {Array<Reaction>} reacts List of reactions to format
* @returns string
*/
function formatReactions(reacts) {
const headline = `<h2>${t('Reactions')}</h2>`;
function formatReactions(type, reacts) {
let html = `
<div class="color--primary" >
<h3 id=`+ type + ` - header"> ` + getIcon(type) + `&nbsp; <span>` + reacts.length + `</span> ` + type + `s </h3>
const markup = reacts.map((r) => reactImage(r)).join('');
<ol class="likes" role = "list" aria - labelledby="`+ type + `-header"> `;
return `
${headline}
<ul class="reacts">${markup}</ul>
`;
reacts.forEach(function (react) {
html += `
<li class="h-card">
<a class="u-url"
href="`+ react.author.url + `
target="_blank"
rel = "noreferrer"
title = "`+ react.author.name + `" >
<img
alt=""
class="lazy mentions__image u-photo"
src="`+ react.author.photo + `"
loading="lazy"
decoding="async"
width="48"
height="48"
>
<span class="p-author visually-hidden" aria-hidden="true">{{ author }}</span>
</a>
</li>
`;
});
html += `
</ol >
</div >
`;
return html;
}
/**
@ -412,9 +326,11 @@ A more detailed example:
apiURL += `&target[]=${encodeURIComponent('http:' + path)}&target[]=${encodeURIComponent('https:' + path)}`;
});
// apiURL = 'http://127.0.0.1:1111/test_webmentions.jf2';
/** @type {WebmentionResponse} */
let json = {};
try {
// const response = await window.fetch(apiURL);
const response = await window.fetch(apiURL);
if (response.status >= 200 && response.status < 300) {
json = await response.json();
@ -422,7 +338,7 @@ A more detailed example:
console.error("Could not parse response");
new Error(response.statusText);
}
} catch(error) {
} catch (error) {
// Purposefully not escalate further, i.e. no UI update
console.error("Request failed", error);
}
@ -430,24 +346,28 @@ A more detailed example:
/** @type {Array<Reaction>} */
let comments = [];
/** @type {Array<Reaction>} */
const collects = [];
if (commentsAreReactions) {
comments = collects;
}
let mentions = [];
/** @type {Array<Reaction>} */
const bookmarks = [];
/** @type {Array<Reaction>} */
const likes = [];
/** @type {Array<Reaction>} */
const reposts = [];
/** @type {Array<Reaction>} */
const follows = [];
/** @type {Record<MentionType, Array<Reaction>>} */
const mapping = {
"in-reply-to": comments,
"like-of": collects,
"repost-of": collects,
"bookmark-of": collects,
"follow-of": collects,
"mention-of": comments,
"like-of": likes,
"repost-of": reposts,
"bookmark-of": bookmarks,
"follow-of": follows,
"mention-of": mentions,
"rsvp": comments
};
json.children.forEach(function(child) {
json.children.forEach(function (child) {
// Map each mention into its respective container
const store = mapping[child['wm-property']];
if (store) {
@ -456,18 +376,35 @@ A more detailed example:
});
// format the comment-type things
let formattedMentions = '';
if (mentions.length > 0) {
formattedMentions = formatComments('mention', dedupe(mentions));
}
let formattedComments = '';
if (comments.length > 0 && comments !== collects) {
formattedComments = formatComments(dedupe(comments));
if (comments.length > 0) {
formattedComments = formatComments('comment', dedupe(comments));
}
// format the other reactions
let reactions = '';
if (collects.length > 0) {
reactions = formatReactions(dedupe(collects));
// format likes
let likesStr = '';
if (likes.length > 0) {
likesStr = formatReactions('like', dedupe(likes));
}
container.innerHTML = `${formattedComments}${reactions}`;
// format reposts
let repostsStr = '';
if (reposts.length > 0) {
repostsStr = formatReactions('repost', dedupe(reposts));
}
// format bookmarks
let bookmarksStr = '';
if (bookmarks.length > 0) {
bookmarksStr = formatReactions('bookmark', dedupe(bookmarks));
}
container.innerHTML = `<div id="webmentions">${repostsStr}${likesStr}${bookmarksStr}${formattedComments}${formattedMentions}</div>`;
});
}());

View file

@ -1,3 +1,66 @@
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt
!function(){"use strict";window.i18next=window.i18next||{t:function(n){return n}};const n=window.i18next.t.bind(window.i18next);function t(n,t){return document.currentScript.getAttribute("data-"+n)||t}const e=t("page-url",window.location.href.replace(/#.*$/,"")),o=t("add-urls",void 0),s=t("id","webmentions"),r=t("wordcount"),i=t("max-webmentions",30),a=t("prevent-spoofing")?"wm-source":"url",l=t("sort-by","published"),c=t("sort-dir","up"),u=t("comments-are-reactions",!1),p={"in-reply-to":n("replied"),"like-of":n("liked"),"repost-of":n("reposted"),"bookmark-of":n("bookmarked"),"mention-of":n("mentioned"),rsvp:n("RSVPed"),"follow-of":n("followed")},f={"in-reply-to":"💬","like-of":"❤️","repost-of":"🔄","bookmark-of":"⭐️","mention-of":"💬",rsvp:"📅","follow-of":"🐜"},m={yes:"✅",no:"❌",interested:"💡",maybe:"💭"};function d(n){return n.replace(/[&<>"]/g,(n=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[n]||n)))}function w(t,e){const o=d(t.author?.name||t.url.split("/")[2]);let s=p[t["wm-property"]]||n("reacted");!e&&t.content&&t.content.text&&(s+=": "+$(t));let r="";r=t.author&&t.author.photo?`\n <img\n src="${d(t.author.photo)}"\n loading="lazy"\n decoding="async"\n alt="${o}"\n >\n `:`\n <img\n class="missing"\n src="data:image/webp;base64,UklGRkoCAABXRUJQVlA4TD4CAAAvP8APAIV0WduUOLr/m/iqY6SokDJSMD5xYX23SQizRsVdZmIj/f6goYUbiOj/BED7MOPReuBNT3vBesSzIex+SeqMFFkjebFmzH3S7POxDSJ1yaCbCmMnS2R46cRMPyQLw4GBK4esdK60pYwsZakecUCl5zsHv/5cPH08nx9/7i6rEEVCg2hR8VSd30PxMZpVoJZQO6Dixgg6X5oKFCmlVHIDmmMFShWumAXgCuyqVN8hHff/k+9fj8+ei7BVjpxBmZCUJv+6DhWGZwWvs+UoLHFCKsPYpfJtIcEXBTopEEsKwedZUv4ku1FZErKULLyQwFGgnmTs2vBD5qu44xwnG9uyjgrFOd+KRVlXyQfwQlauydaU6AVI7OjKXLUEqNtxJBmQegNDZgV7lxxqYMOMrDyC1NdAGbdiH9Ij0skjG+oTyfO0lmjdgvoH8iIgreuBMRYLSH+R3sAztXgL+XfS7E2bmfo6gnS0TrpnzHT7kL+skj7PgHuBwv/zpN8LDLQg7zfJZLBubMKnyeh6ZGyfDEfc2LYpnlUtG7JqsSHq1WoASbUS4KVaLwB8be5mfsGMDwBcm5VxbuxWxx3nkFanB6lYqsqSkOGkKicoDvXsneR7BkKU7DtaEuT7+pxBGVwx+9gVyqf2pVA9sC2CsmjZ1RJqEJHS4Tj/pCcS0JoyBYOsB91Xjh3OFfQPQhvCAYyeLJlaOoFp0XNNuD0BC8exr8uPx7D1JWkwFdZIXmD3MOPReuDNzHjBesSzIbQD"\n alt="${o}$"\n >\n `;let i="";i=f[t["wm-property"]]?`<span class="emoji">${f[t["wm-property"]]}</span>`:'<span class="emoji">💥</span>';let l="";return t.rsvp&&m[t.rsvp]&&(l=`${m[t.rsvp]}`),`\n <a\n class="reaction"\n rel="nofollow ugc"\n title="${o} ${s}"\n href="${t[a]}"\n >\n ${r}\n ${i}\n ${l}\n </a>\n `}function h(n){return n.substr(n.indexOf("//"))}function g(n){const t=[],e={};return n.forEach((function(n){const o=h(n.url);e[o]||(t.push(n),e[o]=!0)})),t}function $(n){let t=d(n.content.text);if(r){let n=t.replace(/\s+/g," ").split(" ",r+1);n.length>r&&(n[r-1]+="&hellip;",n=n.slice(0,r),t=n.join(" "))}return t}window.addEventListener("load",(async function(){const t=document.getElementById(s);if(!t)return;const r=[h(e)];o&&o.split("|").forEach((function(n){r.push(h(n))}));let p=`https://webmention.io/api/mentions.jf2?per-page=${i}&sort-by=${l}&sort-dir=${c}`;r.forEach((function(n){p+=`&target[]=${encodeURIComponent("http:"+n)}&target[]=${encodeURIComponent("https:"+n)}`}));let f={};try{const n=await window.fetch(p);n.status>=200&&n.status<300?f=await n.json():(console.error("Could not parse response"),new Error(n.statusText))}catch(n){console.error("Request failed",n)}let m=[];const x=[];u&&(m=x);const y={"in-reply-to":m,"like-of":x,"repost-of":x,"bookmark-of":x,"follow-of":x,"mention-of":m,rsvp:m};f.children.forEach((function(n){const t=y[n["wm-property"]];t&&t.push(n)}));let k="";m.length>0&&m!==x&&(k=function(t){return`\n <h2>${n("Responses")}</h2>\n <ul class="comments">${t.map((t=>{const e=w(t,!0);let o=d(t.url.split("/")[2]);t.author&&t.author.name&&(o=d(t.author.name));const s=`<a class="source" rel="nofollow ugc" href="${t[a]}">${o}</a>`;let r="name",i=`(${n("mention")})`;return t.name?(r="name",i=d(t.name)):t.content&&t.content.text&&(r="text",i=$(t)),`<li>${e} <span class="${r}">${s} ${i}</span></li>`})).join("")}</ul>\n `}(g(m)));let b="";var B;x.length>0&&(B=g(x),b=`\n <h2>${n("Reactions")}</h2>\n <ul class="reacts">${B.map((n=>w(n))).join("")}</ul>\n `),t.innerHTML=`${k}${b}`}))}();
// @license-end
(()=>{function getCfg(key,dfl){return document.currentScript.getAttribute("data-"+key)||dfl}window.i18next=window.i18next||{t:function(key){return key}},window.i18next.t.bind(window.i18next);let refurl=getCfg("page-url",window.location.href.replace(/#.*$/,"")),addurls=getCfg("add-urls",void 0),containerID=getCfg("id","webmentions"),maxWebmentions=(getCfg("wordcount"),getCfg("max-webmentions",30)),sortBy=(getCfg("prevent-spoofing"),getCfg("sort-by","published")),sortDir=getCfg("sort-dir","up");function stripurl(url){return url.substr(url.indexOf("//"))}function dedupe(mentions){let filtered=[],seen={};return mentions.forEach(function(r){var source=stripurl(r.url);seen[source]||(filtered.push(r),seen[source]=!0)}),filtered}function formatComments(type,comments){let html=`
<div class="webcomments">
<h3 id="replies-header">`+getIcon("comment")+"&nbsp;<span>"+comments.length+"</span> "+type+`s </h3>
<ol aria-labelledby="replies-header" role="list">`;return comments.forEach(function(comment){let content="";var published;comment.hasOwnProperty("content")&&(comment.content.hasOwnProperty("html")?content=comment.content.html:comment.content.hasOwnProperty("text")&&(content=comment.content.text)),html+=`
<li class="comment h-entry">
<div>
<a class="comment_author u-author"
href="`+comment.author.url+`"
target="_blank"
title="`+comment.author.name+`"
rel="noreferrer">
<img
src="`+comment.author.photo+`"
alt=""
class="u-photo"
loading="lazy"
decoding="async"
width="48"
height="48"
>
<span class="p-author">`+comment.author.name+`</span>
</a>`,comment.published&&(published=new Date(comment.published),html+=`
<time class="dt-published" datetime="`+comment.published+'">'+published.toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"})+"</time>"),html+=`
</div>
<p class="e-entry">`+content+`
<a class="u-url"
href="`+comment.url+`"
target="_blank"
rel="noreferrer">
source
</a>
</p>
</li>
`}),html+=`
</ol >
</div >
`}function getIcon(name){return"like"==name?'<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M95.997 41.986l-.026-.035C85.746 28.36 68.428 21.423 51.165 24.881 30.138 29.094 15.004 47.558 15 69.003c0 24.413 14.906 47.964 39.486 70.086 8.43 7.586 17.437 14.468 26.444 20.533.728.49 1.444.967 2.148 1.43l1.39.909 1.355.872 1.317.835.645.403 1.259.78 1.194.726 1.032.619 1.38.807.418.236a6 6 0 005.864 0l1.138-.654 1.154-.684 1.118-.675.614-.376 1.26-.779a212 212 0 00.644-.403l1.317-.835 1.355-.872 1.39-.909c.704-.463 1.42-.94 2.148-1.43 9.007-6.065 18.015-12.947 26.444-20.533C162.094 116.967 177 93.416 177 69.004c-.004-21.446-15.138-39.91-36.165-44.123-17.07-3.42-34.174 3.323-44.43 16.568l-.408.537zm42.48-5.338c15.421 3.09 26.52 16.63 26.523 32.357 0 19.607-12.438 39.847-33.532 59.357l-1.316 1.205c-.22.201-.443.402-.666.603-7.977 7.18-16.548 13.727-25.118 19.498l-.745.5c-.74.494-1.466.973-2.177 1.437l-1.402.906-1.359.864-.662.416-1.292.8-.732.446-.73-.446-1.292-.8-.662-.416-1.36-.864-1.4-.906a235.406 235.406 0 01-2.923-1.937c-8.57-5.77-17.14-12.319-25.118-19.498l-.666-.603-1.316-1.205C39.438 108.852 27 88.612 27 69.004c.003-15.726 11.102-29.267 26.523-32.356 15.253-3.056 30.565 4.954 36.756 19.208l.204.478c2.084 4.878 9.009 4.85 11.053-.045 6.062-14.511 21.52-22.73 36.941-19.641z" fill="currentColor" /></svg> ':"repost"==name?'<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M18.472 146.335l-.075-.184a5.968 5.968 0 01-.216-.684l-.014-.056a5.643 5.643 0 01-.082-.397l-.013-.083a5.886 5.886 0 01-.072-.96V144c0-.157.006-.313.018-.467l.006-.075c.012-.132.028-.261.048-.39l.016-.095c.008-.05.017-.1.027-.149.005-.019.008-.038.012-.058.028-.133.06-.264.096-.393l.026-.088a5.86 5.86 0 01.482-1.159l.043-.077a5.642 5.642 0 01.31-.49l.015-.022.076-.104.044-.059a3.856 3.856 0 01.165-.208l.052-.061c.102-.12.21-.236.321-.348l18-18a6 6 0 018.661 8.303l-.175.183L38.484 138H120c23.196 0 42-18.804 42-42a6 6 0 0112 0c0 29.525-23.696 53.516-53.107 53.993L120 150H38.486l7.757 7.757a6 6 0 01.175 8.303l-.175.183a6 6 0 01-8.303.175l-.183-.175-18-18-.145-.151a6.036 6.036 0 01-.829-1.125l-.058-.105a4.08 4.08 0 01-.06-.114l-.04-.077a4.409 4.409 0 01-.139-.3l-.014-.036zM154.06 25.582l.183.175 18 18a6.036 6.036 0 01.974 1.276l.058.105c.02.035.038.07.056.105l.043.086a4.411 4.411 0 01.14.3l.014.036a5.965 5.965 0 01.291.868l.014.056c.032.13.059.263.082.397l.013.083a5.886 5.886 0 01.067.692v.014a6.11 6.11 0 01-.013.692l-.006.075a5.856 5.856 0 01-.048.39l-.016.095c-.008.05-.017.1-.027.149-.005.019-.008.038-.012.058-.028.133-.06.264-.096.393l-.026.088a5.86 5.86 0 01-.482 1.159l-.043.077-.052.09-.029.048a6.006 6.006 0 01-.32.478l-.044.059a3.857 3.857 0 01-.165.208l-.052.061a6.34 6.34 0 01-.176.197l-.145.15-18 18a6 6 0 01-8.661-8.302l.175-.183L153.514 54H72c-23.196 0-42 18.804-42 42a6 6 0 11-12 0c0-29.525 23.696-53.516 53.107-53.993L72 42h81.516l-7.759-7.757a6 6 0 01-.175-8.303l.175-.183a6 6 0 018.303-.175z" fill="currentColor" /></svg> ':"comment"==name?'<svg width = "24" height = "24" viewBox = "0 0 150 150" xmlns = "http://www.w3.org/2000/svg" > <path d="M75-.006a75 75 0 0174.997 74.31l.003.69c0 41.422-33.579 75-75 75H11.75c-6.49 0-11.75-5.26-11.75-11.75v-63.25a75 75 0 0175-75zm0 12a63 63 0 00-63 63v63h63c34.446 0 62.435-27.645 62.992-61.93l.008-1.041-.003-.633A63 63 0 0075 11.994zm21 72a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45zm0-24a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45z" fill="currentColor" /></svg> ':"bookmark"==name?`<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24">
<path d="M 6.0097656 2 C 4.9143111 2 4.0097656 2.9025988 4.0097656 3.9980469 L 4 22 L 12 19 L 20 22 L 20 20.556641 L 20 4 C 20 2.9069372 19.093063 2 18 2 L 6.0097656 2 z M 6.0097656 4 L 18 4 L 18 19.113281 L 12 16.863281 L 6.0019531 19.113281 L 6.0097656 4 z" fill="currentColor"></path>
</svg>`:void 0}function formatReactions(type,reacts){let html=`
<div class="color--primary" >
<h3 id=`+type+' - header"> '+getIcon(type)+"&nbsp; <span>"+reacts.length+"</span> "+type+`s </h3>
<ol class="likes" role = "list" aria - labelledby="`+type+'-header"> ';return reacts.forEach(function(react){html+=`
<li class="h-card">
<a class="u-url"
href="`+react.author.url+`
target="_blank"
rel = "noreferrer"
title = "`+react.author.name+`" >
<img
alt=""
class="lazy mentions__image u-photo"
src="`+react.author.photo+`"
loading="lazy"
decoding="async"
width="48"
height="48"
>
<span class="p-author visually-hidden" aria-hidden="true">{{ author }}</span>
</a>
</li>
`}),html+=`
</ol >
</div >
`}window.addEventListener("load",async function(){var container=document.getElementById(containerID);if(container){let pages=[stripurl(refurl)],apiURL=(addurls&&addurls.split("|").forEach(function(url){pages.push(stripurl(url))}),`https://webmention.io/api/mentions.jf2?per-page=${maxWebmentions}&sort-by=${sortBy}&sort-dir=`+sortDir),json=(pages.forEach(function(path){apiURL+=`&target[]=${encodeURIComponent("http:"+path)}&target[]=`+encodeURIComponent("https:"+path)}),{});try{var response=await window.fetch(apiURL);200<=response.status&&response.status<300?json=await response.json():(console.error("Could not parse response"),new Error(response.statusText))}catch(error){console.error("Request failed",error)}var response=[],mentions=[],bookmarks=[],likes=[],reposts=[];let mapping={"in-reply-to":response,"like-of":likes,"repost-of":reposts,"bookmark-of":bookmarks,"follow-of":[],"mention-of":mentions,rsvp:response},formattedMentions=(json.children.forEach(function(child){var store=mapping[child["wm-property"]];store&&store.push(child)}),""),formattedComments=(0<mentions.length&&(formattedMentions=formatComments("mention",dedupe(mentions))),""),likesStr=(0<response.length&&(formattedComments=formatComments("comment",dedupe(response))),""),repostsStr=(0<likes.length&&(likesStr=formatReactions("like",dedupe(likes))),""),bookmarksStr=(0<reposts.length&&(repostsStr=formatReactions("repost",dedupe(reposts))),"");0<bookmarks.length&&(bookmarksStr=formatReactions("bookmark",dedupe(bookmarks))),container.innerHTML=`<div id="webmentions">${repostsStr}${likesStr}${bookmarksStr}${formattedComments}${formattedMentions}</div>`}})})();

View file

@ -59,4 +59,4 @@
{%- set dash = "-" -%}
{% endif %}
<div class="webmentions-container" id="webmentions{{ dash}}{{ format }}">hello</div>
<div class="webmentions-container" id="webmentions{{ dash}}{{ format }}"></div>