1/*! 2 * fadeslideshow.js â lightweight fade slideshow wrapper for Phoca Gallery Slideshow Plugin 3 * 4 * Purpose: 5 * - Provides a compatible `fadeSlideShow({...})` constructor used by the Phoca Gallery Slideshow content plugin. 6 * - Replaces the legacy "Ultimate Fade In Slideshow" dependency which is no longer shipped with recent Phoca/Joomla setups. 7 * 8 * Features: 9 * - Fade transitions, autoplay 10 * - Hover pause (desktop) 11 * - Swipe (touch) + optional mouse drag swipe 12 * - Lazy loading / deferred start 13 * 14 * Compatibility: 15 * - Joomla 5/6, modern browsers 16 * 17 * Maintainer: Andy Marchand - Feuerwehr Worb (community patch) 18 * Version: 4.4.0-j6fix.1 (2026-01-06) 19 * License: GNU/GPL v2 or later 20 */ 21 (function () { 22 function toInt(v, fallback) { 23 var n = parseInt(v, 10); 24 return isNaN(n) ? fallback : n; 25 } 26 27 function pickImageUrl(entry) { 28 if (!entry) return null; 29 if (typeof entry === "string") return entry; 30 if (Array.isArray(entry)) return entry[0] || null; 31 return null; 32 } 33 34 function pickDesc(entry) { 35 if (Array.isArray(entry)) return entry[3] || ""; 36 return ""; 37 } 38 39 function absolutizeUrl(url) { 40 if (!url) return url; 41 42 // already absolute or special schemes 43 if (/^(https?:)?\/\//i.test(url) || /^data:/i.test(url) || /^blob:/i.test(url)) { 44 return url; 45 } 46 47 // root-relative 48 if (url.charAt(0) === "/") { 49 return window.location.origin + url; 50 } 51 52 // relative to current path 53 var base = window.location.href.split("#")[0].split("?")[0]; 54 base = base.substring(0, base.lastIndexOf("/") + 1); 55 return base + url; 56 } 57 58 function clampIndex(i, len) { 59 if (!len) return 0; 60 i = i % len; 61 if (i < 0) i += len; 62 return i; 63 } 64 65 // Public API expected by the plugin: new fadeSlideShow({ ... }) 66 window.fadeSlideShow = function (settings) { 67 settings = settings || {}; 68 var wrapperId = settings.wrapperid || settings.wrapperId; 69 var wrapper = wrapperId ? document.getElementById(wrapperId) : null; 70 if (!wrapper) return; 71 72 var images = Array.isArray(settings.imagearray) ? settings.imagearray.slice() : []; 73 images = images 74 .map(function (e) { 75 var u = pickImageUrl(e); 76 return { url: absolutizeUrl(u), desc: pickDesc(e) }; 77 }) 78 .filter(function (x) { 79 return !!x.url; 80 }); 81 82 if (!images.length) return; 83 84 var pause = toInt(settings.displaymode && settings.displaymode.pause, 3000); 85 var fadeDuration = toInt(settings.fadeduration, 600); 86 var randomize = !!(settings.displaymode && settings.displaymode.randomize); 87 88 if (randomize) { 89 images.sort(function () { 90 return Math.random() - 0.5; 91 }); 92 } 93 94 // Wrapper styling + UX 95 wrapper.style.position = "relative"; 96 wrapper.style.overflow = "hidden"; 97 if (!wrapper.style.width) wrapper.style.width = "100%"; 98 if (!wrapper.style.height) wrapper.style.height = "100%"; 99 100 // Cursor + selection/drag behaviour on desktop 101 wrapper.style.cursor = "grab"; 102 wrapper.style.userSelect = "none"; 103 wrapper.style.webkitUserSelect = "none"; 104 wrapper.style.msUserSelect = "none"; 105 wrapper.style.webkitUserDrag = "none"; 106 wrapper.style.touchAction = "pan-y"; // allow vertical scroll, we handle horizontal swipe 107 108 // Prevent native dragstart inside wrapper (image ghost-drag) 109 wrapper.addEventListener( 110 "dragstart", 111 function (e) { 112 e.preventDefault(); 113 }, 114 { passive: false } 115 ); 116 117 function makeImg() { 118 var img = document.createElement("img"); 119 img.style.position = "absolute"; 120 img.style.inset = "0"; 121 img.style.width = "100%"; 122 img.style.height = "100%"; 123 img.style.objectFit = "contain"; 124 img.style.opacity = "0"; 125 img.style.transition = "opacity " + fadeDuration + "ms ease"; 126 img.style.willChange = "opacity"; 127 img.alt = ""; 128 129 // prevent native dragging 130 img.draggable = false; 131 img.style.userSelect = "none"; 132 img.style.webkitUserDrag = "none"; 133 img.style.pointerEvents = "auto"; 134 return img; 135 } 136 137 var imgA = makeImg(); 138 var imgB = makeImg(); 139 wrapper.innerHTML = ""; 140 wrapper.appendChild(imgA); 141 wrapper.appendChild(imgB); 142 143 var idx = 0; 144 var front = imgA; 145 var back = imgB; 146 147 var isHoverPaused = false; 148 var isPointerDown = false; 149 var pointerId = null; 150 var startX = 0; 151 var startY = 0; 152 var lastX = 0; 153 var dragDetected = false; 154 155 var SWIPE_THRESHOLD = 35; // px 156 var VERTICAL_GUARD = 45; // px (ignore if mostly vertical) 157 158 function setCursor(state) { 159 wrapper.style.cursor = state === "down" ? "grabbing" : "grab"; 160 } 161 162 function show(i, first) { 163 var item = images[i]; 164 if (!item) return; 165 166 if (first) { 167 front.onload = function () { 168 front.style.opacity = "1"; 169 front.style.zIndex = "2"; 170 back.style.opacity = "0"; 171 back.style.zIndex = "1"; 172 }; 173 front.onerror = function () { 174 front.onload = front.onerror = null; 175 idx = (idx + 1) % images.length; 176 show(idx, true); 177 }; 178 front.src = item.url; 179 return; 180 } 181 182 back.onload = function () { 183 back.style.zIndex = "2"; 184 front.style.zIndex = "1"; 185 back.style.opacity = "1"; 186 front.style.opacity = "0"; 187 }; 188 189 back.onerror = function () { 190 back.onload = back.onerror = null; 191 idx = (idx + 1) % images.length; 192 193 var tmp = front; 194 front = back; 195 back = tmp; 196 197 show(idx, false); 198 }; 199 200 back.src = item.url; 201 } 202 203 function swapLayers() { 204 var tmp = front; 205 front = back; 206 back = tmp; 207 } 208 209 function next() { 210 idx = clampIndex(idx + 1, images.length); 211 swapLayers(); 212 show(idx, false); 213 } 214 215 function prev() { 216 idx = clampIndex(idx - 1, images.length); 217 swapLayers(); 218 show(idx, false); 219 } 220 221 // First render 222 show(idx, true); 223 224 // Timer 225 var timer = setInterval(function () { 226 if (!isHoverPaused && !isPointerDown) { 227 next(); 228 } 229 }, pause); 230 231 function restartTimer() { 232 clearInterval(timer); 233 timer = setInterval(function () { 234 if (!isHoverPaused && !isPointerDown) { 235 next(); 236 } 237 }, pause); 238 } 239 240 // Hover pause (desktop) 241 wrapper.addEventListener("mouseenter", function () { 242 isHoverPaused = true; 243 });
244 wrapper.addEventListener("mouseleave", function () { 245 isHoverPaused = false; 246 }); 247 248 // Pointer-based swipe (mouse + touch) 249 wrapper.addEventListener( 250 "pointerdown", 251 function (e) { 252 // only primary button for mouse 253 if (e.pointerType === "mouse" && e.button !== 0) return; 254 255 isPointerDown = true; 256 pointerId = e.pointerId; 257 startX = lastX = e.clientX; 258 startY = e.clientY; 259 dragDetected = false; 260 261 setCursor("down"); 262 263 try { 264 wrapper.setPointerCapture(pointerId); 265 } catch (_) {} 266 267 // prevent text selection / native interactions 268 e.preventDefault(); 269 }, 270 { passive: false } 271 ); 272 273 wrapper.addEventListener( 274 "pointermove", 275 function (e) { 276 if (!isPointerDown || e.pointerId !== pointerId) return; 277 278 lastX = e.clientX; 279 var dx = lastX - startX; 280 var dy = e.clientY - startY; 281 282 // If it becomes clearly vertical, let scroll happen (especially touch) 283 if (!dragDetected && Math.abs(dy) > VERTICAL_GUARD && Math.abs(dy) > Math.abs(dx)) { 284 // release drag mode 285 return; 286 } 287 288 if (Math.abs(dx) > 6) { 289 dragDetected = true; 290 } 291 292 // If dragging horizontally, stop browser from doing weird things 293 if (dragDetected) { 294 e.preventDefault(); 295 } 296 }, 297 { passive: false } 298 ); 299 300 function endPointer(e) { 301 if (!isPointerDown || e.pointerId !== pointerId) return; 302 303 isPointerDown = false; 304 pointerId = null; 305 306 setCursor("up"); 307 308 var dx = lastX - startX; 309 var dy = e.clientY - startY; 310 311 // Only treat as swipe if mostly horizontal and above threshold 312 if (Math.abs(dx) >= SWIPE_THRESHOLD && Math.abs(dx) > Math.abs(dy)) { 313 if (dx < 0) { 314 next(); 315 } else { 316 prev(); 317 } 318 restartTimer(); 319 } 320 } 321 322 wrapper.addEventListener("pointerup", endPointer, { passive: true }); 323 wrapper.addEventListener("pointercancel", endPointer, { passive: true }); 324 325 // public stop() like original library 326 this.stop = function () { 327 clearInterval(timer); 328 }; 329 }; 330})();
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.