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