1// Play button Icon 2 3 function media_item_play_button_icon(color = "#fff") { 4 return ` 5 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" data-name="Layer 1" viewBox="0 0 145.2 145.2"><defs> 6 <style> 7 .cls-1 { fill: none; } 8 .cls-2 { clip-path: url(#clip-path); } 9 .cls-3 { opacity: 1; } 10 .cls-4 { clip-path: url(#clip-path-3); } 11 </style> 12 <clipPath id="clip-path" transform="translate(-264.41 -245.59)"> 13 <rect class="cls-1" x="264.41" y="245.59" width="145.2" height="145.2"></rect> 14 </clipPath> 15 <clipPath id="clip-path-3" transform="translate(-264.41 -245.59)"> 16 <rect class="cls-1" x="255.41" y="238.59" width="163.2" height="153.2"></rect> 17 </clipPath></defs> 18 <g class="cls-2"> 19 <g class="cls-2"> 20 <g class="cls-3"> 21 <g class="cls-4"> 22 <path style="fill: ${color}" class="cls-5" d="M378.93,318.19,311,357.4V279Zm30.68,0a72.6,72.6,0,1,0-72.6,72.6,72.6,72.6,0,0,0,72.6-72.6" transform="translate(-264.41 -245.59)"></path> 23 </g></g></g></g> 24 </svg>`; 25 } 26 27 // Audio Playbar 28 29 function audio_play_bar(color = "#fff") { 30 return ` 31 <svg xmlns="http://www.w3.org/2000/svg" id="audio_play_bar" class="audio-play-bar" data-name="Layer 1" viewBox="0 0 453 45"> 32 <defs> 33 <style> 34 .pb-2 { 35 fill: #231f20; 36 } 37 .pb-3 { 38 fill: none; 39 stroke: #231f20; 40 stroke-width: 3px; 41 } 42 </style> 43 </defs> 44 <rect style="fill: ${color}" width="453" height="45"></rect> 45 <polygon class="pb-2" points="42.99 22 17.01 7 17.01 37 42.99 22"></polygon> 46 <line class="pb-3" x1="52" y1="9" x2="52" y2="36"></line><line class="pb-3" x1="58" y1="9" x2="58" y2="36"></line><line class="pb-3" x1="64" y1="9" x2="64" y2="36"></line><line class="pb-3" x1="70" y1="9" x2="70" y2="36"></line><line class="pb-3" x1="76" y1="9" x2="76" y2="36"></line><line class="pb-3" x1="82" y1="9" x2="82" y2="36"></line><line class="pb-3" x1="88" y1="9" x2="88" y2="36"></line><line class="pb-3" x1="94" y1="9" x2="94" y2="36"></line><line class="pb-3" x1="100" y1="9" x2="100" y2="36"></line><line class="pb-3" x1="106" y1="9" x2="106" y2="36"></line><line class="pb-3" x1="112" y1="9" x2="112" y2="36"></line><line class="pb-3" x1="118" y1="9" x2="118" y2="36"></line><line class="pb-3" x1="124" y1="9" x2="124" y2="36"></line><line class="pb-3" x1="130" y1="9" x2="130" y2="36"></line><line class="pb-3" x1="136" y1="9" x2="136" y2="36"></line><line class="pb-3" x1="142" y1="9" x2="142" y2="36"></line><line class="pb-3" x1="148" y1="9" x2="148" y2="36"></line><line class="pb-3" x1="154" y1="9" x2="154" y2="36"></line><line class="pb-3" x1="160" y1="9" x2="160" y2="36"></line><line class="pb-3" x1="166" y1="9" x2="166" y2="36"></line><line class="pb-3" x1="172" y1="9" x2="172" y2="36"></line><line class="pb-3" x1="178" y1="9" x2="178" y2="36"></line><line class="pb-3" x1="184" y1="9" x2="184" y2="36"></line><line class="pb-3" x1="190" y1="9" x2="190" y2="36"></line><line class="pb-3" x1="196" y1="9" x2="196" y2="36"></line><line class="pb-3" x1="202" y1="9" x2="202" y2="36"></line><line class="pb-3" x1="208" y1="9" x2="208" y2="36"></line><line class="pb-3" x1="214" y1="9" x2="214" y2="36"></line><line class="pb-3" x1="220" y1="9" x2="220" y2="36"></line><line class="pb-3" x1="226" y1="9" x2="226" y2="36"></line><line class="pb-3" x1="232" y1="9" x2="232" y2="36"></line><line class="pb-3" x1="238" y1="9" x2="238" y2="36"></line><line class="pb-3" x1="244" y1="9" x2="244" y2="36"></line><line class="pb-3" x1="250" y1="9" x2="250" y2="36"></line><line class="pb-3" x1="256" y1="9" x2="256" y2="36"></line><line class="pb-3" x1="262" y1="9" x2="262" y2="36"></line><line class="pb-3" x1="268" y1="9" x2="268" y2="36"></line><line class="pb-3" x1="274" y1="9" x2="274" y2="36"></line><line class="pb-3" x1="280" y1="9" x2="280" y2="36"></line><line class="pb-3" x1="286" y1="9" x2="286" y2="36"></line><line class="pb-3" x1="292" y1="9" x2="292" y2="36"></line><line class="pb-3" x1="298" y1="9" x2="298" y2="36"></line><line class="pb-3" x1="304" y1="9" x2="304" y2="36"></line><line class="pb-3" x1="310" y1="9" x2="310" y2="36"></line><line class="pb-3" x1="316" y1="9" x2="316" y2="36"></line><line class="pb-3" x1="322" y1="9" x2="322" y2="36"></line><line class="pb-3" x1="328" y1="9" x2="328" y2="36"></line><line class="pb-3" x1="334" y1="9" x2="334" y2="36"></line><line class="pb-3" x1="340" y1="9" x2="340" y2="36"></line><line class="pb-3" x1="346" y1="9" x2="346" y2="36"></line><line class="pb-3" x1="352" y1="9" x2="352" y2="36"></line><line class="pb-3" x1="358" y1="9" x2="358" y2="36"></line><line class="pb-3" x1="364" y1="9" x2="364" y2="36"></line><line class="pb-3" x1="370" y1="9" x2="370" y2="36"></line><line class="pb-3" x1="376" y1="9" x2="376" y2="36"></line><line class="pb-3" x1="382" y1="9" x2="382" y2="36"></line><line class="pb-3" x1="388" y1="9" x2="388" y2="36"></line><line class="pb-3" x1="394" y1="9" x2="394" y2="36"></line><line class="pb-3" x1="400" y1="9" x2="400" y2="36"></line><line class="pb-3" x1="406" y1="9" x2="406" y2="36"></line><line class="pb-3" x1="412" y1="9" x2="412" y2="36"></line><line class="pb-3" x1="418" y1="9" x2="418" y2="36"></line><line class="pb-3" x1="424" y1="9" x2="424" y2="36"></line><line class="pb-3" x1="430" y1="9" x2="430" y2="36"></line><line class="pb-3" x1="436" y1="9" x2="436" y2="36"></line> 47 </svg> 48 ` 49 } 50 51 function escape_html(value) { 52 return String(value ?? "") 53 .replace(/&/g, "&") 54 .replace(/</g, "<") 55 .replace(/>/g, ">") 56 .replace(/"/g, """) 57 .replace(/'/g, "'"); 58 } 59 60 function escape_attr(value) { 61 return escape_html(value); 62 } 63 64 function format_overlay_title(item) { 65 if (item.episode !== -1) { 66 return `Episode ${item.episode}`; 67 } 68 69 const words = String(item.title ?? "").split(" "); 70 if (words.length > 3) { 71 const first = words.filter((l, i) => i < 3).join(" "); 72 const rest = words.filter((l, i) => i >= 3).join(" "); 73 return `${escape_html(first)}<br><span class="sub-text">${escape_html(rest)}</span>`;
74 } 75 76 return escape_html(item.title ?? ""); 77 } 78 79 function media_title_text(item) { 80 return item.episode !== -1 ? `Episode ${item.episode}` : String(item.title ?? ""); 81 } 82 83 function sanitize_multiple_grid_text(value) { 84 const normalized = String(value ?? "").trim().toLowerCase(); 85 return ["title", "description", "both", "numberedtitle", "numberedtitleanddescription"].includes(normalized) ? normalized : ""; 86 } 87 88 // Build the episode/season-aware title text for a numbered grid item. 89 // When the item has no episode number (episode === -1, i.e. the 90 // playlist's sort mode is "Normal" or no number was detected), the 91 // plain video title is used. When an episode number is present and a 92 // valid season was parsed via the season/episode regex, returns 93 // "Season {s}, Episode {e}"; otherwise returns "Episode {e}". 94 function numbered_title_text(item) { 95 const episode = Number(item?.episode); 96 if (!Number.isFinite(episode) || episode === -1) { 97 return String(item?.title ?? "").trim(); 98 } 99 const season = Number(item?.season); 100 if (Number.isFinite(season) && season !== -1) { 101 return `Season ${season} Episode ${episode}`; 102 } 103 return `Episode ${episode}`; 104 } 105 106 function render_multiple_grid_text(item, mode) { 107 if (mode === "both") { 108 const title = String(item?.title ?? "").trim(); 109 const description = String(item?.description ?? "").trim(); 110 if (!title && !description) return ""; 111 let html = ""; 112 if (title) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-title">${escape_html(title)}</div>`; 113 if (description) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-description">${escape_html(description)}</div>`; 114 return html; 115 } 116 117 if (mode === "numberedtitle" || mode === "numberedtitleanddescription") { 118 const title = numbered_title_text(item); 119 const description = String(item?.description ?? "").trim(); 120 let html = ""; 121 if (title) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-title">${escape_html(title)}</div>`; 122 if (mode === "numberedtitleanddescription" && description) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-description">${escape_html(description)}</div>`; 123 return html; 124 } 125 126 if (mode !== "title" && mode !== "description") { 127 return ""; 128 } 129 130 const text = String(item?.[mode] ?? "").trim(); 131 if (!text) { 132 return ""; 133 } 134 135 return `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-${mode}">${escape_html(text)}</div>`; 136 } 137 138 // Render Media Item 139 140 function render_media_item(item, settings, playlistItem, info) { 141 142 // Set Styling For Lightbox Playlist Items 143 144 if (playlistItem) { 145 settings = { 146 showPlayButton: true, 147 playButtonIconImgUrl: null, 148 playButtonStyling: "width: 50%; height: 50%; opacity: 0.3;", 149 showTextOverlay: false, 150 instructionMessage: null, 151 fontFamily: null, 152 lightboxshowplaylist: false, 153 showLightbox: true 154 } 155 } 156 157 // Checks If Podcast And Is The Custom Player 158 159 const customPodcastPlayer = settings.podcastplayermode ? true : false;
160 161 let podcastPlayerData = customPodcastPlayer ? `data-podcastplayermode="${settings.podcastplayermode}" data-podcastplayerbuttoncolor="${settings.podcastplayerbuttoncolor}" 162 data-podcastplayercolor="${settings.podcastplayercolor}" data-podcastprogressplayerbarcolor="${settings.podcastprogressplayerbarcolor}" data-podcastplayerhighlightcolor="${settings.podcastplayerhighlightcolor}" 163 data-podcastplayerfont="${settings.podcastplayerfont}" data-podcastplayerscrollcolor="${settings.podcastplayerscrollcolor}" 164 data-podcastplayertextcolor="${settings.podcastplayertextcolor}" data-showepisodedateaftertitle="${settings.showepisodedateaftertitle}" 165 `: false; 166 167 const { name, type } = info; 168 const mediaType = type === "youtube" || type === "vimeo" ? "video" : "audio"; 169 const { title, thumbnail, publishedDate, id, description } = item; 170 const { showPlayButton, playButtonIconImgUrl, playButtonStyling, showTextOverlay, instructionMessage, fontFamily, lightboxshowplaylist, showPlaybar, playbarColor, showLightbox } = settings; 171 const safeTitleAttr = escape_attr(title ?? ""); 172 const safeOverlayTitle = format_overlay_title(item); 173 const safePlaylistTitle = escape_html(media_title_text(item)); 174 const safeInstructionMessage = escape_html(instructionMessage ?? ""); 175 const multipleGridTextHtml = !playlistItem ? render_multiple_grid_text(item, settings.multipleGridText) : ""; 176 const mediaItemHtml = ` 177 <a ${settings.fontFamily ? `style=${fontFamily}` : ""} class="media_item${showTextOverlay ? `-text-overlay-enabled` : ``}" data-itemclickablemediatype="${mediaType}" data-itemclickable="true" data-itemclickableplaylist="${name}_${type}" 178 data-id="${id}" ${lightboxshowplaylist ? `data-lightboxshowplaylist="true"` : ""} ${item.trackSelect !== null ? `data-trackselect=${Number(item.trackSelect)}` : ""} 179 ${customPodcastPlayer ? podcastPlayerData : ""} 180 ${showLightbox === false ? `data-showlightbox="false"` : ""} 181 > 182 <div class="media-item-thumbnail-text-wrapper"> 183 <img class="media-item-thumbnail" src="${thumbnail.url}" width="${thumbnail.width ? thumbnail.width : 1280}" height="${thumbnail.height ? thumbnail.height : 720}" alt="${safeTitleAttr}"> 184 ${showPlayButton ? 185 ` 186 <div class="media-item-play-button" style="${playButtonStyling}"> 187 ${showPlayButton ? 188 `${playButtonIconImgUrl ? `<img src="${playButtonIconImgUrl}">` : 189 media_item_play_button_icon() 190 }` : `` 191 } 192 </div> 193 ` 194 : ``} 195 ${showTextOverlay ? 196 ` 197 <div class="media-item-text-overlay" ${mediaType === "audio" ? `style="padding-bottom: max(10vw, 48px);"` : "" }> 198 <h3>${safeOverlayTitle}</h3> 199 <p>${safeInstructionMessage}</p> 200 </div> 201 ` 202 : ``} 203 </div> 204 ${ playlistItem ? `<h3 class="playlist-episode-text">${safePlaylistTitle}</h3>` : ""} 205 ${mediaType === "audio" && showPlaybar ? audio_play_bar(playbarColor) : ""} 206 </a> 207 `; 208 209 if (multipleGridTextHtml) { 210 return ` 211 <!-- media item --> 212 <div class="media-item-multiple-grid-entry"> 213 ${mediaItemHtml} 214 ${multipleGridTextHtml} 215 </div> 216 `; 217 } 218 219 return ` 220 <!-- media item --> 221 ${mediaItemHtml} 222 `; 223 } 224 225 // Initialize Loading Of Media Items And Lightbox 226 227 function initialize_media(media_items, media_data, media_name, media_type, podcast_platform) { 228 229 // Variables For Lightbox 230 231 let showThemeColor; 232 let showLogoImgUrl; 233 let lightboxFont; 234 235 if (media_items.length > 0) {
236 media_items.forEach(item => { 237 238 if (!media_data || media_data.length === 0) { 239 if (media_type === "youtube" || media_type === "vimeo") { 240 item.innerHTML = "<h2>Error loading video.</h2>"; 241 } 242 if (media_type === "podcast") { 243 item.innerHTML = "<h2>Error loading podcast.</h2>"; 244 } 245 return; 246 } 247 248 const itemData = item.dataset; 249 250 // Check That Item Corresponds To Correct Playlist. If Not, Return To Exit 251 252 if (itemData.playlistname !== media_name && itemData.mediaplatform !== media_type) { 253 return 254 } 255 256 // CSS Styling 257 258 const showPlayButton = itemData.showplaybutton === "true" ? true : itemData.showplaybutton === "false" ? false : true; 259 260 const playButtonIconImgUrl = itemData.playButtonIconImgUrl ? itemData.playButtonIconImgUrl : null; 261 262 const playButtonStyling = itemData.playbuttonstyling ? itemData.playbuttonstyling : "width: 35%; height: 35%; opacity: 0.3;" 263 264 const showTextOverlay = itemData.showtextoverlay === "true" ? true : itemData.showtextoverlay === "false" ? false : true; 265 266 const fontFamily = itemData.fontfamily ? `"font-family: ${itemData.fontfamily}"` : `"font-family: Roboto;"`; 267 268 showThemeColor = itemData.lightboxthemecolor ? itemData.lightboxthemecolor : showThemeColor; 269 270 showLogoImgUrl = itemData.lightboxshowlogoimgurl ? itemData.lightboxshowlogoimgurl : showLogoImgUrl; 271 272 lightboxFont = itemData.lightboxfont ? itemData.lightboxfont : lightboxFont; 273 274 const showPlaybar = itemData.showplaybar === "true" ? true : false; 275 276 const playbarColor = itemData.playbarcolor ? itemData.playbarcolor : "#fff"; 277 278 // Text Overlay Messages 279 280 const instructionMessage = itemData.instructionMessage ? itemData.instructionMessage : media_type === "youtube" ? "Click Here To Watch" : media_type === "podcast" ? "Click Here To Listen" : ""; 281 282 const lightboxshowplaylist = itemData.lightboxshowplaylist && itemData.lightboxshowplaylist === "true" ? true : false; 283 const showLightbox = itemData.showlightbox !== "fal
283se"; 284 285 // Thumbnail 286 287 const thumbnailimg = itemData.thumbnail ? itemData.thumbnail : null; 288 289 // Data Parsing 290 291 let renderData; 292 293 if (podcast_platform !== "embed") { 294 if (media_type === "podcast") { 295 if (!media_data.channel.item.length) { 296 media_data.channel.item = [media_data.channel.item]; 297 } 298 } 299 renderData = media_type === "youtube" ? media_data 300 : media_type === "podcast" ? media_data.channel.item.map(item => ({...item, thumbnail: { url: thumbnailimg }, publishedDate: item.pubDate, id: item.guid, episode: -1 })) 301 : []; 302 } else { 303 renderData = [{ thumbnail: { url: thumbnailimg} , publishedDate: "unknown", id: "unknown", episode: -1, title: `Embedded Podcast` }]; 304 } 305 306 if (podcast_platform === "soundcloud") { 307 renderData = renderData.map(i => ({...i, trackSelect: Number(itemData.orderdescending) - 1})); 308 } 309 310 if (podcast_platform === "custom") { 311 renderData = renderData.map(i => ({...i, trackSelect: Number(itemData.orderdescending)})); 312 } 313 314 const settings = { 315 showPlayButton, 316 playButtonIconImgUrl, 317 playButtonStyling, 318 showTextOverlay, 319 instructionMessage, 320 fontFamily, 321 lightboxshowplaylist, 322 showPlaybar, 323 playbarColor, 324 showLightbox 325 }; 326 327 const info = { 328 name: media_name, 329 type: media_type 330 }; 331 332 // Select Item 333 334 const nameSelect = itemData.nameselect; 335 const episodeNumber = itemData.episodenumber; 336 let index; 337 338 if (episodeNumber && media_type === "youtube") { 339 index = renderData.findIndex(item => item.episode === Number(episodeNumber)); 340 } 341 if (nameSelect) { 342 index = renderData.findIndex(item => item.title.toLowerCase().includes(nameSelect.toLowerCase())); 343 } 344 if (itemData.orderdescending) { 345 index = Number(itemData.orderdescending) - 1; 346 } 347 348 // If Podcast Is embed or Soundcloud, Index Will Automatically Be Set To 0 349 350 if (podcast_platform === "embed") { 351 index = 0; 352 } 353 354 // If Podcast Is Custom, Add Fields In Settings 355 356 if (podcast_platform === "custom") { 357 settings.podcastplayermode = itemData.podcastplayermode ? itemData.podcastplayermode.slice(1) : "dark"; 358 settings.podcastplayerbuttoncolor = itemData.podcastplayerbuttoncolor ? itemData.podcastplayerbuttoncolor.slice(1) : ""; 359 settings.podcastplayercolor = itemData.podcastplayercolor ? itemData.podcastplayercolor.slice(1) : ""; 360 settings.podcastprogressplayerbarcolor = itemData.podcastprogressplayerbarcolor ? itemData.podcastprogressplayerbarcolor.slice(1) : ""; 361 settings.podcastplayerhighlightcolor = itemData.podcastplayerhighlightcolor ? itemData.podcastplayerhighlightcolor.slice(1) : ""; 362 settings.podcastplayerfont = itemData.podcastplayerfont ? itemData.podcastplayerfont : ""; 363 settings.podcastplayerscrollcolor = itemData.podcastplayerscrollcolor ? itemData.podcastplayerscrollcolor.slice(1) : ""; 364 settings.podcastplayertextcolor = itemData.podcastplayertextcolor ? itemData.podcastplayertextcolor.slice(1) : ""; 365 settings.showepisodedateaftertitle = itemData.showepisodedateaftertitle ? itemData.showepisodedateaftertitle : ""; 366 } 367 368 let trackSelect = null; 369 370 // If Track Is Soundcloud, trackselect will be set from itemData.orderdescending 371 372 if (podcast_platform === "soundcloud") { 373 trackSelect = Number(itemData.orderdescending) - 1; 374 } 375 376 if (podcast_platform === "custom") { 377 trackSelect = Number(itemData.orderdescending) 378 } 379 380 // Output Text For Media Description Or Text Only Text And Exit 381 382 if (itemData.mediatitle === "true" || itemData.mediadescription === "true") { 383 384 if (renderData[index]) { 385 let descriptionOrTitle; 386 387 if (itemData.mediadescription === "true") { 388 descriptionOrTitle = renderData[index].description; 389 } 390 391 if (itemData.mediatitle === "true" && !descriptionOrTitle) { 392 descriptionOrTitle = renderData[index].title; 393 } 394 395 if (descriptionOrTitle && typeof descriptionOrTitle !== "object") { 396 item.outerHTML = `<p style="color: ${itemData.mediadescriptiontextcolor}" class="media-description-text">${escape_html(descriptionOrTitle)}</p>`; 397 } else { 398 console.warn(`No description or title for "${descriptionOrTitle}" under order selection of "${index + 1}". Description or title text will auto populate once the video or podcast has it.`); 399 const parentContainer = item.parentNode; 400 if (parentContainer.classList.toString().includes("elementor-widget-container")) { 401 parentContainer.outerHTML = ""; 402 } else { 403 item.outerHTML = ""; 404 } 405 } 406 return; 407 } 408 } else { 409 410 // Checks If multiplegrid parameter was provided and filters accordingly prior to rendering 411 412 if (itemData.multiplegrid === "true" && media_data.length > 1) { 413 let renderGridData = [...renderData]; 414 const multipleGridText = media_type === "youtube" 415 ? sanitize_multiple_grid_text(itemData.multiplegridtext || itemData.mutiplegridtext) 416 : ""; 417 418 // If multiplegridshowall is not set to true, then playlist will be filtered accordingly. 419 420 if (!itemData.multiplegridshowall || itemData.multiplegridshowall !== "true") { 421 if (itemData.multiplegridsearch) { 422 renderGridData = renderGridData.filter(item => item.title.toLowerCase().includes(itemData.multiplegridsearch.toLowerCase())); 423 } 424 if (Number(itemData.multiplegridlimititems)) { 425 renderGridData = renderGridData.filter((item, index) => index < Number(itemData.multiplegridlimititems)); 426 } 427
428 // Will override search and limit items if parameter is present for episode range 429 430 if (itemData.multiplegridepisoderange && itemData.multiplegridepisoderange !== "" && itemData.multiplegridepisoderange.includes("-") && itemData.multiplegridepisoderange !== "-" && /[0-9]/.test(itemData.multiplegridepisoderange)) { 431 const splitter = itemData.multiplegridepisoderange.split("-"); 432 renderGridData = [...media_data]; 433 renderGridData = renderGridData.filter(item => item.episode >= Number(splitter[0]) && item.episode <= Number(splitter[1])); 434 } 435 } 436 const gapBetweenItems = itemData.multiplegridgap ? itemData.multiplegridgap : "48px"; 437 const minSize = itemData.multiplegridminsize ? itemData.multiplegridminsize : "400px"; 438 if (renderGridData.length > 0) { 439 item.outerHTML = 440 `<div class="media_items_multiple_grid_layout" style="gap: ${gapBetweenItems}; grid-template-columns: repeat(auto-fill, minmax(min(100%, ${minSize}), 1fr));"> 441 ${renderGridData.map(row => render_media_item(row, {...settings, multipleGridText}, false, info)).join("")} 442 </div>` 443 } else item.outerHTML = `<h3 class="media-api-widget-err-msg">No ${media_type} items found in playlist based upon search parameters provided.</h3>` 444 return 445 } 446 447 // Render Single Item 448 449 if (renderData[index]) { 450 item.outerHTML = render_media_item(renderData[index], settings, false, info); 451 return; 452 } else item.outerHTML = `<h3 class="media-api-widget-err-msg">No ${media_type} item found in playlist based upon search parameters provided.</h3>` 453 } 454 }); 455 } 456 457 // Generate Lightbox HTML Root Tag In Body 458 459 const media_lightbox_div = document.createElement("div"); 460 media_lightbox_div.dataset.lightboxmediacontenttype = media_type === "youtube" || media_type === "vimeo" ? "video" : "audio"; 461 media_lightbox_div.dataset.lightboxid = `${media_name}_${media_type}`; 462 document.body.appendChild(media_lightbox_div); 463 const lightbox = document.querySelector(`[data-lightboxid = "${media_name}"]`); 464 465 // Video Lightbox HTML 466 467 function media_video_lightbox_html() { 468 return ` 469 <div class="lightbox-content-container" style="font-family: ${lightboxFont};"> 470 <fieldset style="border: 8px ${showThemeColor} solid;" class="lightbox-playlist-container" data-lightboxplaylistcontainer="true"> 471 <legend> 472 ${showLogoImgUrl ? `<img class="playlist-logo" src="${showLogoImgUrl}">` : `<h1 class="playlist-heading">Videos</h1>`} 473 </legend> 474 <div class="grid-layout" data-lightboxplaylistcontent="true"></div> 475 </fieldset> 476 <div class="lightbox-player-container" data-lightboxplayer="true"> 477 <svg version="1.0" xmlns="http://www.w3.org/2000/svg" class="arrow-left" data-hideonidle="true" data-lightboxarrowdirection="left" data-lightboxarrow="true" 478 width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000" 479 preserveAspectRatio="xMidYMid meet"> 480 <g data-hideonidle="true" transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" 481 fill="#ffffff" stroke="none"> 482 <path data-hideonidle="true" d="M415 720 l-220 -220 223 -222 222 -223 72 73 73 72 -148 148 -147 147 145 145 c80 80 145 149 145 155 0 0 -140 145 -140 145 0 0 -104 -99 -225 -220z"/> 483 </g> 484 </svg> 485 <div class="lightbox-box-frame"> 486 <div class="lightbox-frames" data-lightboxframes="true"> 487 <div data-frameposition="-1" class="frame-transition-left video-thumbnail-wrapper" data-lightboxframe="true"> 488 <iframe width="560" height="315" title="${media_type} video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen> 489 </iframe> 490 <div class="hover-text-container"> 491 <h1></h1> 492 </div> 493 </div> 494 <div data-frameposition="0" class="video-thumbnail-wrapper" data-lightboxframe="true"> 495 <iframe width="560" height="315" title="${media_type} video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen> 496 </iframe> 497 <div class="hover-text-container"> 498 <h1></h1> 499 </div> 500 </div> 501 <div data-frameposition="1" class="frame-transition-right video-thumbnail-wrapper" data-lightboxframe="true"> 502 <iframe width="560" height="315" title="${media_type} video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen> 503 </iframe> 504 <div class="hover-text-container"> 505 <h1></h1> 506 </div> 507 </div> 508 </div> 509 <div class="fast-forward-overlay element-invisible" data-fastforwardoverlay="true"> 510 <h1></h1> 511 </div> 512 </div> 513 <svg version="1.0" xmlns="http://www.w3.org/2000/svg" class="arrow-right" data-hideonidle="true" data-lightboxarrowdirection="right" data-lightboxarrow="true" 514 width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000" 515 preserveAspectRatio="xMidYMid meet"> 516 <g data-hideonidle="true" transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" 517 fill="#ffffff" stroke="none"> 518 <path data-hideonidle="true" d="M415 720 l-220 -220 223 -222 222 -223 72 73 73 72 -148 148 -147 147 145 145 c80 80 145 149 145 155 0 0 -140 145 -140 145 0 0 -104 -99 -225 -220z"/> 519 </g> 520 </svg> 521 </div> 522 <div class="lightbox-close-button" data-hideonidle="true" data-lightboxclosebutton="true">X</div> 523 <svg class="lightbox-playlist-button" data-lightboxplaylistbutton="true" data-hideonidle="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 101.66" style="enable-background:new 0 0 122.88 101.66" xml:space="preserve"> 524 <g> 525 <path xmlns="http://www.w3.org/2000/svg" class="st0" fill="#ffffff" d="M0,0h97.6v16.12H0V0L0,0z M122.88,77.46l-38-24.21v48.41L122.88,77.46L122.88,77.46z M0,61.46h73.62v16.12H0 V61.46L0,61.46z M0,30.77h97.6v16.12H0V30.77L0,30.77z"/> 526 </g> 527 </svg> 528 </div> 529 `; 530 } 531 532 // Initialize Show Playlist Variable 533 534 let showPlaylist = false;
535 536 // Activate Video Lightbox On Video Item Click 537 538 let media_lightbox_activated = false; 539 540 function video_lightbox_activate_handler(itemClicked, lightboxshowplaylist, lightboxStyling = null) { 541 542 // Checks That Type Is Either Youtube Or Vimeo. If Not, Return To Exit 543 544 if (media_type !== "youtube" && media_type !== "vimeo") { 545 return 546 } 547 548 const lightbox = media_lightbox_div; 549 lightbox.innerHTML = ""; 550 lightbox.innerHTML = media_video_lightbox_html(); 551 let startingLightboxActive = lightbox.querySelector(`[data-frameposition="0"]`); 552 let lightboxStartingLeft = lightbox.querySelector(`[data-frameposition="-1"]`); 553 let lightboxStartingRight = lightbox.querySelector(`[data-frameposition="1"]`); 554 const lightboxFramesContainer = lightbox.querySelector(`[data-lightboxframes="true"]`); 555 const lightboxFrames = lightbox.querySelectorAll(`[data-lightboxframe="true"]`); 556 const lightboxFastForwardOverlay = lightbox.querySelector(`[data-fastforwardoverlay="true"]`); 557 const lightboxFastForwardText = lightboxFastForwardOverlay.querySelector(`h1`); 558 const lightboxArrowLeft = lightbox.querySelector(`[data-lightboxarrowdirection="left"]`); 559 const lightboxArrowRight = lightbox.querySelector(`[data-lightboxarrowdirection="right"]`); 560 const lightboxCloseButton = lightbox.querySelector(`[data-lightboxclosebutton="true"]`); 561 const lightboxPlayerContainer = lightbox.querySelector(`[data-lightboxplayer="true"]`); 562 const lightboxPlaylistContainer = lightbox.querySelector(`[data-lightboxplaylistcontainer="true"]`); 563 const lightboxPlaylistContent = lightbox.querySelector(`[data-lightboxplaylistcontent="true"]`); 564 const lightboxPlaylistButton = lightbox.querySelector(`[data-lightboxplaylistbutton="true"]`); 565 566 if (lightboxStyling) { 567 if (lightboxStyling.image) { 568 lightbox.querySelector(`.lightbox-content-container fieldset legend`).innerHTML = `<img class="playlist-logo" src="${lightboxStyling.image}">`; 569 } 570 if (lightboxStyling.themeColor) { 571 lightbox.querySelector(`.lightbox-content-container fieldset`).style.border = `min(8px, 1.5vw) ${lightboxStyling.themeColor} solid`; 572 } 573 if (lightboxStyling.font) { 574 lightbox.querySelector(`.lightbox-content-container`).style.fontFamily = `"${lightboxStyling.font}"`; 575 } 576 } 577 578 // Playlist Button 579 580 const playlistButton = lightboxshowplaylist ? media_item_play_button_icon() : null; 581 582 // `${media_type}` Playlist Selected 583 584 let playListSorted = lightboxshowplaylist ? media_data : media_data.filter(item => item.id === itemClicked.dataset.id); 585 586 // Removes Non Episode Numbered Items From Playlist If Sort Mode Is Set To "number_in_title" 587 588 if (media_type === "youtube" && lightboxshowplaylist && !playListSorted.every(item => item.episode === -1)) { 589 playListSorted = playListSorted.filter(item => item.episode && item.episode !== -1); 590 } 591 592 // Elements To Hide When User Idle In Lightbox After 5 Seconds 593 594 const elementsToHideWhenIdle = lightbox.querySelectorAll(`[data-hideonidle="true"]`); 595 596 let mouseOverElement = false; 597 let lastMouseOverTime = new Date().getTime(); 598 const idleDelayTime = 5000; 599 600 // Checks If Lightbox Arrow Was Clicked 601 602 let lightboxArrowClicked = false;
603 604 // Lightbox Base Url Origin 605 606 const lightboxFrameVideoBaseUrl = `https://www.youtube.com/embed/`; 607 608 // Sets Media Query Break Point Of When Video Containers Should Stack In One Column. Calculation Based Upon minimumWidthOfEachVideo Number. Default Value Is: minimumWidthOfEachGridVideoItem * 2 609 610 const mediaQueryMobileBreakpoint = (400 * 2) + (48 * 1.5); 611 612 // Select HTML tag to Hide Scroll Bar 613 614 const html = document.querySelector(`html`); 615 616 // Set Thumbnail Episode Number Or Title Text 617 618 function setThumbnailText(item) { 619 return media_title_text(item); 620 } 621 622 // Monitors Mouse Movements And Clicks In Lightbox 623 624 function lightboxMouseMoveHandler(e, elementsToHideWhenIdle) { 625 mouseOverElement = false; 626 lastMouseOverTime = new Date().getTime(); 627 if (e && e.path) { 628 e.path.forEach(p => { 629 if (p.dataset && p.dataset.hideonidle) { 630 mouseOverElement = true; 631 } 632 }) 633 } 634 if (elementsToHideWhenIdle && elementsToHideWhenIdle[0] !== null && !mouseOverElement) { 635 elementsToHideWhenIdle.forEach(element => { 636 if (!element.dataset.lightboxplaylistbutton) { 637 element.classList.remove(`element-invisible`); 638 } 639 if (element.dataset.lightboxplaylistbutton && showPlaylist && !element.classList.toString().includes(`element-invisible`)) { 640 element.classList.add(`element-invisible`); 641 } 642 if (!showPlaylist && element.dataset.lightboxplaylistbutton && element.classList.toString().includes(`element-invisible`)) { 643 element.classList.remove(`element-invisible`); 644 } 645 }); 646 setTimeout(() => { 647 if (!mouseOverElement && (new Date().getTime() - lastMouseOverTime) >= idleDelayTime && !showPlaylist) { 648 elementsToHideWhenIdle.forEach(element => element.classList.add(`element-invisible`)); 649 } 650 }, idleDelayTime) 651 } 652 if (!playlistButton) { 653 lightboxPlaylistButton.classList.add(`element-invisible`); 654 } 655 } 656 657 // Sets Data 658 659 function setData(index) { 660 return playListSorted[currentVideoIndex + index] 661 } 662 663 // Sets Video Id 664 665 function setVideoId(index) { 666 return playListSorted[currentVideoIndex + index].id 667 } 668 669 // Calls Upon lightboxMouseMoveHandler Function 670 671 lightboxMouseMoveHandler(); 672 673 lightbox.addEventListener(`mousemove`, e => lightboxMouseMoveHandler(e, elementsToHideWhenIdle)); 674 lightbox.addEventListener(`click`, e => lightboxMouseMoveHandler(e, elementsToHideWhenIdle)); 675 676 // Video Index And Base Url For `${media_type}` Embed Iframe. Will Be Set To 0 If Returned Value Is -1 677 678 let currentVideoIndex; 679 680 if (itemClicked) { 681 currentVideoIndex = playListSorted.findIndex(video => video.id === itemClicked.dataset.id); 682 } 683 684 if (!currentVideoIndex || currentVideoIndex === -1) { 685 currentVideoIndex = 0; 686 } 687 688 // Check For Starting Position To See If Arrows Should Be Shown Based Upon Starting Video Index Position And Set Corresponding Iframe Urls 689 690 function initLightboxFrames() { 691 startingLightboxActive = lightbox.querySelector(`[data-frameposition="0"]`); 692 lightboxStartingLeft = lightbox.querySelector(`[data-frameposition="-1"]`); 693 lightboxStartingRight = lightbox.querySelector(`[data-frameposition="1"]`); 694
695 lightboxFrames.forEach(frame => { 696 frame.classList.remove(`frame-transitioning-left`); 697 frame.classList.remove(`frame-transitioning-right`); 698 }); 699 700 if (!playListSorted[currentVideoIndex + 1]) { 701 lightboxArrowRight.classList.add(`disable-arrow`); 702 } else { 703 lightboxArrowRight.classList.remove(`disable-arrow`); 704 lightboxStartingRight.classList.add(`frame-transitioning-right`); 705 // Preload adjacent carousel videos without playing them. 706 lightboxStartingRight.querySelector(`iframe`).src = lightboxFrameVideoBaseUrl + setVideoId(1); 707 lightboxStartingRight.querySelector(`h1`).textContent = setThumbnailText(setData(1)); 708 } 709 if (!playListSorted[currentVideoIndex - 1]) { 710 lightboxArrowLeft.classList.add(`disable-arrow`); 711 } else { 712 lightboxArrowLeft.classList.remove(`disable-arrow`); 713 lightboxStartingRight.classList.add(`frame-transitioning-left`); 714 lightboxStartingLeft.querySelector(`iframe`).src = lightboxFrameVideoBaseUrl + setVideoId(-1); 715 lightboxStartingLeft.querySelector(`h1`).textContent = setThumbnailText(setData(-1)); 716 } 717 718 // Check If Video Index Has Video And Check For Browser Window Width And Set For Lightbox Or Full Screen Iframe On Mobile, Tablet 719 720 if (playListSorted[currentVideoIndex]) { 721 if (window.innerWidth > mediaQueryMobileBreakpoint || showPlaylist) { 722 startingLightboxActive.querySelector(`iframe`).src = lightboxFrameVideoBaseUrl + setVideoId(0) + `?autoplay=1`; 723 startingLightboxActive.querySelector(`h1`).textContent = setThumbnailText(setData(0)); 724 } else window.open(lightboxFrameVideoBaseUrl + setVideoId(0) + `?autoplay=1`); 725 } 726 if (window.innerWidth > mediaQueryMobileBreakpoint || showPlaylist) { 727 lightbox.classList.add(`show-lightbox`); 728 html.classList.add(`hide-scroll`); 729 } else { 730 lightbox_clear(); 731 } 732 } 733 734 initLightboxFrames(); 735 736 // Render Grid Items In Playlist Container 737 738 // Grid Layout Handling 739 740 function gridItemsProcessing(outputList, playlist) { 741 742 const sortItemsBy = "number-descending"; 743 744 // Removes Non Episode Numbered Items From Playlist 745 746 if (media_type === "youtube" && !outputList.every(item => item.episode === - 1)) { 747 outputList = outputList.filter(item => item.episode && item.episode !== -1); 748 } 749 750 const info = { 751 name: media_name, 752 type: media_type 753 }; 754 755 return outputList.map(item => { 756 if (!item.id) { 757 console.error(`Playlist Item Failed To Load Due To Insufficient Data`); 758 failedItemTally++ 759 } else if (!item.title) { 760 console.error(`Video Playlist Item ${item.id} Did Not Have A Title And Could Not Be Loaded.`) 761 } else if (item.title.toLowerCase() === `deleted video` || item.title.toLowerCase() === `private video`) { 762 console.error(`Video Playlist Item ${item.id} Could Not Be Loaded As Its Status Is: ${item.title}`); 763 }
763 else if (sortItemsBy === `number-ascending` || sortItemsBy === `number-descending` && item.episode.toString() === `NaN`) { 764 console.error(`Video Playlist Item ${item.id} Entitled "${item.title}" Could Not Be Loaded As Number(s) Were Detected In Its Title Name But Could Not Generate An Episode Number. This Can Occur If The Video Title Has Two Or More Numbers In It. It Must Have Only One Number In Its Title Name That Pertains To Its Episode Number When Sorting Items In The "${sortItemsBy}" Mode. If This Is The Case, Please Change The Title Name Accordingly.`) 765 } else return render_media_item(item, null, playlist, info); 766 }).join(``) 767 } 768 769 lightboxPlaylistContent.innerHTML = gridItemsProcessing(playListSorted, true); 770 771 // Event Listeners On Playlist Items Clicked 772 773 lightboxPlaylistContent.addEventListener(`click`, e => { 774 const clickedItem = e.target.closest(`[data-itemclickableplaylist="${media_name}_${media_type}"]`); 775 if (clickedItem) { 776 currentVideoIndex = playListSorted.findIndex(video => video.id === clickedItem.dataset.id); 777 toggleLightboxPlayerOrPlaylist(); 778 initLightboxFrames(); 779 } 780 }); 781 782 // Records When Last Time Carousel Was Advanced To Avoid User Click/Auto Interval Conflict 783 784 let lastTimeAdvanced = new Date().getTime(); 785 786 // Event Listener On Exit Button Click To Exit LightBox 787 788 lightboxCloseButton.addEventListener(`click`, lightbox_clear); 789 790 // Set lightboxArrowClicked To True For 500 Milliseconds Then Changed Back To False 791 792 function setLightboxArrowToggled(fastForwarded) { 793 lightboxArrowClicked = true; 794 setTimeout(() => { 795 lightboxArrowClicked = false; 796 }, 250) 797 } 798 799 // Mouse Hold Speed Through Carousel Frames Event Handling. Variables For Checking For Mouse Down And Running Repeat Interval 800 801 let mouseDownInterval; 802 let mouseDown = false; 803 804 // Lightbox Fast Forward Speed 805 806 const video_lightbox_fast_forward_speed = 150; 807 808 // Fast Forward Handling 809 810 function toggleFastForward(enabled, item) { 811 812 if (enabled) { 813 lightboxFastForwardOverlay.classList.remove(`element-invisible`); 814 lightboxFramesContainer.classList.add(`fast-forward-transitioning`); 815 lightboxFastForwardText.textContent = setThumbnailText(item); 816 } else { 817 lightboxFastForwardText.textContent = ``; 818 lightboxFastForwardOverlay.classList.add(`element-invisible`); 819 lightboxFramesContainer.classList.remove(`fast-forward-transitioning`); 820 } 821 } 822 823 // Left Arrow Click/Mousedown And Auto Transitioning 824 825 function advanceLightboxLeft(fastForward) { 826 // Unload the outgoing player before the preloaded left 827 // frame becomes the sole autoplaying frame. 828 const activeIframe = lightbox.querySelector(`[data-frameposition="0"] iframe`); 829 if (activeIframe) { 830 activeIframe.src = ``; 831 } 832 currentVideoIndex -= 1; 833 834 if (playListSorted[currentVideoIndex - 1]) { 835 lightboxArrowLeft.classList.remove(`disable-arrow`); 836 } else { 837 clearFastForwarding(); 838 lightboxArrowLeft.classList.add(`disable-arrow`); 839 } 840 841 if (playListSorted[currentVideoIndex + 1]) { 842 lightboxArrowRight.classList.remove(`disable-arrow`) 843 } else lightboxArrowRight.classList.add(`disable-arrow`) 844 845 if (fastForward && mouseDown && playListSorted[currentVideoIndex]) { 846 lightboxArrowFastForwarded = true; 847 toggleFastForward(true, playListSorted[currentVideoIndex]); 848 } else { 849 toggleFastForward(); 850 } 851 852 setLightboxArrowToggled(); 853
854 lightboxFrames.forEach(frame => frameTransitionHandler(frame, `left`, `lightbox`, currentVideoIndex)); 855 } 856 857 // Right Arrow Click/Mousedown And Auto Transitioning 858 859 function advanceLightboxRight(fastForward) { 860 // Hidden iframes keep playing audio, so stop the outgoing 861 // frame before promoting the next one. 862 const activeIframe = lightbox.querySelector(`[data-frameposition="0"] iframe`); 863 if (activeIframe) { 864 activeIframe.src = ``; 865 } 866 currentVideoIndex += 1; 867 868 if (playListSorted[currentVideoIndex + 1]) { 869 lightboxArrowRight.classList.remove(`disable-arrow`); 870 } else { 871 clearFastForwarding(); 872 lightboxArrowRight.classList.add(`disable-arrow`); 873 } 874 875 if (playListSorted[currentVideoIndex - 1]) { 876 lightboxArrowLeft.classList.remove(`disable-arrow`); 877 } else lightboxArrowLeft.classList.add(`disable-arrow`); 878 879 if (fastForward && mouseDown && playListSorted[currentVideoIndex]) { 880 lightboxArrowFastForwarded = true; 881 toggleFastForward(true, playListSorted[currentVideoIndex]); 882 } else { 883 toggleFastForward(); 884 } 885 886 setLightboxArrowToggled(); 887 888 lightboxFrames.forEach(frame => frameTransitionHandler(frame, `right`, `lightbox`, currentVideoIndex)); 889 } 890 891 // Left Arrow Click Event Handler 892 893 lightboxArrowLeft.addEventListener(`click`, () => { 894 if (!mouseDown) { 895 lastTimeAdvanced = new Date().getTime(); 896 advanceLightboxLeft() 897 } else { 898 clearInterval(mouseDownInterval); 899 mouseDown = false; 900 } 901 }); 902 903 // Right Arrow Click Event Handler 904 905 lightboxArrowRight.addEventListener(`click`, () => { 906 if (!mouseDown) { 907 lastTimeAdvanced = new Date().getTime(); 908 advanceLightboxRight() 909 } else { 910 clearInterval(mouseDownInterval); 911 mouseDown = false; 912 } 913 }); 914 915 // Left Arrow Mouse Down Event Handler 916 917 lightboxArrowLeft.addEventListener(`mousedown`, () => { 918 mouseDown = true; 919 setTimeout(() => { 920 if (mouseDown && playListSorted[currentVideoIndex - 1]) { 921 mouseDownInterval = setInterval(() => { 922 if (mouseDown) { 923 advanceLightboxLeft(true), video_lightbox_fast_forward_speed; 924 } 925 }, video_lightbox_fast_forward_speed); 926 lightboxArrowLeft.classList.add(`lightbox-arrow-hold-transitioning`); 927 } 928 }, 500) 929 }); 930 931 // Right Arrow Mouse Down Event Handler 932 933 lightboxArrowRight.addEventListener(`mousedown`, () => { 934 mouseDown = true; 935 setTimeout(() => { 936 if (mouseDown && playListSorted[currentVideoIndex + 1]) { 937 mouseDownInterval = setInterval(() => { 938 if (mouseDown) { 939 advanceLightboxRight(true), video_lightbox_fast_forward_speed; 940 } 941 }, video_lightbox_fast_forward_speed); 942 lightboxArrowRight.classList.add(`lightbox-arrow-hold-transitioning`); 943 } 944 }, 500) 945 }); 946 947 // Arrow Mouse Up Event Handling 948 949 function clearFastForwarding() { 950 mouseDown = false;
951 clearInterval(mouseDownInterval); 952 mouseDownInterval = ``; 953 lightboxArrowLeft.classList.remove(`lightbox-arrow-hold-transitioning`); 954 lightboxArrowRight.classList.remove(`lightbox-arrow-hold-transitioning`); 955 toggleFastForward(); 956 setTimeout(() => { 957 mouseDown = false; 958 clearInterval(mouseDownInterval); 959 lightboxArrowLeft.classList.remove(`lightbox-arrow-hold-transitioning`); 960 lightboxArrowRight.classList.remove(`lightbox-arrow-hold-transitioning`); 961 clearInterval(mouseDownInterval); 962 }, 500); 963 } 964 965 window.addEventListener(`mouseup`, clearFastForwarding); 966 967 // Playlist Button Event Listener 968 969 function toggleLightboxPlayerOrPlaylist(initialized) { 970 if (!initialized) { 971 showPlaylist = !showPlaylist; 972 } 973 if (showPlaylist) { 974 lightboxFrames.forEach(frame => { 975 frame.querySelector(`iframe`).src = ``; 976 frame.querySelector(`h1`).textContent = ``; 977 }); 978 lightboxPlaylistButton.classList.add(`element-invisible`); 979 lightboxPlayerContainer.classList.add(`lightbox-container-off`); 980 lightboxPlaylistContainer.classList.remove(`lightbox-container-off`); 981 } else { 982 if (playlistButton) { 983 lightboxPlaylistButton.classList.remove(`element-invisible`); 984 } else lightboxPlaylistButton.classList.add(`element-invisible`); 985 lightboxPlayerContainer.classList.remove(`lightbox-container-off`); 986 lightboxPlaylistContainer.classList.add(`lightbox-container-off`); 987 } 988 } 989 990 toggleLightboxPlayerOrPlaylist(true); 991 992 lightboxPlaylistButton.addEventListener(`click`, () => toggleLightboxPlayerOrPlaylist()); 993 } 994 995 // Lightbox and Carousel Arrow Click Event Handlers 996 997 function frameTransitionHandler(frame, direction, type, currentVideoIndex, transitionType, fastForward) { 998 999 // Video Playlist Selected 1000 1001 const playListSorted = media_data; 1002 1003 // Lightbox Base Url Origin 1004 1005 const lightboxFrameVideoBaseUrl = "https://www.youtube.com/embed/"; 1006 1007 // Frame Transition Time 1008 1009 const frameTransition = 500; 1010 1011 // Lightbox Fast Forward Speed 1012 1013 const video_lightbox_fast_forward_speed = 150; 1014
1015 // Set Thumbnail Episode Number Or Title Text 1016 1017 function setThumbnailText(item) { 1018 return media_title_text(item); 1019 } 1020 1021 const currentFrameData = playListSorted[currentVideoIndex]; 1022 const nextFrameData = playListSorted[currentVideoIndex + 1]; 1023 const previousFrameData = playListSorted[currentVideoIndex - 1]; 1024 1025 const iframe = frame.querySelector(`iframe`); 1026 const iframeText = frame.querySelector(`h1`); 1027 // Arrow navigation loads the newly active video paused. Only 1028 // the item that originally opened the lightbox autoplays. 1029 const currentIframeUrl = currentFrameData ? lightboxFrameVideoBaseUrl + currentFrameData.id : null; 1030 const nextIframeUrl = nextFrameData ? lightboxFrameVideoBaseUrl + nextFrameData.id : null; 1031 const previousIframeUrl = previousFrameData ? lightboxFrameVideoBaseUrl + previousFrameData.id : null; 1032 1033 const transitionSpeed = 500; 1034 1035 if (type === `carousel` && transitionType !== `carouselAutomated`) { 1036 frame.classList.add(`carousel-fast-transition`); 1037 setTimeout(() => frame.classList.remove(`carousel-fast-transition`) ,transitionSpeed) 1038 } 1039 1040 if (fastForward) { 1041 frame.classList.remove(`frame-transitioning-show-text`); 1042 } 1043 1044 switch(frame.dataset.frameposition) { 1045 case `-1`: 1046 if (direction === `left`) { 1047 if (type === `lightbox` && currentFrameData && 1048 iframe.src !== currentIframeUrl ) { 1049 iframe.src = currentIframeUrl 1050 iframeText.textContent = setThumbnailText(currentFrameData); 1051 } 1052 if (type === `carousel` && currentFrameData) { 1053 setCarouselThumbnail(frame, currentFrameData) 1054 } 1055 setTimeout(() => { 1056 frame.classList.remove(`frame-transitioning-show-text`); 1057 }, transitionSpeed) 1058 frame.classList.remove(`frame-transition-left`); 1059 frame.dataset.frameposition = 0; 1060 } 1061 if (direction === `right`) { 1062 frame.classList.remove(`frame-transition-left`); 1063 frame.classList.add(`frame-transition-right`); 1064 frame.dataset.frameposition = 1; 1065 setTimeout(() => { 1066 if (type === `lightbox` && nextFrameData) { 1067 iframe.src = nextIframeUrl; 1068 iframeText.textContent = setThumbnailText(nextFrameData); 1069 } 1070 if (type === `carousel` && nextFrameData) { 1071 setCarouselThumbnail(frame, nextFrameData) 1072 } 1073 if (nextFrameData && !fastForward) { 1074 frame.classList.add(`frame-transitioning-show-text`); 1075 } 1076 }, transitionSpeed) 1077 } 1078 break; 1079 case `0`: 1080 if (direction === `left`) { 1081 frame.classList.add(`frame-transition-right`); 1082 frame.dataset.frameposition = 1; 1083 setTimeout(() => { 1084 if (type === `lightbox` && nextFrameData) { 1085 iframe.src = nextIframeUrl; 1086 iframeText.textContent = setThumbnailText(nextFrameData); 1087 } 1088 if (type === `carousel` && nextFrameData) {
1089 setCarouselThumbnail(frame, nextFrameData) 1090 } 1091 if (nextFrameData && !fastForward) { 1092 frame.classList.add(`frame-transitioning-show-text`); 1093 } 1094 }, transitionSpeed) 1095 } 1096 if (direction === `right`) { 1097 frame.classList.add(`frame-transition-left`); 1098 frame.dataset.frameposition = -1; 1099 setTimeout(() => { 1100 if (type === `lightbox` && previousFrameData) { 1101 iframe.src = previousIframeUrl; 1102 iframeText.textContent = setThumbnailText(previousFrameData); 1103 } 1104 if (type === `carousel` && previousFrameData) { 1105 setCarouselThumbnail(frame, previousFrameData) 1106 } 1107 if (previousFrameData && !fastForward) { 1108 frame.classList.add(`frame-transitioning-show-text`); 1109 } 1110 }, transitionSpeed) 1111 } 1112 break; 1113 case `1`: 1114 if (direction === `left`) { 1115 frame.classList.remove(`frame-transition-right`); 1116 frame.classList.add(`frame-transition-left`); 1117 frame.dataset.frameposition = -1; 1118 setTimeout(() => { 1119 if (type === `lightbox` && previousFrameData) { 1120 iframe.src = previousIframeUrl; 1121 iframeText.textContent = setThumbnailText(previousFrameData); 1122 } 1123 if (type === `carousel` && previousFrameData) { 1124 setCarouselThumbnail(frame, previousFrameData) 1125 } 1126 if (previousFrameData && !fastForward) { 1127 frame.classList.add(`frame-transitioning-show-text`); 1128 } 1129 }, transitionSpeed) 1130 } 1131 if (direction === `right`) { 1132 if (type === `lightbox` && currentFrameData && 1133 iframe.src !== currentIframeUrl) { 1134 iframe.src = currentIframeUrl; 1135 iframeText.innerText = setThumbnailText(currentFrameData); 1136 } 1137 if (type === `carousel` && currentFrameData) { 1138 setCarouselThumbnail(frame, currentFrameData) 1139 } 1140 if (currentFrameData && !fastForward) { 1141 frame.classList.add(`frame-transitioning-show-text`); 1142 setTimeout(() => { 1143 frame.classList.remove(`frame-transitioning-show-text`); 1144 }, transitionSpeed) 1145 } 1146 frame.classList.remove(`frame-transition-right`); 1147 frame.dataset.frameposition = 0; 1148 } 1149 break; 1150 } 1151 } 1152 1153 // Clear Lightbox 1154 1155 function lightbox_clear() { 1156 const lightbox = media_lightbox_div; 1157 lightbox.innerHTML = ``; 1158 lightboxToggled = false; 1159 showPlaylist = false; 1160 lightbox.classList.remove(`show-lightbox`); 1161 document.querySelector(`html`).classList.remove(`hide-scroll`); 1162 } 1163 1164 // Monitor Click Of Video Items Rendered 1165 1166 window.addEventListener("click", e => { 1167 const target = e.target instanceof Element ? e.target : null; 1168 const node = target ? target.closest('a[data-itemclickable="true"]') : null; 1169 if (!node || node.dataset.itemclickablemediatype !== "video") { 1170 return; 1171 } 1172 if (node.closest('[data-lightboxplaylistcontent="true"]')) { 1173 return; 1174 } 1175 if (node.dataset.itemclickableplaylist !== `${media_name}_${media_type}`) { 1176 return; 1177 } 1178 1179 const videoEmbedBaseUrl = media_type === "vimeo" ? "https://player.vimeo.com/video/" : "https://www.youtube.com/embed/"; 1180 const embedUrl = `${videoEmbedBaseUrl}${node.dataset.id}`; 1181 1182 document.dispatchEvent(new CustomEvent("mediaApiWidgetItemClick", { 1183 bubbles: true, 1184 detail: { 1185 playlistName: media_name, 1186 mediaType: media_type, 1187 embedUrl, 1188 itemId: node.dataset.id, 1189 showLightbox: node.dataset.showlightbox !== "fal
1189se", 1190 element: node 1191 } 1192 })); 1193 1194 if (node.dataset.showlightbox === "false") { 1195 return; 1196 } 1197 1198 const lightboxImage = node.dataset.lightboxshowlogoimgurl ? node.dataset.lightboxshowlogoimgurl : null; 1199 const lightboxThemeColor = node.dataset.lightboxthemecolor ? node.dataset.lightboxthemecolor : null; 1200 const lightboxFont = node.dataset.lightboxfont ? node.dataset.lightboxfont : null; 1201 const lightBoxStyling = { 1202 image : lightboxImage, 1203 themeColor : lightboxThemeColor, 1204 font : lightboxFont 1205 }; 1206 1207 if (node.dataset.lightboxshowplaylist && node.dataset.lightboxshowplaylist === "true") { 1208 video_lightbox_activate_handler(node, true, lightBoxStyling); 1209 } else video_lightbox_activate_handler(node, false); 1210 }); 1211 // Clear Lightbox If Window Is Resized Below 872 1212 1213 window.addEventListener(`resize`, () => { 1214 if (!showPlaylist && window.innerWidth < 872) { 1215 lightbox_clear(); 1216 } 1217 }); 1218 1219 // Builds the /podcast/player URL for a custom RSS feed, URL-encoding 1220 // every query value so feed/shortcode-derived data cannot break out 1221 // of the query string (or, downstream, an attribute). 1222 1223 function maw_build_custom_player_url(rssUrl, itemClicked) { 1224 const ds = itemClicked.dataset; 1225 const params = new URLSearchParams(); 1226 params.set("url", rssUrl); 1227 params.set("track", ds.trackselect || ""); 1228 params.set("mode", ds.podcastplayermode || ""); 1229 params.set("buttoncolor", ds.podcastplayerbuttoncolor || ""); 1230 params.set("color1", ds.podcastplayercolor || ""); 1231 params.set("progressbarcolor", ds.podcastprogressplayerbarcolor || ""); 1232 params.set("highlightcolor", ds.podcastplayerhighlightcolor || ""); 1233 params.set("font", ds.podcastplayerfont || ""); 1234 params.set("scrollcolor", ds.podcastplayerscrollcolor || ""); 1235 params.set("textcolor", ds.podcastplayertextcolor || ""); 1236 params.set("autoplay", "1"); 1237 if (ds.showepisodedateaftertitle === "true") { 1238 params.set("adddatetotitle", "true"); 1239 } 1240 return `${window.location.origin}/podcast/player?${params.toString()}`; 1241 } 1242 1243 // Computes the podcast embed URL for a clicked audio item (shared by event dispatch and lightbox handler) 1244 1245 function compute_audio_embed_url(itemClicked) { 1246 if (podcast_platform === "omny") { 1247 const { channel } = media_data; 1248 const showName = channel.item[0].link.split("/")[4]; 1249 return `https://omny.fm/shows/${showName}/playlists/podcast/embed?selectedClip=${itemClicked.dataset.id}&autoplay=1`; 1250 } 1251 if (podcast_platform === "soundcloud") { 1252 const showEpisodeUrl = media_data.channel.item[0].link; 1253 const spliced = showEpisodeUrl.split("/"); 1254 const urlOutput = `${spliced[0]}/${spliced[1]}/${spliced[2]}/${spliced[3]}`; 1255 return `https://w.soundcloud.com/player/?url=${urlOutput}&start_track=${itemClicked.dataset.trackselect}&auto_play=true`; 1256 } 1257 if (podcast_platform === "buzzsprout") { 1258 const { channel } = media_data; 1259 const playlistId = channel.rssUrl.split("/")[3].split(".")[0]; 1260 const episodeId = itemClicked.dataset.id.split("-")[1]; 1261 return `https://www.buzzsprout.com/${playlistId}/${episodeId}`; 1262 } 1263 if (podcast_platform === "embed") { 1264 return typeof media_data === "string" ? media_data : null; 1265 } 1266 if (podcast_platform === "custom") { 1267 const rssUrl = media_data.channel?.rssUrl; 1268 if (!rssUrl) return null; 1269 return maw_build_custom_player_url(rssUrl, itemClicked); 1270 } 1271 return null; 1272 } 1273 1274 // Podcast Audio Lightbox Activate Handler 1275 1276 function audio_lightbox_activate_handler(itemClicked) { 1277 1278 // Set Ifram Url Based Upon Podcast Player Type 1279 1280 let embedPlayerUrl; 1281 1282 if (podcast_platform === "omny") { 1283 1284 // Get Podcast Data, All Episodes, And Episode Clicked On 1285 1286 const { channel } = media_data; 1287 1288 const podcastInfo = { 1289 copyright: channel.copyright, 1290 title: channel.title, 1291 description: channel.description, 1292 playlistId: channel.playlistId, 1293 collectionViewUrl: channel.collectionViewUrl 1294 }; 1295 1296 const showName = channel.item[0].link.split("/")[4]; 1297 1298 embedPlayerUrl = `https://omny.fm/shows/${showName}/playlists/podcast/embed?selectedClip=${itemClicked.dataset.id}&autoplay=1`; 1299 } 1300 1301 if (podcast_platform === "soundcloud") { 1302 const showEpisodeUrl = media_data.channel.item[0].link; 1303 const spliced = showEpisodeUrl.split("/"); 1304 const urlOutput = `${spliced[0]}/${spliced[1]}/${spliced[2]}/${spliced[3]}`; 1305 embedPlayerUrl = `https://w.soundcloud.com/player/?url=${urlOutput}&start_track=${itemClicked.dataset.trackselect}&auto_play=true`; 1306 } 1307 1308 if (podcast_platform === "buzzsprout") { 1309 const { channel } = media_data; 1310 const playlistId = channel.rssUrl.split("/")[3].split(".")[0]; 1311 const episodeId = itemClicked.dataset.id.split("-")[1]; 1312 embedPlayerUrl = `https://www.buzzsprout.com/${playlistId}/${episodeId}`; 1313 } 1314 1315 if (podcast_platform === "embed") { 1316 embedPlayerUrl = media_data; 1317 } 1318 1319 if (podcast_platform === "custom") { 1320 const rssUrl = media_data.channel?.rssUrl; 1321 if (!rssUrl) return; 1322 embedPlayerUrl = maw_build_custom_player_url(rssUrl, itemClicked); 1323 } 1324
1325 // If Window Resolution Is Less Than 872px, Open New Window In Apple Podcasts 1326 1327 if (window.innerWidth < 872) { 1328 window.open(embedPlayerUrl); 1329 return 1330 } 1331 1332 // Init Lightbox 1333 1334 const lightbox = media_lightbox_div; 1335 1336 lightboxToggled = true; 1337 showPlaylist = true; 1338 lightbox.classList.add(`show-lightbox`); 1339 document.querySelector(`html`).classList.add(`hide-scroll`); 1340 1341 // Build the lightbox via DOM nodes so the embed URL is assigned as 1342 // an iframe.src property and is never parsed as HTML â even if a 1343 // feed or shortcode attribute contains markup. 1344 1345 lightbox.replaceChildren(); 1346 1347 const contentContainer = document.createElement("div"); 1348 contentContainer.className = "lightbox-content-container"; 1349 1350 const boxFrame = document.createElement("div"); 1351 boxFrame.className = "lightbox-box-frame"; 1352 1353 const embedIframe = document.createElement("iframe"); 1354 embedIframe.className = "lightbox-podcast-embed-player"; 1355 embedIframe.setAttribute("allow", "autoplay"); 1356 embedIframe.src = embedPlayerUrl; 1357 1358 boxFrame.appendChild(embedIframe); 1359 contentContainer.appendChild(boxFrame); 1360 1361 const closeButtonEl = document.createElement("div"); 1362 closeButtonEl.className = "lightbox-close-button"; 1363 closeButtonEl.dataset.hideonidle = "true"; 1364 closeButtonEl.dataset.lightboxclosebutton = "true"; 1365 closeButtonEl.textContent = "X"; 1366 1367 lightbox.appendChild(contentContainer); 1368 lightbox.appendChild(closeButtonEl); 1369 1370 // Monitor Click Of Close Button 1371 1372 lightbox.querySelector(`[data-lightboxclosebutton="true"]`).addEventListener("click", () => { 1373 lightbox_clear(); 1374 }); 1375 1376 // Monitor Mouse Movement To Hide Close Button If Idle For More Than 5 Seconds 1377 1378 const closeButton = document.querySelector(`[data-lightboxclosebutton="true"]`); 1379 1380 let lastTimeMouseMoved; 1381 1382 function audioLightboxMouseMoveHandler(e) { 1383 lastTimeMouseMoved = new Date().getTime(); 1384 closeButton.classList.remove("element-invisible"); 1385 setTimeout(() => { 1386 if (new Date().getTime() - lastTimeMouseMoved >= 5000 && !e.target.dataset.hideonidle) { 1387 closeButton.classList.add("element-invisible"); 1388 } 1389 }, 5000) 1390 } 1391 1392 lightbox.addEventListener("mousemove", audioLightboxMouseMoveHandler); 1393 lightbox.addEventListener("click", audioLightboxMouseMoveHandler); 1394 } 1395 1396 // Monitor Click Of Podcast Audio Items Rendered 1397 1398 window.addEventListener("click", e => { 1399 const target = e.target instanceof Element ? e.target : null; 1400 const node = target ? target.closest('a[data-itemclickable="true"]') : null; 1401 if (!node) { 1402 return; 1403 } 1404 1405 if ( 1406 node.dataset.itemclickableplaylist === `${media_name}_${media_type}` && 1407 node.dataset.itemclickablemediatype === "audio" 1408 ) { 1409 document.dispatchEvent(new CustomEvent("mediaApiWidgetItemClick", { 1410 bubbles: true, 1411 detail: { 1412 playlistName: media_name, 1413 mediaType: media_type, 1414 embedUrl: compute_audio_embed_url(node), 1415 itemId: node.dataset.id, 1416 showLightbox: node.dataset.showlightbox !== "fal
1416se", 1417 element: node 1418 } 1419 })); 1420 1421 if (node.dataset.lightboxshowplaylist === "true" && node.dataset.showlightbox !== "false") { 1422 audio_lightbox_activate_handler(node); 1423 } 1424 } 1425 }); 1426 } 1427 1428// -- Grid Search & Pagination -- 1429 1430(function () { 1431 "use strict"; 1432 1433 function initMawGridSearch() { 1434 document.querySelectorAll(".maw-grid-search-wrapper").forEach(function (wrapper) { 1435 var gridId = wrapper.dataset.mawGridId; 1436 var playlistName = wrapper.dataset.mawPlaylist; 1437 var mediaType = wrapper.dataset.mawMediatype; 1438 var perPage = parseInt(wrapper.dataset.mawPerpage, 10) || 12; 1439 var gridKey = wrapper.dataset.mawGridKey; 1440 1441 if (!playlistName || !mediaType || !gridKey) { 1442 return; 1443 } 1444 1445 var searchBar = document.querySelector(".maw-grid-search-bar[data-maw-for=\"" + gridId + "\"]"); 1446 var gridItemsEl = wrapper.querySelector(".maw-grid-items"); 1447 var paginationEl = wrapper.querySelector(".maw-grid-pagination"); 1448 1449 var pageParam = wrapper.dataset.mawPageParam || ("maw_page_" + gridId); 1450 var baseUrl = window.location.origin + window.location.pathname; 1451 1452 function readPageFromUrl() { 1453 var p = parseInt(new URLSearchParams(window.location.search).get(pageParam), 10); 1454 return p && p > 0 ? p : 1; 1455 } 1456 1457 var currentPage = readPageFromUrl(); 1458 var currentSearch = ""; 1459 var currentSearchBy = "any"; 1460 var debounceTimer = null; 1461 1462 function syncUrl() { 1463 var params = new URLSearchParams(window.location.search); 1464 if (currentPage > 1) { 1465 params.set(pageParam, currentPage); 1466 } else { 1467 params.delete(pageParam); 1468 } 1469 var qs = params.toString(); 1470 window.history.pushState({ mawPage: currentPage }, "", baseUrl + (qs ? "?" + qs : "")); 1471 } 1472 1473 function doRequest(page, updateUrl) { 1474 currentPage = page || 1; 1475 wrapper.classList.add("loading"); 1476 1477 var formData = new FormData(); 1478 formData.append("action", "maw_grid_search"); 1479 formData.append("nonce", mawGridSearchData.nonce); 1480 formData.append("playlist_name", playlistName); 1481 formData.append("media_platform",mediaType); 1482 formData.append("grid_key", gridKey); 1483 formData.append("search_term", currentSearch); 1484 formData.append("search_by", currentSearchBy); 1485 formData.append("page", currentPage); 1486 formData.append("per_page", perPage); 1487 formData.append("base_url", baseUrl); 1488 1489 fetch(mawGridSearchData.ajaxUrl, { method: "POST", body: formData }) 1490 .then(function (r) { return r.json(); }) 1491 .then(function (data) { 1492 if (data.success) { 1493 if (gridItemsEl) gridItemsEl.innerHTML = data.data.html; 1494 if (paginationEl) paginationEl.innerHTML = data.data.pagination; 1495 bindPaginationButtons(); 1496 // Only plain pagination is reflected in the URL; search 1497 // stays AJAX-only so query variants aren't crawled. 1498 if (updateUrl && currentSearch === "") { 1499 syncUrl(); 1500 } 1501 } 1502 wrapper.classList.remove("loading"); 1503 }) 1504 .catch(function () { 1505 wrapper.classList.remove("loading"); 1506 }); 1507 } 1508 1509 function bindPaginationButtons() { 1510 if (!paginationEl) { return; } 1511 paginationEl.querySelectorAll("a.maw-page-btn, button.maw-page-btn").forEach(function (ctrl) { 1512 ctrl.addEventListener("click", function (e) { 1513 if (ctrl.tagName === "A") { e.preventDefault(); } 1514 if (ctrl.disabled || ctrl.getAttribute("aria-disabled") === "true") { return; } 1515 var page = parseInt(ctrl.dataset.page, 10); 1516 if (page && page !== currentPage) { 1517 doRequest(page, true); 1518 } 1519 }); 1520 }); 1521 } 1522 1523 bindPaginationButtons(); 1524 1525 window.addEventListener("popstate", function () { 1526 if (currentSearch !== "") { return; } 1527 var page = readPageFromUrl(); 1528 if (page !== currentPage) { 1529 doRequest(page, false); 1530 } 1531 }); 1532 1533 if (searchBar) { 1534 var input = searchBar.querySelector(".maw-search-input"); 1535 var select = searchBar.querySelector(".maw-search-by"); 1536 var clearButton = searchBar.querySelector(".maw-search-clear"); 1537 1538 if (input) { 1539 input.addEventListener("input", function () { 1540 clearTimeout(debounceTimer); 1541 debounceTimer = setTimeout(function () { 1542 currentSearch = input.value; 1543 currentSearchBy = select ? select.value : "any"; 1544 doRequest(1, false); 1545 }, 400); 1546 }); 1547 } 1548 1549 if (select) { 1550 select.addEventListener("change", function () { 1551 currentSearchBy = select.value; 1552 if (currentSearch !== "") { 1553 doRequest(1, false); 1554 } 1555 }); 1556 } 1557 1558 if (clearButton && input) { 1559 clearButton.addEventListener("click", function () { 1560 clearTimeout(debounceTimer); 1561 input.value = ""; 1562 currentSearch = ""; 1563 currentSearchBy = select ? select.value : "any"; 1564 doRequest(1, false); 1565 input.focus(); 1566 }); 1567 } 1568 } 1569 }); 1570 } 1571 1572 if (document.readyState === "loading") { 1573 document.addEventListener("DOMContentLoaded", initMawGridSearch); 1574 } else { 1575 initMawGridSearch(); 1576 } 1577})();
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.