1/** 2 * Render Yext social posts from the platform REST API. 3 */ 4 5document.addEventListener('DOMContentLoaded', () => { 6 const feeds = document.querySelectorAll('[data-social-posts="true"][data-social-posts-endpoint]'); 7 8 feeds.forEach((feedElement) => { 9 setSocialFeedSectionHidden(feedElement, true); 10 loadSocialFeed(feedElement); 11 }); 12}); 13 14async function loadSocialFeed(feedElement) { 15 const endpoint = feedElement.dataset.socialPostsEndpoint; 16 const requestUrl = new URL(endpoint, window.location.href); 17 18 // WP Engine and its CDN may replace no-store headers with a short cache TTL. 19 requestUrl.searchParams.set('ppp_cache_bust', Date.now().toString()); 20 21 try { 22 const response = await fetch(requestUrl.toString(), { 23 method: 'GET', 24 headers: { 25 Accept: 'application/json' 26 }, 27 cache: 'no-store', 28 credentials: 'omit' 29 }); 30 31 if (!response.ok) { 32 throw new Error(`Social feed request failed with status ${response.status}`); 33 } 34 35 const feed = await response.json(); 36 const posts = Array.isArray(feed.posts) ? feed.posts : []; 37 38 feedElement.dataset.socialPostsSource = feed.meta?.source || 'unknown'; 39 feedElement.dataset.socialPostsStale = feed.meta?.stale ? 'true' : 'false'; 40 41 if (posts.length === 0) { 42 removeSocialFeedSection(feedElement); 43 return; 44 } 45 46 feedElement.replaceChildren(...posts.map(createSocialPost)); 47 setSocialFeedSectionHidden(feedElement, false); 48 } catch (error) { 49 console.error('Error fetching social posts:', error); 50 removeSocialFeedSection(feedElement); 51 } 52} 53 54function setSocialFeedSectionHidden(feedElement, hidden) { 55 const section = feedElement.closest('.slide-section'); 56 const element = section || feedElement; 57 58 element.hidden = hidden; 59} 60 61function removeSocialFeedSection(feedElement) { 62 const section = feedElement.closest('.slide-section'); 63 64 (section || feedElement).remove(); 65} 66 67function createSocialPost(post) { 68 const postItem = document.createElement('div'); 69 const content = document.createElement('div'); 70 71 postItem.className = 'slide-item text-slide slide--yext'; 72 postItem.dataset.postId = post.postId || ''; 73 postItem.dataset.postDate = post.postDate || ''; 74 75 if (Array.isArray(post.photoUrls) && post.photoUrls.length > 0) { 76 content.appendChild(createPostImage(post.photoUrls[0], post.postUrl)); 77 } 78 79 if (post.clickthroughUrl) { 80 content.appendChild(createClickthrough(post.clickthroughUrl)); 81 } 82 83 if (post.text && post.text !== post.clickthroughUrl) { 84 const text = document.createElement('p'); 85 text.textContent = post.text; 86 content.appendChild(text); 87 } 88 89 postItem.appendChild(content); 90 91 const postDate = document.createElement('small'); 92 postDate.className = 'post-date'; 93 postDate.textContent = formatElapsedTime(post.postDate); 94 postItem.appendChild(postDate); 95 96 return postItem; 97} 98 99function createPostImage(photoUrl, postUrl) { 100 const figure = document.createElement('figure'); 101 const image = document.createElement('img'); 102 103 image.src = photoUrl; 104 image.alt = ''; 105 image.addEventListener('error', () => { 106 image.remove(); 107 108 if (postUrl) { 109 const fallbackLink = document.createElement('a'); 110 fallbackLink.className = 'view-on-facebook'; 111 fallbackLink.href = postUrl; 112 fallbackLink.target = '_blank'; 113 fallbackLink.rel = 'noopener'; 114 fallbackLink.textContent = 'View this post on Facebook'; 115 figure.appendChild(fallbackLink); 116 } 117 }); 118 119 figure.appendChild(image); 120 return figure; 121} 122 123function createClickthrough(url) { 124 const youtubeMatch = url.match(/(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/\s]{11})/i); 125 const tiktokMatch = url.match(/tiktok\.com\/@[^/]+\/video\/(\d+)/i); 126 127 if (youtubeMatch) { 128 return createVideoFrame(`https://www.youtube.com/embed/${youtubeMatch[1]}`, false); 129 } 130 131 if (tiktokMatch) { 132 return createVideoFrame(`https://www.tiktok.com/embed/v2/${tiktokMatch[1]}`, true); 133 } 134 135 const paragraph = document.createElement('p'); 136 const link = document.createElement('a'); 137 138 link.href = url; 139 link.target = '_blank'; 140 link.rel = 'noopener'; 141 link.textContent = url.includes('facebook.com/') ? 'View this on Facebook' : url; 142 paragraph.appendChild(link); 143 144 return paragraph; 145} 146 147function createVideoFrame(url, portrait) { 148 const container = document.createElement('div'); 149 const frame = document.createElement('iframe'); 150 151 container.className = `yext-iframe-container${portrait ? ' portrait' : ''}`; 152 frame.src = url; 153 frame.title = 'Social post video'; 154 frame.loading = 'lazy'; 155 frame.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'; 156 frame.allowFullscreen = true; 157 container.appendChild(frame); 158 159 return container; 160} 161 162function formatElapsedTime(postDate) { 163 const timestamp = Date.parse(postDate); 164 165 if (!Number.isFinite(timestamp)) { 166 return ''; 167 } 168 169 const seconds = Math.max(0, Math.floor((Date.now() - timestamp) / 1000)); 170 const units = [ 171 ['year', 365 * 24 * 60 * 60], 172 ['month', 30 * 24 * 60 * 60], 173 ['day', 24 * 60 * 60], 174 ['hour', 60 * 60], 175 ['minute', 60], 176 ['second', 1] 177 ]; 178 179 for (const [label, duration] of units) { 180 const value = Math.floor(seconds / duration); 181 182 if (value > 0 || label === 'second') { 183 return `${value} ${label}${value === 1 ? '' : 's'} ago`; 184 } 185 } 186 187 return ''; 188}
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.