PageSourceSearch

https://jay.gooby.org/js/webmentions.js

js gooby.org collected 2026-10-03 20:28:09 UTC 15,124 bytes, 431 lines download raw bytes

1/** @preserve Based heavily on the work by Keith Grant (keithjgrant.com) **/
2
3// IIFE to restrict global namespace
4(function(){
5    // link to the anonymous avatar
6    const ANON_AVATAR = '/images/mm.png';
7
8    // var to store the built HTML
9    let webmentionHTML = `<div class='content-webmentions__inner'>`;
10
11    function init(){
12        // grab the URL of the current page
13        let url = document.location.origin + document.location.pathname;
14
15        // these are the url permutations we will check against
16        const targets = getUrlPermutations(url);
17
18        // build each script tag
19        let script = document.createElement('script');
20        let src = 'https://webmention.io/api/mentions?perPage=500&jsonp=parseWebmentions';
21
22        // check the API for each domain
23        targets.forEach(function(targetUrl) {
24            src += `&target[]=${encodeURIComponent(targetUrl)}`;
25        });
26        // cachebuster
27        src += `&_=${Math.random()}`;
28        script.src = src;
29        script.async = true;
30        document.getElementsByTagName('head')[0].appendChild(script);
31    }
32
33    // build the permutations array to check the API for
34    function getUrlPermutations(url) {
35        const urls = [];
36        // useful for local testing, populate localhost with live mentions
37        url = url.replace('http://127.0.0.1:4000', 'https://jay.gooby.org');
38        urls.push(url);
39        // check the non-https version too
40        urls.push(url.replace('https://', 'http://'));
41        // check a version of the URL without the trailing slash
42        if (url.substr(-1) === '/') {
43          var noslash = url.substr(0, url.length - 1);
44          urls.push(noslash);
45          urls.push(noslash.replace('https://', 'http://'));
46        }
47        // return array of permutations
48        return urls;
49    }
50
51    // sort function used for incoming data
52    function webmentionSort(a, b) {
53        const dateA = getWebmentionDate(a);
54        const dateB = getWebmentionDate(b);
55
56        if (dateA < dateB) {
57            return -1;
58        } else if (dateB < dateA) {
59            return 1;
60        }
61        return 0;
62    }
63
64    // return a date for the mention from published or verified
65    function getWebmentionDate(webmention) {
66        if (webmention.data.published) {
67            return new Date(webmention.data.published);
68        }
69        return new Date(webmention.verified_date);
70    }
71
72    function parseWebmentions(data) {
73        // if no mentions, hide the loader
74        if(data.links.length == 0){
75            // console.log("hide the loader");
76            document.querySelector('.content-webmentions__loader').classList.add('visually-hidden');
77
78            renderForm();
79            // close the webmention container
80            webmentionHTML += `</div>`;
81            // now that the HTML is built, add it to the DOM (only a single insertion for optimal performance)
82            document.querySelector('.content-webmentions').innerHTML = webmentionHTML;
83
84            setupFormInteractions();
85            return;
86        }
87
88        // sort the incoming data
89        let links = data.links.sort(webmentionSort);
90
91        // create three arrays to store our webmentions
92        let likes = [];
93        let repostsLinks = [];
94        let replies = [];
95
96        // map over the webmentions and sort the into arrays
97        links.map(function(l) {
98            // if no activity key, unknown type
99            if (!l.activity || !l.activity.type) {
100                console.warning('unknown link type', l);
101                return;
102            }
103            // not verified, then not valid
104            if (!l.verified) {
105                return;
106            }
107
108            // depending on the type, push into an correct array
109            switch (l.activity.type) {
110                case 'like':
111                    likes.push(l);
112                    break;
113                case 'repost':
114                case 'link':
115                    repostsLinks.push(l);
116                    break;
117                default:
118                    replies.push(l);
119                    break;
120            }
121        });
122
123        // render the number of webmentions to the page
124        webmentionHTML += renderWebmentionTotal(data.links.length);
125
126        // render the form
127        renderForm();
128
129        // if we have replies, build the replies HTML
130        if(replies.length){
131            renderReplies(replies);
132        }
133
134        // if we have likes, build the like HTML
135        if(likes.length){
136            renderLikes(likes);
137        }
138
139        // if we have reposts, build the reposts HTML
140        if(repostsLinks.length){
141            renderRepostsLinks(repostsLinks);
142        }
143
144        // close the webmention container
145        webmentionHTML += `</div>`;
146
147        // now that the HTML is built, add it to the DOM (only a single insertion for optimal performance)
148        document.querySelector('.content-webmentions').innerHTML = webmentionHTML;
149
150        // setup the send webmention form interactions
151        setupFormInteractions();
152
153        // hide the loader now we're done
154        document.querySelector('.content-webmentions__loader').classList.add('visually-hidden');
155    }
156
157    // add form events
158    function setupFormInteractions(){
159        // store the form element
160        let formEle = document.querySelector('.content-webmentions__form');
161        // listen for the submit event, execute once heard
162        formEle.addEventListener('submit', formSubmit);
163    }
164
165    // handle the form submission
166    function formSubmit(evt){
167        // stop the default form submission
168        evt.preventDefault();
169
170        // store the page webmention endpoint
171        let linkHref = document.querySelector('link[rel=webmention]').href;
172
173        // store the two webmention URLS
174        let target = document.querySelector('#webmention-target').value;
175        let source = document.querySelector('#webmention-source').value;
176
177        // setup new XMLHttpRequest for the POST
178        let xhr = new XMLHttpRequest();
179
180        // do the post
181        xhr.open('POST', `${linkHref}`);
182        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
183        xhr.send(encodeURI(`source=${source}&target=${target}`));
184
185        // wait for the response
186        xhr.onload = function() {
187            let response = JSON.parse(xhr.responseText);
188            if(xhr.status === 200 || xhr.status === 201){
189                console.info(`XMLHttpRequest success: ${xhr.status}`);
190            } else {
191                console.warn(`XMLHttpRequest failed: ${xhr.status}`);
192            }
193
194            // feedback response to the user
195            formMessages(response);
196        };
197    }
198
199    // populate the feedback message for the user
200    function formMessages(responseData){
201        // message element
202        let messageEle = document.querySelector('.content-webmentions__form-message');
203
204        // messages
205        let thanks = `Thank you for your submission!`;
206        let error = `Looks like something went wrong:`;
207        let message;
208
209        // adjust content depending on the status
210        switch(responseData.status){
211            case 'queued':
212            case 'success':
213                message = `${thanks} ${responseData.summary}. <a href="${responseData.location}">View the current status.</a>`;
214                break;
215            default:
216                message = `${error} ${responseData.summary}.`;
217                break;
218        }
219
220        // set elements HTML to the message
221        messageEle.innerHTML = message;
222
223        // show the message
224        messageEle.classList.remove('visually-hidden');
225    }
226
227    // set to global method for JSONP
228    window.parseWebmentions = parseWebmentions;
229
230    // human friendly month formats
231    var months = [
232        'Jan',
233        'Feb',
234        'Mar',
235        'Apr',
236        'May',
237        'Jun',
238        'Jul',
239        'Aug',
240        'Sep',
241        'Oct',
242        'Nov',
243        'Dec'
244    ];
245
246    // format the date
247    function formatDate(date) {
248        if (!date) {
249            return '';
250        }
251        // e.g. 22 Oct 2019
252        return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`;
253    }
254
255    // simple function to look a 1 vs many results
256    function renderWebmentionTotal(total){
257        return `<h2>${total + (total === 1 ? ' Webmention' : ' Webmentions')}</h2>`;
258    }
259
260    // if we have a url instead of an author, construct the link
261    function getHostName(url) {
262        let a = document.createElement('a');
263        a.href = url;
264        // remove the www for stylistic reasons
265        return (a.hostname || '').replace('www.', '');
266    }
267
268    // render the webmention submit form
269    function renderForm(){
270        // generate the form HTML
271        let formHTML = `<div class="content-webmentions__form">${formTemplate()}</div>`;
272
273        // append the generated like HTML to the single set of HTML
274        webmentionHTML += formHTML;
275    }
276
277    // render the webmention likes from the sorted array
278    function renderLikes(likesArray){
279        // build our likes HTML
280        let likesHTML = `
281            <div class="content-webmentions--likes">
282                <h3>${likesArray.length} ${likesArray.length === 1 ? ' like' : ' likes'}</h3>
283                <ul class='content-webmentions__list'>`;
284
285        // map over our likes, build the data object
286        likesArray.map(function(l){
287            let likeObj = {
288                photo: l.data.author.photo,
289                name: l.data.author.name,
290                authorUrl: l.data.author.url,
291                url: l.data.url,
292                date: new Date(l.data.published || l.verified_date)
293            }
294
295            // create each like
296            likesHTML += likeRepostLinkTemplate(likeObj);
297        });
298
299        // close the like HTML
300        likesHTML +=
301            `   </ul>
302            </div>`;
303
304        // append the generated like HTML to the single set of HTML
305        webmentionHTML += likesHTML;
306    }
307
308    // function to render reposts and links
309    function renderRepostsLinks(repostsArray){
310        // build our repost / links HTML
311        let repostLinksHTML = `
312            <div class="content-webmentions--reposts">
313                <h3>${repostsArray.length} ${repostsArray.length === 1 ? ' repost' : ' reposts'}</h3>
314                <ul class='content-webmentions__list'>`;
315
316        // map over our reposts / likes, build the data object
317        repostsArray.map(function(l){
318            let repostLinkObj;
319            // if a repost (has an author)
320            if (l.data.author) {
321                repostLinkObj = {
322                    photo: l.data.author.photo,
323                    name: l.data.author.name,
324                    authorUrl: l.data.author.url,
325                    url: l.data.url,
326                    date: new Date(l.data.published || l.verified_date)
327                }
328            } else {
329                // this is for a link
330                repostLinkObj = {
331                    photo: ANON_AVATAR,
332                    name: getHostName(l.data.url) || 'inbound link',
333                    authorUrl: l.data.url,
334                    url: l.data.url,
335                    date: new Date(l.data.published || l.verified_date),
336                };
337            }
338
339            // create each like
340            repostLinksHTML += likeRepostLinkTemplate(repostLinkObj);
341        });
342
343        // close the HTML
344        repostLinksHTML +=
345            `   </ul>
346            </div>`;
347
348        // append the generated like HTML to the single set of HTML
349        webmentionHTML += repostLinksHTML;
350    }
351
352    // function to render replies
353    function renderReplies(replyArray){
354        // build the reply HTML
355        let replyHTML = `
356        <div class="content-webmentions--replies">
357            <h3>${replyArray.length} ${replyArray.length === 1 ? ' reply' : ' replies'}</h3>
358            <ul class='content-webmentions__list'>`;
359
360        // map over each reply,
361        replyArray.map(function(l){
362            let replyObj = {
363                photo: l.data.author.photo,
364                name: l.data.author.name,
365                authorUrl: l.data.author.url,
366                url: l.data.url,
367                date: new Date(l.data.published || l.verified_date),
368                content: l.data.content
369            }
370
371            replyHTML += replyTemplate(replyObj);
372        });
373
374        // close the HTML
375        replyHTML +=
376            `   </ul>
377            </div>`;
378
379        // append the generated HTML to the total
380        webmentionHTML += replyHTML;
381    }
382
383    // reply HTML template
384    function replyTemplate(replyObj){
385        return `<li class="content-webmentions__list-item h-entry">
386            <div class="comment">
387                <div class="comment__author reply p-author">
388                    <a class="reply__avatar u-author" href="${replyObj.authorUrl}" title="${replyObj.name}">
389                        <img class="u-photo" src="${replyObj.photo}" alt="${replyObj.name}">
390                    </a>
391                    <a class="reply__bar u-url" href="${replyObj.url}">
392                        <p class="reply__author reply__author p-author">${replyObj.name}</p>
393                        <p class="reply__date" href="">${formatDate(replyObj.date)}</p>
394                    </a>
395                </div>
396                <div class="comment__content e-entry">${replyObj.content}</div>
397            </div>
398        </li>`;
399    }
400
401    // like, repost, link HTML template
402    function likeRepostLinkTemplate(likeObj){
403        return `<li class="content-webmentions__list-item h-entry">
404            <div class="reply h-card p-author">
405                <a class="reply__bar u-url" href="${likeObj.url}">
406                    <p class="reply__author reply__author p-name">${likeObj.name}</p>
407                    <p class="reply__date" href="">${formatDate(likeObj.date)}</p>
408                </a>
409                <a class="reply__avatar u-author" href="${likeObj.authorUrl}" title="${likeObj.name}">
410                    <img class="u-photo" src="${likeObj.photo}" alt="${likeObj.name}">
411                </a>
412            </div>
413        </li>`;
414    }
415
416    // template for the JS version of the form
417    function formTemplate(){
418        return `<form id="webmention-form" class="content-webmentions__form" method="post" action="https://webmention.io/jay.gooby.org/webmention">
419                    <input id="webmention-target" type="hidden" name="target" value="${window.location.href}">
420
421                    <label class="content-webmentions__form-label" for="webmention-source">Written a response to this post? Let me know the URL below:</label>
422                    <input type="text" id="webmention-source" name="source" placeholder="https://yourblog.com/your-post" class="content-webmentions__form-input" required>
423                    <button type="submit" id="webmention-submit" class="content-webmentions__form-button">Send Webmention</button>
424
425                    <p class="content-webmentions__form-message visually-hidden"></p>
426                </form>`;
427    }
428
429    // start the dance
430    init();
431})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.