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.