1(function (window, document) { 2 "use strict"; 3 4 var runtimeConfig = window.FINSTAR_IMAGE_ACCELERATION || {}; 5 var hostname = (window.location && window.location.hostname) || ""; 6 var isLocalHost = 7 hostname === "localhost" || 8 hostname === "127.0.0.1" || 9 hostname === "::1" || 10 /\.local$/.test(hostname); 11 var isPagesPreview = /\.pages\.dev$/i.test(hostname); 12 13 var config = { 14 enabled: runtimeConfig.enabled !== false, 15 provider: runtimeConfig.provider || "cloudflare-cgi", 16 quality: Number(runtimeConfig.quality) || 76, 17 defaultWidth: Number(runtimeConfig.defaultWidth) || 1400, 18 avatarWidth: Number(runtimeConfig.avatarWidth) || 240, 19 newsWidth: Number(runtimeConfig.newsWidth) || 1400, 20 heroWidth: Number(runtimeConfig.heroWidth) || 1920, 21 }; 22 23 function isSameOriginUrl(src) { 24 try { 25 var parsed = new URL(src, window.location.href); 26 return parsed.origin === window.location.origin; 27 } catch (error) { 28 return false; 29 } 30 } 31 32 function normalizePath(src) { 33 if (!src) { 34 return ""; 35 } 36 37 if (/^(data:|blob:|mailto:|javascript:)/i.test(src)) { 38 return ""; 39 } 40 41 if (/^https?:\/\//i.test(src) || /^\/\//.test(src)) { 42 if (!isSameOriginUrl(src)) { 43 return ""; 44 } 45 46 try { 47 var absolute = new URL(src, window.location.href); 48 src = absolute.pathname; 49 } catch (error) { 50 return ""; 51 } 52 } 53 54 src = src.replace(/^\.\//, "").replace(/^\/+/, ""); 55 if (!/^(images|profilepics)\//.test(src)) { 56 return ""; 57 } 58 59 return src; 60 } 61 62 function shouldUseCloudflareCgi() { 63 return ( 64 config.enabled && 65 config.provider === "cloudflare-cgi" && 66 !isLocalHost && 67 !isPagesPreview 68 ); 69 } 70 71 function defaultWidthByKind(kind) { 72 if (kind === "avatar") { 73 return config.avatarWidth; 74 } 75 if (kind === "hero") { 76 return config.heroWidth; 77 } 78 if (kind === "news") { 79 return config.newsWidth; 80 } 81 return config.defaultWidth; 82 } 83 84 function buildCloudflareCgiUrl(src, options) { 85 var normalizedPath = normalizePath(src); 86 if (!normalizedPath) { 87 return src; 88 } 89 90 var opts = options || {}; 91 var requestedFormat = String(opts.format || "auto").toLowerCase(); 92 var format = /^(auto|avif|webp|jpeg|png|json)$/.test(requestedFormat) 93 ? requestedFormat 94 : "auto"; 95 var quality = Number(opts.quality) || config.quality; 96 var width = Number(opts.width) || defaultWidthByKind(opts.kind); 97 var directives = ["format=" + format, "metadata=none", "quality=" + quality]; 98 99 if (width > 0) { 100 directives.push("width=" + width, "fit=scale-down"); 101 } 102 103 return "/cdn-cgi/image/" + directives.join(",") + "/" + normalizedPath; 104 } 105 106 function getTargetWidth(img, kind) { 107 var explicit = Number(img.getAttribute("data-cf-width")); 108 if (explicit > 0) { 109 return explicit; 110 } 111 112 var attrWidth = Number(img.getAttribute("width")); 113 if (attrWidth > 0) { 114 return Math.min(Math.round(attrWidth * 2), 2000); 115 } 116 117 return defaultWidthByKind(kind); 118 } 119 120 function bindFallback(img, originalSrc) { 121 if (img.getAttribute("data-cf-fallback-bound") === "1") { 122 return; 123 } 124 125 img.setAttribute("data-cf-fallback-bound", "1"); 126 img.addEventListener( 127 "error", 128 function () { 129 if (img.getAttribute("src") !== originalSrc) { 130 img.setAttribute("src", originalSrc); 131 } 132 }, 133 { once: true } 134 ); 135 } 136 137 function optimizeImageElement(img, options) { 138 if (!img) { 139 return; 140 } 141 142 var opts = options || {}; 143 var kind = opts.kind || "default"; 144 var keepEager = opts.keepEager === true || img.getAttribute("fetchpriority") === "high"; 145 146 if (!img.hasAttribute("decoding")) { 147 img.setAttribute("decoding", "async"); 148 } 149 if (!keepEager && !img.hasAttribute("loading")) { 150 img.setAttribute("loading", "lazy"); 151 } 152 153 var src = img.getAttribute("src"); 154 if (!src || !shouldUseCloudflareCgi()) { 155 return; 156 } 157 158 var optimizedSrc = buildCloudflareCgiUrl(src, { 159 kind: kind, 160 width: opts.width || getTargetWidth(img, kind), 161 quality: opts.quality, 162 }); 163 164 if (optimizedSrc === src) { 165 return; 166 } 167 168 if (!img.getAttribute("data-origin-src")) { 169 img.setAttribute("data-origin-src", src); 170 } 171 bindFallback(img, src); 172 img.setAttribute("src", optimizedSrc); 173 } 174 175 function optimizeNonSlideImages() { 176 var images = document.querySelectorAll("img:not(.mySlides)"); 177 Array.prototype.forEach.call(images, function (img) { 178 var src = img.getAttribute("src") || ""; 179 var kind = src.indexOf("profilepics/") !== -1 ? "avatar" : "default"; 180 optimizeImageElement(img, { kind: kind }); 181 }); 182 } 183 184 window.finstarImageUrl = function (src, options) { 185 if (!shouldUseCloudflareCgi()) { 186 return src; 187 } 188 return buildCloudflareCgiUrl(src, options); 189 }; 190 191 window.finstarOptimizeImageElement = optimizeImageElement; 192 window.finstarBindImageFallback = bindFallback; 193 194 if (document.readyState === "loading") { 195 document.addEventListener("DOMContentLoaded", optimizeNonSlideImages); 196 } else { 197 optimizeNonSlideImages(); 198 } 199})(window, document);
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.