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()-new Date(a.publishedAt).getTime());case "date":return sorted.sort((a,b)=>new Date(a.publishedAt).getTime()-new Date(b.publishedAt).getTime());case "popular":return sorted.sort((a,b)=>(b.viewCount||0)-(a.viewCount||0));default:return videos}} 6function 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")}} 7return input}catch(e){return input}} 8function resolveHandleToChannelId(handle,apiKey,callback){$.get(`https://www.googleapis.com/youtube/v3/search?part=snippet&q=${encodeURIComponent( 9 handle 10 )}&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)})} 11function 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 ` 12 <div class="ultp-ytg-video-wrapper"> 13 <iframe 14 src="https://www.youtube.com/embed/${videoId}?${params}" 15 title="YouTube Video" 16 frameborder="0" 17 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 18 allowfullscreen 19 ></iframe> 20 </div> 21 `} 22function 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( 23 title, 24 titleLength 25 )}</a></div>`} 26if(enableDesc){html+=`<div class="ultp-ytg-description">${truncateText( 27 description, 28 descriptionLength 29 )}</div>`} 30html+="</div>";return html} 31$(".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,}
31),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)} 32function proceedWithPlaylist(playlistId){if(playlistId.startsWith("UC")){playlistId="UU"+playlistId.substring(2)} 33if(!playlistId||!config.apiKey){$wrapper.html('<p style="color:#888">Please provide both YouTube playlist ID/URL and API key.</p>');return} 34let 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)}} 35function renderPlaylistLayout(videos){if(!videos.length){$container.html("<p>No videos found in this playlist.</p>");return} 36if(!activeVideo){activeVideo=videos[0]} 37let html='<div class="ultp-ytg-main">';const playerWrapper=` 38 <div class="ultp-ytg-video-wrapper"> 39 <iframe 40 src="https://www.youtube.com/embed/${activeVideo.videoId}?${[ 41 `autoplay=${config.autoplay?"1":"0"}`, 42 `loop=${config.loop?"1":"0"}`, 43 `mute=${config.mute?"1":"0"}`, 44 `controls=${config.showPlayerControl?"1":"0"}`, 45 `modestbranding=${config.hideYoutubeLogo?"1":"0"}`, 46 config.loop ? `playlist=${activeVideo.videoId}` : null, 47 ] 48 .filter(Boolean) 49 .join("&")}" 50 title="YouTube Video" 51 frameborder="0" 52 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 53 allowfullscreen 54 ></iframe> 55 ${getYoutubeTextContent( 56 config.showVideoTitle, 57 activeVideo.title, 58 config.videoTitleLength, 59 config.showDescription, 60 activeVideo.description, 61 config.videoDescriptionLength, 62 activeVideo.videoId 63 )} 64 </div> 65 `;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+=` 66 <div class="ultp-ytg-playlist-item ${ 67 isActive ? "active" : "" 68 }" data-video-id="${video.videoId}"> 69 <img src="${video.thumbnail}" alt="${video.title}" loading="lazy" /> 70 <div class="ultp-ytg-playlist-item-content"> 71 <div class="ultp-ytg-playlist-item-title"> 72 ${truncateText(video.title, config.videoTitleLength)} 73 </div> 74 </div> 75 </div> 76 `});html+="</div></div>";$container.html(html)} 77function renderGridLayout(videos,count){if(!videos.length){$container.html("<p>No videos found in this playlist.</p>");return} 78const 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+=` 79 <img src="${video.thumbnail}" alt="${ 80 video.title 81 }" loading="lazy" data-video-id="${ 82 video.videoId 83 }" style="cursor:pointer;" /> 84 <div class="ultp-ytg-play__icon${ 85 config.enableIconAnimation ? " ytg-icon-animation" : "" 86 }"> 87 ${getSvgIcon} 88 </div> 89 `} 90html+=`</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()}} 91function renderVideos(videos,count){if(config.galleryLayout==="playlist"){renderPlaylistLayout(videos)}else{renderGridLayout(videos,count)}} 92const 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} 93const 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(` 94 <div class="ultp-ytg-loading gallery-postx gallery-active"> 95 <div class="skeleton-box"></div> 96 <div class="skeleton-box"></div> 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> 102 `)}else{$container.html(` 103 <div class="ultp-ytg-loading ultp-ytg-playlist-loading"> 104 <div class="ytg-loader"></div> 105 </div>`)} 106$.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">${ 107 data.error.message || "Failed to fetch playlist." 108 }</div>`);return} 109const 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={};
109statsData.items.forEach(function(item){statsMap[item.id]=item.statistics.viewCount});videos.forEach(function(v){v.viewCount=parseInt(statsMap[v.videoId]||0)})} 110allVideos=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)}} 111renderVideos(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)}} 112renderVideos(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)})} 113$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) 114;
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.