# jetpack/16.2/modules/related-posts/related-posts.js

Jetpack – WP Security, Backup, Speed, &amp; Growth, version 16.2. 383 lines.

- Page: https://pluginprobe.com/plugins/jetpack/16.2/code/modules/related-posts/related-posts.js
- Raw: https://pluginprobe.com/plugins/jetpack/16.2/raw/modules/related-posts/related-posts.js
- Modified: 2026-09-08T18:39:34+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/jetpack/16.2/code/modules/related-posts/related-posts.js#L10-L20`.

```javascript
/* globals related_posts_js_options */

/**
 * Load related posts
 */
( function () {
	'use strict';

	var jprp = {
		response: null,

		/**
		 * Utility get related posts JSON endpoint from URLs
		 *
		 * @param {string} URL (optional)
		 * @return {string} Endpoint URL
		 */
		getEndpointURL: function ( URL ) {
			var locationObject,
				is_customizer =
					'undefined' !== typeof wp &&
					wp.customize &&
					wp.customize.settings &&
					wp.customize.settings.url &&
					wp.customize.settings.url.self;

			// If we're in Customizer, write the correct URL.
			if ( is_customizer ) {
				locationObject = document.createElement( 'a' );
				locationObject.href = wp.customize.settings.url.self;
			} else {
				locationObject = document.location;
			}

			if ( 'string' === typeof URL && URL.match( /^https?:\/\// ) ) {
				locationObject = document.createElement( 'a' );
				locationObject.href = URL;
			}

			var args = 'relatedposts=1';
			var relatedPosts = document.querySelector( '#jp-relatedposts' );

			if ( ! relatedPosts ) {
				return false;
			}

			if ( relatedPosts.hasAttribute( 'data-exclude' ) ) {
				args += '&relatedposts_exclude=' + relatedPosts.getAttribute( 'data-exclude' );
			}

			if ( is_customizer ) {
				args += '&jetpackrpcustomize=1';
			}

			var pathname = locationObject.pathname;
			if ( '/' !== pathname[ 0 ] ) {
				pathname = '/' + pathname;
			}

			if ( '' === locationObject.search ) {
				return pathname + '?' + args;
			}
			return pathname + locationObject.search + '&' + args;
		},

		getAnchor: function ( post, classNames ) {
			var anchorTitle = post.title;
			var anchor = document.createElement( 'a' );
			anchor.setAttribute( 'class', classNames );
			anchor.setAttribute( 'href', post.url );
			anchor.setAttribute( 'title', anchorTitle );
			anchor.setAttribute( 'data-origin', post.url_meta.origin );
			anchor.setAttribute( 'data-position', post.url_meta.position );

			if ( '' !== post.rel ) {
				anchor.setAttribute( 'rel', post.rel );
			}

			return anchor;
		},

		cleanHtml: function ( str ) {
			var div = document.createElement( 'div' );
			div.innerHTML = str;
			return div.textContent || div.innerText || '';
		},

		generateMinimalDiv: function ( posts, options ) {
			var self = this;
			var div = document.createElement( 'div' );
			div.setAttribute(
				'class',
				'jp-relatedposts-items jp-relatedposts-items-minimal jp-relatedposts-' + options.layout
			);

			posts.forEach( function ( post, index ) {
				var classes = 'jp-relatedposts-post jp-relatedposts-post' + index;

				if ( post.classes.length > 0 ) {
					classes += ' ' + post.classes.join( ' ' );
				}

				var p = document.createElement( 'p' );
				p.setAttribute( 'class', classes );
				p.setAttribute( 'data-post-id', post.id );
				p.setAttribute( 'data-post-format', post.format );
				div.appendChild( p );

				var titleSpan = document.createElement( 'span' );
				titleSpan.setAttribute( 'class', 'jp-relatedposts-post-title' );
				p.appendChild( titleSpan );

				var anchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
				titleSpan.appendChild( anchor );
				anchor.textContent = self.cleanHtml( post.title );

				if ( options.showDate ) {
					var timeElt = document.createElement( 'time' );
					timeElt.setAttribute( 'class', 'jp-relatedposts-post-date' );
					timeElt.setAttribute( 'datetime', post.date );
					timeElt.textContent = post.date;
					p.appendChild( timeElt );
				}
				if ( options.showContext ) {
					var contextSpan = document.createElement( 'span' );
					contextSpan.setAttribute( 'class', 'jp-relatedposts-post-context' );
					contextSpan.textContent = self.cleanHtml( post.context );
					p.appendChild( contextSpan );
				}
			} );
			return div;
		},

		generateVisualDiv: function ( posts, options ) {
			var self = this;
			var div = document.createElement( 'div' );
			div.setAttribute(
				'class',
				'jp-relatedposts-items jp-relatedposts-items-visual jp-relatedposts-' + options.layout
			);

			posts.forEach( function ( post, index ) {
				var classes = 'jp-relatedposts-post jp-relatedposts-post' + index;

				if ( post.classes.length > 0 ) {
					classes += ' ' + post.classes.join( ' ' );
				}

				if ( ! post.img.src ) {
					classes += ' jp-relatedposts-post-nothumbs';
				} else {
					classes += ' jp-relatedposts-post-thumbs';
				}

				var innerDiv = document.createElement( 'div' );
				innerDiv.setAttribute( 'class', classes );
				innerDiv.setAttribute( 'data-post-id', post.id );
				innerDiv.setAttribute( 'data-post-format', post.format );
				div.appendChild( innerDiv );

				if ( post.img.src ) {
					var imgAnchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
					innerDiv.appendChild( imgAnchor );

					var img = document.createElement( 'img' );
					img.setAttribute( 'class', 'jp-relatedposts-post-img' );
					img.setAttribute( 'loading', 'lazy' );
					img.setAttribute( 'src', post.img.src );
					img.setAttribute( 'width', post.img.width );
					img.setAttribute( 'height', post.img.height );
					if ( post.img.srcset ) {
						img.setAttribute( 'srcset', post.img.srcset );
					}
					if ( post.img.sizes ) {
						img.setAttribute( 'sizes', post.img.sizes );
					}
					img.setAttribute( 'alt', post.img.alt_text );
					imgAnchor.appendChild( img );
				} else {
					var anchor_overlay = self.getAnchor(
						post,
						'jp-relatedposts-post-a jp-relatedposts-post-aoverlay'
					);
					innerDiv.appendChild( anchor_overlay );
				}
				var heading = document.createElement( related_posts_js_options.post_heading );
				heading.setAttribute( 'class', 'jp-relatedposts-post-title' );
				innerDiv.appendChild( heading );

				var headingAnchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
				headingAnchor.textContent = self.cleanHtml( post.title );
				heading.appendChild( headingAnchor );

				var p = document.createElement( 'p' );
				p.setAttribute( 'class', 'jp-relatedposts-post-excerpt' );
				p.textContent = self.cleanHtml( post.excerpt );
				innerDiv.appendChild( p );

				if ( options.showDate ) {
					var timeElt = document.createElement( 'time' );
					timeElt.setAttribute( 'class', 'jp-relatedposts-post-date' );
					timeElt.setAttribute( 'datetime', post.date );
					timeElt.textContent = post.date;
					innerDiv.appendChild( timeElt );
				}
				if ( options.showContext ) {
					var contextP = document.createElement( 'p' );
					contextP.setAttribute( 'class', 'jp-relatedposts-post-context' );
					contextP.textContent = self.cleanHtml( post.context );
					innerDiv.appendChild( contextP );
				}
			} );
			return div;
		},

		/**
		 * We want to set a max height on the excerpt however we want to set
		 * this according to the natual pacing of the page as we never want to
		 * cut off a line of text in the middle so we need to do some detective
		 * work.
		 */
		setVisualExcerptHeights: function () {
			var elements = document.querySelectorAll(
				'#jp-relatedposts .jp-relatedposts-post-nothumbs .jp-relatedposts-post-excerpt'
			);

			if ( ! elements.length ) {
				return;
			}

			var firstElementStyles = getComputedStyle( elements[ 0 ] );

			var fontSize = parseInt( firstElementStyles.fontSize, 10 );
			var lineHeight = parseInt( firstElementStyles.lineHeight, 10 );

			// Show 5 lines of text
			for ( var i = 0; i < elements.length; i++ ) {
				elements[ i ].style.maxHeight = ( 5 * lineHeight ) / fontSize + 'em';
			}
		},

		getTrackedUrl: function ( anchor ) {
			var args = 'relatedposts_hit=1';
			args += '&relatedposts_origin=' + anchor.getAttribute( 'data-origin' );
			args += '&relatedposts_position=' + anchor.getAttribute( 'data-position' );

			var pathname = anchor.pathname;
			if ( '/' !== pathname[ 0 ] ) {
				pathname = '/' + pathname;
			}

			if ( '' === anchor.search ) {
				return pathname + '?' + args;
			}
			return pathname + anchor.search + '&' + args;
		},

		cleanupTrackedUrl: function () {
			if ( 'function' !== typeof history.replaceState ) {
				return;
			}

			var cleaned_search = document.location.search.replace(
				/\brelatedposts_[a-z]+=[0-9]*&?\b/gi,
				''
			);
			if ( '?' === cleaned_search ) {
				cleaned_search = '';
			}
			if ( document.location.search !== cleaned_search ) {
				history.replaceState( {}, document.title, document.location.pathname + cleaned_search );
			}
		},
	};

	function afterPostsHaveLoaded() {
		jprp.setVisualExcerptHeights();
		var posts = document.querySelectorAll( '#jp-relatedposts a.jp-relatedposts-post-a' );

		Array.prototype.forEach.call( posts, function ( post ) {
			document.addEventListener( 'click', function () {
				post.href = jprp.getTrackedUrl( post );
			} );
		} );
	}

	/**
	 * Initialize Related Posts.
	 */
	function startRelatedPosts() {
		jprp.cleanupTrackedUrl();

		var endpointURL = jprp.getEndpointURL();

		if ( ! endpointURL ) {
			return;
		}

		if ( document.querySelectorAll( '#jp-relatedposts .jp-relatedposts-post' ).length ) {
			afterPostsHaveLoaded();
			return;
		}

		var relatedPosts = document.querySelector( '#jp-relatedposts' );
		var request = new XMLHttpRequest();
		request.open( 'GET', endpointURL, true );
		request.setRequestHeader( 'x-requested-with', 'XMLHttpRequest' );

		request.onreadystatechange = function () {
			if ( this.readyState === XMLHttpRequest.DONE && this.status === 200 ) {
				try {
					var response = JSON.parse( request.responseText );

					if ( 0 === response.items.length || 0 === relatedPosts.length ) {
						return;
					}

					jprp.response = response;

					var showThumbnails,
						options = {};

					if ( 'undefined' !== typeof wp && wp.customize ) {
						showThumbnails = wp.customize.instance( 'jetpack_relatedposts[show_thumbnails]' ).get();
						options.showDate = wp.customize.instance( 'jetpack_relatedposts[show_date]' ).get();
						options.showContext = wp.customize
							.instance( 'jetpack_relatedposts[show_context]' )
							.get();
						options.layout = wp.customize.instance( 'jetpack_relatedposts[layout]' ).get();
					} else {
						showThumbnails = response.show_thumbnails;
						options.showDate = response.show_date;
						options.showContext = response.show_context;
						options.layout = response.layout;
					}

					var div = ! showThumbnails
						? jprp.generateMinimalDiv( response.items, options )
						: jprp.generateVisualDiv( response.items, options );

					relatedPosts.appendChild( div );

					if ( options.showDate ) {
						var dates = relatedPosts.querySelectorAll( '.jp-relatedposts-post-date' );

						Array.prototype.forEach.call( dates, function ( date ) {
							date.style.display = 'block';
						} );
					}

					relatedPosts.style.display = 'block';
					afterPostsHaveLoaded();
				} catch {
					// Do nothing
				}
			}
		};

		request.send();
	}

	function init() {
		if ( 'undefined' !== typeof wp && wp.customize ) {
			if ( wp.customize.selectiveRefresh ) {
				wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
					if ( 'jetpack_relatedposts' === placement.partial.id ) {
						startRelatedPosts();
					}
				} );
			}
			wp.customize.bind( 'preview-ready', startRelatedPosts );
		} else {
			startRelatedPosts();
		}
	}

	if ( document.readyState !== 'loading' ) {
		init();
	} else {
		document.addEventListener( 'DOMContentLoaded', init );
	}
} )();

```
