1(function($){("use strict");$(document).ready(function(){initYouTubeGallery();});function initYouTubeGallery(){function parseMaybeJSON(val,fallback){if(typeof val==="string"){try{return JSON.parse(val);}catch(e){return fallback;}} 2return typeof val==="object"&&val!==null?val:fallback;} 3function truncateText(text,length){if(!text)return"";const width=$(window).width();let maxLength;if(width<600){maxLength=length.sm||length.lg;}else if(width<900){maxLength=length.md||length.lg;}else{maxLength=length.lg;} 4return text.length>maxLength?text.substring(0,maxLength)+"...":text;} 5function sortVideos(videos,sortBy){const sorted=[...videos];switch(sortBy){case"title":return sorted.sort((a,b)=>a.title.localeCompare(b.title,undefined,{sensitivity:"base",}));case"latest":return sorted.sort((a,b)=>new Date(b.publishedAt).getTime()- 6new Date(a.publishedAt).getTime());case"date":return sorted.sort((a,b)=>new Date(a.publishedAt).getTime()- 7new Date(b.publishedAt).getTime());case"popular":return sorted.sort((a,b)=>(b.viewCount||0)-(a.viewCount||0));default:return videos;}} 8function getPlaylistId(input){if(!input)return"";try{const url=new URL(input);if(url.hostname==="www.youtube.com"){if(url.pathname.startsWith("/channel/")){return url.pathname.split("/channel/")[1];}else if(url.pathname.startsWith("/@")){return url.pathname.substring(2);}else if(url.searchParams.get("list")){return url.searchParams.get("list");}} 9return input;}catch(e){return input;}} 10function resolveHandleToChannelId(handle,apiKey,callback){$.get(`https://www.googleapis.com/youtube/v3/search?part=snippet&q=${encodeURIComponent( 11 handle 12 )}&type=channel&key=${apiKey}`).done(function(data){if(data.items&&data.items.length>0){callback(data.items[0].snippet.channelId);}else{callback(null);}}).fail(function(){callback(null);});} 13function generatePlayerIframe(videoId,config){const params=[`autoplay=${config.autoplay ? "1" : "0"}`,`loop=${config.loop ? "1" : "0"}`,`mute=${config.mute ? "1" : "0"}`,`controls=${config.showPlayerControl ? "1" : "0"}`,`modestbranding=${config.hideYoutubeLogo ? "1" : "0"}`,config.loop?`playlist=${videoId}`:null,].filter(Boolean).join("&");return` 14 <div class="ultp-ytg-video-wrapper"> 15 <iframe 16 src="https://www.youtube.com/embed/${videoId}?${params}" 17 title="YouTube Video" 18 frameborder="0" 19 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 20 allowfullscreen 21 ></iframe> 22 </div> 23 `;} 24function getYoutubeTextContent(enableTitle,title,titleLength,enableDesc,description,descriptionLength,videoId){let html='<div class="ultp-ytg-content">';if(enableTitle){html+=`<div class="ultp-ytg-title"><a href="https://www.youtube.com/watch?v=${videoId}" target="_blank" rel="noopener noreferrer">${truncateText( 25 title, 26 titleLength 27 )}</a></div>`;} 28if(enableDesc){html+=`<div class="ultp-ytg-description">${truncateText( 29 description, 30 descriptionLength 31 )}</div>`;} 32html+="</div>";return html;} 33$(".wp-block-ultimate-post-youtube-gallery").each(function(){const $block=$(this);const $wrapper=$block.find(".ultp-block-wrapper");let $container=$block.find(".ultp-ytg-view-grid, .ultp-ytg-container");const $loadMoreBtn=$block.find(".ultp-ytg-loadmore-btn");const config={playlistIdOrUrl:$block.data("playlist")||"",apiKey:$block.data("api-key")||"",cacheDuration:parseInt($block.data("cache-duration"))||0,sortBy:$block.data("sort-by")||"date",galleryLayout:$block.data("gallery-layout")||"grid",videosPerPage:parseMaybeJSON($block.data("videos-per-page"),{lg:9,md:6,sm:3,}),showVideoTitle:$block.data("show-video-title")=="1",videoTitleLength:parseMaybeJSON($block.data("video-title-length"),{lg:50,md:50,sm:50,}
33),loadMoreEnable:$block.data("load-more-enable")=="1",moreButtonLabel:$block.data("more-button-label")||"More Videos",autoplay:$block.data("autoplay")=="1",loop:$block.data("loop")=="1",mute:$block.data("mute")=="1",showPlayerControl:$block.data("show-player-control")=="1",hideYoutubeLogo:$block.data("hide-youtube-logo")=="1",showDescription:$block.data("show-description")=="1",videoDescriptionLength:parseMaybeJSON($block.data("video-description-length"),{lg:100,md:100,sm:100,}),imageHeightRatio:$block.data("image-height-ratio")||"16-9",galleryColumn:parseMaybeJSON($block.data("gallery-column"),{lg:3,md:2,sm:1,}),displayType:$block.data("display-type")||"grid",enableListView:$block.data("enable-list-view")=="1",enableIconAnimation:$block.data("enable-icon-animation")=="1",defaultYoutubeIcon:$block.data("enable-youtube-icon")=="1",imgHeight:$block.data("img-height"),};let playlistId=getPlaylistId(config.playlistIdOrUrl);if(playlistId.startsWith("@")){const handle=playlistId.substring(1);resolveHandleToChannelId(handle,config.apiKey,function(channelId){if(channelId){playlistId=channelId;proceedWithPlaylist(playlistId);}else{$wrapper.html('<p style="color:#888">Invalid handle or API key.</p>');}});}else{proceedWithPlaylist(playlistId);} 34function proceedWithPlaylist(playlistId){if(playlistId.startsWith("UC")){playlistId="UU"+playlistId.substring(2);} 35if(!playlistId||!config.apiKey){$wrapper.html('<p style="color:#888">Please provide both YouTube playlist ID/URL and API key.</p>');return;} 36let allVideos=[];let shownCount=config.videosPerPage.lg||9;let activeVideo=null;let playingId=null;function updateResponsiveCounts(){const width=$(window).width();if(width<600){shownCount=Math.max(shownCount,config.videosPerPage.sm||3);}else if(width<900){shownCount=Math.max(shownCount,config.videosPerPage.md||6);}else{shownCount=Math.max(shownCount,config.videosPerPage.lg||9);}} 37function renderPlaylistLayout(videos){if(!videos.length){$container.html("<p>No videos found in this playlist.</p>");return;} 38if(!activeVideo){activeVideo=videos[0];} 39let html='<div class="ultp-ytg-main">';const playerWrapper=` 40 <div class="ultp-ytg-video-wrapper"> 41 <iframe 42 src="https://www.youtube.com/embed/${activeVideo.videoId}?${[ 43 `autoplay=${config.autoplay?"1":"0"}`, 44 `loop=${config.loop?"1":"0"}`, 45 `mute=${config.mute?"1":"0"}`, 46 `controls=${config.showPlayerControl?"1":"0"}`, 47 `modestbranding=${config.hideYoutubeLogo?"1":"0"}`, 48 config.loop ? `playlist=${activeVideo.videoId}` : null, 49 ] 50 .filter(Boolean) 51 .join("&")}" 52 title="YouTube Video" 53 frameborder="0" 54 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 55 allowfullscreen 56 ></iframe> 57 ${getYoutubeTextContent( 58 config.showVideoTitle, 59 activeVideo.title, 60 config.videoTitleLength, 61 config.showDescription, 62 activeVideo.description, 63 config.videoDescriptionLength, 64 activeVideo.videoId 65 )} 66 </div> 67 `;html+=playerWrapper;html+="</div>";html+='<div class="ultp-ytg-playlist-sidebar">';html+='<div class="ultp-ytg-playlist-items">';videos.forEach(function(video){const isActive=video.videoId===activeVideo.videoId;html+=` 68 <div class="ultp-ytg-playlist-item ${ 69 isActive ? "active" : "" 70 }" data-video-id="${video.videoId}"> 71 <img src="${video.thumbnail}" alt="${video.title}" loading="lazy" /> 72 <div class="ultp-ytg-playlist-item-content"> 73 <div class="ultp-ytg-playlist-item-title"> 74 ${truncateText(video.title, config.videoTitleLength)} 75 </div> 76 </div> 77 </div> 78 `;});html+="</div></div>";$container.html(html);} 79function renderGridLayout(videos,count){if(!videos.length){$container.html("<p>No videos found in this playlist.</p>");return;} 80const displayedVideos=videos.slice(0,count);let html="";displayedVideos.forEach(function(video){const isPlaying=playingId===video.videoId;html+=`<div class="ultp-ytg-item${isPlaying ? " active" : ""}">`;html+=`<div class="ultp-ytg-video">`;if(isPlaying){html+=generatePlayerIframe(video.videoId,config);}else{const getSvgIcon=$(".ultp-ytg-play__icon").html();html+=` 81 <img src="${video.thumbnail}" alt="${ 82 video.title 83 }" loading="lazy" data-video-id="${ 84 video.videoId 85 }" style="cursor:pointer;" /> 86 <div class="ultp-ytg-play__icon${ 87 config.enableIconAnimation ? " ytg-icon-animation" : "" 88 }"> 89 ${getSvgIcon} 90 </div> 91 `;} 92html+=`</div>`;html+=`<div class="ultp-ytg-inside">`;html+=getYoutubeTextContent(config.showVideoTitle,video.title,config.videoTitleLength,config.showDescription,video.description,config.videoDescriptionLength,video.videoId);html+=`</div></div>`;});$container.html(html);if(config.loadMoreEnable&&count<videos.length){$loadMoreBtn.show();}else{$loadMoreBtn.hide();}} 93function renderVideos(videos,count){if(config.galleryLayout==="playlist"){renderPlaylistLayout(videos);}else{renderGridLayout(videos,count);}} 94const cacheKey=`ultp_youtube_gallery_${playlistId}_${config.apiKey}_${config.sortBy}_${config.imgHeight}`;const duration=config.cacheDuration;let cached=null;try{cached=JSON.parse(localStorage.getItem(cacheKey));}catch(e){cached=null;} 95const now=Date.now();if(cached&&cached.data&&cached.timestamp&&duration>0&&now-cached.timestamp<duration*1000){allVideos=sortVideos(cached.data,config.sortBy);renderVideos(allVideos,shownCount);}else{if(config.galleryLayout!=="playlist"){$container.html(` 96 <div class="ultp-ytg-loading gallery-postx gallery-active"> 97 <div class="skeleton-box"></div> 98 <div class="skeleton-box"></div> 99 <div class="skeleton-box"></div> 100 <div class="skeleton-box"></div> 101 <div class="skeleton-box"></div> 102 <div class="skeleton-box"></div> 103 </div> 104 `);}else{$container.html(` 105 <div class="ultp-ytg-loading ultp-ytg-playlist-loading"> 106 <div class="ytg-loader"></div> 107 </div>`);} 108$.get("https://www.googleapis.com/youtube/v3/playlistItems",{part:"snippet",maxResults:50,playlistId:playlistId,key:config.apiKey,}).done(function(data){setTimeout(function(){$container.empty();if(data.error){$container.html(`<div class="ultp-ytg-error">${ 109 data.error.message || "Failed to fetch playlist." 110 }</div>`);return;} 111const videos=(data.items||[]).filter(function(item){return(item.snippet.title!=="Private video"&&item.snippet.title!=="Deleted video");}).map(function(item){return{videoId:item.snippet.resourceId.videoId,title:item.snippet.title,thumbnail:(item.snippet.thumbnails&&item.snippet.thumbnails[config.imgHeight]&&item.snippet.thumbnails[config.imgHeight].url)||item.snippet.thumbnails[config.imgHeight].url||item.snippet.thumbnails?.medium?.url||"",publishedAt:item.snippet.publishedAt||"",description:item.snippet.description||"",viewCount:0,};});if(config.sortBy==="popular"){const videoIds=videos.map((v)=>v.videoId).join(",");$.get(`https://www.googleapis.com/youtube/v3/videos?part=statistics&id=${videoIds}&key=${config.apiKey}`).done(function(statsData){if(statsData.items){const statsMap={};
111statsData.items.forEach(function(item){statsMap[item.id]=item.statistics.viewCount;});videos.forEach(function(v){v.viewCount=parseInt(statsMap[v.videoId]||0);});} 112allVideos=sortVideos(videos,config.sortBy);if(duration>0){try{localStorage.setItem(cacheKey,JSON.stringify({data:videos,timestamp:now,}));}catch(e){console.warn("Failed to cache videos:",e);}} 113renderVideos(allVideos,shownCount);}).fail(function(){console.warn("Failed to fetch video statistics for popular sorting.");allVideos=sortVideos(videos,config.sortBy);renderVideos(allVideos,shownCount);});}else{allVideos=sortVideos(videos,config.sortBy);if(duration>0){try{localStorage.setItem(cacheKey,JSON.stringify({data:videos,timestamp:now,}));}catch(e){console.warn("Failed to cache videos:",e);}} 114renderVideos(allVideos,shownCount);}},2000);}).fail(function(){setTimeout(function(){$container.empty();$container.html('<div class="ultp-ytg-error">Failed to fetch videos. Please try again.</div>');},3000);});} 115$block.on("click",".ultp-ytg-playlist-item",function(){const videoId=$(this).data("video-id");if(!videoId)return;activeVideo=allVideos.find(function(v){return v.videoId===videoId;});if(activeVideo){renderPlaylistLayout(allVideos);}});$block.on("click",".ultp-ytg-play__icon",function(){const $img=$(this).siblings("img[data-video-id]");const videoId=$img.data("video-id");if(!videoId)return;const $item=$(this).closest(".ultp-ytg-item");const $videoDiv=$item.find(".ultp-ytg-video");$videoDiv.html('<div class="ultp-ytg-loading"><div class="ytg-loader"></div></div>');$item.addClass("active").siblings(".ultp-ytg-item").removeClass("active");setTimeout(function(){playingId=videoId;renderGridLayout(allVideos,shownCount);},1000);});$block.on("click",".ultp-ytg-video img[data-video-id]",function(){const videoId=$(this).data("video-id");if(!videoId)return;playingId=videoId;renderGridLayout(allVideos,shownCount);});$loadMoreBtn.on("click",function(){shownCount+=config.videosPerPage.lg;renderGridLayout(allVideos,shownCount);});$(window).on("resize",function(){if(allVideos.length){updateResponsiveCounts();renderVideos(allVideos,shownCount);}});}});}})(jQuery);
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.