1jQuery(document).ready(function ($) { 2 3 function escape_html(str) { 4 if (str === null || str === undefined) { 5 return ''; 6 } 7 return $('<div>').text(String(str)).html(); 8 } 9 10 function escape_attr(str) { 11 return escape_html(str).replace(/"/g, '"').replace(/'/g, '''); 12 } 13 14 function escape_url(url) { 15 if (!url) { 16 return ''; 17 } 18 19 let parsed = new URL(String(url), window.location.origin); 20 21 if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { 22 return ''; 23 } 24 25 return parsed.href; 26 } 27 28 function sanitize_youtube_id(id) { 29 if (!id) { 30 return ''; 31 } 32 let match = String(id).match(/[A-Za-z0-9_-]{11}/); 33 return match ? match[0] : ''; 34 } 35 36 // ===== Video play mode: LIGHTBOX, INLINE, NEWTAB ===== 37 $('.socialfeeds-youtube-feed').on('click','.socialfeeds-video-item a, .socialfeeds-media-link, .socialfeeds-carousel-item a, .socialfeeds-list-item iframe',function(e){ 38 let $link = $(this).closest('a'); 39 if (!$link.length) $link = $(this); 40 41 // If it's an iframe click (list view), let it be 42 if ($link.is('iframe')) return; 43 44 let $feed = $link.closest('.socialfeeds-youtube-feed'); 45 if (!$feed.length) return; 46 47 let click_action = $feed.data('click') || $feed.attr('data-click') || 'newtab'; 48 49 // 1. New Tab (Default behavior) 50 if (click_action === 'newtab') { 51 return; // Let default click happen 52 } 53 54 e.preventDefault(); 55 let href = $link.attr('href'); 56 if (!href) return; 57 58 let vid_match = href.match(/[?&]v=([^&]+)/) || href.match(/youtu\.be\/([^?&]+)/) || href.split('/').pop().match(/^([a-zA-Z0-9_-]{11})/); 59 let vid = vid_match ? vid_match[1] : ''; 60 61 if (!vid) { 62 // Fallback if video ID not found 63 window.open(href, '_blank'); 64 return; 65 } 66 67 // 2. Lightbox 68 if (click_action === 'lightbox'){ 69 let modal_html = ` 70 <div class="socialfeeds-modal"> 71 <div class="socialfeeds-modal-backdrop"></div> 72 <div class="socialfeeds-modal-inner" style="width: 900px; max-width: 90vw; background: #000;"> 73 <button class="socialfeeds-modal-close" style="z-index:100; color:#fff; background:rgba(0,0,0,0.5);">Ã</button> 74 <div class="socialfeeds-modal-content" style="position:relative; width:100%; aspect-ratio:16/9;"> 75 <iframe src="https://www.youtube.com/embed/${encodeURIComponent(vid)}?autoplay=1&rel=0" 76 frameborder="0" 77 allow="autoplay; encrypted-media" 78 allowfullscreen 79 style="position:absolute; top:0; left:0; width:100%; height:100%;"> 80 </iframe> 81 </div> 82 </div> 83 </div>`; 84 85 $('body').append(modal_html); 86 setTimeout(() => $('.socialfeeds-modal').addClass('active'), 10); 87 return; 88 } 89 90 // 3. Inline 91 if(click_action === 'inline'){ 92 // Find the play overlay and hide it (if it exists) 93 let $overlay = $link.siblings('.socialfeeds-play-overlay'); 94 if ($overlay.length) $overlay.hide(); 95 96 // Should specifically target the media container to keep title/desc intact 97 $link.replaceWith(` 98 <iframe src="https://www.youtube.com/embed/${encodeURIComponent(vid)}?autoplay=1&mute=0&rel=0&playsinline=1" 99 frameborder="0" 100 allow="autoplay; encrypted-media" 101 allowfullscreen 102 style="width:100%; aspect-ratio:16/9; display:block;"> 103 </iframe> 104 `); 105 } 106 }); 107 108 $(document).on('click', '.socialfeeds-modal-close, .socialfeeds-modal-backdrop', function (e) { 109 $('.socialfeeds-modal').removeClass('active'); 110 setTimeout(() => $('.socialfeeds-modal').remove(), 300); 111 }); 112 113 // ===== LOAD MORE (AJAX) ===== 114 $('.socialfeeds-load-more-btn').on('click', function (e){ 115 e.preventDefault(); 116 let $btn = $(this); 117 if ($btn.data('feed-type') === 'instagram') { 118 return; 119 } 120 let feed_id = $btn.data('feed-id'), 121 feed_type = $btn.data('feed-type'), 122 feed_input = $btn.data('feed-input'), 123 limit = parseInt($btn.data('limit'), 10), 124 loaded = parseInt($btn.data('loaded'), 10), 125 page_token = $btn.attr('data-page-token') || '', 126 $feed = $btn.closest('.socialfeeds-youtube-feed'); 127 128 129 if (!feed_type || !feed_input) { 130 console.error('SocialFeeds: Missing required parameters for Load More'); 131 return; 132 } 133 134 $btn.prop('disabled', true).addClass('socialfeeds-loading'); 135 let original_text = $btn.text(); 136 137 let ajax_data = { 138 action: 'socialfeeds_load_more_videos', 139 feed_id: feed_id, 140 feed_type: feed_type, 141 feed_input: feed_input, 142 limit: limit, 143 pageToken: page_token, 144 nonce: socialfeeds_ajax.nonce 145 }; 146 147 $.ajax({ 148 url: socialfeeds_ajax.ajax_url, 149 type: 'POST', 150 data: ajax_data, 151 dataType: 'json', 152 success: function (response) { 153 if (!response.success) { 154 $btn.prop('disabled', false).removeClass('socialfeeds-loading').text('Error loading videos'); 155 return; 156 } 157 158 let data = response.data; 159 160 if (data && data.html) { 161 162 let $feed = $btn.closest('.socialfeeds-youtube-feed'); 163 let $itemsContainer = $feed.find('.socialfeeds-youtube-grid, .socialfeeds-youtube-list, .socialfeeds-youtube-carou
163sel'); 164 165 // Fallback if not found 166 if (!$itemsContainer.length) { 167 $itemsContainer = $btn.closest('.socialfeeds-load-more-container'); 168 } 169 170 if ($itemsContainer.length && !$itemsContainer.hasClass('socialfeeds-load-more-container')) { 171 $itemsContainer.append(data.html); 172 } else { 173 $btn.closest('.socialfeeds-load-more-container').before(data.html); 174 } 175 176 let loaded_count = (data.items && data.items.length) ? data.items.length : 1; 177 178 let new_loaded = loaded + loaded_count; 179 $btn.data('loaded', new_loaded).attr('data-loaded', new_loaded); 180 181 if (data.nextPageToken) { 182 $btn.attr('data-page-token', data.nextPageToken); 183 $btn.prop('disabled', false).removeClass('socialfeeds-loading').text(original_text); 184 } else { 185 $btn.text($btn.data('no-more-text') || 'No More Videos').hide(); 186 } 187 188 if ($itemsContainer.hasClass('socialfeeds-youtube-carousel')) { 189 $(window).trigger('resize'); 190 } 191 192 } else if (data && data.items && data.items.length > 0) { 193 let items = data.items; 194 195 // Find the inner container where items live 196 let $itemsContainer = $feed.find('.socialfeeds-youtube-grid, .socialfeeds-youtube-list, .socialfeeds-youtube-carousel'); 197 198 // Fallback if not found (shouldn't happen if structure is valid) 199 if(!$itemsContainer.length){ 200 $itemsContainer = $btn.closest('.socialfeeds-load-more-container'); 201 } 202 203 let is_grid = $itemsContainer.hasClass('socialfeeds-youtube-grid'); 204 let is_carousel = $itemsContainer.hasClass('socialfeeds-youtube-carousel'); 205 let is_list = $itemsContainer.hasClass('socialfeeds-youtube-list'); 206 207 items.forEach(function (item) { 208 let vid = sanitize_youtube_id(item.videoId); 209 if (!vid) { 210 return; 211 } 212 let title = escape_html(item.title || 'Video'); 213 let thumb = escape_url((item.thumbnails && item.thumbnails.medium) ? item.thumbnails.medium.url : 214 ((item.thumbnails && item.thumbnails.default) ? item.thumbnails.default.url : '')); 215 let desc = item.description || ''; 216 let short_desc = desc ? escape_html(desc.substring(0, 54) + '...') : ''; 217 218 let item_html = ''; 219 if (is_grid) { 220 item_html = ` 221 <div class="socialfeeds-video-item"> 222 <a href="https://www.youtube.com/watch?v=${encodeURIComponent(vid)}" target="_blank" rel="noopener"> 223 <img src="${thumb}" alt="${escape_attr(item.title || 'Video')}" /> 224 </a> 225 ${title ? `<h5 class="socialfeeds-video-title">${title}</h5>` : ''} 226 ${short_desc ? `<p class="socialfeeds-card-desc">${short_desc}</p>` : ''} 227 </div>`; 228 } else if (is_carousel) { 229 item_html = ` 230 <div class="socialfeeds-video-item socialfeeds-carousel-item"> 231 <a href="https://www.youtube.com/watch?v=${encodeURIComponent(vid)}" target="_blank" rel="noopener" style="display:block; position:relative;"> 232 <img src="${thumb}" alt="${escape_attr(item.title || 'Video')}" /> 233 <span class="socialfeeds-play-overlay"></span> 234 </a> 235 ${title ? `<p class="socialfeeds-video-title">${title}</p>` : ''} 236 </div>`; 237 } else if (is_list) { 238 item_html = ` 239 <div class="socialfeeds-video-item socialfeeds-list-item"> 240 ${title ? `<h3 class="socialfeeds-video-title">${title}</h3>` : ''} 241 <div class='socialfeeds-video-iframe-wrap'> 242 <iframe width="560" height="315" src="https://www.youtube.com/embed/${encodeURIComponent(vid)}" frameborder="0" allowfullscreen></iframe> 243 </div> 244 ${short_desc ? `<p class="socialfeeds-video-desc">${short_desc}</p>` : ''} 245 </div>`; 246 } else { 247 // Default/Fallback 248 item_html = ` 249 <div class="socialfeeds-video-item"> 250 <a href="https://www.youtube.com/watch?v=${encodeURIComponent(vid)}" target="_blank" rel="noopener"> 251 <img src="${thumb}" alt="${escape_attr(item.title || 'Video')}" /> 252 </a> 253 ${title ? `<h5 class="socialfeeds-video-title">${title}</h5>` : ''} 254 ${short_desc ? `<p class="socialfeeds-card-desc">${short_desc}</p>` : ''} 255 </div>`; 256 } 257 258 // Append to the specific items container 259 if ($itemsContainer.length && !$itemsContainer.hasClass('socialfeeds-load-more-container')) { 260 $itemsContainer.append(item_html); 261 } else { 262 // Fallback: insert before button if we couldn't find the container 263 $btn.closest('.socialfeeds-load-more-container').before(item_html); 264 } 265 }); 266 267 let new_loaded = loaded + items.length; 268 $btn.data('loaded', new_loaded).attr('data-loaded', new_loaded); 269 270 if (data.nextPageToken) { 271 $btn.attr('data-page-token', data.nextPageToken); 272 $btn.prop('disabled', false).removeClass('socialfeeds-loading').text(original_text); 273 } else { 274 $btn.text($btn.data('no-more-text') || 'No More Videos').hide(); 275 } 276 277 // If we are in a carousel, trigger resize to update dots/nav 278 if(is_carousel){ 279 $(window).trigger('resize'); 280 } 281 282 // If items loaded are less than limit, we likely reached the end 283 if (items.length < limit) { 284 $btn.text($btn.data('no-more-text') || 'No More Videos').hide(); 285 } 286 287 } else { 288 $btn.text($btn.data('no-more-text') || 'No More Videos').hide(); 289 } 290 }, 291 error: function (xhr, status, error) { 292 console.error('SocialFeeds AJAX Error:', error); 293 $btn.prop('disabled', false).removeClass('socialfeeds-loading').text('Error loading videos'); 294 } 295 }); 296 }); 297 298 // ===== CAROUSEL INITIALIZATION =====
299 $('.socialfeeds-youtube-carousel').each(function () { 300 let $carousel = $(this); 301 let $feedWrapper = $carousel.closest('.socialfeeds-youtube-feed'); 302 303 // Prevent double init 304 if ($feedWrapper.find('.socialfeeds-carousel-stage').length) return; 305 306 // 1. Wrap carousel in a stage for absolute positioning of arrows 307 // We want the stage to include the carousel but NOT the load more button if it's there 308 $carousel.wrap('<div class="socialfeeds-carousel-stage" style="position:relative;"></div>'); 309 let $stage = $carousel.parent(); 310 311 // 2. Create Nav Buttons (Inside Stage) 312 let $nav = $('<div class="socialfeeds-carousel-nav">' + 313 '<button class="socialfeeds-carousel-btn prev" aria-label="Previous" type="button"><span class="dashicons dashicons-arrow-left-alt2" style="font-family:dashicons; line-height:inherit;"></span></button>' + 314 '<button class="socialfeeds-carousel-btn next" aria-label="Next" type="button"><span class="dashicons dashicons-arrow-right-alt2" style="font-family:dashicons; line-height:inherit;"></span></button>' + 315 '</div>'); 316 317 $stage.append($nav); 318 319 // 3. Create Dots (After Stage) 320 let $dots = $('<div class="socialfeeds-carousel-dots"></div>'); 321 $stage.after($dots); 322 323 // If there is a load more container, ensure it is after dots 324 let $loadMore = $feedWrapper.find('.socialfeeds-load-more-container'); 325 if ($loadMore.length) { 326 $feedWrapper.append($loadMore); // Move it to the very end 327 } 328 329 function updateDots() { 330 $dots.empty(); 331 let scrollW = $carousel[0].scrollWidth; 332 let clientW = $carousel[0].clientWidth; 333 if (clientW >= scrollW) { 334 $dots.hide(); 335 $nav.hide(); 336 return; 337 } 338 339 $dots.show(); 340 $nav.show(); 341 342 // Simple pagination: items per page 343 let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300; 344 let itemsPerPage = Math.floor(clientW / itemW) || 1; 345 let totalItems = $carousel.find('.socialfeeds-carousel-item').length; 346 let pageCount = Math.ceil(totalItems / itemsPerPage); 347 348 // If scrolling is continuous, we approximate pages based on scrollWidth 349 pageCount = Math.ceil(scrollW / clientW); 350 351 for (let i = 0; i < pageCount; i++) { 352 let $d = $('<span class="socialfeeds-dot"></span>'); 353 if (i === 0) $d.addClass('active'); 354 $d.data('page', i); 355 $dots.append($d); 356 } 357 } 358 359 // Initial calc 360 setTimeout(updateDots, 500); // delay for layout settle 361 $(window).on('resize', updateDots); 362 363 // 4. Scroll Logic 364 $nav.find('.prev').on('click', function (e) { 365 e.preventDefault(); 366 let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300; 367 $carousel.animate({ scrollLeft: $carousel.scrollLeft() - itemW }, 300, update_active_dot); 368 }); 369 370 $nav.find('.next').on('click', function (e) { 371 e.preventDefault(); 372 let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300; 373 $carousel.animate({ scrollLeft: $carousel.scrollLeft() + itemW }, 300, update_active_dot); 374 }); 375 376 // Dots Click 377 $dots.on('click', '.socialfeeds-dot', function () { 378 let page = $(this).data('page'); 379 let clientW = $carousel[0].clientWidth; 380 $carousel.animate({ scrollLeft: page * clientW }, 400, update_active_dot); 381 }); 382 383 function update_active_dot() { 384 let scroll_left = $carousel.scrollLeft(); 385 let clientW = $carousel[0].clientWidth; 386 let page = Math.round(scroll_left / clientW); 387 $dots.find('.socialfeeds-dot').removeClass('active').eq(page).addClass('active'); 388 } 389 390 $carousel.on('scroll', function () { 391 clearTimeout($carousel.data('scrollTimer')); 392 $carousel.data('scrollTimer', setTimeout(update_active_dot, 100)); 393 }); 394 395 // 5. Autoplay 396 if ($carousel.data('autoplay')) { 397 let speed = parseInt($carousel.data('autoplay-speed')) || 3000; 398 let interval; 399 400 function start_autoplay() { 401 interval = setInterval(function () { 402 if ($carousel.scrollLeft() + $carousel.innerWidth() >= $carousel[0].scrollWidth - 10) { 403 $carousel.animate({ scrollLeft: 0 }, 600, update_active_dot); 404 } else { 405 let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300; 406 $carousel.animate({ scrollLeft: $carousel.scrollLeft() + itemW }, 600, update_active_dot); 407 } 408 }, speed); 409 } 410 411 start_autoplay(); 412 413 $stage.hover( 414 function () { clearInterval(interval); }, 415 function () { start_autoplay(); } 416 ); 417 } 418 }); 419 420});
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.