PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 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 All 507 releases
← All changes | modules/related-posts/related-posts.js +382 -0 16.2-beta → 16.3-beta View file →
@@ -1,0 +1,382 @@
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 +} )();