/ js / core.js
core.js
   1  var $L = {
   2    nws: {"aco":1,"b":1,"bant":1,"d":1,"e":1,"f":1,"gif":1,"h":1,"hc":1,"hm":1,"hr":1,"i":1,"ic":1,"pol":1,"r":1,"r9k":1,"s":1,"s4s":1,"soc":1,"t":1,"trash":1,"u":1,"wg":1,"y":1},
   3    blue: '4chan.org', red: '4chan.org',
   4    d: function(b) {
   5      return $L.nws[b] ? $L.red : $L.blue;
   6    }
   7  };
   8  
   9  /**
  10   * Captcha
  11  */
  12  var TCaptcha = {
  13    node: null,
  14    
  15    frameNode: null,
  16    imgCntNode: null,
  17    bgNode: null,
  18    fgNode: null,
  19    msgNode: null,
  20    sliderNode: null,
  21    respNode: null,
  22    reloadNode: null,
  23    helpNode: null,
  24    challengeNode: null,
  25    
  26    ticketCaptchaNode: null,
  27    
  28    challenge: null,
  29    
  30    reloadTs: null,
  31    reloadTimeout: null,
  32    expireTimeout: null,
  33    frameTimeout: null,
  34    
  35    pcdBypassable: false,
  36    
  37    errorCb: null,
  38    
  39    path: '/captcha',
  40    
  41    ticketKey: '4chan-tc-ticket',
  42    
  43    domain: '4chan.org',
  44    
  45    failCd: 60,
  46    
  47    tabindex: null,
  48    
  49    hCaptchaSiteKey: '49d294fa-f15c-41fc-80ba-c2544c52ec2a',
  50    
  51    init: function(el, board, thread_id, tabindex) {
  52      if (this.node) {
  53        this.destroy();
  54      }
  55      
  56      if (tabindex) {
  57        this.tabindex = tabindex;
  58      }
  59      
  60      this.node = el;
  61      
  62      el.style.position = 'relative';
  63      el.style.width = '300px';
  64      
  65      this.frameNode = null;
  66      this.imgCntNode = this.buildImgCntNode();
  67      this.bgNode = this.buildImgNode('bg');
  68      this.fgNode = this.buildImgNode('fg');
  69      this.sliderNode = this.buildSliderNode();
  70      
  71      this.respNode = this.buildRespField();
  72      this.reloadNode = this.buildReloadNode(board, thread_id);
  73      this.helpNode = this.buildHelpNode();
  74      this.msgNode = this.buildMsgNode();
  75      this.challengeNode = this.buildChallengeNode();
  76      
  77      el.appendChild(this.reloadNode);
  78      el.appendChild(this.respNode);
  79      el.appendChild(this.helpNode);
  80      
  81      this.imgCntNode.appendChild(this.bgNode);
  82      this.imgCntNode.appendChild(this.fgNode);
  83      el.appendChild(this.imgCntNode);
  84      
  85      el.appendChild(this.sliderNode);
  86      el.appendChild(this.msgNode);
  87      el.appendChild(this.challengeNode);
  88      
  89      window.addEventListener('message', this.onFrameMessage);
  90    },
  91    
  92    destroy: function() {
  93      let self = TCaptcha;
  94      
  95      if (!self.node) {
  96        return;
  97      }
  98      
  99      window.removeEventListener('message', self.onFrameMessage);
 100      
 101      clearTimeout(self.frameTimeout);
 102      clearTimeout(self.reloadTimeout);
 103      clearTimeout(self.expireTimeout);
 104      
 105      self.node.textContent = '';
 106      
 107      self.node = null;
 108      self.frameNode = null;
 109      self.imgCntNode = null;
 110      self.bgNode = null;
 111      self.fgNode = null;
 112      self.msgNode = null;
 113      self.sliderNode = null;
 114      self.respNode = null;
 115      self.reloadNode = null;
 116      self.helpNode = null;
 117      self.challengeNode = null;
 118      
 119      self.ticketCaptchaNode = null;
 120      
 121      self.challenge = null;
 122      
 123      self.pcdBypassable = false;
 124      
 125      self.errorCb = null;
 126      
 127      self.reloadTs = null;
 128      
 129      self.onReloadCdDone = null;
 130    },
 131    
 132    setErrorCb: function(func) {
 133      TCaptcha.errorCb = func;
 134    },
 135    
 136    toggleImgCntNode: function(flag) {
 137      TCaptcha.imgCntNode.style.display = flag ? 'block' : 'none';
 138    },
 139    
 140    getTicket: function() {
 141      return localStorage.getItem(TCaptcha.ticketKey);
 142    },
 143    
 144    setTicket: function(val) {
 145      if (val) {
 146        localStorage.setItem(TCaptcha.ticketKey, val);
 147      }
 148      else if (val === false) {
 149        localStorage.removeItem(TCaptcha.ticketKey);
 150      }
 151    },
 152    
 153    buildFrameNode: function() {
 154      let el = document.createElement('iframe');
 155      el.id = 't-frame';
 156      el.style.border = '0';
 157      el.style.width = '100%';
 158      el.style.height = '80px';
 159      el.style.marginTop = '2px';
 160      el.style.position = 'relative';
 161      el.style.display = 'block';
 162      el.onerror = TCaptcha.onFrameError;
 163      return el;
 164    },
 165    
 166    buildImgCntNode: function() {
 167      let el = document.createElement('div');
 168      el.id = 't-cnt';
 169      el.style.height = '80px';
 170      el.style.marginTop = '2px';
 171      el.style.position = 'relative';
 172      return el;
 173    },
 174    
 175    buildImgNode: function(id) {
 176      let el = document.createElement('div');
 177      el.id = 't-' + id;
 178      el.style.width = '100%';
 179      el.style.height = '100%';
 180      el.style.position = 'absolute';
 181      el.style.backgroundRepeat = 'no-repeat';
 182      el.style.backgroundPosition = 'top left';
 183      el.style.pointerEvents = 'none';
 184      return el;
 185    },
 186    
 187    buildMsgNode: function() {
 188      let el = document.createElement('div');
 189      el.id = 't-msg';
 190      el.style.width = '100%';
 191      el.style.height = 'calc(100% - 20px)';
 192      el.style.position = 'absolute';
 193      el.style.top = '20px';
 194      el.style.textAlign = 'center';
 195      el.style.fontSize = '12px';
 196      el.style.filter = 'inherit';
 197      el.style.display = 'none';
 198      el.style.alignContent = 'center';
 199      return el;
 200    },
 201    
 202    buildRespField: function() {
 203      let el = document.createElement('input');
 204      el.id = 't-resp';
 205      el.name = 't-response';
 206      el.placeholder = 'Type the CAPTCHA here';
 207      el.setAttribute('autocomplete', 'off');
 208      el.type = 'text';
 209      el.style.width = '160px';
 210      el.style.boxSizing = 'border-box';
 211      el.style.textTransform = 'uppercase';
 212      el.style.fontSize = '11px';
 213      el.style.height = '18px';
 214      el.style.margin = '0';
 215      el.style.padding = '0 2px';
 216      el.style.fontFamily = 'monospace';
 217      el.style.verticalAlign = 'middle';
 218      if (this.tabindex) {
 219        el.setAttribute('tabindex', this.tabindex + 2);
 220      }
 221      return el;
 222    },
 223    
 224    buildSliderNode: function() {
 225      let el = document.createElement('input');
 226      el.id = 't-slider';
 227      el.setAttribute('autocomplete', 'off');
 228      el.type = 'range';
 229      el.style.width = '100%';
 230      el.style.boxSizing = 'border-box';
 231      el.style.visibility = 'hidden';
 232      el.style.margin = '0';
 233      el.style.transition = 'box-shadow 15s linear';
 234      el.style.boxShadow = '0 0 6px 4px #1d8dc4';
 235      el.style.position = 'relative';
 236      el.value = 0;
 237      el.min = 0;
 238      el.max = 100;
 239      el.addEventListener('input', this.onSliderInput, false);
 240      if (this.tabindex) {
 241        el.setAttribute('tabindex', this.tabindex + 1);
 242      }
 243      return el;
 244    },
 245    
 246    buildChallengeNode: function() {
 247      let el = document.createElement('input');
 248      el.name = 't-challenge';
 249      el.type = 'hidden';
 250      return el;
 251    },
 252    
 253    buildReloadNode: function(board, thread_id) {
 254      let el = document.createElement('button');
 255      el.id = 't-load';
 256      el.type = 'button';
 257      el.style.fontSize = '11px';
 258      el.style.padding = '0';
 259      el.style.width = '90px';
 260      el.style.boxSizing = 'border-box';
 261      el.style.margin = '0 6px 0 0';
 262      el.style.verticalAlign = 'middle';
 263      el.style.height = '18px';
 264      el.textContent = 'Get Captcha';
 265      el.setAttribute('data-board', board);
 266      el.setAttribute('data-tid', thread_id);
 267      el.addEventListener('click', this.onReloadClick, false);
 268      if (this.tabindex) {
 269        el.setAttribute('tabindex', this.tabindex);
 270      }
 271      return el;
 272    },
 273    
 274    buildHelpNode: function() {
 275      let el = document.createElement('button');
 276      el.id = 't-help';
 277      el.type = 'button';
 278      el.style.fontSize = '11px';
 279      el.style.padding = '0';
 280      el.style.width = '20px';
 281      el.style.boxSizing = 'border-box';
 282      el.style.margin = '0 0 0 6px';
 283      el.style.verticalAlign = 'middle';
 284      el.style.height = '18px';
 285      el.textContent = '?';
 286      el.setAttribute('data-tip', 'Help');
 287      el.tabIndex = -1;
 288      el.addEventListener('click', this.onHelpClick, false);
 289      return el;
 290    },
 291    
 292    onHelpClick: function() {
 293      let str = `- Only type letters and numbers displayed in the image.
 294  - If needed, use the slider to align the image to make it readable.
 295  - Make sure to not block any cookies set by 4chan.`;
 296      alert(str);
 297    },
 298    
 299    onTicketCaptchaError: function() {
 300      TCaptcha.toggleMsgOverlay(true, "Couldn't load the captcha.<br><br>Please check your browser's content blocker.");
 301    },
 302    
 303    onTicketCaptchaDone: function(resp) {
 304      TCaptcha.reloadNode.setAttribute('data-ticket-resp', resp);
 305      TCaptcha.destroyTicketCaptcha();
 306      TCaptcha.onReloadClick();
 307    },
 308    
 309    loadTicketCaptcha: function() {
 310      window.pcd_c_loaded = TCaptcha.buildTicketCaptcha;
 311      window.pcd_c_done = TCaptcha.onTicketCaptchaDone;
 312      TCaptcha.toggleMsgOverlay(true, 'Loading…');
 313      let s = document.createElement('script');
 314      s.src = 'https://js.hcaptcha.com/1/api.js?onload=pcd_c_loaded&render=explicit&recaptchacompat=off';
 315      s.onerror = TCaptcha.onTicketCaptchaError;
 316      document.head.appendChild(s);
 317    },
 318    
 319    buildTicketCaptcha: function() {
 320      let self = TCaptcha;
 321      
 322      self.toggleMsgOverlay(false);
 323      
 324      if (!window.hcaptcha) {
 325        self.loadTicketCaptcha();
 326        return;
 327      }
 328      
 329      let el = document.createElement('div');
 330      el.id = 't-tc-cnt';
 331      self.imgCntNode.appendChild(el);
 332      
 333      let wid = window.hcaptcha.render('t-tc-cnt', {
 334        sitekey: self.hCaptchaSiteKey,
 335        callback: 'pcd_c_done'
 336      });
 337      
 338      el.setAttribute('data-wid', wid);
 339      
 340      self.ticketCaptchaNode = el;
 341    },
 342    
 343    destroyTicketCaptcha: function() {
 344      let self = TCaptcha;
 345      
 346      if (!window.hcaptcha || !self.ticketCaptchaNode) {
 347        return;
 348      }
 349      
 350      let wid = self.ticketCaptchaNode.getAttribute('data-wid');
 351      window.hcaptcha.reset(wid);
 352      self.imgCntNode.removeChild(self.ticketCaptchaNode);
 353      self.ticketCaptchaNode = null;
 354    },
 355    
 356    onReloadClick: function() {
 357      let btn = TCaptcha.reloadNode;
 358      let board = btn.getAttribute('data-board');
 359      let thread_id = btn.getAttribute('data-tid');
 360      let ticket_resp = btn.getAttribute('data-ticket-resp');
 361      btn.removeAttribute('data-ticket-resp');
 362      TCaptcha.toggleReloadBtn(false, 'Loading');
 363      TCaptcha.load(board, thread_id, ticket_resp);
 364    },
 365    
 366    onFrameMessage: function(e) {
 367      if (e.origin !== `https://sys.${TCaptcha.domain}`) {
 368        return;
 369      }
 370      
 371      if (e.data && e.data.twister) {
 372        TCaptcha.destroyFrame();
 373        TCaptcha.buildFromJson(e.data.twister);
 374      }
 375    },
 376    
 377    onFrameError: function(e) {
 378      TCaptcha.unlockReloadBtn();
 379      
 380      console.log(e);
 381      
 382      if (TCaptcha.errorCb) {
 383        TCaptcha.errorCb.call(null,
 384          "Couldn't load the captcha frame. Check your content blocker settings."
 385        );
 386      }
 387    },
 388    
 389    load: function(board, thread_id, ticket_resp) {
 390      let self = TCaptcha;
 391      
 392      clearTimeout(self.frameTimeout);
 393      clearTimeout(self.reloadTimeout);
 394      clearTimeout(self.expireTimeout);
 395      
 396      let params = ['framed=1'];
 397      
 398      if (board) {
 399        params.push('board=' + board);
 400      }
 401      
 402      if (thread_id > 0) {
 403        params.push('thread_id=' + thread_id);
 404      }
 405      
 406      if (ticket_resp) {
 407        params.push('ticket_resp=' + encodeURIComponent(ticket_resp));
 408      }
 409      
 410      let ticket = self.getTicket();
 411      
 412      if (ticket) {
 413        params.push('ticket=' + ticket);
 414      }
 415      
 416      if (params.length > 0) {
 417        params = '?' + params.join('&');
 418      }
 419      
 420      let src = 'https://sys.' + self.domain + self.path + params;
 421      
 422      self.frameNode = self.buildFrameNode();
 423      self.toggleImgCntNode(false);
 424      self.node.insertBefore(self.frameNode, self.imgCntNode);
 425      self.frameTimeout = setTimeout(self.onFrameTimeout, 60000, src);
 426      self.frameNode.src = src;
 427    },
 428    
 429    onFrameTimeout: function(src) {
 430      let self = TCaptcha;
 431      
 432      self.destroyFrame();
 433      
 434      console.log('Captcha frame timeout');
 435      
 436      if (self.errorCb) {
 437        self.errorCb.call(null, `Couldn't get the captcha.
 438  Make sure your browser doesn't block content on 4chan then click
 439  <a href="${src.replace('framed=1', 'opened=1')}" target="_blank">here</a>.`);
 440      }
 441    },
 442    
 443    destroyFrame: function() {
 444      let self = TCaptcha;
 445      
 446      clearTimeout(self.frameTimeout);
 447      self.frameTimeout = null;
 448      if (self.frameNode) {
 449        self.frameNode.remove();
 450        self.frameNode = null;
 451      }
 452      self.toggleImgCntNode(true);
 453      self.unlockReloadBtn();
 454    },
 455    
 456    unlockReloadBtn: function() {
 457      TCaptcha.reloadTs = null;
 458      TCaptcha.toggleReloadBtn(true, 'Get Captcha');
 459    },
 460    
 461    toggleReloadBtn: function(flag, label) {
 462      let self = TCaptcha;
 463      
 464      if (self.reloadNode) {
 465        self.reloadNode.disabled = !flag;
 466        
 467        if (label !== undefined) {
 468          self.reloadNode.textContent = label;
 469        }
 470      }
 471    },
 472    
 473    onCaptchaFailed: function() {
 474      let self = TCaptcha;
 475      
 476      let cd = self.failCd * 1000;
 477      
 478      if (self.reloadTs && self.reloadTs < cd) {
 479        self.setReloadCd(cd, true);
 480      }
 481    },
 482    
 483    setReloadCd: function(cd, visible, onDone) {
 484      let self = TCaptcha;
 485      
 486      if (!self.node) {
 487        return;
 488      }
 489      
 490      clearTimeout(self.reloadTimeout);
 491      
 492      self.onReloadCdDone = onDone;
 493      
 494      self.pcdBypassable = visible === -1;
 495      
 496      if (cd) {
 497        self.toggleReloadBtn(false);
 498        if (visible) {
 499          self.reloadTs = Date.now() + cd;
 500          self.onReloadCdTick();
 501        }
 502        else {
 503          self.reloadTimeout = setTimeout(self.stopReloadCd, cd);
 504        }
 505      }
 506      else {
 507        self.stopReloadCd();
 508      }
 509    },
 510    
 511    stopReloadCd: function() {
 512      let self = TCaptcha;
 513      self.unlockReloadBtn();
 514      if (self.onReloadCdDone) {
 515        self.onReloadCdDone.call(self);
 516      }
 517    },
 518    
 519    onReloadCdTick: function() {
 520      let self = TCaptcha;
 521      
 522      if (!self.reloadNode || !self.reloadTs) {
 523        return;
 524      }
 525      
 526      let cd = self.reloadTs - Date.now();
 527      
 528      if (self.pcdBypassable) {
 529        if (document.cookie.indexOf('_ev1=') !== -1) {
 530          cd = 0;
 531        }
 532      }
 533      
 534      if (cd > 0) {
 535        self.reloadNode.textContent = Math.ceil(cd / 1000);
 536        self.reloadTimeout = setTimeout(self.onReloadCdTick, Math.min(cd, 1000));
 537      }
 538      else {
 539        self.stopReloadCd();
 540      }
 541    },
 542    
 543    clearChallenge: function() {
 544      let self = TCaptcha;
 545      
 546      if (self.node) {
 547        self.challengeNode.value = '';
 548        self.respNode.value = '';
 549        self.fgNode.style.backgroundImage = '';
 550        self.bgNode.style.backgroundImage = '';
 551        self.toggleSlider(false);
 552        self.toggleMsgOverlay(false);
 553      }
 554    },
 555    
 556    toggleSlider: function(flag) {
 557      TCaptcha.sliderNode.style.visibility = flag ? '' : 'hidden';
 558      TCaptcha.sliderNode.style.boxShadow = flag ? '' : '0 0 4px 2px #1d8dc4';
 559    },
 560    
 561    toggleMsgOverlay: function(flag, txt) {
 562      if (txt !== undefined) {
 563        TCaptcha.msgNode.innerHTML = `<div>${txt}</div>`;
 564      }
 565      TCaptcha.msgNode.style.display = flag ? 'grid' : 'none';
 566    },
 567    
 568    onSliderInput: function() {
 569      var m = -Math.floor((+this.value) / 100 * this.twisterDelta);
 570      TCaptcha.bgNode.style.backgroundPositionX = m + 'px';
 571    },
 572    
 573    onTicketPcdTick: function() {
 574      let self = TCaptcha;
 575      
 576      let el = document.getElementById('t-pcd');
 577      
 578      if (!el) {
 579        return;
 580      }
 581      
 582      let pcd = +el.getAttribute('data-pcd');
 583      
 584      pcd = pcd - (0 | (Date.now() / 1000));
 585      
 586      if (pcd <= 0) {
 587        self.onTicketPcdEnd();
 588        return;
 589      }
 590      
 591      el.textContent = pcd;
 592      
 593      setTimeout(self.updateTicketPcd, 1000);
 594    },
 595    
 596    clearTicketOverlay: function() {
 597      TCaptcha.toggleMsgOverlay(false);
 598    },
 599    
 600    buildFromJson: function(data) {
 601      let self = TCaptcha;
 602      
 603      if (!self.node) {
 604        return;
 605      }
 606      
 607      self.unlockReloadBtn();
 608      self.toggleSlider(false);
 609      self.toggleMsgOverlay(false);
 610      
 611      self.setTicket(data.ticket);
 612      
 613      if (TCaptcha.errorCb) {
 614        TCaptcha.errorCb.call(null, '');
 615      }
 616      
 617      if (data.cd) {
 618        self.setReloadCd(data.cd * 1000, !data.challenge);
 619      }
 620      
 621      if (data.mpcd) {
 622        self.clearChallenge();
 623        self.destroyTicketCaptcha();
 624        self.buildTicketCaptcha();
 625        return;
 626      }
 627      
 628      if (data.pcd) {
 629        self.buildTicket(data);
 630        return;
 631      }
 632      
 633      if (data.error) {
 634        console.log(data.error);
 635        
 636        if (TCaptcha.errorCb) {
 637          TCaptcha.errorCb.call(null, data.error);
 638        }
 639        
 640        return;
 641      }
 642      
 643      self.imgCntNode.style.width = data.img_width + 'px';
 644      self.imgCntNode.style.height = data.img_height + 'px';
 645      
 646      self.challengeNode.value = data.challenge;
 647      
 648      self.expireTimeout = setTimeout(self.clearChallenge, data.ttl * 1000 - 3000);
 649      
 650      if (data.bg_width) {
 651        self.buildTwister(data);
 652      }
 653      else if (data.img) {
 654        self.buildStatic(data);
 655      }
 656      else {
 657        self.buildNoop(data);
 658      }
 659    },
 660    
 661    buildTwister: function(data) {
 662      let self = TCaptcha;
 663      
 664      self.fgNode.style.backgroundImage = 'url(data:image/png;base64,' + data.img + ')';
 665      self.bgNode.style.backgroundImage = 'url(data:image/png;base64,' + data.bg + ')';
 666      
 667      self.bgNode.style.backgroundPositionX = '0px';
 668      
 669      self.toggleSlider(true);
 670      self.sliderNode.value = 0;
 671      self.sliderNode.twisterDelta = data.bg_width - data.img_width;
 672      self.sliderNode.focus();
 673    },
 674    
 675    buildStatic: function(data) {
 676      let self = TCaptcha;
 677      self.fgNode.style.backgroundImage = 'url(data:image/png;base64,' + data.img + ')';
 678      self.bgNode.style.backgroundImage = '';
 679    },
 680    
 681    buildTicket: function(data) {
 682      let self = TCaptcha;
 683      self.toggleMsgOverlay(true, data.pcd_msg || 'Please wait a while.');
 684      self.fgNode.style.backgroundImage = '';
 685      self.bgNode.style.backgroundImage = '';
 686      self.setReloadCd(data.pcd * 1000, data.bpcd ? -1 : true, self.clearTicketOverlay);
 687    },
 688    
 689    buildNoop: function(data) {
 690      let self = TCaptcha;
 691      self.toggleMsgOverlay(true, 'Verification not required.');
 692      self.fgNode.style.backgroundImage = '';
 693      self.bgNode.style.backgroundImage = '';
 694    }
 695  };
 696  
 697  /**
 698   * Tooltips
 699   */
 700  var Tip = {
 701    node: null,
 702    timeout: null,
 703    delay: 300,
 704    
 705    init: function() {
 706      document.addEventListener('mouseover', this.onMouseOver, false);
 707      document.addEventListener('mouseout', this.onMouseOut, false);
 708    },
 709    
 710    onMouseOver: function(e) {
 711      var cb, data, t;
 712      
 713      t = e.target;
 714      
 715      if (Tip.timeout) {
 716        clearTimeout(Tip.timeout);
 717        Tip.timeout = null;
 718      }
 719      
 720      if (t.hasAttribute('data-tip')) {
 721        data = null;
 722        
 723        if (t.hasAttribute('data-tip-cb')) {
 724          cb = t.getAttribute('data-tip-cb');
 725          if (window[cb]) {
 726            data = window[cb](t);
 727          }
 728        }
 729        Tip.timeout = setTimeout(Tip.show, Tip.delay, e.target, data);
 730      }
 731    },
 732    
 733    onMouseOut: function(e) {
 734      if (Tip.timeout) {
 735        clearTimeout(Tip.timeout);
 736        Tip.timeout = null;
 737      }
 738      
 739      Tip.hide();
 740    },
 741    
 742    show: function(t, data, pos) {
 743      var el, rect, style, left, top;
 744      
 745      rect = t.getBoundingClientRect();
 746      
 747      el = document.createElement('div');
 748      el.id = 'tooltip';
 749      
 750      if (data) {
 751        el.innerHTML = data;
 752      }
 753      else {
 754        el.textContent = t.getAttribute('data-tip');
 755      }
 756      
 757      if (!pos) {
 758        pos = 'top';
 759      }
 760      
 761      el.className = 'tip-' + pos;
 762      
 763      document.body.appendChild(el);
 764      
 765      left = rect.left - (el.offsetWidth - t.offsetWidth) / 2;
 766      
 767      if (left < 0) {
 768        left = rect.left + 2;
 769        el.className += '-right';
 770      }
 771      else if (left + el.offsetWidth > document.documentElement.clientWidth) {
 772        left = rect.left - el.offsetWidth + t.offsetWidth + 2;
 773        el.className += '-left';
 774      }
 775      
 776      top = rect.top - el.offsetHeight - 5;
 777      
 778      style = el.style;
 779      style.top = (top + window.pageYOffset) + 'px';
 780      style.left = left + window.pageXOffset + 'px';
 781      
 782      Tip.node = el;
 783    },
 784    
 785    hide: function() {
 786      if (Tip.node) {
 787        document.body.removeChild(Tip.node);
 788        Tip.node = null;
 789      }
 790    }
 791  };
 792  
 793  /**
 794   * Settings Syncher
 795   */
 796  /*
 797  var StorageSync = {
 798    queue: [],
 799    
 800    init: function() {
 801      var el, self = StorageSync;
 802      
 803      if (self.inited || !document.body) {
 804        return;
 805      }
 806      
 807      self.remoteFrame = null;
 808      
 809      self.remoteOrigin = location.protocol + '//boards.'
 810        + (location.host === 'boards.4channel.org' ? '4chan' : '4channel')
 811        + '.org';
 812      
 813      window.addEventListener('message', self.onFrameMessage, false);
 814      
 815      el = document.createElement('iframe');
 816      el.width = 0;
 817      el.height = 0;
 818      el.style.display = 'none';
 819      el.style.visibility = 'hidden';
 820      
 821      el.src = self.remoteOrigin + '/syncframe.html';
 822      
 823      document.body.appendChild(el);
 824      
 825      self.inited = true;
 826    },
 827    
 828    onFrameMessage: function(e) {
 829      var self = StorageSync;
 830      
 831      if (e.origin !== self.remoteOrigin) {
 832        return;
 833      }
 834      
 835      if (e.data === 'ready') {
 836        self.remoteFrame = e.source;
 837        
 838        if (self.queue.length) {
 839          self.send();
 840        }
 841        
 842        return;
 843      }
 844    },
 845    
 846    sync: function(key) {
 847      var self = StorageSync;
 848      
 849      self.queue.push(key);
 850      self.send();
 851    },
 852    
 853    send: function() {
 854      var i, key, data, self = StorageSync;
 855      
 856      if (!self.inited) {
 857        return self.init();
 858      }
 859      
 860      if (!self.remoteFrame) {
 861        return;
 862      }
 863      
 864      data = {};
 865      
 866      for (i = 0; key = self.queue[i]; ++i) {
 867        data[key] = localStorage.getItem(key);
 868      }
 869      
 870      self.queue = [];
 871      
 872      self.remoteFrame.postMessage({ storage: data }, self.remoteOrigin);
 873    }
 874  };
 875  */
 876  function mShowFull(t) {
 877    var el, data;
 878    
 879    if (t.className === 'name') {
 880      if (el = t.parentNode.parentNode.parentNode
 881          .getElementsByClassName('name')[1]) {
 882        data = el.innerHTML;
 883      }
 884    }
 885    else if (t.parentNode.className === 'subject') {
 886      if (el = t.parentNode.parentNode.parentNode.parentNode
 887          .getElementsByClassName('subject')[1]) {
 888        data = el.innerHTML;
 889      }
 890    }
 891    else if (/fileThumb/.test(t.parentNode.className)) {
 892      if (el = t.parentNode.parentNode.getElementsByClassName('fileText')[0]) {
 893        el = el.firstElementChild;
 894        data = el.getAttribute('title') || el.innerHTML;
 895      }
 896    }
 897    
 898    return data;
 899  }
 900  
 901  function loadBannerImage() {
 902    var cnt = document.getElementById('bannerCnt');
 903    
 904    if (!cnt || cnt.offsetWidth <= 0) {
 905      return;
 906    }
 907    
 908    cnt.innerHTML = '<img alt="4chan" src="//s.4cdn.org/image/title/'
 909      + cnt.getAttribute('data-src') + '">';
 910  }
 911  
 912  function onMobileSelectChange() {
 913    var board, page;
 914    
 915    board = this.options[this.selectedIndex].value;
 916    page = (board !== 'f' && /\/catalog$/.test(location.pathname)) ? 'catalog' : '';
 917    
 918    window.location = '//boards.' + $L.d(board) + '/' + board + '/' + page;
 919  }
 920  
 921  function buildMobileNav() {
 922    var el, boards, i, b, html, order;
 923    
 924    if (el = document.getElementById('boardSelectMobile')) {
 925      html = '';
 926      order = [];
 927      
 928      boards = document.querySelectorAll('#boardNavDesktop .boardList a');
 929      
 930      for (i = 0; b = boards[i]; ++i) {
 931        order.push(b);
 932      }
 933      
 934      order.sort(function(a, b) {
 935        if (a.textContent < b.textContent) {
 936          return -1;
 937        }
 938        if (a.textContent > b.textContent) {
 939          return 1;
 940        }
 941        return 0;
 942      });
 943      
 944      for (i = 0; b = order[i]; ++i) {
 945        html += '<option class="'
 946          + (b.parentNode.classList.contains('nwsb') ? 'nwsb' : '') + '" value="'
 947          + b.textContent + '">/'
 948          + b.textContent + '/ - '
 949          + b.title + '</option>';
 950      }
 951      
 952      el.innerHTML = html;
 953    }
 954  }
 955  
 956  function cloneTopNav() {
 957    var navT, navB, ref, el;
 958    
 959    navT = document.getElementById('boardNavDesktop');
 960    
 961    if (!navT) {
 962      return;
 963    }
 964    
 965    ref = document.getElementById('absbot');
 966    
 967    navB = navT.cloneNode(true);
 968    navB.id = navB.id + 'Foot';
 969    
 970    if (el = navB.querySelector('#navtopright')) {
 971      el.id = 'navbotright';
 972    }
 973    
 974    if (el = navB.querySelector('#settingsWindowLink')) {
 975      el.id = el.id + 'Bot';
 976    }
 977    
 978    document.body.insertBefore(navB, ref);
 979  }
 980  
 981  function initPass() {
 982    if (get_cookie("pass_enabled") == '1' || get_cookie('extra_path')) {
 983      window.passEnabled = true;
 984    }
 985    else {
 986      window.passEnabled = false;
 987    }
 988  }
 989  
 990  function initBlotter() {
 991    var mTime, seenTime, el;
 992    
 993    el = document.getElementById('toggleBlotter');
 994    
 995    if (!el) {
 996      return;
 997    }
 998    
 999    el.addEventListener('click', toggleBlotter, false);
1000    
1001    seenTime = localStorage.getItem('4chan-blotter');
1002    
1003    if (!seenTime) {
1004      return;
1005    }
1006    
1007    mTime = +el.getAttribute('data-utc');
1008    
1009    if (mTime <= +seenTime) {
1010      toggleBlotter();
1011    }
1012  }
1013  
1014  function toggleBlotter(e) {
1015    var el, btn;
1016    
1017    e && e.preventDefault();
1018    
1019    el = document.getElementById('blotter-msgs');
1020    
1021    if (!el) {
1022      return;
1023    }
1024    
1025    btn = document.getElementById('toggleBlotter');
1026    
1027    if (el.style.display == 'none') {
1028      el.style.display = '';
1029      localStorage.removeItem('4chan-blotter');
1030      btn.textContent = 'Hide';
1031      
1032      el = btn.nextElementSibling;
1033      
1034      if (el.style.display) {
1035        el.style.display = '';
1036      }
1037    }
1038    else {
1039      el.style.display = 'none';
1040      localStorage.setItem('4chan-blotter', btn.getAttribute('data-utc'));
1041      btn.textContent = 'Show Blotter';
1042      btn.nextElementSibling.style.display = 'none';
1043    }
1044  }
1045  
1046  function onRecaptchaLoaded() {
1047    if (document.getElementById('postForm').style.display == 'table') {
1048      initRecaptcha();
1049    }
1050  }
1051  
1052  function initRecaptcha() {
1053    var el;
1054    
1055    el = document.getElementById('g-recaptcha');
1056    
1057    if (!el || el.firstElementChild) {
1058      return;
1059    }
1060    
1061    if (!window.passEnabled && window.grecaptcha) {
1062      grecaptcha.render(el, {
1063        sitekey: window.recaptchaKey,
1064        theme: (activeStyleSheet === 'Tomorrow' || window.dark_captcha) ? 'dark' : 'light'
1065      });
1066    }
1067  }
1068  
1069  function initTCaptcha() {
1070    let el = document.getElementById('t-root');
1071    
1072    if (el) {
1073      let board = location.pathname.split(/\//)[1];
1074      
1075      let thread_id;
1076      
1077      if (document.forms.post && document.forms.post.resto) {
1078        thread_id = +document.forms.post.resto.value;
1079      }
1080      else {
1081        thread_id = 0;
1082      }
1083      
1084      TCaptcha.init(el, board, thread_id, 5);
1085      TCaptcha.setErrorCb(window.showPostFormError);
1086    }
1087  }
1088  
1089  function initAnalytics() {
1090    var tid = location.host.indexOf('.4channel.org') !== -1 ? 'UA-166538-5' : 'UA-166538-1';
1091    
1092    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
1093    
1094    ga('create', tid, {'sampleRate': 1});
1095    ga('set', 'anonymizeIp', true);
1096    ga('send','pageview');
1097  }
1098  
1099  function initAdsPF(cnt, slot_id) {
1100    let sid, nid;
1101    
1102    if (slot_id == 1) {
1103      sid = '657b2d8958f9186175770b1f';
1104      nid = 'pf-6892-1';
1105    }
1106    else if (slot_id == 2) {
1107      sid = '657b2d9d58f9186175770b37';
1108      nid = 'pf-6893-1';
1109    }
1110    else if (slot_id == 3) {
1111      sid = '657b2d56256794003cd16fe4';
1112      nid = 'pf-6890-1';
1113    }
1114    else if (slot_id == 4) {
1115      sid = '657b2d74256794003cd17019';
1116      nid = 'pf-6891-1';
1117    }
1118    else {
1119      return;
1120    }
1121    
1122    cnt.innerHTML = '';
1123    
1124    let d = document.createElement('div');
1125    d.id = nid;
1126    cnt.appendChild(d);
1127    
1128    window.pubfuturetag = window.pubfuturetag || [];
1129    window.pubfuturetag.push({unit: sid, id: nid});
1130  }
1131  
1132  function initAdsADT(scope) {
1133    var el, nodes, i, cls, s;
1134    
1135    if (window.matchMedia && window.matchMedia('(max-width: 480px)').matches && localStorage.getItem('4chan_never_show_mobile') != 'true') {
1136      cls = 'adg-m';
1137    }
1138    else {
1139      cls = 'adg';
1140    }
1141    
1142    nodes = (scope || document).getElementsByClassName(cls);
1143    
1144    for (i = 0; el = nodes[i]; ++i) {
1145      if (el.hasAttribute('data-abc')) {
1146        s = document.createElement('iframe');
1147        s.setAttribute('scrolling', 'no');
1148        s.setAttribute('frameborder', '0');
1149        s.setAttribute('allowtransparency', 'true');
1150        s.setAttribute('marginheight', '0');
1151        s.setAttribute('marginwidth', '0');
1152        
1153        if (cls === 'adg') {
1154          s.setAttribute('width', '728');
1155          s.setAttribute('height', '90');
1156        }
1157        else {
1158          s.setAttribute('width', '300');
1159          s.setAttribute('height', '250');
1160        }
1161        
1162        s.setAttribute('name', 'spot_id_' + el.getAttribute('data-abc'));
1163        s.src = 'https://a.adtng.com/get/' + el.getAttribute('data-abc') + '?time=' + Date.now();
1164        el.appendChild(s);
1165      }
1166    }
1167  }
1168  
1169  function danboAddSlot(n, b, m, s) {
1170    let pubid = 27;
1171    
1172    let el = document.createElement('div');
1173    el.className = 'danbo_dta';
1174    
1175    if (m) {
1176      if (s) {
1177        s = '3';
1178      }
1179      else {
1180        s = '4';
1181        el.id = 'js-danbo-rld';
1182      }
1183      el.setAttribute('data-danbo', `${pubid}-${b}-${s}-300-250`);
1184      el.classList.add('danbo-m');
1185    }
1186    else {
1187      if (s) {
1188        s = '1';
1189      }
1190      else {
1191        s = '2';
1192        el.id = 'js-danbo-rld';
1193      }
1194      el.setAttribute('data-danbo', `${pubid}-${b}-${s}-728-90`);
1195      el.classList.add('danbo-d');
1196    }
1197    
1198    n.appendChild(el);
1199    
1200    return el;
1201  }
1202  
1203  function initAdsDanbo() {
1204    if (!window.Danbo) {
1205      return;
1206    }
1207    
1208    let b = location.pathname.split(/\//)[1] || '_';
1209    
1210    let m = window.matchMedia && window.matchMedia('(max-width: 480px)').matches;
1211    
1212    let nodes = document.getElementsByClassName('danbo-slot');
1213    
1214    for (let cnt of nodes) {
1215      let s = cnt.id === 'danbo-s-t';
1216      danboAddSlot(cnt, b, m, s);
1217    }
1218    
1219    window.addEventListener('message', function(e) {
1220      if (e.origin === 'https://hakurei.cdnbo.org' && e.data && e.data.origin === 'danbo') {
1221        window.initAdsFallback(e.data.unit_id);
1222      }
1223    });
1224    
1225    window.Danbo.initialize();
1226  }
1227  
1228  function reloadAdsDanbo() {
1229    let cnt = document.getElementById('danbo-s-b');
1230    
1231    if (!cnt) {
1232      return;
1233    }
1234    
1235    cnt.innerHTML = '';
1236    
1237    let b = 'a';//location.pathname.split(/\//)[1] || '_';
1238    
1239    let m = window.matchMedia && window.matchMedia('(max-width: 480px)').matches;
1240    
1241    danboAddSlot(cnt, b, m, false);
1242    
1243    window.Danbo.reload('js-danbo-rld');
1244  }
1245  
1246  function initAdsFallback(slot_id) {
1247    let fb = window.danbo_fb;
1248    
1249    let cnt_id;
1250    
1251    if (slot_id == 1 || slot_id == 3) {
1252      cnt_id = 'danbo-s-t';
1253    }
1254    else {
1255      cnt_id = 'danbo-s-b';
1256    }
1257    
1258    let cnt = document.getElementById(cnt_id);
1259    
1260    if (!cnt) {
1261      return;
1262    }
1263    
1264    let is_burichan = document.body.classList.contains('ws');
1265    
1266    let hr = is_burichan ? 0.1 : 0.01;
1267    
1268    if (Math.random() < hr) {
1269      return initAdsHome(cnt);
1270    }
1271    
1272    if (cnt_id === 'danbo-s-t') {
1273      if (is_burichan) {
1274        initAdsPF(cnt, slot_id);
1275      }
1276      else if (fb) {
1277        if (slot_id == 1 && fb.t_abc_d) {
1278          cnt.innerHTML = `<div class="adg-rects desktop"><div class="adg adp-90" data-abc="${fb.t_abc_d}"></div></div>`;
1279          initAdsADT(cnt);
1280        }
1281        else if (slot_id == 3 && fb.t_abc_m) {
1282          cnt.innerHTML = `<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="${fb.t_abc_m}"></div></div>`;
1283          initAdsADT(cnt);
1284        }
1285        else {
1286          initAdsHome(cnt);
1287        }
1288      }
1289      else {
1290        initAdsHome(cnt);
1291      }
1292    }
1293    else if (cnt_id === 'danbo-s-b') {
1294      if (is_burichan) {
1295        initAdsPF(cnt, slot_id);
1296      }
1297      else if (fb) {
1298        if (slot_id == 4 && fb.b_abc_m) {
1299          cnt.innerHTML = `<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="${fb.b_abc_m}"></div></div>`;
1300          initAdsADT(cnt);
1301        }
1302        else {
1303          initAdsHome(cnt);
1304        }
1305      }
1306      else {
1307        initAdsHome(cnt);
1308      }
1309    }
1310    else {
1311      console.log('Fallback', slot_id);
1312    }
1313  }
1314  
1315  function initAdsHome(cnt) {
1316    let banners = [
1317      ['advertise', '1.png', '2.png', '3.png'],
1318      ['pass', '4.png'],
1319    ];
1320    
1321    let banners_m = [
1322      ['advertise', '1m.png'],
1323    ];
1324    
1325    let d;
1326    
1327    if (location.host.indexOf('4channel')) {
1328      d = '4channel';
1329    }
1330    else {
1331      d = '4chan';
1332    }
1333    
1334    let b;
1335    
1336    if (window.matchMedia && window.matchMedia('(max-width: 480px)').matches) {
1337      b = banners_m;
1338    }
1339    else {
1340      b = banners;
1341    }
1342    
1343    b = b[Math.floor(Math.random() * b.length)];
1344    
1345    let href = b[0];
1346    let src = b[1 + Math.floor(Math.random() * (b.length - 1))];
1347    
1348    let a = document.createElement('a');
1349    a.href = `https://www.${d}.org/${href}`;
1350    a.target = '_blank';
1351    
1352    let img = document.createElement('img');
1353    img.src = '//s.4cdn.org/image/banners/' + src;
1354    
1355    a.appendChild(img);
1356    
1357    if (cnt.children.length) {
1358      cnt.innerHTML = '';
1359    }
1360    
1361    cnt.appendChild(a);
1362  }
1363  
1364  function applySearch(e) {
1365    var str;
1366    
1367    e && e.preventDefault();
1368    
1369    str = document.getElementById('search-box').value;
1370    
1371    if (str !== '') {
1372      window.location.href = 'catalog#s=' + str;
1373    }
1374  }
1375  
1376  function onKeyDownSearch(e) {
1377    if (e.keyCode == 13) {
1378      applySearch();
1379    }
1380  }
1381  
1382  function onReportClick(e) {
1383    var i, input, nodes, board;
1384    
1385    nodes = document.getElementsByTagName('input');
1386    
1387    board = location.pathname.split(/\//)[1];
1388    
1389    for (i = 0; input = nodes[i]; ++i) {
1390      if (input.type == 'checkbox' && input.checked && input.value == 'delete') {
1391        return reppop('https://sys.' + $L.d(board) + '/' + board + '/imgboard.php?mode=report&no='
1392          + input.name.replace(/[a-z]+/, '')
1393        );
1394      }
1395    }
1396  }
1397  
1398  function onStyleSheetChange(e) {
1399    setActiveStyleSheet(this.value);
1400  }
1401  
1402  function onPageSwitch(e) {
1403    e.preventDefault();
1404    window.location = this.action;
1405  }
1406  
1407  function onMobileFormClick(e) {
1408    var index = location.pathname.split(/\//).length < 4;
1409    
1410    e.preventDefault();
1411    
1412    if (window.QR && Main.tid && QR.enabled) {
1413      QR.show(Main.tid);
1414    }
1415    else if (this.parentNode.id == 'mpostform') {
1416      toggleMobilePostForm(index);
1417    }
1418    else {
1419      toggleMobilePostForm(index, 1);
1420    }
1421  }
1422  
1423  function onMobileRefreshClick(e) {
1424    locationHashChanged(this);
1425  }
1426  
1427  function toggle(name) {
1428    var a = document.getElementById(name);
1429    a.style.display = ((a.style.display != 'block') ? 'block' : 'none');
1430  }
1431  
1432  function quote(text) {
1433    if (document.selection) {
1434      document.post.com.focus();
1435      var sel = document.selection.createRange();
1436      sel.text = ">>" + text + "\n";
1437    } else if (document.post.com.selectionStart || document.post.com.selectionStart == "0") {
1438      var startPos = document.post.com.selectionStart;
1439      var endPos = document.post.com.selectionEnd;
1440      document.post.com.value = document.post.com.value.substring(0, startPos) + ">>" + text + "\n" + document.post.com.value.substring(endPos, document.post.com.value.length);
1441    } else {
1442      document.post.com.value += ">>" + text + "\n";
1443    }
1444  }
1445  
1446  function repquote(rep) {
1447    if (document.post.com.value == "") {
1448      quote(rep);
1449    }
1450  }
1451  
1452  function reppop(url) {
1453    var height;
1454    
1455    if (window.passEnabled || !window.grecaptcha) {
1456      height = 205;
1457    }
1458    else {
1459      height = 510;
1460    }
1461    
1462    window.open(url, Date.now(), 
1463      'toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,width=380,height=' + height
1464    );
1465    
1466    return false;
1467  }
1468  
1469  function recaptcha_load() {
1470    var d = document.getElementById("recaptcha_div");
1471    if (!d) return;
1472  
1473    Recaptcha.create("6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc", "recaptcha_div",{theme: "clean"});
1474  }
1475  
1476  function onParsingDone(e) {
1477    var i, nodes, n, p, tid, offset, limit;
1478  
1479    tid = e.detail.threadId;
1480    offset = e.detail.offset;
1481    
1482    if (!offset) {
1483      return;
1484    }
1485  
1486    nodes = document.getElementById('t' + tid).getElementsByClassName('nameBlock');
1487    limit = e.detail.limit ? (e.detail.limit * 2) : nodes.length;
1488    for (i = offset * 2 + 1; i < limit; i+=2) {
1489      if (n = nodes[i].children[1]) {
1490        if (currentHighlighted
1491          && n.className.indexOf('id_' + currentHighlighted) != -1) {
1492          p = n.parentNode.parentNode.parentNode;
1493          p.className = 'highlight ' + p.className;
1494        }
1495        n.addEventListener('click', idClick, false);
1496      }
1497    }
1498  }
1499  
1500  function loadExtraScripts() {
1501    var el, path;
1502    
1503    path = readCookie('extra_path');
1504    
1505    if (!path || !/^[a-z0-9]+$/.test(path)) {
1506      return false;
1507    }
1508    
1509    if (window.FC) {
1510      el = document.createElement('script');
1511      el.type = 'text/javascript';
1512      el.src = 'https://s.4cdn.org/js/' + path + '.' + jsVersion + '.js';
1513      document.head.appendChild(el);
1514    }
1515    else {
1516      document.write('<script type="text/javascript" src="https://s.4cdn.org/js/' + path + '.' + jsVersion + '.js"></script>');
1517    }
1518    
1519    return true;
1520  }
1521  
1522  
1523  function toggleMobilePostForm(index, scrolltotop) {
1524    var elem = document.getElementById('mpostform').firstElementChild;
1525    var postForm = document.getElementById('postForm');
1526    
1527    if (elem.className.match('hidden')) {
1528      elem.className = elem.className.replace('hidden', 'shown');
1529      postForm.className = postForm.className.replace(' hideMobile', '');
1530      elem.innerHTML = 'Close Post Form';
1531      initRecaptcha();
1532      initTCaptcha();
1533      checkIncognito();
1534    }
1535    else {
1536      elem.className = elem.className.replace('shown', 'hidden');
1537      postForm.className += ' hideMobile';
1538      elem.innerHTML = (index) ? 'Start New Thread' : 'Post Reply';
1539    }
1540    
1541    if (scrolltotop) {
1542      elem.scrollIntoView();
1543    }
1544  }
1545  
1546  function toggleGlobalMessage(e) {
1547    var elem, postForm;
1548    
1549    if (e) {
1550      e.preventDefault();
1551    }
1552    
1553    elem = document.getElementById('globalToggle');
1554    postForm = document.getElementById('globalMessage');
1555  
1556    if( elem.className.match('hidden') ) {
1557      elem.className = elem.className.replace('hidden', 'shown');
1558      postForm.className = postForm.className.replace(' hideMobile', '');
1559  
1560      elem.innerHTML = 'Close Announcement';
1561    } else {
1562      elem.className = elem.className.replace('shown', 'hidden');
1563      postForm.className += ' hideMobile';
1564  
1565      elem.innerHTML = 'View Announcement';
1566    }
1567  }
1568  
1569  function checkRecaptcha()
1570  {
1571    if( typeof RecaptchaState.timeout != 'undefined' ) {
1572      if( RecaptchaState.timeout == 1800 ) {
1573        RecaptchaState.timeout = 570;
1574        Recaptcha._reset_timer();
1575        clearInterval(captchainterval);
1576      }
1577    }
1578  }
1579  
1580  function setPassMsg() {
1581    var el, msg;
1582    
1583    el = document.getElementById('captchaFormPart');
1584    
1585    if (!el) {
1586      return;
1587    }
1588    
1589    msg = 'You are using a 4chan Pass. [<a href="https://sys.' + $L.d(location.pathname.split(/\//)[1]) + '/auth?act=logout" onclick="confirmPassLogout(event);" tabindex="-1">Logout</a>]';
1590    el.children[1].innerHTML = '<div style="padding: 5px;">' + msg + '</div>';
1591  }
1592  
1593  function confirmPassLogout(event)
1594  {
1595    var conf = confirm('Are you sure you want to logout?');
1596    if( !conf ) {
1597      event.preventDefault();
1598      return false;
1599    }
1600  }
1601  
1602  var activeStyleSheet;
1603  
1604  function initStyleSheet() {
1605    var i, rem, link, len;
1606    
1607    // fix me
1608    if (window.FC) {
1609      return;
1610    }
1611    
1612    if (window.style_group) {
1613      var cookie = readCookie(style_group);
1614      activeStyleSheet = cookie ? cookie : getPreferredStyleSheet();
1615    }
1616    
1617    if (window.css_event && localStorage.getItem('4chan_stop_css_event') !== `${window.css_event}-${window.css_event_v}`) {
1618      activeStyleSheet = '_special';
1619    }
1620    
1621    switch(activeStyleSheet) {
1622      case "Yotsuba B":
1623        setActiveStyleSheet("Yotsuba B New", true);
1624        break;
1625  
1626      case "Yotsuba":
1627        setActiveStyleSheet("Yotsuba New", true);
1628        break;
1629  
1630      case "Burichan":
1631        setActiveStyleSheet("Burichan New", true);
1632        break;
1633  
1634      case "Futaba":
1635        setActiveStyleSheet("Futaba New", true);
1636        break;
1637  
1638      default:
1639        setActiveStyleSheet(activeStyleSheet, true);
1640      break;
1641    }
1642    
1643    if (localStorage.getItem('4chan_never_show_mobile') == 'true') {
1644      link = document.querySelectorAll('link');
1645      len = link.length;
1646      for (i = 0; i < len; i++) {
1647        if (link[i].getAttribute('href').match('mobile')) {
1648          (rem = link[i]).parentNode.removeChild(rem);
1649        }
1650      }
1651    }
1652  }
1653  
1654  function pageHasMath() {
1655    var i, el, nodes;
1656    
1657    nodes = document.getElementsByClassName('postMessage');
1658    
1659    for (i = 0; el = nodes[i]; ++i) {
1660      if (/\[(?:eqn|math)\]|"math">/.test(el.innerHTML)) {
1661        return true;
1662      }
1663    }
1664    
1665    return false;
1666  }
1667  
1668  function cleanWbr(el) {
1669    var i, nodes, n;
1670    
1671    nodes = el.getElementsByTagName('wbr');
1672    
1673    for (i = nodes.length - 1; n = nodes[i]; i--) {
1674      n.parentNode.removeChild(n);
1675    }
1676  }
1677  
1678  function parseMath() {
1679    var i, el, nodes;
1680    
1681    nodes = document.getElementsByClassName('postMessage');
1682    
1683    for (i = 0; el = nodes[i]; ++i) {
1684      if (/\[(?:eqn|math)\]/.test(el.innerHTML)) {
1685        cleanWbr(el);
1686      }
1687    }
1688    
1689    MathJax.Hub.Queue(['Typeset', MathJax.Hub, nodes]);
1690  }
1691  
1692  function loadMathJax() {
1693    var head, script;
1694    
1695    head = document.getElementsByTagName('head')[0];
1696    
1697    script = document.createElement('script');
1698    script.type = 'text/x-mathjax-config';
1699    script.text = "MathJax.Hub.Config({\
1700  extensions: ['Safe.js'],\
1701  tex2jax: { processRefs: false, processEnvironments: false, preview: 'none', inlineMath: [['[math]','[/math]']], displayMath: [['[eqn]','[/eqn]']] },\
1702  Safe: { allow: { URLs: 'none', classes: 'none', cssIDs: 'none', styles: 'none', fontsize: 'none', require: 'none' } },\
1703  displayAlign: 'left', messageStyle: 'none', skipStartupTypeset: true,\
1704  'CHTML-preview': { disabled: true }, MathMenu: { showRenderer: false, showLocale: false },\
1705  TeX: { Macros: { color: '{}', newcommand: '{}', renewcommand: '{}', newenvironment: '{}', renewenvironment: '{}', def: '{}', let: '{}'}}});";
1706    head.appendChild(script);  
1707    
1708    script = document.createElement('script');
1709    script.src = '//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-AMS_HTML-full';
1710    script.onload = parseMath;
1711    head.appendChild(script);
1712  }
1713  
1714  captchainterval = null;
1715  function init() {
1716    var el, i;
1717    var error = typeof is_error != "undefined";
1718    var board = location.href.match(/(?:4chan|4channel)\.org\/(\w+)/)[1];
1719    var arr = location.href.split(/#/);
1720    if( arr[1] && arr[1].match(/q[0-9]+$/) ) {
1721      repquote( arr[1].match(/q([0-9]+)$/)[1] );
1722    }
1723  
1724  
1725    if (window.math_tags && pageHasMath()) {
1726      loadMathJax();
1727    }
1728  
1729    if(navigator.userAgent) {
1730      if( navigator.userAgent.match( /iP(hone|ad|od)/i ) ) {
1731        links = document.querySelectorAll('s');
1732        len = links.length;
1733  
1734        for(i = 0; i < len; i++ ) {
1735          links[i].onclick = function() {
1736            if (this.hasAttribute('style')) {
1737              this.removeAttribute('style');
1738            }
1739            else {
1740              this.setAttribute('style', 'color: #fff!important;');
1741            }
1742          };
1743        }
1744      }
1745    }
1746  
1747    if( document.getElementById('styleSelector') ) {
1748          styleSelect = document.getElementById('styleSelector');
1749          len = styleSelect.options.length;
1750          for (i = 0; i < len; i++) {
1751              if (styleSelect.options[i].value == activeStyleSheet) {
1752                  styleSelect.selectedIndex = i;
1753                  continue;
1754              }
1755          }
1756      }
1757  
1758    if (!error && document.forms.post) {
1759      if (board != 'i' && board != 'ic' && board != 'f') {
1760        if (window.File && window.FileReader && window.FileList && window.Blob) {
1761          el = document.getElementById('postFile');
1762          el && el.addEventListener('change', handleFileSelect, false);
1763        }
1764      }
1765    }
1766  
1767    //window.addEventListener('onhashchange', locationHashChanged, false);
1768  
1769    if( typeof extra != "undefined" && extra && !error ) extra.init();
1770  }
1771  
1772  var coreLenCheckTimeout = null;
1773  function onComKeyDown() {
1774    clearTimeout(coreLenCheckTimeout);
1775    coreLenCheckTimeout = setTimeout(coreCheckComLength, 500);
1776  }
1777  
1778  function coreCheckComLength() {
1779    var byteLength, comField, error;
1780    
1781    if (comlen) {
1782      comField = document.getElementsByName('com')[0];
1783      byteLength = encodeURIComponent(comField.value).split(/%..|./).length - 1;
1784      
1785      if (byteLength > comlen) {
1786        if (!(error = document.getElementById('comlenError'))) {
1787          error = document.createElement('div');
1788          error.id = 'comlenError';
1789          error.style.cssText = 'font-weight:bold;padding:5px;color:red;';
1790          comField.parentNode.appendChild(error);
1791        }
1792        error.textContent = 'Error: Comment too long (' + byteLength + '/' + comlen + ').';
1793      }
1794      else if (error = document.getElementById('comlenError')) {
1795        error.parentNode.removeChild(error);
1796      }
1797    }
1798  }
1799  
1800  function disableMobile() {
1801    localStorage.setItem('4chan_never_show_mobile', 'true');
1802    location.reload(true);
1803  }
1804  
1805  function enableMobile() {
1806    localStorage.removeItem('4chan_never_show_mobile');
1807    location.reload(true);
1808  }
1809  
1810  var currentHighlighted = null;
1811  function enableClickableIds()
1812  {
1813    var i = 0, len = 0;
1814    var elems = document.getElementsByClassName('posteruid');
1815    var capcode = document.getElementsByClassName('capcode');
1816  
1817    if( capcode != null ) {
1818      for( i = 0, len = capcode.length; i < len; i++ ) {
1819        capcode[i].addEventListener("click", idClick, false);
1820      }
1821    }
1822  
1823    if( elems == null ) return;
1824    for( i = 0, len = elems.length; i < len; i++ ) {
1825      elems[i].addEventListener("click", idClick, false);
1826    }
1827  }
1828  
1829  function idClick(evt)
1830  {
1831    var i = 0, len = 0, node;
1832    var uid = evt.target.className == 'hand' ? evt.target.parentNode.className.match(/id_([^ $]+)/)[1] : evt.target.className.match(/id_([^ $]+)/)[1];
1833  
1834    // remove all .highlight classes
1835    var hl = document.getElementsByClassName('highlight');
1836    len = hl.length;
1837    for( i = 0; i < len; i++ ) {
1838      var cn = hl[0].className.toString();
1839      hl[0].className = cn.replace(/highlight /g, '');
1840    }
1841  
1842    if( currentHighlighted == uid ) {
1843      currentHighlighted = null;
1844      return;
1845    }
1846    currentHighlighted = uid;
1847  
1848    var nhl = document.getElementsByClassName('id_' + uid);
1849    len = nhl.length;
1850    for( i = 0; i < len; i++ ) {
1851      node = nhl[i].parentNode.parentNode.parentNode;
1852      if( !node.className.match(/highlight /) ) node.className = "highlight " + node.className;
1853    }
1854  }
1855  
1856  function showPostFormError(msg) {
1857    var el = document.getElementById('postFormError');
1858    
1859    if (msg) {
1860      el.innerHTML = msg;
1861      el.style.display = 'block';
1862    }
1863    else {
1864      el.textContent = '';
1865      el.style.display = '';
1866    }
1867  }
1868  
1869  function handleFileSelect() {
1870    var fsize, ftype, maxFilesize;
1871    
1872    if (this.files) {
1873      maxFilesize = window.maxFilesize;
1874      
1875      fsize = this.files[0].size;
1876      ftype = this.files[0].type;
1877      
1878      if (ftype.indexOf('video/') !== -1 && window.maxWebmFilesize) {
1879        maxFilesize = window.maxWebmFilesize;
1880      }
1881      
1882      if (fsize > maxFilesize) {
1883        showPostFormError('Error: Maximum file size allowed is '
1884          + Math.floor(maxFilesize / 1048576) + ' MB');
1885      }
1886      else {
1887        showPostFormError();
1888      }
1889    }
1890  }
1891  
1892  function locationHashChanged(e)
1893  {
1894    var css = document.getElementById('id_css');
1895  
1896    switch( e.id )
1897    {
1898      case 'refresh_top':
1899        url = window.location.href.replace(/#.+/, '#top');
1900        if( !/top$/.test(url) ) url += '#top';
1901        css.innerHTML = '<meta http-equiv="refresh" content="0;URL=' + url + '">';
1902        document.location.reload(true);
1903        break;
1904  
1905      case 'refresh_bottom':
1906        url = window.location.href.replace(/#.+/, '#bottom');
1907        if( !/bottom$/.test(url) ) url += '#bottom';
1908        css.innerHTML = '<meta http-equiv="refresh" content="0;URL=' + url + '">';
1909        document.location.reload(true);
1910        break;
1911  
1912      default:break;
1913    }
1914  
1915    return true;
1916  
1917  }
1918  
1919  function setActiveStyleSheet(title, init) {
1920    var a, link, href, i, nodes, fn;
1921    
1922    if( document.querySelectorAll('link[title]').length == 1 ) {
1923      return;
1924    }
1925    
1926    href = '';
1927    
1928    nodes = document.getElementsByTagName('link');
1929    
1930    for (i = 0; a = nodes[i]; i++) {
1931      if (a.getAttribute("title") == "switch") {
1932        link = a;
1933      }
1934      
1935      if (a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
1936        if (a.getAttribute("title") == title) {
1937          href = a.href;
1938        }
1939      }
1940    }
1941  
1942    link && link.setAttribute("href", href);
1943  
1944    if (!init) {
1945      if (title !== '_special') {
1946        createCookie(style_group, title, 365, location.host.indexOf('4channel.org') === -1 ? '4chan.org' : '4channel.org');
1947        
1948        if (window.css_event) {
1949          fn = window['fc_' + window.css_event + '_cleanup'];
1950          localStorage.setItem('4chan_stop_css_event', `${window.css_event}-${window.css_event_v}`);
1951        }
1952      }
1953      else if (window.css_event) {
1954        fn = window['fc_' + window.css_event + '_init'];
1955        localStorage.removeItem('4chan_stop_css_event');
1956      }
1957      
1958      //StorageSync.sync('4chan_stop_css_event');
1959      
1960      activeStyleSheet = title;
1961      
1962      fn && fn();
1963    }
1964  }
1965  
1966  function getActiveStyleSheet() {
1967    var i, a;
1968    var link;
1969  
1970      if( document.querySelectorAll('link[title]').length == 1 ) {
1971          return 'Yotsuba P';
1972      }
1973  
1974    for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
1975      if (a.getAttribute("title") == "switch")
1976                 link = a;
1977      else if (a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && a.href==link.href) return a.getAttribute("title");
1978    }
1979    return null;
1980  }
1981  
1982  function getPreferredStyleSheet() {
1983    return (style_group == "ws_style") ? "Yotsuba B New" : "Yotsuba New";
1984  }
1985  
1986  function createCookie(name, value, days, domain) {
1987    let expires;
1988    
1989    if (days) {
1990      var date = new Date();
1991      date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
1992      expires = "; expires=" + date.toGMTString();
1993    }
1994    else {
1995      expires = '';
1996    }
1997    
1998    if (domain) {
1999      domain = "; domain=" + domain;
2000    }
2001    else {
2002      domain = '';
2003    }
2004    
2005    document.cookie = name + "=" + value + expires + "; path=/" + domain;
2006  }
2007  
2008  function readCookie(name) {
2009    var nameEQ = name + "=";
2010    var ca = document.cookie.split(';');
2011    for (var i = 0; i < ca.length; i++) {
2012      var c = ca[i];
2013      while (c.charAt(0) == ' ') c = c.substring(1, c.length);
2014      if (c.indexOf(nameEQ) == 0) {
2015        return decodeURIComponent(c.substring(nameEQ.length, c.length));
2016      }
2017    }
2018    return '';
2019  }
2020  
2021  // legacy
2022  var get_cookie = readCookie;
2023  
2024  function setRetinaIcons() {
2025    var i, j, nodes;
2026    
2027    nodes = document.getElementsByClassName('retina');
2028    
2029    for (i = 0; j = nodes[i]; ++i) {
2030      j.src = j.src.replace(/\.(gif|png)$/, "@2x.$1");
2031    }
2032  }
2033  
2034  function onCoreClick(e) {
2035    if (/flag flag-/.test(e.target.className) && e.which == 1) {
2036      window.open('//s.4cdn.org/image/country/'
2037        + e.target.className.match(/flag-([a-z]+)/)[1]
2038        + '.gif', '');
2039    }
2040  }
2041  
2042  function showPostForm(e) {
2043    var el;
2044    
2045    e && e.preventDefault();
2046    
2047    if (el = document.getElementById('postForm')) {
2048      $.id('togglePostFormLink').style.display = 'none';
2049      el.style.display = 'table';
2050      initRecaptcha();
2051      initTCaptcha();
2052    }
2053  }
2054  
2055  function oeCanvasPreview(e) {
2056    var t, el, sel;
2057    
2058    if (el = document.getElementById('oe-canvas-preview')) {
2059      el.parentNode.removeChild(el);
2060    }
2061    
2062    if (e.target.nodeName == 'OPTION' && e.target.value != '0') {
2063      t = document.getElementById('f' + e.target.value);
2064      
2065      if (!t) {
2066        return;
2067      }
2068      
2069      t = t.getElementsByTagName('img')[0];
2070      
2071      if (!t || !t.hasAttribute('data-md5')) {
2072        return;
2073      }
2074      
2075      el = t.cloneNode();
2076      el.id = 'oe-canvas-preview';
2077      sel = e.target.parentNode;
2078      sel.parentNode.insertBefore(el, sel.nextSibling);
2079    }
2080  }
2081  
2082  function oeClearPreview(e) {
2083    var el;
2084    
2085    if (el = document.getElementById('oe-canvas-preview')) {
2086      el.parentNode.removeChild(el);
2087    }
2088  }
2089  
2090  var PainterCore = {
2091    init: function() {
2092      var cnt, btns;
2093      
2094      if (!document.forms.post) {
2095        return;
2096      }
2097      
2098      cnt = document.forms.post.getElementsByClassName('painter-ctrl')[0];
2099      
2100      if (!cnt) {
2101        return;
2102      }
2103      
2104      btns = cnt.getElementsByTagName('button');
2105      
2106      if (!btns[1]) {
2107        return;
2108      }
2109      
2110      this.data = null;
2111      this.replayBlob = null;
2112      
2113      this.time = 0;
2114      
2115      this.btnDraw = btns[0];
2116      this.btnClear = btns[1];
2117      this.btnFile = document.getElementById('postFile');
2118      this.btnSubmit = document.forms.post.querySelector('input[type="submit"]');
2119      this.inputNodes = cnt.getElementsByTagName('input');
2120      this.replayCb = cnt.getElementsByClassName('oe-r-cb')[0];
2121      
2122      btns[0].addEventListener('click', this.onDrawClick, false);
2123      btns[1].addEventListener('click', this.onCancel, false);
2124    },
2125    
2126    onDrawClick: function() {
2127      var w, h, dims = this.parentNode.getElementsByTagName('input');
2128      
2129      w = +dims[0].value;
2130      h = +dims[1].value;
2131      
2132      if (w < 1 || h < 1) {
2133        return;
2134      }
2135      
2136      window.Keybinds && (Keybinds.enabled = false);
2137      
2138      Tegaki.open({
2139        onDone: PainterCore.onDone,
2140        onCancel: PainterCore.onCancel,
2141        saveReplay: PainterCore.replayCb && PainterCore.replayCb.checked,
2142        width: w,
2143        height: h
2144      });
2145    },
2146    
2147    replay: function(id) {
2148      id = +id;
2149      
2150      Tegaki.open({
2151        replayMode: true,
2152        replayURL: '//i.4cdn.org/' + location.pathname.split(/\//)[1] + '/' + id + '.tgkr'
2153      });
2154    },
2155    
2156    // move this to tegaki.js
2157    b64toBlob: function(data) {
2158      var i, bytes, ary, bary, len;
2159      
2160      bytes = atob(data);
2161      len = bytes.length;
2162      
2163      ary = new Array(len);
2164      
2165      for (i = 0; i < len; ++i) {
2166        ary[i] = bytes.charCodeAt(i);
2167      }
2168      
2169      bary = new Uint8Array(ary);
2170      
2171      return new Blob([bary]);
2172    },
2173    
2174    onDone: function() {
2175      var self, el;
2176      
2177      self = PainterCore;
2178      
2179      window.Keybinds && (Keybinds.enabled = true);
2180      
2181      self.btnFile.disabled = true;
2182      self.btnClear.disabled = false;
2183      
2184      self.data = Tegaki.flatten().toDataURL('image/png');
2185      
2186      if (Tegaki.saveReplay) {
2187        self.replayBlob = Tegaki.replayRecorder.toBlob();
2188      }
2189      
2190      if (!Tegaki.hasCustomCanvas && Tegaki.startTimeStamp) {
2191        self.time = Math.round((Date.now() - Tegaki.startTimeStamp) / 1000);
2192      }
2193      else {
2194        self.time = 0;
2195      }
2196      
2197      self.btnFile.style.visibility = 'hidden';
2198      
2199      self.btnDraw.textContent = 'Edit';
2200      
2201      for (el of self.inputNodes) {
2202        el.disabled = true;
2203      }
2204      
2205      document.forms.post.addEventListener('submit', self.onSubmit, false);
2206    },
2207    
2208    onCancel: function() {
2209      var self = PainterCore;
2210      
2211      window.Keybinds && (Keybinds.enabled = true);
2212      
2213      self.data = null;
2214      self.replayBlob = null;
2215      self.time = 0;
2216      
2217      self.btnFile.disabled = false;
2218      self.btnClear.disabled = true;
2219      
2220      self.btnFile.style.visibility = '';
2221      
2222      self.btnDraw.textContent = 'Draw';
2223      
2224      for (var el of self.inputNodes) {
2225        el.disabled = false;
2226      }
2227      
2228      document.forms.post.removeEventListener('submit', self.onSubmit, false);
2229    },
2230    
2231    onSubmit: function(e) {
2232      var formdata, blob, xhr;
2233      
2234      e.preventDefault();
2235      
2236      formdata = new FormData(this);
2237      
2238      blob = PainterCore.b64toBlob(PainterCore.data.slice(PainterCore.data.indexOf(',') + 1));
2239      
2240      if (blob) {
2241        formdata.append('upfile', blob, 'tegaki.png');
2242        
2243        if (PainterCore.replayBlob) {
2244          formdata.append('oe_replay', PainterCore.replayBlob, 'tegaki.tgkr');
2245        }
2246      }
2247      
2248      formdata.append('oe_time', PainterCore.time);
2249      
2250      xhr = new XMLHttpRequest();
2251      xhr.open('POST', this.action, true);
2252      xhr.withCredentials = true;
2253      xhr.onerror = PainterCore.onSubmitError;
2254      xhr.onload = PainterCore.onSubmitDone;
2255      
2256      xhr.send(formdata);
2257      
2258      PainterCore.btnSubmit.disabled = true;
2259    },
2260    
2261    onSubmitError: function() {
2262      PainterCore.btnSubmit.disabled = false;
2263      showPostFormError('Connection Error.');
2264    },
2265    
2266    onSubmitDone: function() {
2267      var resp, ids, tid, pid, board;
2268      
2269      PainterCore.btnSubmit.disabled = false;
2270      
2271      if (ids = this.responseText.match(/<!-- thread:([0-9]+),no:([0-9]+) -->/)) {
2272        tid = +ids[1];
2273        pid = +ids[2];
2274        
2275        if (!tid) {
2276          tid = pid;
2277        }
2278        
2279        board = location.pathname.split(/\//)[1];
2280        
2281        window.location.href = '/' + board + '/thread/' + tid + '#p' + pid;
2282        
2283        PainterCore.onCancel();
2284        
2285        if (tid != pid) {
2286          PainterCore.btnClear.disabled = true;
2287          window.location.reload();
2288        }
2289        
2290        return;
2291      }
2292      
2293      if (resp = this.responseText.match(/"errmsg"[^>]*>(.*?)<\/span/)) {
2294        showPostFormError(resp[1]);
2295      }
2296    }
2297  };
2298  
2299  function oeReplay(id) {
2300    PainterCore.replay(id);
2301  }
2302  
2303  /*! https://github.com/Joe12387/detectIncognito */
2304  function checkIncognito() {
2305    if (window.isIncognito !== undefined) {
2306      return;
2307    }
2308    
2309    if (!navigator.maxTouchPoints || navigator.vendor === undefined) {
2310      window.isIncognito = false;
2311      return;
2312    }
2313    
2314    (new Promise(function(resolve, reject) {
2315      let eh = eval.toString().length;
2316      
2317      if (navigator.vendor.indexOf('Apple') === 0 && eh === 37) {
2318        if (navigator.maxTouchPoints === undefined) {
2319          resolve(false);
2320        }
2321        
2322        let db_name = Math.random().toString();
2323        
2324        try {
2325          let db = window.indexedDB.open(db_name, 1);
2326          db.onupgradeneeded = function (e) {
2327            let res = e.target.result;
2328            try {
2329              res.createObjectStore('test', { autoIncrement: true }).put(new Blob);
2330              resolve(false);
2331            }
2332            catch(err) {
2333              let msg;
2334              if (err instanceof Error) {
2335                msg = err.message;
2336              }
2337              if (typeof msg !== 'string') {
2338                resolve(false);
2339              }
2340              resolve(/BlobURLs are not yet supported/.test(msg));
2341            }
2342            finally {
2343              res.close();
2344              window.indexedDB.deleteDatabase(db_name);
2345            }
2346          };
2347        }
2348        catch(err) {
2349          resolve(false);
2350        }
2351      }
2352      else if (navigator.vendor.indexOf('Google') === 0 && eh === 33) {
2353        let hsl;
2354        
2355        try {
2356          hsl = performance.memory.jsHeapSizeLimit;
2357        }
2358        catch(err) {
2359          hsl = 1073741824;
2360        }
2361        
2362        navigator.webkitTemporaryStorage.queryUsageAndQuota(function (_, quota) {
2363          let q = Math.round(quota / (1024 * 1024));
2364          let q_lim = Math.round(hsl / (1024 * 1024)) * 2;
2365          resolve(q < q_lim);
2366        }, function (err) {
2367          resolve(false);
2368        });
2369      }
2370      else if (document.body.style.MozAppearance !== undefined && eh === 37) {
2371        resolve(navigator.serviceWorker === undefined);
2372      }
2373      else {
2374        resolve(false);
2375      }
2376    })).then((v) => window.isIncognito = v);
2377  }
2378  
2379  function onPostFormSubmit(e) {
2380    let el = $.id('postFile');
2381    if (el && el.value && window.isIncognito) {
2382      e.stopPropagation()
2383      e.preventDefault();
2384      el.value = '';
2385      showPostFormError('Uploading files in incognito mode is not allowed.'
2386        + '<br>The File field has been cleared.');
2387      return false;
2388    }
2389  }
2390  
2391  function contentLoaded() {
2392    var i, el, el2, nodes, len, mobileSelect, params, board, val, fn;
2393    
2394    document.removeEventListener('DOMContentLoaded', contentLoaded, true);
2395    
2396    initAdsADT();
2397    
2398    initAdsDanbo();
2399    
2400    if (document.post) {
2401      document.post.name.value = get_cookie("4chan_name");
2402      document.post.email.value = get_cookie("options");
2403      document.post.addEventListener('submit', onPostFormSubmit, false);
2404    }
2405    
2406    cloneTopNav();
2407    
2408    initAnalytics();
2409    
2410    params = location.pathname.split(/\//);
2411    
2412    board = params[1];
2413    
2414    if (window.passEnabled) {
2415      setPassMsg();
2416    }
2417    
2418    if (window.Tegaki) {
2419      PainterCore.init();
2420    }
2421    
2422    if (el = document.getElementById('bottomReportBtn')) {
2423      el.addEventListener('click', onReportClick, false);
2424    }
2425    
2426    if (el = document.getElementById('styleSelector')) {
2427      el.addEventListener('change', onStyleSheetChange, false);
2428    }
2429    
2430    // Post form toggle
2431    if (el = document.getElementById('togglePostFormLink')) {
2432      if (el = el.firstElementChild) {
2433        el.addEventListener('click', showPostForm, false);
2434      }
2435      if (location.hash === '#reply') {
2436        showPostForm();
2437      }
2438    }
2439    
2440    // Selectable flags
2441    if ((el = document.forms.post) && el.flag) {
2442      el.flag.addEventListener('change', onBoardFlagChanged, false);
2443      
2444      if ((val = localStorage.getItem('4chan_flag_' + board)) && (el2 = el.querySelector('option[value="' + val + '"]'))) {
2445        el2.setAttribute('selected', 'selected');
2446      }
2447    }
2448    
2449    // Mobile nav menu
2450    buildMobileNav();
2451    
2452    // Mobile global message toggle
2453    if (el = document.getElementById('globalToggle')) {
2454      el.addEventListener('click', toggleGlobalMessage, false);
2455    }
2456    
2457    if (localStorage.getItem('4chan_never_show_mobile') == 'true') {
2458      if (el = document.getElementById('disable-mobile')) {
2459        el.style.display = 'none';
2460        el = document.getElementById('enable-mobile');
2461        el.parentNode.style.cssText = 'display: inline !important;';
2462      }
2463    }
2464    
2465    if (mobileSelect = document.getElementById('boardSelectMobile')) {
2466      len = mobileSelect.options.length;
2467      for ( i = 0; i < len; i++) {
2468        if (mobileSelect.options[i].value == board) {
2469          mobileSelect.selectedIndex = i;
2470          continue;
2471        }
2472      }
2473      
2474      mobileSelect.addEventListener('change', onMobileSelectChange, false);
2475    }
2476    
2477    if (document.forms.oeform && (el = document.forms.oeform.oe_src)) {
2478      el.addEventListener('mouseover', oeCanvasPreview, false);
2479      el.addEventListener('mouseout', oeClearPreview, false);
2480    }
2481    
2482    if (params[2] != 'catalog') {
2483      // Mobile post form toggle
2484      nodes = document.getElementsByClassName('mobilePostFormToggle');
2485      
2486      for (i = 0; el = nodes[i]; ++i) {
2487        el.addEventListener('click', onMobileFormClick, false);
2488      }
2489      
2490      if (el = document.getElementsByName('com')[0]) {
2491        el.addEventListener('keydown', onComKeyDown, false);
2492        el.addEventListener('paste', onComKeyDown, false);
2493        el.addEventListener('cut', onComKeyDown, false);
2494      }
2495      
2496      // Mobile refresh buttons
2497      if (el = document.getElementById('refresh_top')) {
2498        el.addEventListener('mouseup', onMobileRefreshClick, false);
2499      }
2500      
2501      if (el = document.getElementById('refresh_bottom')) {
2502        el.addEventListener('mouseup', onMobileRefreshClick, false);
2503      }
2504      
2505      // Clickable flags
2506      if (board == 'int' || board == 'sp' || board == 'pol') {
2507        el = document.getElementById('delform');
2508        el.addEventListener('click', onCoreClick, false);
2509      }
2510      
2511      // Page switcher + Search field
2512      if (!params[3]) {
2513        nodes = document.getElementsByClassName('pageSwitcherForm');
2514        
2515        for (i = 0; el = nodes[i]; ++i) {
2516          el.addEventListener('submit', onPageSwitch, false);
2517        }
2518        
2519        if (el = document.getElementById('search-box')) {
2520          el.addEventListener('keydown', onKeyDownSearch, false);
2521        }
2522      }
2523      
2524      if (window.clickable_ids) {
2525        enableClickableIds();
2526      }
2527      
2528      Tip.init();
2529    }
2530    
2531    if (window.devicePixelRatio >= 2) {
2532      setRetinaIcons();
2533    }
2534    
2535    initBlotter();
2536    
2537    loadBannerImage();
2538    
2539    if (window.css_event && activeStyleSheet === '_special') {
2540      fn = window['fc_' + window.css_event + '_init'];
2541      fn && fn();
2542    }
2543  }
2544  
2545  function onBoardFlagChanged() {
2546    var key = '4chan_flag_' + location.pathname.split(/\//)[1];
2547    
2548    if (this.value === '0') {
2549      localStorage.removeItem(key);
2550    }
2551    else {
2552      localStorage.setItem(key, this.value);
2553    }
2554  }
2555  
2556  initPass();
2557  
2558  window.onload = init;
2559  
2560  if (window.clickable_ids) {
2561    document.addEventListener('4chanParsingDone', onParsingDone, false);
2562  }
2563  
2564  document.addEventListener('4chanMainInit', loadExtraScripts, false);
2565  document.addEventListener('DOMContentLoaded', contentLoaded, true);
2566  
2567  initStyleSheet();