1// dlm 2// https://www.dlm.com/main4a.js https://www.dlm.com/style4.css 3 4/* global reSpell, Author, mainMsg, loginDo */ 5 6let main = { theme: 'lite', version: '0.804', timePrev: performance.now(), searchScript: 'no', searchStatus: 'no', searchData: '', name: '', stamp: '', loginQ: false, iframes: [], adsense:'ca-pub-1389989178296449', dom: 'dlm', server: 'www.mathbro.com', port: '3003' } 7 8if (location.hostname === 'localhost') main.port = '3004' 9 10// console.log('document.lastModified', document.lastModified) 11 12let menuVis = false 13let searchVis = false 14// console.log('menuVis', menuVis) 15// localization 16let userLang = window.navigator.userLanguage || window.navigator.language 17 18main.decSep = (1.5).toLocaleString().charAt(1) 19main.thouSep = String.fromCharCode(90 - main.decSep.charCodeAt(0)) 20if (main.decSep == ',') { 21 main.decType = 'c' 22} else { 23 main.decType = '' 24} 25//console.log('DecSep',DecSep,ThouSep,userLang) 26 27function pathAbsolute(base, relative) { 28 let stack = base.split('/'), 29 parts = relative.split('/') 30 stack.pop() // remove current file name (or empty string) 31 // (omit if "base" is the current folder without trailing slash) 32 for (let i = 0; i < parts.length; i++) { 33 if (parts[i] == '.') continue 34 if (parts[i] == '..') stack.pop() 35 else stack.push(parts[i]) 36 } 37 return stack.join('/') 38} 39 40function decfmt() { 41 if (main.decSep == ',') { 42 // 0,00=>0#00 then 0.0=>0,0, then 0#0=>0.0 43 fixSpells(document.body, 'd') 44 //document.body.innerHTML = document.body.innerHTML.replace(/(\d),(\d\d)/g, '$1#$2').replace(/(\d)\.(\d)/g, '$1,$2').replace(/(\d)#(\d)/g, '$1.$2'); 45 // images replace "." with "c." 46 let imgs = document.body.getElementsByTagName('img') 47 for (let i = 0; i < imgs.length; i++) { 48 if (imgs[i].getAttribute('hasdec') != null && imgs[i].getAttribute('hasdec') != '') { 49 imgs[i].src = imgs[i].src.replace(/\.(gif|jpg|png)/g, 'c.$1') 50 } 51 } 52 } 53} 54 55function doSpell() { 56 if (typeof reSpell == 'undefined') return 57 let userLang = window.navigator.userLanguage || window.navigator.language 58 59 switch (userLang.toLowerCase()) { 60 case 'en-us': 61 break 62 case 'en-au': 63 case 'en-ca': 64 case 'en-gb': 65 case 'en-ie': 66 case 'en-nz': 67 case 'en-za': 68 fixSpells(document.body, 's') 69 break 70 default: 71 } 72} 73 74function fixSpells(elem, tp) { 75 // check if parameter is a an ELEMENT_NODE 76 if (!(elem instanceof Node) || elem.nodeType !== Node.ELEMENT_NODE) return 77 let children = elem.childNodes 78 for (let i = 0; children[i]; ++i) { 79 let node = children[i] 80 switch (node.nodeType) { 81 case Node.ELEMENT_NODE: // call recursively 82 fixSpells(node, tp) 83 break 84 case Node.TEXT_NODE: // fix spelling 85 if (tp == 's') fixSpell(node) 86 if (tp == 'd') fixDec(node) 87 break 88 } 89 } 90} 91 92function fixSpell(node) { 93 let s = node.nodeValue 94 if (s.length < 4) return // leave quickly if small 95 if (!s.match(/(?=.*[a-zA-Z])/)) return // leave if no ordinary chars inside 96 //s = "(" + s + ")" 97 let sStt = s 98 for (let j = 0; j < reSpell.length; j++) { 99 let s0 = reSpell[j][0] 100 let s1 = reSpell[j][1] 101 s = s.replace(new RegExp('\\b' + s0 + '\\b', 'g'), s1) 102 s = s.replace(new RegExp('\\b' + proper(s0) + '\\b', 'g'), proper(s1)) 103 } 104 if (s != sStt) node.nodeValue = s // only update if changed 105} 106 107function fixDec(node) { 108 let s = node.nodeValue; 109 let sStt = s; 110 111 s = s 112 // 1. Replace thousands commas with '#' using lookahead for 3-digit groups 113 .replace(/\b(\d+)(?:,(\d{3}))+\b/g, (match) => match.replace(/,/g, '#')) 114 // 2. Convert decimal point to Spanish comma 115 .replace(/(\d)\.(\d)/g, '$1,$2') 116 // 3. Convert temporary '#' placeholders to Spanish dots 117 .replace(/#/g, '.'); 118 119 if (s !== sStt) { 120 node.nodeValue = s; 121 } 122} 123 124function fixDecz2(node) { 125 let s = node.nodeValue; 126 let sStt = s; 127 128 // Use a temporary placeholder character '#' that won't appear in text 129 s = s
130 // 1. Swap US thousands commas (1,234) with temporary placeholder '#' 131 .replace(/(\d),(\d{3})/g, '$1#$2') 132 // 2. Swap US decimal points (1.23) with Spanish decimal commas (1,23) 133 .replace(/(\d)\.(\d)/g, '$1,$2') 134 // 3. Swap placeholder '#' to Spanish thousands dots (1.234) or space 135 .replace(/(\d)#(\d{3})/g, '$1.$2'); 136 137 if (s !== sStt) { 138 node.nodeValue = s; 139 } 140} 141 142function fixDecz1(node) { 143 let s = node.nodeValue 144 let sStt = s 145 s = s 146 .replace(/(\d),(\d\d)/g, '$1#$2') 147 .replace(/(\d)\.(\d)/g, '$1,$2') 148 .replace(/(\d)#(\d)/g, '$1.$2') 149 150 151 //if (s.match(/(?=.*[a-zA-Z])/)) { // any chars inside at all? 152 if (s != sStt) { 153 //console.log("fixDec",sStt,"=>",s); 154 node.nodeValue = s // only update if changed 155 } 156 //} 157} 158 159function doLocal() { 160 decfmt() 161 doSpell() 162 relatedLinks() 163} 164 165function eqFmts() { 166 let roots = document.querySelectorAll('p, li, h3, .large, .larger, .huge') 167 console.log('eqFmts roots', roots.length) 168 let chgN = 0 169 let sttTm = performance.now() 170 171 for (let root of roots) { 172 let walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT) 173 174 let nodes = [] 175 while (walker.nextNode()) nodes.push(walker.currentNode) 176 177 for (let node of nodes) { 178 let s = node.textContent 179 if (!s.includes(' = ')) continue 180 // if (node.parentNode.children.length > 0) continue // over excludes, because of <sup> etc 181 182 let span = document.createElement('span') 183 span.innerHTML = s.replace(/ = /g, ' <span class="eq">=</span> ') 184 chgN++ 185 node.parentNode.replaceChild(span, node) 186 } 187 } 188 let elapsed = performance.now() - sttTm 189 console.log('eqFmts chgN', chgN, elapsed) 190} 191 192function proper(s) { 193 return s.charAt(0).toUpperCase() + s.substring(1, s.length).toLowerCase() 194} 195 196function tellAFriend() { 197 let msg = "\nI found '" + document.title + "' here: " + location.href + '\n' 198 window.location = 'mailto:?subject=' + encodeURIComponent(document.title) + '&body=' + encodeURIComponent(msg) 199} 200 201function addFavorites() { 202 if (window.sidebar) { 203 // Mozilla Firefox Bookmark 204 window.sidebar.addPanel(document.title, location.href, '') 205 } else if (window.external) { 206 // IE Favorite 207 window.external.AddFavorite(location.href, document.title) 208 } 209} 210 211function openEnglish() { 212 if (typeof tranfrom == 'undefined') tranfrom = 'index.htm' 213 let path = tranfrom // only relative path to avoid spoofing 214 let url = 'https://www.mathsisfun.com/' + path 215 window.location = url 216} 217 218function linkToUs() { 219 let dom = location.hostname 220 if (dom == 'localhost') dom = `localhost/${main.dom}` 221 let pgURL = dom + '/link-to-us.html' 222 pgURL += '?path=' + toHex(location.pathname + location.search) 223 pgURL += '&title=' + toHex(document.title) 224 console.log('dom', dom, pgURL) 225 226 window.location = 'https://' + pgURL 227} 228 229function donate() { 230 let pg = location.pathname 231 let pgHex = toHex(pg) 232 logSend(pgHex, 'donate', window.location.hostname) 233 234 let dom = location.hostname 235 if (dom == 'localhost') dom = `localhost/${main.dom}` 236 let pgURL = dom + '/donate.html' 237 238 window.location = 'https://' + pgURL 239} 240 241function citation() { 242 let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 243 let path = location.pathname + location.search // only relative path to avoid spoofing 244 let atitle = document.title 245 let md = new Date(document.lastModified) 246 let mDate = md.getDate() + ' ' + months[md.getMonth()] + ' ' + md.getFullYear() 247 // if (typeof Author == 'undefined') Author = 'Pierce, Rod' 248 let author = typeof Author == 'undefined' ? 'Pierce, Rod' : Author 249 250 let data = { path: path, title: atitle, moddate: mDate, author: author } 251 localStorage.setItem('citation', JSON.stringify(data)) 252 253 let urlStt = urlSttGet() 254 let to = urlStt + 'citation.html' 255 console.log('citation', data, to) 256 window.open(to) 257 // postWith(to, data) 258} 259 260function postWith(to, p) { 261 // TOOD: fetch() API 262 263 // from http://mentaljetsam.wordpress.com 264 let myForm = document.createElement('form') 265 myForm.method = 'post' 266 myForm.action = to 267 for (let k in p) { 268 let myInput = document.createElement('input') 269 myInput.setAttribute('name', k) 270 myInput.setAttribute('value', p[k]) 271 myForm.appendChild(myInput) 272 } 273 document.body.appendChild(myForm) 274 myForm.submit() 275 document.body.removeChild(myForm) 276} 277 278function URLEncode(text) { 279 // The Javascript escape and unescape functions do not correspond with what browsers actually do... 280 let SAFECHARS = '0123456789' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + "-_.!~*'()" // numeric,alpha, and RFC2396 Mar
280k characters 281 let HEX = '0123456789ABCDEF' 282 283 let s = '' 284 for (let i = 0; i < text.length; i++) { 285 let ch = text.charAt(i) 286 if (ch == ' ') { 287 s += '+' // x-www-urlencoded, rather than %20 288 } else if (SAFECHARS.indexOf(ch) != -1) { 289 s += ch 290 } else { 291 let charCode = ch.charCodeAt(0) 292 if (charCode > 255) { 293 // Unicode cannot be encoded using standard URL encoding 294 s += '+' 295 } else { 296 s += '%' 297 s += HEX.charAt((charCode >> 4) & 0xf) 298 s += HEX.charAt(charCode & 0xf) 299 } 300 } 301 } 302 303 return s 304} 305 306function copyToClipboard(txtArea) { 307 txtArea.focus() 308 txtArea.select() 309 let copiedTxt = document.selection.createRange() 310 copiedTxt.execCommand('Copy') 311} 312 313function toHex(s) { 314 let hex = '' 315 for (let i = 0; i < s.length; i++) { 316 let cc = s.charCodeAt(i).toString(16) 317 if (cc.length < 2) cc = '0' + cc 318 hex += '' + cc 319 } 320 return hex 321} 322 323/* ads */ 324 325function adOffQ() { 326 let item = localStorage.getItem('adoff') 327 if (item === null) return false 328 329 let object = JSON.parse(item) 330 let dateString = object.when 331 let until = new Date(dateString) 332 // until.setTime(until.getTime() + 1 * 60 * 1000) // plus 1 minute 333 until.setTime(until.getTime() + 8 * 24 * 60 * 60 * 1000) // plus 8 days 334 335 let now = new Date() 336 //console.log('adOffGet', until, now, until > now) 337 338 return until > now 339} 340 341function adOffSet() { 342 let object = { value: false, when: new Date() } 343 localStorage.setItem('adoff', JSON.stringify(object)) 344 location.reload() // reload page so "no ads" takes effect 345} 346 347function adOffReset() { 348 let object = { value: false, when: 0 } 349 localStorage.setItem('adoff', JSON.stringify(object)) 350 location.reload() // reload page so it takes effect 351} 352 353function adsSet(onQ) { 354 let adIDs = [ 355 { id: 'adTop', withAdsQ: true }, 356 { id: 'adend', withAdsQ: true }, 357 { id: 'adsHide1', withAdsQ: true }, 358 { id: 'adsShow1', withAdsQ: false }, 359 ] 360 361 //let styles = ['none', 'inline']; 362 for (let i = 0; i < adIDs.length; i++) { 363 let ad = adIDs[i] 364 let div = document.getElementById(ad.id) 365 if (div) { 366 let showQ = ad.withAdsQ 367 if (!onQ) showQ = !showQ 368 if (showQ) { 369 div.style.display = 'inline' 370 } else { 371 div.style.display = 'none' 372 } 373 //console.log('adsSet', ad, div, showQ) 374 } 375 } 376} 377 378function adsHideHTML() { 379 let s = '' 380 s += '<div id="adsShow1"><a href="javascript:adsShow()">Show Ads</a></div>' 381 s += '<div id="adsHide1"><a href="javascript:adsHide()">Hide Ads</a> | ' 382 383 let imgHome = location.hostname == 'localhost' ? `/${main.dom}/` : '/' 384 s += '<a href="' + imgHome + 'about-ads.html">About Ads</a> </div>' 385 386 return s 387} 388 389// do hide ads based on local storage 390function adsHideSet() { 391 if (adOffQ()) { 392 adsSet(false) 393 } 394} 395 396function adsHide() { 397 adsSet(false) 398 adOffSet() 399} 400 401function adsShow() { 402 adsSet(true) 403 adOffReset() 404} 405 406function adsDo() { 407 let adType = 'adsense' 408 409 if (location.hostname == 'localhost') adType = 'dummy' 410 if (location.hostname == '127.0.0.1') adType = 'dummy' 411 412 console.log('adsDo', location.hostname, adType) 413 414 if (adType == 'adsense') { 415 adsenseDo() 416 } 417 418 if (adType == 'dummy') { 419 adsDummy() 420 } 421} 422 423function adsDummy() { 424 let dummy = document.createElement('img') 425 dummy.setAttribute('src', `/${main.dom}/images/style/320x60.jpg`) 426 dummy.setAttribute('alt', 'dummy image') 427 dummy.setAttribute('width', '320') 428 dummy.setAttribute('height', '60') 429 console.log('adsDummy', adsDummy) 430 431 let dest = document.getElementById('adend') 432 dest.appendChild(dummy) 433} 434 435function adsenseDo() { 436 // as recommended by gemini ... testing! 437 if (document.body.classList.contains('minimal')) { 438 console.log('No Ads due to Minimal') 439 return 440 } 441 442 // 1. Inject main AdSense script if not already present 443 let script = document.querySelector('script[src*="adsbygoogle.js"]') 444 if (!script) { 445 script = document.createElement('script') 446 script.async = true 447 script.crossOrigin = 'anonymous' 448 script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${adsense}}` 449 document.head.appendChild(script) 450 } 451 452 // 2. Target destination 453 let dest = document.getElementById('adend') 454 if (!dest) { 455 console.error("Element with id 'adend' not found!") 456 return 457 } 458 459 // 3. Create and append the <ins> tag 460 let ins = document.createElement('ins') 461 ins.className = 'adsbygoogle' 462 ins.style.display = 'block' 463 ins.dataset.adClient = adsense 464 ins.dataset.adSlot = '2009442555' 465 ins.dataset.adFormat = 'auto' 466 ins.dataset.fullWidthResponsive = 'true' 467 dest.appendChild(ins) 468 469 // 4. Trigger the ad push directly in JavaScript 470 ;(window.adsbygoogle = window.adsbygoogle || []).push({}) 471 472 console.log('adsenseDo: done') 473} 474 475 476function adsenseDoRP() { 477 if (document.body.classList.contains('minimal')) { 478 console.log('No Ads due to Minimal') 479 return 480 } 481 482 // Create and append AdSense script, but only once 483 let script = document.querySelector('script[src*="adsbygoogle.js"]') 484 if (!script) { 485 script = document.createElement('script') 486 script.async = true 487 script.crossOrigin = 'anonymous' 488 // script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1389989178296449' 489 script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${adsense}}` 490 491 document.head.appendChild(script) 492 } 493 494 // Wait for script to load before pushing ad 495 //script.onload = () => { 496 let ins = document.createElement('ins') 497 ins.className = 'adsbygoogle' 498 ins.style.display = 'block' 499 ins.dataset.adClient = adsense 500 ins.dataset.adSlot = '2009442555' 501 ins.dataset.adFormat = 'auto' 502 ins.dataset.fullWidthResponsive = 'true' 503 504 let dest = document.getElementById('adend') 505 console.log('dest', dest) 506 if (dest) { 507 dest.appendChild(ins) 508 509 let inlineScript = document.createElement('script') 510 inlineScript.text = '(adsbygoogle = window.adsbygoogle || []).push({})' 511 dest.appendChild(inlineScript) 512 513 console.log('adsenseDo: done') 514 } else { 515 console.error("Element with id 'adend' not found!") 516 } 517} 518 519function printImg(s) { 520 window.open(s, '_blank') 521 setTimeout('pwin.print()', 2000) 522} 523 524/* Question Database */ 525function doQ(qid, qs) { 526 let fromPath = location.pathname + location.search // only relative path to avoid spoofing 527 let title = document.title 528 let url = 'http://www.mathopolis.com/questions/q.html?qid=' + parseInt(qid) + '&t=mif' 529 if (typeof qs == 'undefined') { 530 // url += '&qs=0' 531 } else { 532 url += '&qs=' + qs 533 } 534 url += '&site=1' + '&ref=' + toHex(fromPath) + '&title=' + toHex(title) 535 window.open(url, 'mathopolis') 536} 537 538function doQLocal(qid, s) { 539 let wd = Math.min(window.innerWidth - 50, 800) 540 let urlStt = urlSttGet() // to cope with pages at root or one level down 541 542 console.log('wd', wd, s, '(' + urlStt + ')') 543 window.open(urlStt + 'quiz/q.html?qid=' + parseInt(qid) + '&qs=' + s, 'popup', 'width=' + wd + ',height=600,left=50') 544 // http://localhost/${main.dom}/quiz/q.html?678_679_1424_1425_1429_2149_2150_3765_3766_3767 545} 546 547function getQ() { 548 let qs = '' 549 for (let i = 0; i < arguments.length; i++) { 550 if (i > 0) qs += '_' 551 qs += arguments[i] 552 } 553 let s = '' 554 for (let i = 0; i < arguments.length; i++) { 555 s += '<a href="javascript:doQ(' + arguments[i] + ",'" + qs + "'" + ')">Question ' + (i + 1) + '</a> ' 556 } 557 document.write(s) 558} 559 560function getQueryVar(varName, defaultVal = '') { 561 let parms = new URLSearchParams(window.location.search) 562 let val = parms.get(varName) 563 564 return val !== null ? val : defaultVal 565} 566 567function urlSttGet() { 568 // let stack = location.href.split("/"); 569 // stack.pop(); // remove current file name (or empty string) 570 // let url = stack.join("/"); // remove filename 571 572 let url = location.href 573 574 url = url.replace('localhost/', '') // first remove any 'localhost/' to have consistent '/' count 575 let slashN = url.split('/').length - 4 // next count '/', and subtract 3 from 'https://www.${main.dom}.com/' and 1 because split has extra 1 576 577 let urlStt = '' 578 for (let i = 0; i < slashN; i++) urlStt += '../' 579 580 //console.log('urlSttGet', location.href, slashN, url, urlStt) 581 582 return urlStt 583} 584 585function menuHTML(type) { 586 // cope with urls at root or down one folder NB: what about URLs down two folders? 587 588 let urlStt = urlSttGet() // to cope with pages at root or one level down 589 590 let links = [ 591 ['index.html', 'Inicio', 0], 592 ['algebra/index.html', 'Ãlgebra', 0], 593 ['calculo/index.html', 'Cálculo', 1], 594 ['datos/index.html', 'Datos', 0],
595 ['geometria/index.html', 'GeometrÃa', 0], 596 ['medida/index.html', 'Medida', 0], 597 ['dinero/index.html', 'Dinero', 1], 598 ['numeros/index.html', 'Números', 0], 599 ['fisica/index.html', 'FÃsica', 0], 600 ['actividades/index.html', 'Actividades', 1], 601 ['definiciones/index.html', 'Diccionario', 0], 602 ['juegos/index.html', 'Juegos', 0], 603 ['puzzles/index.html', 'Puzzles', 0], 604 ['ejercicios/index.php', 'Ejercicios', 1], 605 ] 606 607 let s = '' 608 609 let linkLen = links.length 610 let i 611 if (type == 0) { 612 s += '<ul role="list">' 613 for (i = 0; i < linkLen; i++) { 614 if (links[i][2] == 0) { 615 s += '<li role="listitem" tabindex="0"><a href="' + urlStt + links[i][0] + '">' + links[i][1] + '</a></li>' 616 s += '\n' 617 } 618 } 619 //s += '<li role="listitem" tabindex="0">'; 620 //s += '</li>'; 621 s += '</ul>' 622 } 623 if (type == 1) { 624 // bar menu at top 625 s += '<div class="menuPop">' 626 627 let ht = 36 628 629 // close 630 s += '<div class="menuCol">' 631 s += `<a href="javascript:adsHide()">` 632 s += '<img src="' + urlStt + 'images/style/no.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Calculator" />' 633 s += '</a>' 634 s += '</div>' 635 636 // home 637 //s += '<li><a role="listitem" href="' + urlStt + links[0][0] + '">' + links[0][1] + '</a></li>' 638 s += '<div class="menuCol">' 639 s += '<a href="' + urlStt + 'index.htm" style="text-decoration: none; padding:0px;" aria-label="Home">' 640 s += '<img src="' + urlStt + 'images/style/home.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Home" />' 641 s += '</a>' 642 s += '</div>' 643 644 // search 645 s += '<div class="menuCol">' 646 s += '<a href="' + urlStt + 'search/search.html" style="text-decoration: none; padding:0px;" aria-label="Show Search">' 647 s += '<img src="' + urlStt + 'images/style/search.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Search" />' 648 s += '</a>' 649 s += '</div>' 650 651 // subjects 652 s += '<div class="menuCol">' 653 for (i = 1; i <= 8; i++) { 654 s += '<a role="listitem" href="' + urlStt + links[i][0] + '">' + links[i][1] + '</a><br>' 655 } 656 s += '</div>' 657 658 // more 659 s += '<div class="menuCol">' 660 for (i = 9; i < links.length; i++) { 661 s += '<a role="listitem" href="' + urlStt + links[i][0] + '">' + links[i][1] + '</a><br>' 662 } 663 s += '</div>' 664 665 // Ads 666 667 // Determine root path based on hostname 668 let imgHome = location.hostname === 'localhost' ? `/${main.dom}/` : '/' 669 670 // Create Ads dropdown menu 671 s += '<div class="menuCol">' 672 s += `<a href="javascript:adsHide()">Hide Ads</a><br>` 673 s += `<a href="javascript:adsShow()">Show Ads</a><br>` 674 s += `<a href="${imgHome}about-ads.html">About Ads</a><br>` 675 s += `<a href="${imgHome}donate.html">Donate</a><br>` 676 s += '</div>' 677 678 // calculator 679 s += '<div class="menuCol">' 680 s += '<a href="' + urlStt + 'numbers/calculator.html" style="text-decoration: none; padding:0px;" aria-label="Show Calculator">' 681 s += '<img src="' + urlStt + 'images/style/calculator.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Calculator" />' 682 s += '</a>' 683 s += '</div>' 684 685 s += '</div>' 686 } 687 688 if (type == 11) { 689 // bar menu at top 690 s += '<ul role="list" class="bga1" style="display:inline-block; margin-top:25px; padding:6px; border-radius:10px; border: 1px solid; z-index:2; box-shadow: 3px 2px; ">' 691 692 let ht = 36 693 694 // home 695 //s += '<li><a role="listitem" href="' + urlStt + links[0][0] + '">' + links[0][1] + '</a></li>' 696 s += '<li style=" padding:0px 7px;">' 697 s += '<a href="' + urlStt + 'index.htm" style="text-decoration: none; padding:0px;" aria-label="Home">' 698 s += '<img src="' + urlStt + 'images/style/home.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Home" />' 699 s += '</a>' 700 s += '</li>' 701 702 // search 703 s += '<li style=" padding:0px 7px;">' 704 s += '<a href="' + urlStt + 'search/search.html" style="text-decoration: none; padding:0px;" aria-label="Show Search">' 705 s += '<img src="' + urlStt + 'images/style/search.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Search" />' 706 s += '</a>' 707 s += '</li>' 708 709 // subjects 710 s += '<li><a role="listitem" href="#">Subjects ▼</a>' 711 s += '<ul>' 712 for (i = 1; i <= 8; i++) { 713 s += '<li><a role="listitem" href="' + urlStt + links[i][0] + '">' + links[i][1] + '</a></li>' 714 } 715 s += '</ul>' 716 s += '</li>' 717 718 // more 719 s += '<li><a role="listitem" href="#">More ▼</a>' 720 s += '<ul role="list">' 721 for (i = 9; i < links.length; i++) { 722 s += '<li><a role="listitem" href="' + urlStt + links[i][0] + '">' + links[i][1] + '</a></li>' 723 } 724 s += '</ul>' 725 s += '</li>' 726 727 // Ads 728 729 // Determine root path based on hostname 730 let imgHome = location.hostname === 'localhost' ? `/${main.dom}/` : '/' 731 732 // Create Ads dropdown menu 733 s += '<li><a href="#">Ads ▼</a>' 734 s += '<ul>' 735 s += `<li><a href="javascript:adsHide()">Hide Ads</a></li>` 736 s += `<li><a href="javascript:adsShow()">Show Ads</a></li>` 737 s += `<li><a href="${imgHome}about-ads.html">About Ads</a></li>` 738 s += `<li><a href="${imgHome}donate.html">Donate</a></li>` 739 s += '</ul>' 740 s += '</li>' 741 742 // s += '<li><a role="listitem" href="#">Ads ▼</a>' 743 // s += '<ul>' 744 745 // //s += '<li><a role="listitem" href="' + urlStt + links[i][0] + '">' + links[i][1] + '</a></li>' 746 // let imgHome = location.hostname == 'localhost' ? `/${main.dom}/` : '/' 747 748 // s += '<li><a role="listitem" href="javascript:adsHide()">Hide</a></li>' 749 // s += '<li><a role="listitem" href="javascript:adsShow()">Show</a></li>' 750 // s += '<li><a href="' + imgHome + 'contribute.html">
750Contribute</a></li>' 751 // s += '<li><a href="' + imgHome + 'about-ads.html">About Ads</a></li>' 752 // // s += '<a href="' + imgHome + 'about-ads.html">About Ads</a> </div>' 753 754 // s += '</ul>' 755 // s += '</li>' 756 757 // s += '<li style="margin:0; padding:0px 7px;">' 758 // s += '<a href="javascript:adsHide()" style="margin:0; padding:0px 0px;">Hide Ads</a>' 759 //s += '<br>' 760 //s += '<a href="javascript:adsHide()" style="margin:0; padding:0px 0px;">Contribute</a>' 761 762 // s += adsHideHTML() 763 // s += '</li>' 764 765 // calculator 766 s += '<li style=" padding:0px 7px;">' 767 s += '<a href="' + urlStt + 'numbers/calculator.html" style="text-decoration: none; padding:0px;" aria-label="Show Calculator">' 768 s += '<img src="' + urlStt + 'images/style/calculator.svg" style="height:' + ht + 'px; vertical-align:middle;" alt="Calculator" />' 769 s += '</a>' 770 s += '</li>' 771 772 s += '</ul>' 773 } 774 if (type == 2) { 775 s += '<div id="login" style="display: inline-block; vertical-align:top; text-align:center; line-height:110%; padding:4px; ">' 776 777 s += '</div>' 778 779 s += ' ' 780 s += '<a href="javascript:menuShow()" style="text-decoration: none;" aria-label="Show Menu">' 781 s += '<img src="' + urlStt + 'images/style/menu.svg" width="39" height="39" class="hov" alt="Menu" />' 782 s += '</a>' 783 784 s += '<div id="loginPop" class="pop" style="position:absolute; width:300px; left:200px; opacity:0;">' 785 s += '<div id="loginContent" style="width:95%; margin: 0 0 5px 10px;"> </div>' 786 s += '<div id="loginMsg" style="width:95%; margin: 0 0 5px 10px;"> </div>' 787 s += '<button onclick="loginHide()" style="z-index:2;" class="btn" >Close</button>' 788 s += '</div>' 789 } 790 791 // alert(s); 792 //console.log('getMenu',typ, s) 793 return s 794} 795 796function menuShow() { 797 console.log('menuShow', menuVis) 798 let menuDiv = document.getElementById('menuSlim') 799 let srchDiv = document.getElementById('search') 800 if (menuVis) { 801 menuDiv.style.display = 'none' 802 srchDiv.style.visibility = 'visible' 803 // srchDiv.style.display = 'block' 804 } else { 805 menuDiv.style.display = 'block' 806 srchDiv.style.visibility = 'hidden' 807 // srchDiv.style.display = 'none' 808 // if (searchVis) searchShow() // turn off 809 } 810 menuVis = !menuVis 811} 812 813function searchShow() { 814 console.log('searchShow', searchVis) 815 let div = document.getElementById('search') 816 if (searchVis) { 817 div.style.display = 'none' 818 div.style.position = 'relative' 819 } else { 820 div.style.display = 'block' 821 div.style.position = 'absolute' 822 if (menuVis) menuShow() // turn off 823 } 824 searchVis = !searchVis 825} 826 827function translateHTML() { 828 //return '' 829 let s = '' 830 //let url = location.href; // safe? 831 s += '<div id="google_translate_element"></div>' 832 s += '<script type="text/javascript">' 833 s += ' function googleTranslateElementInit() {' 834 s += "new google.translate.TranslateElement({pageLanguage: 'en', layout: google.translate.TranslateElement.InlineLayout.SIMPLE}, 'google_translate_element');" 835 s += '}' 836 if (false) { 837 s += '</script>' 838 s += ' <script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>' 839 } else { 840 console.log('async translate2') 841 s += 'let googleTranslateScript = document.createElement("script");' 842 s += 'googleTranslateScript.type = "text/javascript";' 843 s += 'googleTranslateScript.async = true;' 844 s += 'googleTranslateScript.src = "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";' 845 s += '( document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0] ).appendChild(googleTranslateScript);' 846 s += '</script>' 847 } 848 return s 849} 850 851function cookMsg() { 852 let s = '' 853 854 let imgHome = location.hostname == 'localhost' ? `/${main.dom}/` : '/' 855 s += 'We may use <a href="' + imgHome + 'about-ads.html">Cookies</a> ' 856 s += '<div class="btn" style="display:inline-block; cursor: pointer;" onclick="cookOK()">OK</div>' 857 858 return s 859} 860 861function cookOK() { 862 console.log('cookOK') 863 document.getElementById('cookOK').style.display = 'none' 864 localStorage.setItem('cookie', 'ok') 865} 866 867// function docInsert(s) { 868// let div = document.createElement('div') 869// div.innerHTML = s 870// let script = document.currentScript 871// script.parentElement.insertBefore(div, script); // Add the newly-created div before script location 872// } 873 874function linksHTML() { 875 const url = encodeURIComponent(location.href) 876 const title = encodeURIComponent(document.title) 877 const baseLink = (href, title, id) => `<a target="_blank" rel="noopener nofollow" href="${href}" title="${title}" id="${id}" aria-label="${title}"></a>`
878 879 // Define social media share URLs 880 let facebookLink = baseLink(`https://www.facebook.com/sharer/sharer.php?u=${url}&t=${title}`, 'Share on Facebook', 'linkfb') 881 let twitterLink = baseLink(`https://x.com/intent/tweet?url=${url}&text=${title}`, 'Tweet it on X', 'linktw') 882 let whatsappLink = baseLink(`https://wa.me/?text=${title} ${url}`, 'Share on WhatsApp', 'linkwa') 883 let pinterestLink = baseLink(`https://www.pinterest.com/pin/create/button/?url=${url}&description=${title}`, 'Pin it', 'linkpi') 884 885 // Construct links HTML 886 let links = '' 887 links += facebookLink 888 links += twitterLink 889 links += whatsappLink 890 //links += pinterestLink 891 links += '<a href="javascript:tellAFriend()" id="linkem" title="eMail a Friend" aria-label="eMail a Friend"></a>' 892 links += '<a href="javascript:linkToUs()" id="linkus" title="Link To Us" aria-label="Link To Us"></a>' 893 //links += '<a href="javascript:donate()" id="linkdo" title="Donate" aria-label="Donate"></a>' 894 895 return links 896} 897 898function footerHTML() { 899 let s = '' 900 let urlStt = urlSttGet() 901 let sep = ' ○ ' 902 s += '<div id="footMenu">' 903 s += '<a href="' + urlStt + 'search/search.html">Búsqueda</a>' + sep 904 s += '<a href="' + urlStt + 'index.html">Ãndice</a>' + sep 905 s += '<a href="' + urlStt + 'aboutus.html">Acerca de</a>' + sep 906 s += '<a href="' + urlStt + 'contact.html">Contacto</a>' + sep 907 s += '<a href="javascript:citation()">Cita esta página</a>' + sep 908 s += '<b><a href="' + urlStt + 'privacy.html">Privacidad</a></b>' 909 s += '</div>' 910 return s 911} 912 913function themeChgHTML() { 914 let s = '' 915 s += '<label aria-label="Toggle theme">' 916 s += '<input type="checkbox" onchange="themeChg()" id="themeSlider">' 917 s += '<span id="themeSlider1" class="round"></span>' 918 s += '</label>' 919 return s 920} 921 922function themeChg() { 923 let was = themeGet() 924 let theme = was == 'lite' ? 'dark' : 'lite' 925 themeSet(theme) 926 document.getElementById('themeSlider').checked = theme == 'dark' 927} 928 929function themeGet() { 930 let theme = localStorage.getItem('theme') 931 if (theme !== 'lite' && theme !== 'dark') { 932 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches 933 theme = prefersDark ? 'dark' : 'lite' 934 localStorage.setItem('theme', theme) 935 } 936 return theme 937} 938 939function themeSet(theme) { 940 localStorage.setItem('theme', theme) 941 // console.log('themeSet', theme, document.documentElement.hasAttribute('theme')) 942 // if (document.documentElement.hasAttribute('theme')) { 943 // document.documentElement.removeAttribute('theme') 944 // } else { 945 document.documentElement.setAttribute('theme', theme) 946 // } 947} 948 949// function gAnalyticsStr() { 950// let s = ""; 951// s += '<script type="text/javascript">'; 952// s += 'let _gaq = _gaq || [];'; 953// s += "_gaq.push(['_setAccount', 'UA-29771508-1']);"; 954// s += "_gaq.push(['_trackPageview']);"; 955// s += '(function() {'; 956// s += "let ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;"; 957// s += "ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';"; 958// s += "let s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);"; 959// s += '})();'; 960// s += '</script>'; 961// return s; 962// } 963 964function logErr() { 965 let pg = location.pathname 966 let pgHex = toHex(pg) 967 logSend(pgHex, 'err', window.location.hostname) 968} 969 970function logVisit() { 971 let pg = location.pathname 972 if (pg == '/') return // NB because getting too many root page visits for some reason. 973 let pgHex = toHex(pg) 974 logSend(pgHex, 'visit', window.location.hostname) 975} 976if (Math.random() <= 1) logVisit() 977 978function logSend(pg, viewtype, hostname) { 979 let req = new XMLHttpRequest() 980 981 let browser = (function (agent) { 982 switch (true) { 983 case agent.indexOf('edge') > -1: 984 return 'EdgeOld' 985 case agent.indexOf('edg') > -1: 986 return 'Edge' 987 case agent.indexOf('opr') > -1 && !!window.opr: 988 return 'Opera' 989 case agent.indexOf('chrome') > -1 && !!window.chrome: 990 return 'Chrome' 991 case agent.indexOf('trident') > -1: 992 return 'IE' 993 case agent.indexOf('firefox') > -1: 994 return 'Firefox' 995 case agent.indexOf('safari') > -1: 996 return 'Safari' 997 default: 998 return 'Other' 999 } 1000 })(window.navigator.userAgent.toLowerCase()) 1001 let os = '' 1002 let tz = Intl.DateTimeFormat().resolvedOptions().timeZone
1003 let agent = navigator.userAgent.substring(0, 90) 1004 1005 let params = 'type=' + viewtype 1006 params += '&site=dlm' 1007 params += '&pg=' + encodeURIComponent(pg) 1008 params += '&lang=' + encodeURIComponent(window.navigator.language) 1009 params += '&ref=' + encodeURIComponent(toHex(document.referrer)) 1010 params += '&wd=' + parseInt(window.innerWidth) 1011 params += '&ht=' + parseInt(window.innerHeight) 1012 params += '&ht=' + parseInt(window.innerHeight) 1013 params += '&browser=' + encodeURIComponent(browser) 1014 params += '&os=' + encodeURIComponent(os) 1015 params += '&tz=' + encodeURIComponent(tz) 1016 params += '&agent=' + encodeURIComponent(agent) 1017 1018 console.log('logSend', hostname) 1019 1020 req.open('POST', 'https://mi2f.com/update.php', true) // NB: false=synchronous 1021 1022 req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') 1023 //req.setRequestHeader("Content-length", params.length); 1024 //req.setRequestHeader("Connection", "close"); 1025 req.send(params) 1026} 1027 1028//window.addEventListener("scroll", onScroll); 1029function onScroll() { 1030 let scrollTop = document.documentElement ? document.documentElement.scrollTop : document.body.scrollTop 1031 let menu = document.getElementById('menu') 1032 //alert(scrollTop); 1033 if (scrollTop > 100) { 1034 menu.style.top = '0px' 1035 menu.style.width = '1000px' 1036 //menu.style.margin = "0 auto 0 auto"; 1037 //menu.style.borderBottom = "2px solid #ffffff"; 1038 menu.style.border = '1px solid #ffffff' 1039 menu.style.boxShadow = '0px 12px 12px -12px #77cc77' 1040 menu.style.backgroundColor = 'white' 1041 //menu.style.right = "0"; 1042 //menu.style.left = "0"; 1043 menu.style.textAlign = 'center' 1044 menu.style.position = 'fixed' 1045 menu.style.zIndex = '5000' 1046 } else { 1047 //menu.style.backgroundColor = "transparent"; 1048 menu.style.boxShadow = 'none' 1049 menu.style.position = 'static' 1050 } 1051} 1052 1053function relatedLinks() { 1054 //this.id = typeof id !== 'undefined' ? id : "searchBox"; 1055 1056 if (window.innerWidth < 860) { 1057 return // don't bother if screen not wide enough 1058 } 1059 let rels = document.getElementsByClassName('related') 1060 if (rels.length == 0) { 1061 return 1062 } 1063 let rel = rels[0] 1064 1065 let links = rel.getElementsByTagName('a') 1066 let count = links.length 1067 1068 let right = null 1069 let left = null 1070 if (count == 1) right = links[0] 1071 if (count > 1) { 1072 left = links[0] 1073 right = links[1] 1074 } 1075 1076 let s = '' 1077 1078 let top = 160 1079 1080 if (left != null) { 1081 s += '<div style="position: absolute; top: ' + top + 'px; left: 0px; text-align: left; z-index:2;">' 1082 s += fmtMenuBox(left.href, left.text, 0) 1083 s += '</div>' 1084 } 1085 if (right != null) { 1086 s += '<div style="position: absolute; top: ' + top + 'px; right: 0px; text-align: right; z-index:2;">' 1087 s += fmtMenuBox(right.href, right.text, 1) 1088 s += '</div>' 1089 } 1090 1091 document.getElementById('stt').innerHTML += s 1092 //document.getElementById("header").innerHTML += s; // NB stops google translate from working 1093 //document.body.innerHTML = s + document.body.innerHTML; 1094 //document.getElementById("midfull").innerHTML += s; 1095} 1096 1097function fmtMenuBox(url, txt, dirn) { 1098 let s = '' 1099 let boxID = dirn == 0 ? 'menuLt' : 'menuRt' 1100 s += '<a href="' + url + '" style="text-decoration: none; color: #8ac; line-height:1.3rem; ">' 1101 s += '<div id="' + boxID + '">' 1102 s += txt 1103 s += '</div>' 1104 s += '</a>' 1105 return s 1106} 1107 1108function visFlip(id) { 1109 let div = document.getElementById(id) 1110 console.log('visFlip', id, div.style.visibility) 1111 if (div.style.visibility === 'hidden') { 1112 div.style.visibility = 'visible' 1113 } else { 1114 div.style.visibility = 'hidden' 1115 } 1116} 1117 1118function vidDoLiteral(id, spanid) { 1119 let visIndex = videoVis.indexOf(spanid) 1120 let visQ = visIndex > -1 ? true : false 1121 1122 if (visQ) { 1123 // Turn off 1124 let frame = document.getElementById(spanid + 'v1') 1125 if (frame) frame.parentNode.removeChild(frame) 1126 videoVis.splice(visIndex, 1) 1127 visQ = false 1128 1129 // Optional: Clear the container content so the border disappears? 1130 // document.getElementById(spanid).innerHTML = ''; 1131 } else { 1132 // Turn on 1133 // Using Template Literal (Backticks) for cleaner HTML 1134 let s = ` 1135 <div style="width:100%; clear:both;"></div> 1136 <iframe id="${spanid}v1" 1137 src="https://www.youtube-nocookie.com/embed/${id}?rel=0&autoplay=1&modestbranding=1" 1138 frameborder="0" 1139 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 1140 allowfullscreen 1141 style="vertical-align:top;"> 1142 </iframe> 1143 <div style="width:100%;"></div> 1144 ` 1145 1146 let vid = document.getElementById(spanid) 1147 vid.innerHTML = s 1148 //vid.style.border = '5px solid blue;' // Keeping your test border for now 1149 1150 videoVis.push(spanid) 1151 visQ = true 1152 1153 // Resize immediately 1154 resizeVideo(spanid) 1155 } 1156} 1157 1158function vidDo(id, spanid) { 1159 let s = '' 1160 1161 let visIndex = videoVis.indexOf(spanid) 1162 let visQ = visIndex > -1 ? true : false // is it currently visible? 1163 1164 if (visQ) { 1165 // turn off 1166 let frame = document.getElementById(spanid + 'v1') 1167 if (frame) frame.parentNode.removeChild(frame) 1168 videoVis.splice(visIndex, 1) 1169 visQ = false 1170 } else { 1171 // turn on 1172 s += '<div style="clear:both;"></div>' 1173 1174 s += `<iframe id="${spanid}v1" 1175 src="https://www.youtube-nocookie.com/embed/${id}?rel=0&autoplay=1" 1176 frameborder="0" 1177 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
1178 allowfullscreen 1179 style="vertical-align:top;"></iframe>` 1180 1181 s += '<div style="clear:both;""></div>' 1182 videoVis.push(spanid) 1183 visQ = true 1184 console.log('vidDo', s) 1185 } 1186 1187 let vid = document.getElementById(spanid) 1188 vid.innerHTML = s 1189 1190 // we can likely remove the border now, but keep for testing: 1191 //vid.style.border = '5px solid blue;' 1192 console.log('vidDo', vid) 1193 1194 if (visQ) vidResize(spanid) 1195} 1196 1197function vidResize(spanid) { 1198 let v1 = document.getElementById(spanid + 'v1') 1199 if (!v1) return 1200 1201 // let wd = window.innerWidth - 40 1202 let contentBox = document.getElementById('content') 1203 // We subtract a small buffer (e.g. 5px) just to prevent rounding errors causing scrollbars 1204 let wd = contentBox ? contentBox.offsetWidth - 5 : window.innerWidth - 40 1205 if (wd > 800) wd = 800 1206 console.log('vidResize', wd) 1207 1208 v1.style.width = wd + 'px' 1209 // v1.style.height = wd * (340 / 640) + 80 + 'px' 1210 v1.style.height = (wd * 9) / 16 + 'px' 1211} 1212 1213let videoVis = [] 1214 1215function mainDo() { 1216 // Dynamically add hreflang tag to the head section 1217 // <link rel="alternate" hreflang="en" href="..." /> 1218 let hreflangLink = document.createElement('link') 1219 hreflangLink.rel = 'alternate' 1220 hreflangLink.hreflang = 'en' 1221 hreflangLink.href = window.location.href 1222 document.head.appendChild(hreflangLink) 1223 1224 if (document.getElementById('menuWide') == null) return 1225 1226 document.getElementById('menuWide').innerHTML = menuHTML(0) 1227 document.getElementById('menuSlim').innerHTML = menuHTML(1) 1228 document.getElementById('menuTiny').innerHTML = menuHTML(2) 1229 1230 document.getElementById('adHide').innerHTML = adsHideHTML() 1231 if (localStorage.getItem('cookie') != 'ok') { 1232 document.getElementById('cookOK').innerHTML = cookMsg() 1233 } 1234 1235 document.getElementById('search').innerHTML = searchHTML() 1236 document.getElementById('linkto').innerHTML = linksHTML() 1237 1238 let themeDiv = document.getElementById('themeChg') 1239 if (themeDiv) { 1240 themeDiv.innerHTML = themeChgHTML() 1241 } else { 1242 let newDiv = document.createElement('div') 1243 newDiv.id = 'themeChg' 1244 1245 let logoDiv = document.getElementById('logo') 1246 if (logoDiv) { 1247 console.log('ADD theme div') 1248 logoDiv.insertAdjacentElement('afterend', newDiv) 1249 newDiv.innerHTML = themeChgHTML() 1250 } 1251 } 1252 1253 document.getElementById('themeSlider').checked = themeGet() == 'dark' // must be on page before using 1254 1255 document.getElementById('footer').innerHTML = footerHTML() 1256 1257 let qDivs = document.querySelectorAll('.questions') 1258 //console.log('qDivs', qDivs) 1259 for (let i = 0; i < qDivs.length; i++) { 1260 qsExpand(qDivs[i]) 1261 } 1262 1263 let vidDivs = document.querySelectorAll('.video') 1264 //console.log('vidDivs', vidDivs) 1265 for (let i = 0; i < vidDivs.length; i++) { 1266 vidExpand(vidDivs[i], i) 1267 } 1268 1269 let scriptDivs = document.querySelectorAll('.script') 1270 //console.log('scriptDivs', scriptDivs) 1271 for (let i = 0; i < scriptDivs.length; i++) { 1272 scriptExpand(scriptDivs[i]) 1273 } 1274 1275 adsHideSet() 1276 1277 if (!adOffQ()) { 1278 setTimeout(adsDo, 2000) 1279 } 1280 1281 doLocal() 1282 eqFmts() 1283 document.getElementById('searchFld').addEventListener('keyup', searchKey) 1284 1285 main.sock = new MainSock() 1286 if (typeof mainMsg === 'function') mainMsg({ msg: 'ready' }) 1287 1288 loginPrompt() 1289 console.log('loginName', loginName()) 1290} 1291 1292function mainVar() { 1293 return main 1294} 1295 1296window.addEventListener('message', function (event) { 1297 if (event.origin !== window.location.origin) return 1298 // console.log('>> main4 rcvd', event.data) 1299 1300 if (event.data && event.data.msg) { 1301 let msg = event.data.msg 1302 1303 // if (msg == 'gamesList') { 1304 main.sock.send(JSON.stringify(event.data)) 1305 // } 1306 } 1307 1308 // if (event.data === 'iframe-ready') { 1309 // let iframe = event.source 1310 // console.log('******* iframe',event) 1311 // // iframe.postMessage('main-recvd', '*') 1312 // main.iframes.push(iframe) 1313 // } 1314}) 1315 1316function searchHTML() { 1317 let s = '' 1318 s += '<form autocomplete="off" action="' + urlSttGet() + 'search/search.html" method="get">' 1319 s += '<input type="submit" id="searchBtn" name="submit" value="" aria-label="Search Button" />' 1320 s += '<input type="text" id="searchFld" name="query" value="" placeholder="Search" aria-label="Search" />' 1321 s += '<input type="hidden" name="search" value="1" />' 1322 s += '</form>' 1323 1324 return s 1325} 1326 1327function searchKey(ev) { 1328 let val = ev.target.value 1329 console.log('searchKey', val) 1330 1331 // run external script to handle autocomplete etc (better than adding heaps of code and possible errors to main script ) 1332 if (main.searchScript == 'no') { 1333 main.searchScript = 'loading' 1334 let script = document.createElement('script') 1335 script.onload = function () { 1336 console.log('Script loaded') 1337 searchDo(ev) 1338 main.searchScript = 'yes' 1339 } 1340 let imgHome = location.hostname == 'localhost' ? `/${main.dom}/` : '/' 1341 script.src = imgHome + 'search/images/search-lib.js' 1342 document.getElementsByTagName('head')[0].appendChild(script) 1343 } 1344 if (main.searchScript == 'yes') searchDo(ev) 1345 return 1346} 1347 1348function qsExpand(node) { 1349 let str = node.innerHTML 1350 if (str.includes('getQ(')) return // don't change an existing 'inline' getQ() style 1351 1352 console.log('qsExpand', str) 1353 let newStr = qsHTML(str) 1354 node.innerHTML = newStr 1355} 1356 1357function qsHTML(qStr) { 1358 let qs = qStr.trim().split(/[^0-9]+/) // use '+' (1 to many) of anything that is not a number as a separator 1359 let joinedStr = qs.join('_') 1360 let s = '' 1361 1362 s += '<span style="display:inline-block; min-width:8rem;">Mathopolis:</span>' 1363 for (let i = 0; i < qs.length; i++) { 1364 s += '<a href="javascript:doQ(' + qs[i] + ",'" + joinedStr + "'" + ')">Q' + (i + 1) + '</a> ' 1365 } 1366 1367 let path = location.pathname 1368 path = path.split(`/${main.dom}/`).join('/') // for local 1369 let dir = path.substring(0, path.lastIndexOf('/')) 1370 console.log('dir', path, dir) 1371 1372 // if (dir == '/data') { 1373 // s += '<br>' 1374 // s += '<span style="display:inline-block; min-width:8rem;">Local popup:</span>' 1375 // for (let i = 0; i < qs.length; i++) { 1376 // s += '<a href="javascript:doQLocal(' + qs[i] + ",'" + joinedStr + "'" + ')">Q' + (i + 1) + '</a> ' 1377 // } 1378 // } 1379 1380 return s 1381} 1382 1383function vidExpand(div, n) { 1384 let str = div.innerHTML 1385 if (str.trim().length <= 1) return // don't change an empty (old style) video tag 1386 1387 let newStr = vidHTML(str, 'title', 'video' + n) 1388 div.innerHTML = newStr 1389} 1390 1391function vidHTML(id, titleid = 'title', spanid = 'video', style = 'span') { 1392 // it is possible that a '-' gets converted to '−' in html, so revert 1393 id = id.replace(/−/g, '-') 1394 1395 console.log('vidHTML', id) 1396 1397 let s = '' 1398 switch (style) { 1399 case 'span': 1400 //s += '<div class="video">'; 1401 s += '<a href="javascript:vidDo(\'' + id + "','" + spanid + '\')">' 1402 s += '<img src="' + urlSttGet() + 'images/style/video.svg" alt="Video" style="width:56px; height:49px;vertical-align:middle; border:none;" />' 1403 s += '</a>' 1404 //s += '</div>'; 1405 s += '<span id="' + spanid + '" style=""></span>' 1406 break 1407 1408 case 'h1': 1409 let title = document.getElementById(titleid).innerHTML 1410 1411 s += '<div class="centerfull" style="clear:both; font-weight:400; padding: 0;">' 1412 s += '<div style="float:left; width:60px; text-align:left;">' 1413 s += ' <a href="javascript:vidDo(\'' + id + "','" + spanid + '\')">' 1414 s += ' <img src="' + urlSttGet() + 'images/style/video.svg" alt="Video" height="32" style="vertical-align:middle; border:none;" />' 1415 s += ' </a>' 1416 s += '</div>' 1417 s += '<div style="float:right; width:60px; text-align:right;"> </div>' 1418 s += ' <div style="margin:0 auto;">' 1419 s += ' <h1>' + title + '</h1>' 1420 s += ' </div>' 1421 s += '</div>' 1422 break 1423 1424 case 'h2': 1425 title = document.getElementById(titleid).innerHTML 1426 1427 s += '<div style="float:right; width:100px; margin: -10px 0 5px 0;">' 1428 s += ' <a href="javascript:vidDo(\'' + id + "','" + spanid + '\')">' 1429 s += ' <img src="' + urlSttGet() + 'images/style/video.svg" alt="Video" height="32" style="vertical-align:middle; border:none;" />' 1430 s += ' </a>' 1431 s += '</div>' 1432 s += ' <h2>' + title + '</h2>' 1433 break 1434 } 1435 console.log('vidHTML', s) 1436 return s 1437} 1438 1439function initVideo(id, titleid, spanid, style) { 1440 // defaults 1441 titleid = typeof titleid !== 'undefined' ? titleid : 'title' 1442 spanid = typeof spanid !== 'undefined' ? spanid : 'video' 1443 style = typeof style !== 'undefined' ? style : 'h1' 1444 1445 document.getElementById(titleid).innerHTML = vidHTML(id, titleid, spanid, style) 1446} 1447 1448function scriptExpand(div) { 1449 let str = div.innerHTML 1450 if (str.trim().length <= 1) return 1451 1452 iframeDo(div, str) 1453} 1454 1455function htmlSafe(input) { 1456 // Return empty string for undefined or empty input 1457 if (input == undefined || input.length <= 0) return '' 1458 1459 let safeInput = input.replace(/javascript/gi, 'js') 1460 safeInput = safeInput.replace(/script/gi, '') 1461 1462 // Strip out potentially harmful characters 1463 safeInput = safeInput.replace(/[^a-z0-9/\-[\]=_ :;,.()\w\s!?]/gi, '') 1464 1465 return safeInput 1466} 1467function sqlSafe(sql) { 1468 let s = '' 1469 const length = sql.length 1470 1471 for (let i = 0; i < length; i++) { 1472 const char = sql[i] 1473 if (char === "'") { 1474 s += "''" 1475 } else if (char === '"') { 1476 s += '\\"' 1477 } else if (char === '\\') { 1478 s += '\\\\' 1479 } else { 1480 s += char 1481 } 1482 } 1483 1484 return s 1485} 1486 1487function putFlash6(w, h, fn, querystring, clr) { 1488 // w=width, h=height, fn=filename(minus '.swf'), querystring=..., clr=bg color 1489 if (!querystring) { 1490 querystring = '' 1491 } else { 1492 querystring = '?' + querystring 1493 } 1494 1495 if (!clr) { 1496 clr = '#d6d9e6' 1497 } // default background color 1498 1499 if (fn.substring(fn.lastIndexOf('.swf')) != '.swf') fn = fn + '.swf' // append .swf if absent 1500 1501 var s = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/s
1501hockwave/cabs/flash/swflash.cab#version=6,0,79,0" width="' + w + '" height="' + h + '" id="' + fn + '">\n' 1502 s += '<param name="movie" value="' + fn + querystring + '"/> ' 1503 s += '<param name="quality" value="high"/> ' 1504 s += '<param name="bgcolor" value="' + clr + '"/> ' 1505 s += '<param name="menu" value="false"/> ' 1506 s += '<param name="allowScriptAccess" value="sameDomain"/> ' // always 1507 s += '<param name="allowFullScreen" value="true"/> ' 1508 s += '<embed src="' + fn + querystring + '" quality="high" bgcolor="' + clr + '" ' 1509 s += 'menu="false" width="' + w + '" height="' + h + '" type="application/x-shockwave-flash" ' 1510 s += 'pluginspage="http://www.macromedia.com/go/getflashplayer" ' 1511 s += 'swLiveConnect="true" allowscriptaccess="sameDomain" allowFullScreen="true" id="' + fn + '" name="' + fn + '"><noembed></noembed></embed>\n' // always 1512 s += '</object>\n' 1513 1514 document.write(s) 1515} 1516 1517function putFlash7(w, h, swf, querystring, clr, noflash) { 1518 // https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#web 1519 console.log('putFlash7: ' + swf) 1520 1521 let s = '<div id="container"></div>' 1522 document.write(s) 1523 1524 window.RufflePlayer = window.RufflePlayer || {} 1525 window.addEventListener('load', (event) => { 1526 const ruffle = window.RufflePlayer.newest() 1527 const player = ruffle.createPlayer() 1528 const container = document.getElementById('container') 1529 container.appendChild(player) 1530 player.load(swf) 1531 }) 1532} 1533 1534function putFlash8(w, h, swf, querystring, clr, noflash) { 1535 console.log('putFlash8: ' + swf) 1536 1537 window.RufflePlayer = window.RufflePlayer || {} 1538 window.addEventListener('load', (event) => { 1539 const ruffle = window.RufflePlayer.newest() 1540 const player = ruffle.createPlayer() 1541 const container = document.getElementById('container') 1542 console.log('container', container) 1543 container.appendChild(player) 1544 player.load(swf) 1545 }) 1546} 1547 1548function iframeDo(div, fileStr, focusQ = true) { 1549 // create an iframe and then load filename into it 1550 let isPreview = div.classList.contains('preview') // Check if we should start small 1551 let previewHtVal = getComputedStyle(div).getPropertyValue('--preview-ht') || '72px' 1552 let previewHt = parseInt(previewHtVal) 1553 1554 let fileNames = fileStr.split(',') 1555 let filename = fileNames[fileNames.length - 1].trim() // last file name is our main app 1556 1557 let imgHome = location.hostname == 'localhost' ? `/${main.dom}/` : '/' 1558 1559 let sttWd = parseInt(div.getBoundingClientRect().width) 1560 let sttHt = parseInt(div.getBoundingClientRect().height) 1561 1562 let aspect = parseFloat(div.style.gap) 1563 let maxQ = !isNaN(aspect) 1564 console.log('iframeDo:', filename, div) 1565 1566 let htQ = div.style.height.length > 0 1567 1568 let iframe = document.createElement('iframe') 1569 //let maxQ = false 1570 if (htQ) { 1571 //if (sttHt>=1000) maxQ = true 1572 iframe.width = sttWd 1573 iframe.style.width = iframe.width + 'px' 1574 1575 // iframe.height = isPreview ? 91 : sttHt // Toggle initial height 1576 iframe.height = isPreview ? previewHt : sttHt 1577 iframe.style.height = iframe.height + 'px' 1578 } else { 1579 iframe.height = 200 // speculative height, adjust later 1580 iframe.style.height = iframe.height + 'px' 1581 } 1582 1583 iframe.style = `display:block; margin:auto; border:none; background-color:transparent; overflow:hidden; 1584 transition:all 0.6s ease-in-out;` 1585
1586 if (isPreview) iframe.style.pointerEvents = 'none' // Don't let iframe steal click 1587 1588 iframe.scrolling = 'no' // sadly needed for chrome 1589 iframe.setAttribute('title', 'JavaScript Animation') 1590 1591 let html = `<!doctype html><html theme="${themeGet()}"> 1592 <head> 1593 <meta http-equiv="content-type" content="text/html; charset=utf-8" />` 1594 1595 // supporting files 1596 for (let i = 0; i < fileNames.length - 1; i++) { 1597 html += '<script src="' + imgHome + fileNames[i].trim() + '"></script>' 1598 } 1599 1600 html += '<link rel="stylesheet" href="' + imgHome + 'style4.css">' 1601 html += '<script src="' + imgHome + 'iframe.js" defer></script>' 1602 html += '<style>body { background: transparent; }</style>' // don't want to double up on background in iframe 1603 html += '</head>' 1604 1605 html += '<body style="margin:auto; text-align:center; ">' 1606 html += `<script defer src="${filename}"></script>` 1607 html += '</body>' 1608 html += '</html>' 1609 1610// console.log('html',html) 1611 1612 // Handle the click-to-expand overlay 1613 let targetElement = iframe 1614 1615 if (isPreview) { 1616 let container = document.createElement('div') 1617 container.className = 'script-container preview' // Add classes here 1618 1619 let overlay = document.createElement('div') 1620 overlay.className = 'script-overlay' // Add class here 1621 1622 overlay.height = previewHt 1623 overlay.style.height = iframe.height + 'px' 1624 1625 // overlay.innerHTML = '<span>Click to Interact</span>' 1626 1627 container.onclick = function () { 1628 isPreview = false 1629 iframe.height = sttHt 1630 iframe.style.height = iframe.height + 'px' 1631 1632 iframe.style.pointerEvents = 'auto' 1633 container.classList.remove('preview') // Clean up classes 1634 overlay.remove() 1635 } 1636 1637 container.appendChild(iframe) 1638 container.appendChild(overlay) 1639 targetElement = container 1640 } 1641 1642 div.parentElement.insertBefore(targetElement, div) // place iframe (or container) 1643 div.style.display = 'none' // hide 'calling' div 1644 1645 iframe.contentWindow.document.open() 1646 iframe.contentWindow.document.write(html) 1647 // iframe.contentWindow.document.outerHTML=html // doesn't work 1648 iframe.contentWindow.document.close() 1649 1650 iframe.addEventListener('load', function () { 1651 main.iframes.push(iframe.contentWindow) 1652 1653 if (focusQ && !isPreview) { 1654 iframe.contentWindow.focus() 1655 } 1656 1657 let btnQ = false 1658 let aQ = false 1659 1660 iframe.style.maxWidth = '100%' 1661 //let bestWd = iframe.contentDocument.body.scrollWidth 1662 let bestWd = 0, 1663 bestHt = 0 1664 if (htQ) { 1665 bestWd = sttWd 1666 bestHt = isPreview ? previewHt : sttHt 1667 } else { 1668 bestWd = 0 1669 iframe.width = bestWd 1670 iframe.style.width = iframe.width + 'px' 1671 1672 bestHt = iframe.contentDocument.body.scrollHeight + 1 1673 if (!isPreview) { 1674 iframe.height = bestHt 1675 iframe.style.height = iframe.height + 'px' 1676 } 1677 } 1678 1679 setInterval(function () { 1680 if (isPreview) return // Stay at previewHt until clicked 1681 1682 if (maxQ) { 1683 let wd = window.innerWidth 1684 let ht = window.innerHeight - 60 1685 let newAspect = wd / ht 1686 1687 //iframe.style.border = '1px solid blue' 1688 1689 // console.log('aspect',aspect,newAspect) 1690 if (newAspect > aspect) { 1691 wd = ht * aspect 1692 } else { 1693 ht = wd / aspect 1694 } 1695 //console.log('maxQ',screen.height,screen.availHeight,window.innerHeight,aspect,wd,ht) 1696 iframe.width = wd 1697 iframe.style.width = iframe.width + 'px' 1698 1699 iframe.height = ht 1700 iframe.style.height = iframe.height + 'px' 1701 1702 return 1703 } 1704 let wd, ht 1705 let byBoundingRect = false 1706 if (byBoundingRect) { 1707 let div = iframe.contentDocument.body.children[0].children[0] 1708 let rect = div.getBoundingClientRect() 1709 wd = rect.width 1710 ht = rect.height 1711 } else { 1712 wd = iframe.contentDocument.body.scrollWidth + 1 1713 ht = iframe.contentDocument.body.scrollHeight + 1 1714 } 1715 1716 if (wd != bestWd) { 1717 console.log('chg wd', bestWd, 'to', wd) 1718 bestWd = wd 1719 // iframe.style.width = bestWd + 'px'; 1720 if (!aQ) { 1721 iframe.width = '100%' 1722 iframe.style.width = '100%' 1723 } else { 1724 iframe.width = bestWd 1725 iframe.style.width = bestWd + 'px' 1726 aQ = true 1727 } 1728 } 1729 1730 // console.log('ht', ht, bestHt) 1731 if (ht > bestHt || ht < bestHt - 10) { 1732 console.log('chg ht', bestHt, 'to', ht) 1733 bestHt = ht 1734 iframe.height = bestHt 1735 iframe.style.height = iframe.height + 'px' 1736 } 1737 if (!btnQ) { 1738 let wd = iframe.contentDocument.body.scrollWidth + 40 1739 let ht = iframe.contentDocument.body.scrollHeight + 1 1740 let div = iframe.contentDocument.getElementById('resize') 1741 if (div != null) { 1742 let btnStr = '<button type="button" class="btn" style="z-index:2; position:absolute; right:0; bottom:0;" onclick="iframeMax(\'' + filename + "'," + wd + ',' + ht + ')">▭</button>' 1743 div.innerHTML = btnStr 1744 //console.log('qqq', wd, ht, btnStr) 1745 } 1746 //iframe.contentDocument.body.innerHTML += btnStr 1747 btnQ = true 1748 } 1749 }, 650) 1750 }) 1751} 1752 1753function rpLog(id) { 1754 let now = performance.now() 1755 if (main.timeStt == undefined) { 1756 main.timeStt = performance.now() 1757 main.timePrev = performance.now() 1758 } 1759 1760 console.log('' + id + ': ' + parseInt(now - main.timeStt) + 'ms => ' + parseInt(now - main.timePrev) + 'ms') 1761 main.timePrev = now 1762} 1763 1764function loginShow(type) { 1765 let s = '' 1766 1767 if (type == 0) { 1768 // not logged in 1769 let name = '' 1770 let pass = '' 1771 s += '<div style="position:relative; margin:auto; text-align:center; font:14px Arial; margin: 5px 0 1px 0; padding: 5px 0 5px 0;">' 1772 s += 'Username: ' 1773 s += '<input id="name" style="width: 120px; border-radius: 10px; font: 18px Arial; background-color: #eeffee; text-align:center;" value="' + name + '" />' 1774 s += '<br>' 1775 s += 'Password: ' 1776 s += '<input id="pass" type="password" style="width: 120px; border-radius: 10px; font: 18px Arial; background-color: #eeffee; text-align:center;" value="' + pass + '" />' 1777 s += '</div>' 1778 1779 s += '<div style="margin: 0 0 5px 10px;">' 1780 s += '<button id="optLogin" onclick="loginGo()" style="z-index:2;" class="btn" >Login</button>' 1781 s += '<button id="optNewUser" onclick="loginNew()" style="z-index:2;" class="btn" >New User</button>' 1782 s += '</div>' 1783 } 1784 1785 if (type == 1) { 1786 // logged in 1787 s += ` 1788 Hi ${loginName()}<br> 1789 <div style="position:relative; margin:auto; text-align:right; font:14px Arial; margin: 5px 0 1px 0; padding: 5px 0 5px 0;"> 1790 Password: <input id="passOld" type="password" style="width: 120px; border-radius: 10px; font: 18px Arial; background-color: #eeffee; text-align:center;" value="" /> 1791 <br> 1792 New Password: <input id="passNew" type="password" style="width: 120px; border-radius: 10px; font: 18px Arial; background-color: #eeffee; text-align:center;" value="" /> 1793 <button onclick="loginPassChg()" style="z-index:2;" class="btn" >
1793Change Password</button> 1794 </div> 1795 <br> 1796 <button onclick="userGames()" style="z-index:2;" class="btn" >Games</button><br> 1797 <button onclick="loginOut()" style="z-index:2;" class="btn" >Logout</button> 1798 ` 1799 } 1800 1801 document.getElementById('loginContent').innerHTML = s 1802 1803 let div = document.getElementById('loginPop') 1804 div.style.left = '-250px' 1805 div.style.opacity = 1 1806 1807 document.getElementById('name').value = localStorage.getItem('user.name') 1808} 1809 1810function loginName() { 1811 let name = localStorage.getItem('user.name') || '' 1812 if (name.length < 2) return '' 1813 1814 let stamp = localStorage.getItem('user.stamp') || '' 1815 if (stamp.length < 2) return '' 1816 1817 return name 1818} 1819 1820function loginType() { 1821 let type = 'none' // or 'guest'? 1822 1823 let name = localStorage.getItem('user.name') || '' 1824 if (name.length < 2) return type 1825 1826 let userType = localStorage.getItem('user.type') || type 1827 if (userType == 1 || userType == 2) return 'admin' 1828 1829 return 'user' 1830} 1831 1832function loginPrompt() { 1833 // return 1834 let name = loginName() 1835 1836 let s = '' 1837 if (name.length > 0) { 1838 s += '<a href="javascript:loginShow(1)" style="aria-label="Logout">' 1839 s += name 1840 s += '</a>' 1841 } else { 1842 s += '<a href="javascript:loginShow(0)" style="aria-label="Login">' 1843 s += 'Login' 1844 s += '</a>' 1845 } 1846 1847 let div = document.getElementById('login') 1848 div.innerHTML = s 1849} 1850 1851function loginGo() { 1852 let name = document.getElementById('name').value 1853 localStorage.setItem('user.name', name) 1854 let pass = document.getElementById('pass').value 1855 console.log('loginGo', name, pass) 1856 loginMsg() 1857 1858 main.sock.login(name, pass) 1859} 1860function loginNew() { 1861 let name = document.getElementById('name').value 1862 localStorage.setItem('user.name', name) // remember for user later on 1863 let pass = document.getElementById('pass').value 1864 console.log('loginNew', name, pass) // don't store password at all 1865 loginMsg() 1866 1867 // validate 1868 if (name.length < 6) { 1869 loginMsg('Username needs 6 or more characters') 1870 return 1871 } 1872 if (name.length > 20) { 1873 loginMsg('Username exceeds maxiumum of 20 characters') 1874 return 1875 } 1876 let safeRegex = /^[a-zA-Z0-9_\-!$]+$/ 1877 if (!safeRegex.test(name)) { 1878 loginMsg('Username has unsafe characters') 1879 return 1880 } 1881 if (pass.length < 6) { 1882 loginMsg('Password needs 6 or more characters') 1883 return 1884 } 1885 if (!safeRegex.test(pass)) { 1886 loginMsg('Password: has unsafe characters') 1887 return 1888 } 1889 1890 main.sock.loginNew(name, pass) 1891} 1892function loginPassChg() { 1893 let name = loginName() 1894 let passOld = document.getElementById('passOld').value 1895 let passNew = document.getElementById('passNew').value 1896 console.log('loginPassChg', passOld, passNew) 1897 1898 // validate 1899 if (passNew.length < 6) { 1900 loginMsg('Password: 6 or more characters') 1901 return 1902 } 1903 1904 main.sock.loginPassChg(name, passOld, passNew) 1905} 1906function loginOut() { 1907 localStorage.setItem('user.stamp', '') // unsetting stamp invalidates login 1908 loginPrompt() 1909 loginHide() 1910 if (typeof loginDo === 'function') loginDo() 1911} 1912function loginMsg(s = ' ') { 1913 console.log('loginMsg', s) 1914 let div = document.getElementById('loginMsg') 1915 if (div != null) div.innerHTML = s 1916} 1917 1918function loginHide() { 1919 let div = document.getElementById('loginPop') 1920 div.style.left = '200px' 1921 div.style.opacity = 0 1922} 1923function userGames() { 1924 let home = location.hostname == 'localhost' ? `/${main.dom}/` : '/' 1925 window.location = home + 'games/user.html' 1926} 1927 1928function fmtTm(dt) { 1929 let hours = dt.getHours().toString().padStart(2, '0') 1930 let minutes = dt.getMinutes().toString().padStart(2, '0') 1931 let seconds = dt.getSeconds().toString().padStart(2, '0') 1932 return `${hours}:${minutes}:${seconds}` 1933} 1934 1935function getSock() { 1936 // this is how we get the main socket when in an iframe 1937 return main.sock 1938} 1939 1940class MainSock { 1941 constructor(keepAliveQ = false) { 1942 this.keepAliveQ = keepAliveQ 1943 this.socket = null 1944 this.queue = [] 1945 this.lines = [] 1946 this.connectTryN = 0 1947 } 1948 1949 send(msg) { 1950 // 0=CONNECTING (Socket created, connection not yet open) 1=OPEN (connection is open and ready to communicate) 2=CLOSING (connection is in the process of closing) 3=CLOSED 1951 1952 if (!this.socket || this.socket.readyState === WebSocket.CLOSED) { 1953 this.connect() 1954 } 1955 1956 if (this.socket.readyState === WebSocket.OPEN) { 1957 this.socket.send(msg) 1958 } else { 1959 // If the WebSocket is not OPEN, queue the message 1960 this.queue.push(msg) 1961 } 1962 } 1963 1964 send1(msg) { 1965 if (this.socket == null) { 1966 // console.log('this.queue.push',msg) 1967 this.queue.push(msg) 1968 this.connect() 1969 } else { 1970 console.log('this.socket.readyState', this.socket.readyState) 1971 if (this.socket.readyState === 1) { 1972 // console.log('this.socket.send',msg) 1973 this.socket.send(msg) 1974 } 1975 } 1976 //this.waitForConnection(function () { this.socket.send(msg); }, 1000) 1977 } 1978 1979 waitForConnection(callback, interval) { 1980 if (this.socket.readyState === 1) { 1981 callback() 1982 } else { 1983 let self = this 1984 interval *= 2 1985 setTimeout(function () { 1986 self.waitForConnection(callback, interval) 1987 }, interval) 1988 } 1989 } 1990 connect() { 1991 // console.trace('connect', fmtTm(new Date())) 1992 1993 let socket = new WebSocket(`wss://${main.server}:${main.port}/`) 1994 console.log('socket', socket) 1995 let self = this 1996 socket.onopen = function (ev) { 1997 // console.log('[open] Connection established') 1998 self.connectTryN = 0 1999 // console.log('Sending to server') 2000 2001 // Empty the queue by sending queued messages. 2002 while (self.queue.length > 0) { 2003 let msg = self.queue.shift() 2004 // console.log('msg from queue:', msg) 2005 socket.send(msg) 2006 } 2007 2008 // Make sure we only add this listener once after a successful connection. 2009 window.addEventListener( 2010 'beforeunload', 2011 () => { 2012 console.log('Browser window is closing, closing WebSocket.') 2013 socket.close(1000, 'Window closing') // Use standard WebSocket close code 1000 for normal closure. 2014 }, 2015 { once: true }, 2016 ) 2017 } 2018 socket.onclose = function (ev) { 2019 console.log(ev.wasClean ? 'Disconnected' : 'Connection break: ' + ev.code + ' ' + ev.reason) 2020 // if (!ev.wasClean ) { 2021 // if (this.keepAliveQ) self.attemptReconnect() 2022 // } 2023 } 2024 socket.onmessage = function (ev) { 2025 //console.log('RECV:', ev.data) 2026 let data 2027 try { 2028 data = JSON.parse(ev.data) 2029 } catch (err) { 2030 data = {} 2031 //console.log('socket message error:', err); 2032 } 2033 self.msgRcv(data) 2034 } 2035 socket.onerror = function (err) { 2036 console.log('Socket Error ' + err.message) 2037 if (self.keepAliveQ) self.attemptReconnect() 2038 2039 // either it has a mainMsg function or is an iframe that we need to postMessage to 2040 if (typeof mainMsg === 'function') mainMsg({ msg: 'nosocket' })
2041 main.iframes.forEach((iframe) => { 2042 iframe.postMessage({ msg: 'nosocket' }, '*') 2043 }) 2044 } 2045 this.socket = socket 2046 // console.log('socket running:' + this.socket) 2047 } 2048 2049 attemptReconnect() { 2050 this.connectTryN++ 2051 2052 // Delay times for each reconnect attempt. 2053 let delays = [ 2054 { tm: 1, txt: '', why: 'unused' }, 2055 { tm: 5, txt: '', why: 'net glitch' }, 2056 { tm: 30, txt: '', why: 'server restart' }, 2057 { tm: 120, txt: '', why: 'bad server restart' }, 2058 { tm: 600, txt: '', why: 'one last try' }, 2059 ] 2060 2061 if (this.connectTryN < delays.length) { 2062 let delay = delays[this.connectTryN].tm 2063 console.log(`retry connection ${this.connectTryN} at ${fmtTm(new Date())} after ${delay}s`) 2064 setTimeout(() => this.connect(), delay * 1000) 2065 } else { 2066 // Handle the case when all reconnect attempts fail. 2067 // For example, show a reconnect failure message to the user. 2068 } 2069 } 2070 2071 msgRcv(data) { 2072 //console.log('msgRcv', JSON.stringify(data).substring(0, 200)) 2073 // this.histAdd('msgRcv:' + JSON.stringify(data)) 2074 switch (data.msg) { 2075 case 'usergood': { 2076 this.histAdd('Login success!') 2077 this.userSet('stamp', data.stamp) 2078 this.userSet('stampWhen', new Date()) 2079 2080 let row = data 2081 let props = { grp: row.grp, usertype: row.usertype, regdate: row.regdate, lastdate: row.lastdate, merits: row.merits, qCount: row.qCount, achieveCount: row.achieveCount, qScore: row.qScore, lastQs: row.lastQs, failQs: row.failQs, mentor: row.mentor } 2082 console.log('row', row) 2083 for (let key in row) { 2084 if (row.hasOwnProperty(key)) { 2085 // Check if the property is directly on the row object 2086 this.userSet(key, row[key]) 2087 } 2088 } 2089 loginPrompt() 2090 loginHide() 2091 break 2092 } 2093 2094 case 'usernew': { 2095 this.histAdd('New User success!') 2096 this.userSet('stamp', data.stamp) 2097 2098 let date = new Date() 2099 let now = date.toISOString() 2100 let props = { grp: '', regdate: now, lastdate: now, merits: 0, qCount: 0, achieveCount: 0, qScore: 0, lastQs: '', failQs: '', mentor: '' } 2101 console.log('props', props) 2102 for (let key in props) { 2103 if (props.hasOwnProperty(key)) { 2104 this.userSet(key, props[key]) 2105 } 2106 } 2107 loginPrompt() 2108 loginHide() 2109 break 2110 } 2111 2112 case 'userchg': 2113 this.userSet('stamp', data.stamp) 2114 loginMsg('Change successful!') 2115 //loginHide() 2116 break 2117 2118 case 'usertaken': 2119 loginMsg('User taken') 2120 this.userSet('stamp', '') 2121 break 2122 2123 case 'userinvalid': 2124 case 'passinvalid': 2125 loginMsg('User or Password invalid') 2126 this.userSet('stamp', '') 2127 break 2128 2129 case 'userfail': 2130 loginMsg('Incorrect name or password') 2131 this.userSet('stamp', '') 2132 break 2133 2134 case 'usernot': 2135 loginMsg('No such user') 2136 break 2137 2138 default: 2139 // either it has a mainMsg function or is an ifram that we need to postMessage to 2140 if (typeof mainMsg === 'function') mainMsg(data) 2141 main.iframes.forEach((iframe) => { 2142 iframe.postMessage(data, '*') 2143 }) 2144 } 2145 2146 // this lets other code on page also receive socket messages (safe?) 2147 // console.log('fdhfdhfs',typeof mainMsg === 'function') 2148 // if (typeof mainMsg === 'function') mainMsg(data) 2149 2150 // const iframes = document.querySelectorAll('iframe'); 2151 2152 // // Loop through them 2153 // iframes.forEach((iframe) => { 2154 // // Do something with each iframe, like logging its src attribute 2155 // console.log(`Iframe src: ${iframe.src}`, iframe); 2156 // }); 2157 } 2158 userSet(name, val) { 2159 localStorage.setItem(`user.${name}`, val) 2160 console.log('userSet', name, val) 2161 } 2162 2163 histAdd(s) { 2164 console.log('hist', s.substring(0, 200)) 2165 this.lines = this.lines.slice(Math.max(this.lines.length - 4, 0)) 2166 this.lines.push(s) 2167 } 2168 2169 login(name, pass) { 2170 //my.sttTime = performance.now() 2171 let data = { msg: 'login', name: name, pass: pass } 2172 this.send(JSON.stringify(data)) 2173 this.histAdd('login: ' + JSON.stringify(data)) 2174 } 2175 loginNew(name, pass) { 2176 let data = { msg: 'loginNew', name: name, pass: pass } 2177 this.send(JSON.stringify(data)) 2178 this.histAdd('loginNew: ' + JSON.stringify(data)) 2179 } 2180 loginPassChg(name, passOld, passNew) { 2181 let data = { msg: 'loginPassChg', name: name, passOld: passOld, passNew } 2182 this.send(JSON.stringify(data)) 2183 console.log('loginPassChg: ' + JSON.stringify(data)) 2184 } 2185 passHash(pass) { 2186 return '#' + pass + '#' 2187 } 2188} 2189 2190themeSet(themeGet()) // can do before window loaded? 2191window.onload = mainDo 2192 2193main.cache = { 2194 set: function (key, value, ttl) { 2195 const expire = new Date().getTime() + ttl * 1000 2196 const item = { value, expire } 2197 // Serialize the item to JSON and store in localStorage with the key 2198 console.log('cache set', item) 2199 localStorage.setItem('cache.' + key, JSON.stringify(item)) 2200 }, 2201 get: function (key) { 2202 const itemStr = localStorage.getItem('cache.' + key) 2203 console.log('cache get', itemStr.substring(0, 80)) 2204 if (!itemStr) { 2205 return null 2206 } 2207 const item = JSON.parse(itemStr) 2208 return item.value 2209 }, 2210 isOK: function (key) { 2211 const itemStr = localStorage.getItem('cache.' + key) 2212 if (!itemStr) { 2213 return false 2214 } 2215 const item = JSON.parse(itemStr)
2216 const currentTime = new Date().getTime() 2217 console.log('cache isOK', currentTime, item.expire, currentTime < item.expire) 2218 if (currentTime < item.expire) { 2219 return true 2220 } else { 2221 // If the item is expired, remove it from localStorage 2222 localStorage.removeItem(key) 2223 return false 2224 } 2225 }, 2226}
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.