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