PageSourceSearch

https://www.johnsonarmsnottingham.com/wp-content/plugins/punchpubs…/integrations/js/yext-social.js?ver=2.0.3

js johnsonarmsnottingham.com collected 2026-10-08 07:55:57 UTC 5,818 bytes, 188 lines download raw bytes

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.