1var GLOBAL = {}; 2GLOBAL.DotNetReference = null; 3GLOBAL.SetDotnetReference = function (pDotNetReference) 4{ 5 GLOBAL.DotNetReference = pDotNetReference; 6}; 7 8// https://github.com/conficient/BlazorDynamicScriptLoad 9// loadScript: returns a promise that completes when the script loads 10window.loadScript = function (scriptPath) 11{ 12 // console.log("load script " + scriptPath); 13 14 // check list - if already loaded we can ignore 15 if (loaded[scriptPath]) 16 { 17 // console.log(scriptPath + " already loaded"); 18 // return 'empty' promise 19 return new Promise(function (resolve, reject) 20 { 21 resolve(); 22 }); 23 } 24 25 return new Promise(function (resolve, reject) 26 { 27 // create JS library script element 28 var script = document.createElement("script"); 29 script.src = scriptPath; 30 script.type = "text/javascript"; 31 // console.log(scriptPath + " created"); 32 33 // flag as loading/loaded 34 loaded[scriptPath] = true; 35 36 // if the script returns okay, return resolve 37 script.onload = function () 38 { 39 // console.log(scriptPath + " loaded ok"); 40 resolve(scriptPath); 41 }; 42 43 // if it fails, return reject 44 script.onerror = function () 45 { 46 console.log(scriptPath + " load failed"); 47 reject(scriptPath); 48 } 49 50 // scripts will load at end of body 51 document["body"].appendChild(script); 52 }); 53} 54// store list of what scripts we've loaded 55loaded = []; 56 57// Improved mobile detection 58window.isMobileDevice = function () 59{ 60 const userAgent = navigator.userAgent || navigator.vendor || window.opera; 61 return /android/i.test(userAgent) || 62 /iPad|iPhone|iPod/.test(userAgent) || 63 (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); 64}; 65 66 67window.getVisitInfo = function () 68{ 69 const referrer = document.referrer; 70 const url = window.location.href; // Get the current page URL 71 const userAgent = navigator.userAgent; 72 73 return { 74 referrer: referrer ? (new URL(referrer)).origin : "direct", // Extract origin or "direct" 75 url: url, // Current page URL 76 agent: userAgent 77 }; 78 79}; 80 81// Check page is not being loaded in iframe 82window.iframeCheck = function () 83{ 84 if (window.self !== window.top) 85 { 86 // window.location.href = "/noredirect/"; // + window.location.href; 87 88 GLOBAL.DotNetReference.invokeMethodAsync('OnRedirect'); 89 } 90}; 91 92window.breakOutIframe = function () 93{ 94 if (window.self !== window.top) 95 { 96 window.top.location.href = window.location.href; 97 } 98}; 99 100// encryption 101function urlBase64ToUint8Array(base64String) 102{ 103 const padding = '='.repeat((4 - base64String.length % 4) % 4); 104 const base64 = (base64String + padding) 105 .replace(/\-/g, '+') 106 .replace(/_/g, '/'); 107 108 const rawData = atob(base64); 109 return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0))); 110} 111 112function uint8ArrayToUrlBase64(uint8Array) 113{ 114 const base64String = btoa(String.fromCharCode.apply(null, uint8Array)); 115 return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); 116} 117 118window.cryptoHelper = { 119 async encrypt(data, keyBase64, ivBase64) 120 { 121 const encoder = new TextEncoder();
122 const encodedData = encoder.encode(data); 123 const key = urlBase64ToUint8Array(keyBase64); 124 const iv = urlBase64ToUint8Array(ivBase64); 125 126 const cryptoKey = await crypto.subtle.importKey( 127 'raw', 128 key, 129 { name: 'AES-GCM', length: 256 }, 130 false, 131 ['encrypt'] 132 ); 133 134 const encryptedData = await crypto.subtle.encrypt( 135 { 136 name: 'AES-GCM', 137 iv: iv 138 }, 139 cryptoKey, 140 encodedData 141 ); 142 143 return uint8ArrayToUrlBase64(new Uint8Array(encryptedData)); 144 }, 145 146 async decrypt(encryptedDataBase64, keyBase64, ivBase64) 147 { 148 const encryptedData = urlBase64ToUint8Array(encryptedDataBase64); 149 const key = urlBase64ToUint8Array(keyBase64); 150 const iv = urlBase64ToUint8Array(ivBase64); 151 152 const cryptoKey = await crypto.subtle.importKey( 153 'raw', 154 key, 155 { name: 'AES-GCM', length: 256 }, 156 false, 157 ['decrypt'] 158 ); 159 160 const decryptedData = await crypto.subtle.decrypt( 161 { 162 name: 'AES-GCM', 163 iv: iv 164 }, 165 cryptoKey, 166 encryptedData 167 ); 168 169 const decoder = new TextDecoder(); 170 return decoder.decode(decryptedData); 171 } 172}; 173 174 175// file save/load 176window.saveFile = (filename, content) => 177{ 178 const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); 179 const link = document.createElement("a"); 180 link.href = URL.createObjectURL(blob); 181 link.download = filename; 182 link.click(); 183} 184 185window.loadFile = () => 186{ 187 document.getElementById('fileInput').click(); 188} 189 190function downloadICSFile(filename, content) 191{ 192 console.log("downloadICSFile"); 193 194 var element = document.createElement('a'); 195 element.setAttribute('href', 'data:text/calendar;charset=utf-8,' + encodeURIComponent(content)); 196 element.setAttribute('download', filename); 197 198 element.style.display = 'none'; 199 document.body.appendChild(element); 200 201 element.click(); 202 203 document.body.removeChild(element); 204} 205 206// video 207function playVideo() 208{ 209 var video = document.getElementById("videoPlayer"); 210 video.play(); 211} 212 213 214 215document.addEventListener('DOMContentLoaded', (event) => 216{ 217 var video = document.getElementById("videoPlayer"); 218 219 if (video) 220 { 221 video.onmouseover = function () 222 { 223 video.play(); 224 }; 225 } 226}); 227 228// iframe 229function getIframeLocalStorageValue(iframeId, key) 230{ 231 var iframe = document.getElementById(iframeId); 232 if (iframe && iframe.contentWindow) 233 { 234 iframe.contentWindow.postMessage({ type: 'getLocalStorageValue', key: key }, '*'); 235 } 236 else 237 { 238 console.log("iframe not found"); 239 } 240} 241 242window.addEventListener('message', function (event) 243{ 244 if (event.data.type === 'localStorageValue') 245 { 246 // You can handle the local storage value here, for example, pass it to Blazor 247 GLOBAL.DotNetReference.invokeMethodAsync('HandleLocalStorageValue', "test");// event.data.value); 248 } 249}); 250 251window.adsenseFunctions = { 252 // Adds adsbygoogle.js to the page, once. Called from MainLayout after its first render, so the script 253 // runs against the app's real DOM. It waits a frame and then a task so the render has been painted and 254 // Blazor has finished replacing the prerendered HTML first. Any adsbygoogle.push({}) made before the 255 // library arrives (AdUnit does this as it renders) just sits in the queue and is processed on load. 256 load: function () 257 { 258 if (window.adsenseFunctions._loading || document.getElementById('tg-adsense')) return; 259 window.adsenseFunctions._loading = true; 260 261 const add = function () 262 { 263 const script = document.createElement('script'); 264 script.id = 'tg-adsense'; 265 script.async = true; 266 script.crossOrigin = 'anonymous'; 267 script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5408854154696215'; 268 document.head.appendChild(script); 269 }; 270 requestAnimationFrame(function () { setTimeout(add, 0); }); 271 }, 272 273 initializeAds: function () 274 { 275 try 276 { 277 // console.log("initialize ads"); 278 (adsbygoogle = window.adsbygoogle || []).push({}); 279 // console.info("initialize ads ok"); 280 } 281 catch 282 { 283 // console.log("initialize ads error"); 284 } 285 }, 286 287 refreshAd: function (adElementId, adClient, adSlot) 288 { 289 // console.log("refresh ad"); 290 291 let adElement = document.getElementById(adElementId); 292 if (adElement) 293 { 294 // console.log("refresh ad start"); 295 adElement.innerHTML = ""; // Clear the current ad content 296 297 // Re-create the AdSense ins tag 298 let ins = document.createElement('ins'); 299 ins.className = "adsbygoogle"; 300 ins.style.display = "block"; 301 ins.style.width = "100%"; 302 ins.style.height = "150px"; 303 ins.dataset.adClient = adClient; 304 ins.dataset.adSlot = adSlot; 305 // ins.dataset.adFormat = "auto"; 306 ins.dataset.fullWidthResponsive = "true"; 307 308 adElement.appendChild(ins); 309 310 // Request a new ad 311 (adsbygoogle = window.adsbygoogle || []).push({}); 312 313 // console.log("refresh ad ok"); 314 315 } 316 else 317 { 318 console.log("ERROR : ad div " + adElementId + " not found"); 319 } 320 } 321 322}; 323 324window.saveAndRestoreScroll = { 325 scrollPosition: 0, 326 327 // Save current position 328 savePosition: function () 329 { 330 this.scrollPosition = window.scrollY; 331 }, 332 333 // Restore saved position 334 restorePosition: function () 335 { 336 window.scrollTo(0, this.scrollPosition); 337 } 338}; 339 340window.scrollInterop = { 341 enableScroll: function () 342 { 343 document.body.style.overflow = ''; 344 document.documentElement.style.overflow = ''; 345 }, 346 disableScroll: function () 347 { 348 document.body.style.overflow = 'hidden'; 349 document.documentElement.style.overflow = 'hidden'; 350 } 351}; 352 353window.loadCSS = function (href) 354{ 355 return new Promise((resolve, reject) => 356 { 357 if (!document.querySelector(`link[href="${href}"]`)) 358 { 359 const link = document.createElement('link'); 360 link.rel = 'stylesheet'; 361 link.href = href; 362 link.onload = () => resolve(); 363 link.onerror = () => reject(); 364 document.head.appendChild(link); 365 } else 366 { 367 resolve(); // CSS already loaded 368 } 369 });
370}; 371 372window.showtop = function () 373{ 374} 375 376// Add this to wwwroot/index.html in the <script> section or to a JS file you reference 377window.setDocumentTitle = function (title) 378{ 379 document.title = title; 380}; 381 382// Add this to your global.js file or create a new script file 383window.fixNavMenu = function () 384{ 385 // Fix for nav menu toggle on mobile 386 document.addEventListener('DOMContentLoaded', function () 387 { 388 // Get the navbar toggler button 389 const navbarToggler = document.querySelector('.navbar-toggler'); 390 391 // Get the collapse element 392 const navMenu = document.getElementById('navMenu'); 393 394 if (navbarToggler && navMenu) 395 { 396 // Add click event listener 397 navbarToggler.addEventListener('click', function () 398 { 399 // Toggle the 'show' class on the menu 400 navMenu.classList.toggle('show'); 401 }); 402 403 // Check if we're on mobile and apply appropriate initial state 404 const isMobile = window.innerWidth < 641; 405 if (!isMobile) 406 { 407 navMenu.classList.add('show'); 408 } 409 410 // Handle resize events 411 window.addEventListener('resize', function () 412 { 413 const isMobile = window.innerWidth < 641; 414 if (!isMobile) 415 { 416 navMenu.classList.add('show'); 417 } else if (!navbarToggler.getAttribute('aria-expanded') === 'true') 418 { 419 navMenu.classList.remove('show'); 420 } 421 }); 422 } 423 }); 424 425 // Fix CSS application issues - ensure proper styles are being applied 426 function addGlobalStyle(css) 427 { 428 var head = document.getElementsByTagName('head')[0]; 429 if (!head) { return; } 430 var style = document.createElement('style'); 431 style.type = 'text/css'; 432 style.innerHTML = css; 433 head.appendChild(style); 434 } 435 436 // Add critical styles that might be getting overridden 437 addGlobalStyle(` 438 @media (min-width: 641px) { 439 .collapse { 440 display: block !important; 441 } 442 .navbar-toggler { 443 display: none !important; 444 } 445 } 446 447 .nav-item a { 448 color: #d7d7d7 !important; 449 } 450 451 .nav-item a:hover, 452 .nav-item a.active { 453 color: white !important; 454 } 455 456 .tg-sidebar { 457 background-image: linear-gradient(180deg, rgb(32, 32, 32) 0%, #FF6700 70%) !important; 458 z-index: 1000; 459 } 460 `); 461}; 462 463// Add this JavaScript function to your page or any loaded JS file 464// For example, you could add it to your _Host.cshtml or an imported script 465 466window.checkImageExists = function (url) 467{ 468 return new Promise((resolve) => 469 { 470 // Create an image cache for performance 471 if (!window._imageCache) 472 { 473 window._imageCache = {}; 474 } 475 476 // Check if we've already tested this URL 477 if (window._imageCache[url] !== undefined) 478 { 479 resolve(window._imageCache[url]); 480 return; 481 } 482 483 // Create a new image to test 484 const img = new Image(); 485 486 // Set a short timeout (500ms) to avoid long waits 487 const timeoutId = setTimeout(() => 488 { 489 // Image took too long to load, assume it doesn't exist 490 window._imageCache[url] = false; 491 resolve(false); 492 }, 500); 493 494 // Image loaded successfully 495 img.onload = () => 496 { 497 clearTimeout(timeoutId); 498 window._imageCache[url] = true; 499 resolve(true); 500 }; 501 502 // Image failed to load (404, etc) 503 img.onerror = () => 504 { 505 clearTimeout(timeoutId); 506 window._imageCache[url] = false; 507 resolve(false); 508 }; 509 510 // Start loading the image 511 img.src = url; 512 });
513}; 514 515 516// Add this to your global.js file 517 518// Track when a navigation link is clicked 519window.navigationHelper = { 520 // Flag to track if a navigation is in progress 521 isNavigating: false, 522 523 // Function to be called when a navigation link is clicked 524 onNavLinkClick: function () 525 { 526 // console.log("Navigation link clicked - resetting scroll position"); 527 // Set the flag 528 this.isNavigating = true; 529 530 // Immediately scroll to top 531 window.scrollTo(0, 0); 532 533 // Set a safety timeout to ensure scroll position remains at top 534 // This helps with Blazor navigation which might restore scroll position 535 setTimeout(() => 536 { 537 window.scrollTo(0, 0); 538 }, 50); 539 }, 540 541 // Function to be called after navigation/rendering is complete 542 resetScrollAfterNavigation: function () 543 { 544 if (this.isNavigating) 545 { 546 // console.log("Resetting scroll position after navigation complete"); 547 window.scrollTo(0, 0); 548 549 // Reset the flag 550 this.isNavigating = false; 551 } 552 } 553}; 554 555// Sidebar is a Bootstrap offcanvas drawer at every viewport width now 556// (collapsed by default, opened via the navbar-toggler), so it should 557// auto-close on nav-link click at every width, not just mobile. 558function setupMobileMenuAutoClose() 559{ 560 // Get the navbar offcanvas element 561 const navMenu = document.getElementById('navMenu'); 562 if (!navMenu) 563 { 564 return; 565 } 566 567 // Get all navigation links inside the navMenu 568 const navLinks = document.querySelectorAll('#navMenu .nav-link'); 569 570 // Reuse the existing Bootstrap offcanvas instance (created when the 571 // toggler first opens it) rather than creating a second one. 572 const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(navMenu); 573 574 // Add click event listeners to all nav links 575 navLinks.forEach(link => 576 { 577 link.addEventListener('click', () => 578 { 579 // Close the menu 580 bsOffcanvas.hide(); 581 582 // Call our navigation helper to reset scroll position 583 window.navigationHelper.onNavLinkClick(); 584 }); 585 }); 586} 587 588// Also, apply this to all navigation links, not just mobile menu 589document.addEventListener('DOMContentLoaded', function () 590{ 591 // Set up mobile menu 592 setupMobileMenuAutoClose(); 593 594 // Get ALL navigation links anywhere in the document 595 const allNavLinks = document.querySelectorAll('a.nav-link'); 596 597 // Add scroll reset to all navigation links 598 allNavLinks.forEach(link => 599 { 600 // Skip external links (those with target="_blank" or starting with http) 601 const href = link.getAttribute('href'); 602 const isExternal = link.getAttribute('target') === '_blank' || 603 (href && (href.startsWith('http') || href.startsWith('//'))); 604 605 if (!isExternal && href && href !== '#' && !link.classList.contains('scroll-handled')) 606 { 607 link.classList.add('scroll-handled'); 608 link.addEventListener('click', function () 609 { 610 window.navigationHelper.onNavLinkClick(); 611 }); 612 } 613 }); 614}); 615 616// Also run it again when Blazor is done rendering 617window.blazorExtensions = { 618 // This function can be called from Blazor after navigation 619 setupMobileMenu: function () 620 { 621 setupMobileMenuAutoClose(); 622 } 623}; 624 625window.preloadImageWithTimeout = function (url, timeoutMs) 626{ 627 return new Promise((resolve) => 628 { 629 const img = new Image(); 630 631 // Set timeout to prevent waiting too long 632 const timeout = setTimeout(() => 633 { 634 console.warn("Image load timed out:", url); 635 resolve(false); 636 }, timeoutMs || 5000); 637 638 // Clear timeout if image loads or errors 639 img.onload = function () 640 { 641 clearTimeout(timeout); 642 resolve(true); 643 }; 644 645 img.onerror = function () 646 { 647 clearTimeout(timeout); 648 console.warn("Failed to load image:", url); 649 resolve(false); 650 }; 651 652 // Start loading the image 653 img.src = url; 654 });
655}; 656 657window.scrollToElement = function (elementId) 658{ 659 // console.log('scrollToElement called with:', elementId); 660 const element = document.getElementById(elementId); 661 if (element) 662 { 663 // console.log('Element found, scrolling...'); 664 element.scrollIntoView({ behavior: 'smooth', block: 'start' }); 665 return true; 666 } else 667 { 668 console.log('Element not found:', elementId); 669 return false; 670 } 671}; 672 673window.lazyScrollToElement = function (elementId, maxRetries = 20, delay = 200) 674{ 675 let attempts = 0; 676 677 const tryScroll = () => 678 { 679 attempts++; 680 // console.log(`scrollToElement attempt ${attempts}/${maxRetries} for:`, elementId); 681 682 const element = document.getElementById(elementId); 683 if (element) 684 { 685 // console.log('Element found, scrolling...'); 686 element.scrollIntoView({ behavior: 'smooth', block: 'start' }); 687 return true; 688 } else 689 { 690 // console.log('Element not found yet:', elementId); 691 692 if (attempts < maxRetries) 693 { 694 setTimeout(tryScroll, delay); 695 } else 696 { 697 console.log('Max retries reached, element not found:', elementId); 698 } 699 return false; 700 } 701 }; 702 703 tryScroll(); 704}; 705 706window.tripgeoFile = { 707 download: function (fileName, contentType, content) 708 { 709 const blob = new Blob([content], { type: contentType }); 710 const url = URL.createObjectURL(blob); 711 const a = document.createElement('a'); 712 a.href = url; 713 a.download = fileName; 714 document.body.appendChild(a); 715 a.click(); 716 document.body.removeChild(a); 717 URL.revokeObjectURL(url); 718 } 719};
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.