1/* globals related_posts_js_options */ 2 3/** 4 * Load related posts 5 */ 6( function () { 7 'use strict'; 8 9 var jprp = { 10 response: null, 11 12 /** 13 * Utility get related posts JSON endpoint from URLs 14 * 15 * @param {string} URL (optional) 16 * @return {string} Endpoint URL 17 */ 18 getEndpointURL: function ( URL ) { 19 var locationObject, 20 is_customizer = 21 'undefined' !== typeof wp && 22 wp.customize && 23 wp.customize.settings && 24 wp.customize.settings.url && 25 wp.customize.settings.url.self; 26 27 // If we're in Customizer, write the correct URL. 28 if ( is_customizer ) { 29 locationObject = document.createElement( 'a' ); 30 locationObject.href = wp.customize.settings.url.self; 31 } else { 32 locationObject = document.location; 33 } 34 35 if ( 'string' === typeof URL && URL.match( /^https?:\/\// ) ) { 36 locationObject = document.createElement( 'a' ); 37 locationObject.href = URL; 38 } 39 40 var args = 'relatedposts=1'; 41 var relatedPosts = document.querySelector( '#jp-relatedposts' ); 42 43 if ( ! relatedPosts ) { 44 return false; 45 } 46 47 if ( relatedPosts.hasAttribute( 'data-exclude' ) ) { 48 args += '&relatedposts_exclude=' + relatedPosts.getAttribute( 'data-exclude' ); 49 } 50 51 if ( is_customizer ) { 52 args += '&jetpackrpcustomize=1'; 53 } 54 55 var pathname = locationObject.pathname; 56 if ( '/' !== pathname[ 0 ] ) { 57 pathname = '/' + pathname; 58 } 59 60 if ( '' === locationObject.search ) { 61 return pathname + '?' + args; 62 } 63 return pathname + locationObject.search + '&' + args; 64 }, 65 66 getAnchor: function ( post, classNames ) {
67 var anchorTitle = post.title; 68 var anchor = document.createElement( 'a' ); 69 anchor.setAttribute( 'class', classNames ); 70 anchor.setAttribute( 'href', post.url ); 71 anchor.setAttribute( 'title', anchorTitle ); 72 anchor.setAttribute( 'data-origin', post.url_meta.origin ); 73 anchor.setAttribute( 'data-position', post.url_meta.position ); 74 75 if ( '' !== post.rel ) { 76 anchor.setAttribute( 'rel', post.rel ); 77 } 78 79 var div = document.createElement( 'div' ); 80 div.appendChild( anchor ); 81 82 var anchorHTML = div.innerHTML; 83 return [ anchorHTML.substring( 0, anchorHTML.length - 4 ), '</a>' ]; 84 }, 85 86 generateMinimalHtml: function ( posts, options ) { 87 var self = this; 88 var html = ''; 89 90 posts.forEach( function ( post, index ) { 91 var anchor = self.getAnchor( post, 'jp-relatedposts-post-a' ); 92 var classes = 'jp-relatedposts-post jp-relatedposts-post' + index; 93 94 if ( post.classes.length > 0 ) { 95 classes += ' ' + post.classes.join( ' ' ); 96 } 97 98 html += 99 '<p class="' + 100 classes + 101 '" data-post-id="' + 102 post.id + 103 '" data-post-format="' + 104 post.format + 105 '">'; 106 html += 107 '<span class="jp-relatedposts-post-title">' + 108 anchor[ 0 ] + 109 post.title + 110 anchor[ 1 ] + 111 '</span>'; 112 if ( options.showDate ) { 113 html += 114 '<time class="jp-relatedposts-post-date" datetime="' + 115 post.date + 116 '">' + 117 post.date + 118 '</time>'; 119 } 120 if ( options.showContext ) { 121 html += '<span class="jp-relatedposts-post-context">' + post.context + '</span>'; 122 } 123 html += '</p>'; 124 } ); 125 return ( 126 '<div class="jp-relatedposts-items jp-relatedposts-items-minimal jp-relatedposts-' + 127 options.layout + 128 ' ">' + 129 html + 130 '</div>' 131 ); 132 }, 133 134 generateVisualHtml: function ( posts, options ) { 135 var self = this; 136 var html = ''; 137 138 posts.forEach( function ( post, index ) { 139 var anchor = self.getAnchor( post, 'jp-relatedposts-post-a' ); 140 var classes = 'jp-relatedposts-post jp-relatedposts-post' + index; 141 142 if ( post.classes.length > 0 ) { 143 classes += ' ' + post.classes.join( ' ' ); 144 } 145 146 if ( ! post.img.src ) { 147 classes += ' jp-relatedposts-post-nothumbs'; 148 } else { 149 classes += ' jp-relatedposts-post-thumbs'; 150 } 151 152 var dummyContainer = document.createElement( 'p' ); 153 dummyContainer.innerHTML = post.excerpt; 154 var excerpt = dummyContainer.textContent; 155 156 html += 157 '<div class="' + 158 classes + 159 '" data-post-id="' + 160 post.id + 161 '" data-post-format="' + 162 post.format + 163 '">'; 164 if ( post.img.src ) { 165 html += 166 anchor[ 0 ] + 167 '<img class="jp-relatedposts-post-img" loading="lazy" src="' + 168 post.img.src + 169 '" width="' + 170 post.img.width + 171 '" height="' + 172 post.img.height + 173 ( post.img.srcset ? '" srcset="' + post.img.srcset : '' ) + 174 ( post.img.sizes ? '" sizes="' + post.img.sizes : '' ) + 175 '" alt="' + 176 post.img.alt_text + 177 '" />' + 178 anchor[ 1 ]; 179 } else { 180 var anchor_overlay = self.getAnchor( 181 post, 182 'jp-relatedposts-post-a jp-relatedposts-post-aoverlay' 183 ); 184 html += anchor_overlay[ 0 ] + anchor_overlay[ 1 ]; 185 } 186 html += 187 '<' + 188 related_posts_js_options.post_heading + 189 ' class="jp-relatedposts-post-title">' + 190 anchor[ 0 ] + 191 post.title + 192 anchor[ 1 ] + 193 '</' + 194 related_posts_js_options.post_heading + 195 '>'; 196 html += '<p class="jp-relatedposts-post-excerpt">' + excerpt + '</p>'; 197 if ( options.showDate ) { 198 html += 199 '<time class="jp-relatedposts-post-date" datetime="' + 200 post.date + 201 '">' + 202 post.date + 203 '</time>'; 204 } 205 if ( options.showContext ) { 206 html += '<p class="jp-relatedposts-post-context">' + post.context + '</p>'; 207 } 208 html += '</div>'; 209 } ); 210 return ( 211 '<div class="jp-relatedposts-items jp-relatedposts-items-visual jp-relatedposts-' + 212 options.layout + 213 ' ">' + 214 html + 215 '</div>' 216 ); 217 }, 218 219 /** 220 * We want to set a max height on the excerpt however we want to set 221 * this according to the natual pacing of the page as we never want to 222 * cut off a line of text in the middle so we need to do some detective 223 * work. 224 */ 225 setVisualExcerptHeights: function () { 226 var elements = document.querySelectorAll( 227 '#jp-relatedposts .jp-relatedposts-post-nothumbs .jp-relatedposts-post-excerpt' 228 ); 229 230 if ( ! elements.length ) { 231 return; 232 } 233 234 var firstElementStyles = getComputedStyle( elements[ 0 ] ); 235 236 var fontSize = parseInt( firstElementStyles.fontSize, 10 ); 237 var lineHeight = parseInt( firstElementStyles.lineHeight, 10 ); 238 239 // Show 5 lines of text 240 for ( var i = 0; i < elements.length; i++ ) { 241 elements[ i ].style.maxHeight = ( 5 * lineHeight ) / fontSize + 'em'; 242 } 243 }, 244 245 getTrackedUrl: function ( anchor ) {
246 var args = 'relatedposts_hit=1'; 247 args += '&relatedposts_origin=' + anchor.getAttribute( 'data-origin' ); 248 args += '&relatedposts_position=' + anchor.getAttribute( 'data-position' ); 249 250 var pathname = anchor.pathname; 251 if ( '/' !== pathname[ 0 ] ) { 252 pathname = '/' + pathname; 253 } 254 255 if ( '' === anchor.search ) { 256 return pathname + '?' + args; 257 } 258 return pathname + anchor.search + '&' + args; 259 }, 260 261 cleanupTrackedUrl: function () { 262 if ( 'function' !== typeof history.replaceState ) { 263 return; 264 } 265 266 var cleaned_search = document.location.search.replace( 267 /\brelatedposts_[a-z]+=[0-9]*&?\b/gi, 268 '' 269 ); 270 if ( '?' === cleaned_search ) { 271 cleaned_search = ''; 272 } 273 if ( document.location.search !== cleaned_search ) { 274 history.replaceState( {}, document.title, document.location.pathname + cleaned_search ); 275 } 276 }, 277 }; 278 279 function afterPostsHaveLoaded() { 280 jprp.setVisualExcerptHeights(); 281 var posts = document.querySelectorAll( '#jp-relatedposts a.jp-relatedposts-post-a' ); 282 283 Array.prototype.forEach.call( posts, function ( post ) { 284 document.addEventListener( 'click', function () { 285 post.href = jprp.getTrackedUrl( post ); 286 } ); 287 } ); 288 } 289 290 /** 291 * Initialize Related Posts. 292 */ 293 function startRelatedPosts() { 294 jprp.cleanupTrackedUrl(); 295 296 var endpointURL = jprp.getEndpointURL(); 297 298 if ( ! endpointURL ) { 299 return; 300 } 301 302 if ( document.querySelectorAll( '#jp-relatedposts .jp-relatedposts-post' ).length ) { 303 afterPostsHaveLoaded(); 304 return; 305 } 306 307 var relatedPosts = document.querySelector( '#jp-relatedposts' ); 308 var request = new XMLHttpRequest(); 309 request.open( 'GET', endpointURL, true ); 310 request.setRequestHeader( 'x-requested-with', 'XMLHttpRequest' ); 311 312 request.onreadystatechange = function () { 313 if ( this.readyState === XMLHttpRequest.DONE && this.status === 200 ) { 314 try { 315 var response = JSON.parse( request.responseText ); 316 317 if ( 0 === response.items.length || 0 === relatedPosts.length ) { 318 return; 319 } 320 321 jprp.response = response; 322 323 var html, 324 showThumbnails, 325 options = {}; 326 327 if ( 'undefined' !== typeof wp && wp.customize ) {
328 showThumbnails = wp.customize.instance( 'jetpack_relatedposts[show_thumbnails]' ).get(); 329 options.showDate = wp.customize.instance( 'jetpack_relatedposts[show_date]' ).get(); 330 options.showContext = wp.customize 331 .instance( 'jetpack_relatedposts[show_context]' ) 332 .get(); 333 options.layout = wp.customize.instance( 'jetpack_relatedposts[layout]' ).get(); 334 } else { 335 showThumbnails = response.show_thumbnails; 336 options.showDate = response.show_date; 337 options.showContext = response.show_context; 338 options.layout = response.layout; 339 } 340 341 html = ! showThumbnails 342 ? jprp.generateMinimalHtml( response.items, options ) 343 : jprp.generateVisualHtml( response.items, options ); 344 345 var div = document.createElement( 'div' ); 346 relatedPosts.appendChild( div ); 347 div.outerHTML = html; 348 349 if ( options.showDate ) { 350 var dates = relatedPosts.querySelectorAll( '.jp-relatedposts-post-date' ); 351 352 Array.prototype.forEach.call( dates, function ( date ) { 353 date.style.display = 'block'; 354 } ); 355 } 356 357 relatedPosts.style.display = 'block'; 358 afterPostsHaveLoaded(); 359 } catch { 360 // Do nothing 361 } 362 } 363 }; 364 365 request.send(); 366 } 367 368 function init() { 369 if ( 'undefined' !== typeof wp && wp.customize ) { 370 if ( wp.customize.selectiveRefresh ) { 371 wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
372 if ( 'jetpack_relatedposts' === placement.partial.id ) { 373 startRelatedPosts(); 374 } 375 } ); 376 } 377 wp.customize.bind( 'preview-ready', startRelatedPosts ); 378 } else { 379 startRelatedPosts(); 380 } 381 } 382 383 if ( document.readyState !== 'loading' ) { 384 init(); 385 } else { 386 document.addEventListener( 'DOMContentLoaded', init ); 387 } 388} )();
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.