PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 All 502 releases
jetpack / modules / related-posts / related-posts.js

related-posts.js in Jetpack – WP Security, Backup, Speed, & Growth 16.2, at modules/related-posts/related-posts.js

383 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 return anchor;
80 },
81
82 cleanHtml: function ( str ) {
83 var div = document.createElement( 'div' );
84 div.innerHTML = str;
85 return div.textContent || div.innerText || '';
86 },
87
88 generateMinimalDiv: function ( posts, options ) {
89 var self = this;
90 var div = document.createElement( 'div' );
91 div.setAttribute(
92 'class',
93 'jp-relatedposts-items jp-relatedposts-items-minimal jp-relatedposts-' + options.layout
94 );
95
96 posts.forEach( function ( post, index ) {
97 var classes = 'jp-relatedposts-post jp-relatedposts-post' + index;
98
99 if ( post.classes.length > 0 ) {
100 classes += ' ' + post.classes.join( ' ' );
101 }
102
103 var p = document.createElement( 'p' );
104 p.setAttribute( 'class', classes );
105 p.setAttribute( 'data-post-id', post.id );
106 p.setAttribute( 'data-post-format', post.format );
107 div.appendChild( p );
108
109 var titleSpan = document.createElement( 'span' );
110 titleSpan.setAttribute( 'class', 'jp-relatedposts-post-title' );
111 p.appendChild( titleSpan );
112
113 var anchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
114 titleSpan.appendChild( anchor );
115 anchor.textContent = self.cleanHtml( post.title );
116
117 if ( options.showDate ) {
118 var timeElt = document.createElement( 'time' );
119 timeElt.setAttribute( 'class', 'jp-relatedposts-post-date' );
120 timeElt.setAttribute( 'datetime', post.date );
121 timeElt.textContent = post.date;
122 p.appendChild( timeElt );
123 }
124 if ( options.showContext ) {
125 var contextSpan = document.createElement( 'span' );
126 contextSpan.setAttribute( 'class', 'jp-relatedposts-post-context' );
127 contextSpan.textContent = self.cleanHtml( post.context );
128 p.appendChild( contextSpan );
129 }
130 } );
131 return div;
132 },
133
134 generateVisualDiv: function ( posts, options ) {
135 var self = this;
136 var div = document.createElement( 'div' );
137 div.setAttribute(
138 'class',
139 'jp-relatedposts-items jp-relatedposts-items-visual jp-relatedposts-' + options.layout
140 );
141
142 posts.forEach( function ( post, index ) {
143 var classes = 'jp-relatedposts-post jp-relatedposts-post' + index;
144
145 if ( post.classes.length > 0 ) {
146 classes += ' ' + post.classes.join( ' ' );
147 }
148
149 if ( ! post.img.src ) {
150 classes += ' jp-relatedposts-post-nothumbs';
151 } else {
152 classes += ' jp-relatedposts-post-thumbs';
153 }
154
155 var innerDiv = document.createElement( 'div' );
156 innerDiv.setAttribute( 'class', classes );
157 innerDiv.setAttribute( 'data-post-id', post.id );
158 innerDiv.setAttribute( 'data-post-format', post.format );
159 div.appendChild( innerDiv );
160
161 if ( post.img.src ) {
162 var imgAnchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
163 innerDiv.appendChild( imgAnchor );
164
165 var img = document.createElement( 'img' );
166 img.setAttribute( 'class', 'jp-relatedposts-post-img' );
167 img.setAttribute( 'loading', 'lazy' );
168 img.setAttribute( 'src', post.img.src );
169 img.setAttribute( 'width', post.img.width );
170 img.setAttribute( 'height', post.img.height );
171 if ( post.img.srcset ) {
172 img.setAttribute( 'srcset', post.img.srcset );
173 }
174 if ( post.img.sizes ) {
175 img.setAttribute( 'sizes', post.img.sizes );
176 }
177 img.setAttribute( 'alt', post.img.alt_text );
178 imgAnchor.appendChild( img );
179 } else {
180 var anchor_overlay = self.getAnchor(
181 post,
182 'jp-relatedposts-post-a jp-relatedposts-post-aoverlay'
183 );
184 innerDiv.appendChild( anchor_overlay );
185 }
186 var heading = document.createElement( related_posts_js_options.post_heading );
187 heading.setAttribute( 'class', 'jp-relatedposts-post-title' );
188 innerDiv.appendChild( heading );
189
190 var headingAnchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
191 headingAnchor.textContent = self.cleanHtml( post.title );
192 heading.appendChild( headingAnchor );
193
194 var p = document.createElement( 'p' );
195 p.setAttribute( 'class', 'jp-relatedposts-post-excerpt' );
196 p.textContent = self.cleanHtml( post.excerpt );
197 innerDiv.appendChild( p );
198
199 if ( options.showDate ) {
200 var timeElt = document.createElement( 'time' );
201 timeElt.setAttribute( 'class', 'jp-relatedposts-post-date' );
202 timeElt.setAttribute( 'datetime', post.date );
203 timeElt.textContent = post.date;
204 innerDiv.appendChild( timeElt );
205 }
206 if ( options.showContext ) {
207 var contextP = document.createElement( 'p' );
208 contextP.setAttribute( 'class', 'jp-relatedposts-post-context' );
209 contextP.textContent = self.cleanHtml( post.context );
210 innerDiv.appendChild( contextP );
211 }
212 } );
213 return div;
214 },
215
216 /**
217 * We want to set a max height on the excerpt however we want to set
218 * this according to the natual pacing of the page as we never want to
219 * cut off a line of text in the middle so we need to do some detective
220 * work.
221 */
222 setVisualExcerptHeights: function () {
223 var elements = document.querySelectorAll(
224 '#jp-relatedposts .jp-relatedposts-post-nothumbs .jp-relatedposts-post-excerpt'
225 );
226
227 if ( ! elements.length ) {
228 return;
229 }
230
231 var firstElementStyles = getComputedStyle( elements[ 0 ] );
232
233 var fontSize = parseInt( firstElementStyles.fontSize, 10 );
234 var lineHeight = parseInt( firstElementStyles.lineHeight, 10 );
235
236 // Show 5 lines of text
237 for ( var i = 0; i < elements.length; i++ ) {
238 elements[ i ].style.maxHeight = ( 5 * lineHeight ) / fontSize + 'em';
239 }
240 },
241
242 getTrackedUrl: function ( anchor ) {
243 var args = 'relatedposts_hit=1';
244 args += '&relatedposts_origin=' + anchor.getAttribute( 'data-origin' );
245 args += '&relatedposts_position=' + anchor.getAttribute( 'data-position' );
246
247 var pathname = anchor.pathname;
248 if ( '/' !== pathname[ 0 ] ) {
249 pathname = '/' + pathname;
250 }
251
252 if ( '' === anchor.search ) {
253 return pathname + '?' + args;
254 }
255 return pathname + anchor.search + '&' + args;
256 },
257
258 cleanupTrackedUrl: function () {
259 if ( 'function' !== typeof history.replaceState ) {
260 return;
261 }
262
263 var cleaned_search = document.location.search.replace(
264 /\brelatedposts_[a-z]+=[0-9]*&?\b/gi,
265 ''
266 );
267 if ( '?' === cleaned_search ) {
268 cleaned_search = '';
269 }
270 if ( document.location.search !== cleaned_search ) {
271 history.replaceState( {}, document.title, document.location.pathname + cleaned_search );
272 }
273 },
274 };
275
276 function afterPostsHaveLoaded() {
277 jprp.setVisualExcerptHeights();
278 var posts = document.querySelectorAll( '#jp-relatedposts a.jp-relatedposts-post-a' );
279
280 Array.prototype.forEach.call( posts, function ( post ) {
281 document.addEventListener( 'click', function () {
282 post.href = jprp.getTrackedUrl( post );
283 } );
284 } );
285 }
286
287 /**
288 * Initialize Related Posts.
289 */
290 function startRelatedPosts() {
291 jprp.cleanupTrackedUrl();
292
293 var endpointURL = jprp.getEndpointURL();
294
295 if ( ! endpointURL ) {
296 return;
297 }
298
299 if ( document.querySelectorAll( '#jp-relatedposts .jp-relatedposts-post' ).length ) {
300 afterPostsHaveLoaded();
301 return;
302 }
303
304 var relatedPosts = document.querySelector( '#jp-relatedposts' );
305 var request = new XMLHttpRequest();
306 request.open( 'GET', endpointURL, true );
307 request.setRequestHeader( 'x-requested-with', 'XMLHttpRequest' );
308
309 request.onreadystatechange = function () {
310 if ( this.readyState === XMLHttpRequest.DONE && this.status === 200 ) {
311 try {
312 var response = JSON.parse( request.responseText );
313
314 if ( 0 === response.items.length || 0 === relatedPosts.length ) {
315 return;
316 }
317
318 jprp.response = response;
319
320 var showThumbnails,
321 options = {};
322
323 if ( 'undefined' !== typeof wp && wp.customize ) {
324 showThumbnails = wp.customize.instance( 'jetpack_relatedposts[show_thumbnails]' ).get();
325 options.showDate = wp.customize.instance( 'jetpack_relatedposts[show_date]' ).get();
326 options.showContext = wp.customize
327 .instance( 'jetpack_relatedposts[show_context]' )
328 .get();
329 options.layout = wp.customize.instance( 'jetpack_relatedposts[layout]' ).get();
330 } else {
331 showThumbnails = response.show_thumbnails;
332 options.showDate = response.show_date;
333 options.showContext = response.show_context;
334 options.layout = response.layout;
335 }
336
337 var div = ! showThumbnails
338 ? jprp.generateMinimalDiv( response.items, options )
339 : jprp.generateVisualDiv( response.items, options );
340
341 relatedPosts.appendChild( div );
342
343 if ( options.showDate ) {
344 var dates = relatedPosts.querySelectorAll( '.jp-relatedposts-post-date' );
345
346 Array.prototype.forEach.call( dates, function ( date ) {
347 date.style.display = 'block';
348 } );
349 }
350
351 relatedPosts.style.display = 'block';
352 afterPostsHaveLoaded();
353 } catch {
354 // Do nothing
355 }
356 }
357 };
358
359 request.send();
360 }
361
362 function init() {
363 if ( 'undefined' !== typeof wp && wp.customize ) {
364 if ( wp.customize.selectiveRefresh ) {
365 wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
366 if ( 'jetpack_relatedposts' === placement.partial.id ) {
367 startRelatedPosts();
368 }
369 } );
370 }
371 wp.customize.bind( 'preview-ready', startRelatedPosts );
372 } else {
373 startRelatedPosts();
374 }
375 }
376
377 if ( document.readyState !== 'loading' ) {
378 init();
379 } else {
380 document.addEventListener( 'DOMContentLoaded', init );
381 }
382 } )();
383