1/** 2 * CSP-safe action shim. 3 * Replaces inline onclick handlers (blocked by strict-dynamic CSP) with 4 * data-attribute dispatch. 5 * 6 * NAMED-ACTION dispatch (preferred for page-specific behaviors): 7 * window.CSPActions.register('foo', function(el, ev) { ... }); 8 * <button data-csp-action="foo">...</button> 9 * 10 * GENERIC FUNCTION-CALL dispatch (replaces inline onclick="fn(args)"): 11 * <button data-csp-fn="fn"> -> fn(el, ev) 12 * <button data-csp-fn="fn" data-csp-args='[1,"x"]'> -> fn(1, "x", el, ev) 13 * The string "$el" anywhere in args is replaced with the element itself, 14 * so onclick="pickOne(1,'x',this)" -> data-csp-fn="pickOne" data-csp-args='[1,"x","$el"]'. 15 * data-csp-stop="true" stops event propagation (replaces event.stopPropagation()). 16 * 17 * COMMON-PATTERN helpers: 18 * data-toggle-active="self|parent|.css" classList.toggle('active' or data-toggle-class) 19 * data-scroll-to="#id" smooth scroll to element 20 * data-href="url" window.location.href = url 21 * data-href="url" data-href-target="_blank" window.open(url, '_blank') 22 * data-csp-modal-close="#modalId" close a modal on backdrop click 23 * data-csp-show="#id" / data-csp-hide="#id" set display='block' / 'none' 24 * data-csp-click-trigger="#id" relay click to another element 25 */ 26(function () { 27 var registry = {}; 28 window.CSPActions = { 29 register: function (name, fn) { registry[name] = fn; }, 30 run: function (name, el, ev) { if (registry[name]) registry[name](el, ev); } 31 }; 32 33 // Submit handler: replaces onsubmit="return fnName(event)" or onsubmit="fn(event)". 34 document.addEventListener('submit', function (e) { 35 var form = e.target.closest('form[data-csp-form-handler]'); 36 if (form) { 37 var fn = window[form.dataset.cspFormHandler]; 38 if (typeof fn === 'function') { 39 var ret = fn(e); 40 if (ret === false) e.preventDefault(); 41 } 42 } 43 // Generic submit-fn dispatch (data-csp-submit-fn="fn", optional data-csp-prevent="true"). 44 var subFnEl = e.target.closest('[data-csp-submit-fn]'); 45 if (subFnEl) dispatchFn(subFnEl, e, subFnEl.dataset.cspSubmitFn); 46 // Confirm-on-submit 47 var confirmForm = e.target.closest('form[data-csp-confirm-form]'); 48 if (confirmForm && !window.confirm(confirmForm.dataset.cspConfirmForm || 'Submit?')) { 49 e.preventDefault(); 50 } 51 }, true); 52 53 // Shared dispatcher: parse args, substitute placeholders, call window[fnName]. 54 // Supports dotted paths e.g. "FindingModal._applyKB" -> window.FindingModal._applyKB 55 // bound to its parent object so `this` is correct inside the method. 56 function resolveFn(fnName) { 57 var fn = window[fnName]; 58 if (typeof fn === 'function') return { fn: fn, ctx: window }; 59 if (fnName && fnName.indexOf('.') !== -1) { 60 var parts = fnName.split('.'); 61 var obj = window, ctx = window; 62 for (var i = 0; i < parts.length; i++) { 63 ctx = obj; 64 obj = obj ? obj[parts[i]] : undefined; 65 } 66 if (typeof obj === 'function') return { fn: obj, ctx: ctx }; 67 } 68 return null; 69 } 70 71 function dispatchFn(el, e, fnName) { 72 var resolved = resolveFn(fnName); 73 if (!resolved) return; 74 var fn = resolved.fn, ctx = resolved.ctx; 75 if (el.dataset.cspStop === 'true') e.stopPropagation(); 76 if (el.dataset.cspPrevent === 'true') e.preventDefault(); 77 var args = []; 78 var raw = el.dataset.cspArgs; 79 if (raw) { try { args = JSON.parse(raw); } catch (err) { args = []; } } 80 args = args.map(function (a) { 81 if (a === '$el') return el; 82 if (a === '$ev') return e; 83 if (a === '$elvalue') return el.value; 84 if (a === '$elchecked') return el.checked; 85 if (a === '$eldataset') return el.dataset; 86 return a; 87 }); 88 args.push(el, e); 89 try { fn.apply(ctx, args); } catch (err) { console.error('[csp-shim] "' + fnName + '" threw:', err); } 90 } 91
92 // Input handler: live mirror of value into a target element's textContent. 93 document.addEventListener('input', function (e) { 94 var mirror = e.target.closest('[data-csp-mirror-text]'); 95 if (mirror) { 96 var tgt = document.getElementById(mirror.dataset.cspMirrorText); 97 if (tgt) tgt.textContent = e.target.value; 98 } 99 var fnEl = e.target.closest('[data-csp-input-fn]'); 100 if (fnEl) dispatchFn(fnEl, e, fnEl.dataset.cspInputFn); 101 }, true); 102 103 // Change handler: toggle a class on closest ancestor based on `this.checked`, 104 // plus generic data-csp-change-fn dispatch. 105 document.addEventListener('change', function (e) { 106 var t = e.target.closest('[data-csp-toggle-closest]'); 107 if (t) { 108 var ancestor = t.closest(t.dataset.cspToggleClosest); 109 if (ancestor) { 110 var c = t.dataset.cspToggleClassOnChange || 'selected'; 111 ancestor.classList.toggle(c, !!t.checked); 112 } 113 } 114 var fnEl = e.target.closest('[data-csp-change-fn]'); 115 if (fnEl) dispatchFn(fnEl, e, fnEl.dataset.cspChangeFn); 116 }, true); 117 118 // Drag-and-drop generic dispatch. 119 ['dragover', 'dragleave', 'drop', 'dragstart', 'dragend'].forEach(function (evt) { 120 var attr = 'data-csp-' + evt + '-fn'; 121 var prop = 'csp' + evt.charAt(0).toUpperCase() + evt.slice(1) + 'Fn'; 122 document.addEventListener(evt, function (e) { 123 var t = e.target.closest('[' + attr + ']'); 124 if (t) dispatchFn(t, e, t.dataset[prop]); 125 }, true); 126 }); 127 128 // Keydown Enter -> dispatch a window function. `data-csp-on-enter="fnName"`. 129 document.addEventListener('keydown', function (e) { 130 if (e.key !== 'Enter') return; 131 var t = e.target.closest('[data-csp-on-enter]'); 132 if (!t) return; 133 var fn = window[t.dataset.cspOnEnter]; 134 if (typeof fn === 'function') fn(); 135 }); 136 137 // img onerror fallback via data-csp-img-fallback="url" or generic data-csp-error-fn. 138 document.addEventListener('error', function (e) { 139 var tgt = e.target; 140 if (tgt && tgt.tagName === 'IMG' && tgt.dataset && tgt.dataset.cspImgFallback) { 141 var fb = tgt.dataset.cspImgFallback; 142 tgt.removeAttribute('data-csp-img-fallback'); 143 tgt.src = fb; 144 } 145 if (tgt && tgt.dataset && tgt.dataset.cspErrorFn) { 146 dispatchFn(tgt, e, tgt.dataset.cspErrorFn); 147 } 148 // Hide-on-error: data-csp-error-hide="true" sets display:none on the element. 149 if (tgt && tgt.dataset && tgt.dataset.cspErrorHide === 'true') { 150 tgt.style.display = 'none'; 151 } 152 // Remove-on-error: data-csp-error-remove="true" removes the element from DOM. 153 if (tgt && tgt.dataset && tgt.dataset.cspErrorRemove === 'true') { 154 tgt.remove(); 155 } 156 }, true); 157 158 // Built-in helper functions exposed on window for data-csp-fn dispatch. 159 window.cspMulti = function (fnA, argA, fnB, argB) { 160 if (typeof window[fnA] === 'function') window[fnA](argA); 161 if (typeof window[fnB] === 'function') window[fnB](argB); 162 }; 163 window.cspToggleSelfWithChecked = function (cls, innerSel) { 164 // For onclick on a wrapper element where inner checkbox state drives toggle. 165 // dispatcher passes (el, ev) appended; the first 2 args are cls, innerSel. 166 // The element is the last-but-one arg, but our dispatcher uses placeholders: 167 // we receive args + el + ev. Detect el from arguments. 168 var el = arguments[arguments.length - 2]; 169 if (el && el.querySelector) { 170 var inner = el.querySelector(innerSel); 171 el.classList.toggle(cls, !!(inner && inner.checked)); 172 } 173 }; 174 window.cspClipCopy = function (val, toast) { 175 if (navigator.clipboard) navigator.clipboard.writeText(val); 176 if (window.toastr && window.toastr.success) window.toastr.success(toast || 'Copied'); 177 }; 178 window.cspSubmitForm = function (formId) { 179 var f = document.getElementById(formId); 180 if (f && typeof f.submit === 'function') f.submit(); 181 }; 182 window.cspSpOpenThread = function (partner) { 183 if (window.SP && typeof window.SP.openThread === 'function') window.SP.openThread(partner); 184 }; 185 window.cspSpDial = function (phone) { 186 if (window.SP && typeof window.SP.dial === 'function') window.SP.dial(phone); 187 }; 188 window.cspHistoryBack = function () { if (window.history && window.history.length > 1) window.history.back(); }; 189 // No-op helper. Used with data-csp-prevent="true" to gate default link nav. 190 window.cspNoop = function () {}; 191 // window.print helper for data-csp-fn dispatch. 192 window.cspWindowPrint = function () { window.print(); }; 193 // location.reload() helper for data-csp-fn dispatch. 194 window.cspReload = function () { window.location.reload(); }; 195 // Hide self and show next sibling inline. For broken img with a fallback span. 196 window.cspImgErrShowSibling = function () { 197 var el = arguments[arguments.length - 2]; 198 if (!el) return; 199 el.style.display = 'none'; 200 if (el.nextElementSibling) el.nextElementSibling.style.display = 'inline'; 201 }; 202 // Remove a class from parent then remove self. Used for blog hero images. 203 window.cspImgErrRemoveWithParentCls = function (cls) { 204 var el = arguments[arguments.length - 2]; 205 if (!el) return; 206 if (cls && el.parentElement) el.parentElement.classList.remove(cls); 207 el.remove(); 208 }; 209 210 document.addEventListener('click', function (e) { 211 // Named handler dispatch 212 var actionEl = e.target.closest('[data-csp-action]'); 213 if (actionEl) { 214 var name = actionEl.dataset.cspAction; 215 if (registry[name]) { 216 registry[name](actionEl, e); 217 } 218 } 219 220 // Generic: toggle a class on self / parent / closest / selector 221 // data-toggle-active="self|parent|closest:.sel|.global-sel" toggles 'active' (or 222 // data-toggle-class) on the matching element. 223 var toggleEl = e.target.closest('[data-toggle-active]'); 224 if (toggleEl) { 225 var sel = toggleEl.dataset.toggleActive; 226 var cls = toggleEl.dataset.toggleClass || 'active'; 227 if (sel === 'self') { 228 toggleEl.classList.toggle(cls); 229 } else if (sel === 'parent') { 230 if (toggleEl.parentElement) toggleEl.parentElement.classList.toggle(cls); 231 } else if (sel.indexOf('closest:') === 0) {
232 var closestTarget = toggleEl.closest(sel.slice('closest:'.length)); 233 if (closestTarget) closestTarget.classList.toggle(cls); 234 } else if (sel) { 235 var t = document.querySelector(sel); 236 if (t) t.classList.toggle(cls); 237 } 238 } 239 240 // Generic: smooth-scroll to element by selector 241 var scrollEl = e.target.closest('[data-scroll-to]'); 242 if (scrollEl) { 243 var target = document.querySelector(scrollEl.dataset.scrollTo); 244 if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth' }); } 245 } 246 247 // Generic: navigate to URL on click (replaces onclick="window.location.href=..."). 248 // Use data-href-target="_blank" to open in new tab. 249 var hrefEl = e.target.closest('[data-href]'); 250 if (hrefEl) { 251 var url = hrefEl.dataset.href; 252 if (url) { 253 if (hrefEl.dataset.hrefTarget === '_blank') window.open(url, '_blank'); 254 else window.location.href = url; 255 } 256 } 257 258 // Generic: show/hide an element by selector. 259 var showEl = e.target.closest('[data-csp-show]'); 260 if (showEl) { 261 var s = document.querySelector(showEl.dataset.cspShow); 262 if (s) s.style.display = showEl.dataset.cspShowAs || 'block'; 263 } 264 var hideEl = e.target.closest('[data-csp-hide]'); 265 if (hideEl) { 266 var h = document.querySelector(hideEl.dataset.cspHide); 267 if (h) h.style.display = 'none'; 268 } 269 270 // Generic: relay click to another element. 271 var trigEl = e.target.closest('[data-csp-click-trigger]'); 272 if (trigEl) { 273 var tgt = document.querySelector(trigEl.dataset.cspClickTrigger); 274 if (tgt) tgt.click(); 275 } 276 277 // Generic: remove a CSS class from an element by selector. 278 var rmEl = e.target.closest('[data-csp-remove-class-from]'); 279 if (rmEl) { 280 var rmTgt = document.querySelector(rmEl.dataset.cspRemoveClassFrom); 281 if (rmTgt) rmTgt.classList.remove(rmEl.dataset.cspRemoveClass || ''); 282 } 283 // Symmetric add-class. 284 var addEl = e.target.closest('[data-csp-add-class-to]'); 285 if (addEl) { 286 var addTgt = document.querySelector(addEl.dataset.cspAddClassTo); 287 if (addTgt) addTgt.classList.add(addEl.dataset.cspAddClass || ''); 288 } 289 290 // (form handlers are wired up via submit listener below, not here) 291 // FAQ accordion (open parent + measure next-sibling height). 292 // Replaces onclick="this.parentElement.classList.toggle('open');var a=this.nextElementSibling;a.style.maxHeight=a.style.maxHeight?null:a.scrollHeight+'px'" 293 var faqEl = e.target.closest('[data-csp-faq-toggle]'); 294 if (faqEl && faqEl.parentElement) { 295 faqEl.parentElement.classList.toggle('open'); 296 var ans = faqEl.nextElementSibling; 297 if (ans) ans.style.maxHeight = ans.style.maxHeight ? null : ans.scrollHeight + 'px'; 298 } 299 300 // Confirm-then-action helper. data-csp-confirm="msg" + data-csp-fn=... still 301 // dispatches via the named-fn block above only if confirmed. If no fn, just 302 // gates the default form submit. 303 var confirmEl = e.target.closest('[data-csp-confirm]'); 304 if (confirmEl) { 305 if (!window.confirm(confirmEl.dataset.cspConfirm || 'Are you sure?')) { 306 e.preventDefault(); 307 e.stopPropagation(); 308 return; // prevent further dispatchers 309 } 310 } 311 312 // Bootstrap modal show (replaces inline `new bootstrap.Modal(...).show()`). 313 var bsModalEl = e.target.closest('[data-csp-bs-modal-show]'); 314 if (bsModalEl && window.bootstrap && window.bootstrap.Modal) { 315 var modal = document.querySelector(bsModalEl.dataset.cspBsModalShow); 316 if (modal) new window.bootstrap.Modal(modal).show(); 317 } 318 319 // Toggle display:block <-> none on a target by selector. 320 var toggleDispEl = e.target.closest('[data-csp-toggle-display]'); 321 if (toggleDispEl) { 322 var dispTgt = document.querySelector(toggleDispEl.dataset.cspToggleDisplay); 323 if (dispTgt) { 324 dispTgt.style.display = (dispTgt.style.display === 'block') ? 'none' : 'block'; 325 } 326 } 327 328 // Generic: submit the closest ancestor matching selector. 329 var subEl = e.target.closest('[data-csp-submit-closest]'); 330 if (subEl) { 331 var form = subEl.closest(subEl.dataset.cspSubmitClosest); 332 if (form && typeof form.submit === 'function') form.submit(); 333 } 334
335 // Generic: set a global variable then dispatch a fn. Used for paginators 336 // like `currentPage=N; renderPosts()` -> data-csp-set="currentPage" data-csp-set-value="N" data-csp-fn="renderPosts" 337 var setEl = e.target.closest('[data-csp-set]'); 338 if (setEl) { 339 var k = setEl.dataset.cspSet; 340 var v = setEl.dataset.cspSetValue; 341 try { 342 if (v === 'inc') { window[k] = (window[k] || 0) + 1; } 343 else if (v === 'dec') { window[k] = (window[k] || 0) - 1; } 344 else { 345 var parsed = (v !== undefined && v.match && v.match(/^-?\d+(?:\.\d+)?$/)) ? Number(v) : v; 346 window[k] = parsed; 347 } 348 } catch (err) { /* ignore */ } 349 } 350 351 // Modal-backdrop close: replaces onclick="if(event.target===this){...}" 352 var modalEl = e.target.closest('[data-csp-modal-close]'); 353 if (modalEl && e.target === modalEl) { 354 var mc = modalEl.dataset.cspModalClose; 355 // Either close a target by selector, or hide self when no selector given. 356 var closeTarget = mc ? document.querySelector(mc) : modalEl; 357 if (closeTarget) closeTarget.style.display = 'none'; 358 } 359 360 // Generic function-call dispatch (replaces onclick="fn(args)"). 361 // window.<fn> is invoked with parsed args; "$el" placeholders are 362 // substituted with the clicked element, "$ev" with the event itself. 363 var fnEl = e.target.closest('[data-csp-fn]'); 364 if (fnEl) dispatchFn(fnEl, e, fnEl.dataset.cspFn); 365 }); 366})();
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.