/ js / mod-unminified.js
mod-unminified.js
   1  /**
   2   * Mod Extension
   3   */
   4  
   5  (function () {
   6  var J = {
   7    isCatalog: false,
   8    colours: {},
   9    posterids: {},
  10    nextChunkIndex: 0,
  11    nextChunk: null,
  12    chunkSize: 100,
  13    sameIDActive: false,
  14    
  15    parserEventBound: false,
  16    
  17    autoReloadCatInterval: null,
  18    autoReloadCatDelay: 30000,
  19    
  20    samePostersMap: {},
  21    
  22    xhrs: {},
  23    
  24    reportsSubDomain: 'reports',
  25    teamSubDomain: 'team',
  26    
  27    flags: []
  28  };
  29  
  30  J.bin2hex = function(data) {
  31    var i, l, hex, c;
  32    
  33    hex = '';
  34    l = data.length;
  35    
  36    for (i = 0; i < l; ++i) {
  37      c = data.charCodeAt(i);
  38      hex += (c >> 4).toString(16);
  39      hex += (c & 0xF).toString(16);
  40    }
  41    
  42    return hex;
  43  };
  44  
  45  J.getFileMD5FromPid = function(pid) {
  46    var el, data;
  47    
  48    el = $.id('f' + pid);
  49    
  50    if (!el) {
  51      return false;
  52    }
  53    
  54    el = $.qs('img[data-md5]', el);
  55    
  56    if (!el) {
  57      return false;
  58    }
  59    
  60    data = window.atob(el.getAttribute('data-md5'));
  61    
  62    return J.bin2hex(data);
  63  };
  64  
  65  J.onGetMD5Click = function(el) {
  66    var md5, pid = el.getAttribute('data-id');
  67    
  68    md5 = J.getFileMD5FromPid(pid);
  69    
  70    if (md5 === false) {
  71      alert('Post or file not found');
  72    }
  73    else {
  74      prompt('', md5);
  75    }
  76  };
  77  
  78  J.apiUrlFilter = function(url) {
  79    return url + '?' + Math.round(Date.now() / 1000 / 3);
  80  };
  81  
  82  J.openDeletePrompt = function(id) {
  83    var html, cnt;
  84    
  85    id = id.getAttribute('data-id');
  86    
  87    html = '<div class="extPanel reply"><div class="panelHeader">Delete Post No.' + id
  88      + '<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="close-delete-prompt" src="'
  89      + Main.icons.cross + '"></a>'
  90      + '</span></div><span id="delete-prompt-inner">'
  91      + '<input type="button" value="Delete Post" tabindex="-1" data-cmd="delete-post" data-id="' + id + '"> '
  92      + '<input type="button" value="Delete Image Only" data-cmd="delete-image" data-id="' + id + '">';
  93    
  94    if ($.id((J.isCatalog ? 'thread-' : 't') + id) && !window.thread_archived) {
  95      html += ' <input type="button" value="Archive Thread" data-cmd="force-archive" data-id="' + id + '">';
  96    }
  97    
  98    if (!window.thread_archived && !J.isCatalog) {
  99      html += '<br>[<input type="checkbox" id="delete-all-by-ip"><label for="delete-all-by-ip">Delete all by IP?</label>]';
 100    }
 101    
 102    html += '</span></div>';
 103  
 104    cnt = document.createElement('div');
 105    cnt.className = 'UIPanel';
 106    cnt.id = 'delete-prompt';
 107  
 108    cnt.innerHTML = html;
 109    
 110    document.addEventListener('keydown', J.onKeyDown, false);
 111    cnt.addEventListener('click', J.closeDeletePrompt, false);
 112    document.body.appendChild(cnt);
 113    
 114    $.id('delete-prompt-inner').firstElementChild.focus();
 115  };
 116  
 117  J.addPosterIds = function(pid, hash, isMobile) {
 118    var post, cnt, el, name, hand, p;
 119    
 120    post = !isMobile ? $.id('pi' + pid) : $.id('pim' + pid);
 121    
 122    if (!window.user_ids || !(el = $.cls('posteruid', post)[0])) {
 123      el = $.el('span');
 124      
 125      cnt = $.cls('nameBlock', post)[0];
 126      name = $.cls('name', cnt)[0];
 127      
 128      if (name.classList.contains('capcode')) {
 129        return;
 130      }
 131      
 132      cnt.insertBefore(el, name.nextSibling);
 133      
 134      if (!isMobile) {
 135        cnt.insertBefore(document.createTextNode(' '), name.nextSibling);
 136      }
 137    }
 138    
 139    el.innerHTML = '(ID: <span class="hand" title="Highlight posts by this ID">' + hash + '</span>)';
 140    el.className = 'posteruid id_' + hash;
 141    
 142    hand = el.firstElementChild;
 143    
 144    IDColor.apply(hand);
 145    
 146    el.addEventListener('click', window.idClick, false);
 147    
 148    if (window.currentHighlighted && el.className.indexOf('id_' + window.currentHighlighted) != -1) {
 149      p = el.parentNode.parentNode.parentNode;
 150      p.className = 'highlight ' + p.className;
 151    }
 152  }
 153  
 154  J.onSamePostersLoaded = function() {
 155    var posts, hash, pid, tmp, isMobile;
 156    
 157    if (this.status != 200 && this.status != 304) {
 158      return;
 159    }
 160    
 161    posts = JSON.parse(this.responseText);
 162    
 163    if (!posts) {
 164      return;
 165    }
 166    
 167    isMobile = Main.hasMobileLayout;
 168    
 169    if (!IDColor.enabled) {
 170      tmp = window.user_ids;
 171      window.user_ids = true;
 172      IDColor.init();
 173      window.user_ids = tmp;
 174    }
 175    
 176    if (!J.sameIDActive) {
 177      J.sameIDActive = true;
 178    }
 179    
 180    for (pid in posts) {
 181      if (J.samePostersMap[pid]) {
 182        continue;
 183      }
 184      
 185      hash = posts[pid];
 186      
 187      J.samePostersMap[pid] = true;
 188      
 189      J.addPosterIds(pid, hash, isMobile);
 190    }
 191  }
 192  
 193  J.loadSamePosters = function(from) {
 194    var url, theNode, xhr;
 195    
 196    if (!J.parserEventBound) {
 197      document.addEventListener('4chanParsingDone', J.onParsingDone, false);
 198    }
 199    
 200    url = 'https://sys.' + $L.d(Main.board) + '/' + Main.board + '/admin?admin=adminext&thread=' + Main.tid;
 201    
 202    if (from) {
 203      url += '&from=' + from;
 204    }
 205  
 206    xhr = new XMLHttpRequest();
 207    xhr.open('GET', url);
 208    xhr.withCredentials = true;
 209    xhr.onload = J.onSamePostersLoaded;
 210  
 211    xhr.send(null);
 212  };
 213  
 214  J.closeDeletePrompt = function (e) {
 215    var prompt;
 216  
 217    if (!e || e.target.id == 'delete-prompt') {
 218      if (prompt = $.id('delete-prompt')) {
 219        document.removeEventListener('keydown', J.onKeyDown, false);
 220        prompt.removeEventListener('click', J.closeDeletePrompt, false);
 221        document.body.removeChild(prompt);
 222      }
 223    }
 224  };
 225  
 226  J.checkDeletedPosts = function () {
 227    var url, xhr;
 228  
 229    if (!Main.tid) {
 230      return;
 231    }
 232  
 233    url = '//a.4cdn.org/' + Main.board + '/res/' + Main.tid + '.json';
 234  
 235    xhr = new XMLHttpRequest();
 236    xhr.open('GET', url);
 237    xhr.onload = function () {
 238      if (this.status == 200 || this.status == 304) {
 239        ThreadUpdater.markDeletedReplies(Parser.parseThreadJSON(this.responseText));
 240      }
 241    };
 242  
 243    xhr.send(null);
 244  };
 245  
 246  J.get_random_light_color = function () {
 247    var letters = 'ABCDE'.split('');
 248    var color = '#';
 249    for (var i = 0; i < 3; i++) {
 250      color += letters[Math.floor(Math.random() * letters.length)];
 251    }
 252    return color;
 253  };
 254  
 255  J.deletePost = function (btn, imageOnly) {
 256    var id, xhr, form, msg, el, url, mode, delall, del, isOp, resp;
 257  
 258    id = btn.getAttribute('data-id');
 259  
 260    isOp = !J.isCatalog && $.id('t' + id);
 261  
 262    form = new FormData();
 263    msg = 'Delete Post No.';
 264    url = 'https://sys.' + $L.d(Main.board) + '/' + Main.board;
 265    
 266    if (window.thread_archived) {
 267      mode = 'arcdel';
 268    }
 269    else {
 270      mode = 'usrdel';
 271      delall = !J.isCatalog && $.id('delete-all-by-ip').checked;
 272    }
 273  
 274    if (delall) {
 275      mode = 'admin.php';
 276      form.append('admin', 'delall');
 277      form.append('id', id);
 278    }
 279  
 280    if (delall) {
 281      url += '/admin';
 282    }
 283    else {
 284      url += '/post';
 285    }
 286  
 287    if (imageOnly) {
 288      msg = 'Delete Image No.';
 289      form.append('onlyimgdel', 'on');
 290    }
 291  
 292    form.append(id, 'delete');
 293    form.append('mode', mode);
 294    form.append('pwd', 'janitorise');
 295  
 296    (del = $.id('delete-prompt-inner')).textContent = 'Deleting...';
 297  
 298    xhr = new XMLHttpRequest();
 299    xhr.open('POST', url);
 300    xhr.withCredentials = true;
 301    xhr.onload = function () {
 302      var builtMsg;
 303      btn.src = Main.icons.cross;
 304      if (this.status == 200) {
 305        if ((!delall && this.responseText.indexOf('Updating') != -1) || (delall && this.responseText.indexOf('deleted') != -1)) {
 306          if (J.isCatalog) {
 307            if (el = $.id('thread-' + id)) {
 308              $.addClass(el, 'disabled');
 309            }
 310          }
 311          else if (!imageOnly) {
 312            if (id == Main.tid) {
 313              location.href = '//boards.' + $L.d(Main.board) + '/' + Main.board + '/';
 314              return;
 315            }
 316            else {
 317              if (delall) {
 318                builtMsg = document.createElement('span');
 319                builtMsg.innerHTML = '<br><br><strong style="font-color: red;">(YOU HAVE DELETED ALL POSTS BY THIS IP)</strong>';
 320                el = $.id('m' + id);
 321                el.appendChild(builtMsg);
 322                J.checkDeletedPosts();
 323              }
 324              else {
 325                if (isOp) {
 326                  el = isOp.parentNode;
 327                  el.removeChild(isOp.nextSibling);
 328                  el.removeChild(isOp);
 329                }
 330                else {
 331                  el = $.id('pc' + id);
 332                  el.parentNode.removeChild(el);
 333                }
 334              }
 335            }
 336          }
 337          else {
 338            el = $.id('f' + id);
 339            el.innerHTML = '<span class="fileThumb"><img alt="File deleted."'
 340              + ' src="//s.4cdn.org/image/filedeleted' + (isOp ? '' : '-res') + '.gif"></span>';
 341  
 342            if (delall) {
 343              builtMsg = document.createElement('span');
 344              builtMsg.innerHTML = '<br><br><strong style="font-color: red;">(YOU HAVE DELETED ALL IMAGES BY THIS IP)</strong>';
 345              el = $.id('m' + id);
 346              el.appendChild(builtMsg);
 347            }
 348          }
 349  
 350          J.closeDeletePrompt();
 351        }
 352        else {
 353          if (resp = this.responseText.match(/"errmsg"[^>]*>(.*?)<\/span/)) {
 354            del.textContent = resp[1];
 355          }
 356          else {
 357            del.textContent = 'Error: Something went wrong.';
 358          }
 359        }
 360      }
 361      else {
 362        del.textContent = 'Error: Wrong status while deleting No.' + id + ' (Status: ' + this.status + ').';
 363      }
 364    };
 365    xhr.onerror = function () {
 366      del.textContent = 'Error: Error while deleting No.' + id + ' (Status: ' + this.status + ').';
 367    };
 368  
 369    xhr.send(form);
 370  };
 371  
 372  J.forceArchive = function(btn) {
 373    var id, xhr, form, msg, url, del, resp;
 374  
 375    id = btn.getAttribute('data-id');
 376  
 377    form = new FormData();
 378    msg = 'Archive Thread No.';
 379    
 380    url = 'https://sys.' + $L.d(Main.board) + '/' + Main.board + '/post';
 381    
 382    form.append('id', id);
 383    form.append('mode', 'forcearchive');
 384  
 385    (del = $.id('delete-prompt-inner')).textContent = 'Archiving...';
 386  
 387    xhr = new XMLHttpRequest();
 388    xhr.open('POST', url);
 389    xhr.withCredentials = true;
 390    xhr.onload = function () {
 391      var el;
 392      if (btn.src) {
 393        btn.src = Main.icons.cross;
 394      }
 395      if (this.status == 200) {
 396        if (this.responseText.indexOf('Updating') != -1) {
 397          if (J.isCatalog) {
 398            if (el = $.id('thread-' + id)) {
 399              $.addClass(el, 'disabled');
 400            }
 401          }
 402          J.closeDeletePrompt();
 403        }
 404        else {
 405          if (resp = this.responseText.match(/"errmsg"[^>]*>(.*?)<\/span/)) {
 406            del.textContent = resp[1];
 407          }
 408          else {
 409            del.textContent = 'Error: Something went wrong.';
 410          }
 411        }
 412      }
 413      else {
 414        del.textContent = 'Error: Wrong status while archiving No.' + id + ' (Status: ' + this.status + ').';
 415      }
 416    };
 417    xhr.onerror = function () {
 418      del.textContent = 'Error: Error while archiving No.' + id + ' (Status: ' + this.status + ').';
 419    };
 420  
 421    xhr.send(form);
 422  };
 423  
 424  J.openBanWindow = function (btn) {
 425    var id;
 426  
 427    id = btn.getAttribute('data-id');
 428    window.open('https://sys.' + $L.d(Main.board) + '/' + Main.board + '/admin?mode=admin&admin=ban&id=' + id, '_blank', 'scrollBars=yes,resizable=no,toolbar=no,menubar=no,location=no,directories=no,width=400,height=470');
 429  };
 430  
 431  J.openBanFrame = function(btn) {
 432    var id;
 433    
 434    if (this.banReqCnt) {
 435      this.closeBanFrame();
 436    }
 437    
 438    id = btn.getAttribute('data-id');
 439    
 440    this.banReqCnt = document.createElement('div');
 441    this.banReqCnt.id = 'banReq';
 442    this.banReqCnt.className = 'extPanel reply';
 443    this.banReqCnt.setAttribute('data-trackpos', 'banReq-position');
 444    
 445    if (Config['banReq-position']) {
 446      this.banReqCnt.style.cssText = Config['banReq-position'];
 447    }
 448    else {
 449      this.banReqCnt.style.right = '0px';
 450      this.banReqCnt.style.top = '50px';
 451    }
 452    
 453    this.banReqCnt.innerHTML =
 454      '<div id="banReqHeader" class="drag postblock">Ban No.' + id
 455      + '<img alt="X" src="' + Main.icons.cross + '" id="banReqClose" '
 456      + 'class="extButton" title="Close Window"></div>'
 457      + '<iframe src="https://sys.' + $L.d(Main.board) + '/'
 458      + Main.board + '/admin?mode=admin&admin=ban&id=' + id
 459      + '&noheader=true" width="400" height="470" frameborder="0"></iframe>';
 460    
 461    document.body.appendChild(this.banReqCnt);
 462    
 463    window.addEventListener('message', J.onMessage, false);
 464    document.addEventListener('keydown', J.onKeyDown, false);
 465    
 466    $.id('banReqClose').addEventListener('click', J.closeBanFrame, false);
 467    Draggable.set($.id('banReqHeader'));
 468  };
 469  
 470  J.closeBanFrame = function() {
 471    window.removeEventListener('message', J.onMessage, false);
 472    document.removeEventListener('keydown', J.onKeyDown, false);
 473    Draggable.unset($.id('banReqHeader'));
 474    $.id('banReqClose').removeEventListener('click', J.closeBanFrame, false);
 475    document.body.removeChild(J.banReqCnt);
 476    J.banReqCnt = null;
 477  };
 478  
 479  J.processMessage = function(data) {
 480    if (!data) {
 481      return {};
 482    }
 483    
 484    data = data.split('-');
 485    
 486    return {
 487      cmd: data[0],
 488      type: data[1],
 489      id: data.slice(2).join('-')
 490    };
 491  };
 492  
 493  J.onKeyDown = function(e) {
 494    if (e.keyCode == 27 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
 495      if (J.banReqCnt) {
 496        J.closeBanFrame();
 497      }
 498      if (J.threadOptsCnt) {
 499        J.closeThreadOptionsFrame();
 500      }
 501      if ($.id('delete-prompt')) {
 502        J.closeDeletePrompt();
 503      }
 504    }
 505  };
 506  
 507  J.onCatalogKeyDown = function(e) {
 508    if (e.keyCode == 82 && e.shiftKey) {
 509      J.initCatAutoReload();
 510    }
 511  };
 512  
 513  J.onMessage = function(e) {
 514    var msg;
 515    
 516    if (e.origin !== 'https://sys.' + $L.d(Main.board)) {
 517      return;
 518    }
 519    
 520    msg = J.processMessage(e.data);
 521    
 522    if (msg.type !== 'ban') {
 523      return;
 524    }
 525    
 526    if (msg.cmd === 'done' || msg.cmd === 'cancel') {
 527      J.closeBanFrame();
 528    }
 529  };
 530  
 531  J.initCatAutoReload = function(init) {
 532    var flag;
 533    
 534    flag = sessionStorage.getItem('4chan-c-ar');
 535    
 536    if (flag) {
 537      if (init) {
 538        window.scrollTo(0, +flag);
 539        J.toggleCatAutoReload(true);
 540      }
 541      else {
 542        J.toggleCatAutoReload(false);
 543      }
 544    }
 545    else {
 546      if (init) {
 547        return;
 548      }
 549      J.toggleCatAutoReload(true);
 550    }
 551  };
 552  
 553  J.toggleCatAutoReload = function(flag) {
 554    if (flag) {
 555      sessionStorage.setItem('4chan-c-ar', document.documentElement.scrollTop);
 556      J.autoReloadCatInterval = setInterval(J.autoRefreshWindow, J.autoReloadCatDelay);
 557      $.addClass($.id('refresh-btn'), 'active-btn');
 558    }
 559    else {
 560      sessionStorage.removeItem('4chan-c-ar');
 561      clearInterval(J.autoReloadCatInterval);
 562      $.removeClass($.id('refresh-btn'), 'active-btn');
 563    }
 564  };
 565  
 566  J.autoRefreshWindow = function() {
 567    var el = $.id('ctrl');
 568    
 569    if (document.documentElement.scrollTop <= el.offsetTop + el.offsetHeight) {
 570      sessionStorage.setItem('4chan-c-ar', document.documentElement.scrollTop);
 571      location.href = location.href;
 572    }
 573  }
 574  
 575  J.openThreadOptions = function(btn) {
 576    var id = btn.getAttribute('data-id');
 577    window.open('https://sys.' + $L.d(Main.board) + '/' + Main.board + '/admin?mode=admin&admin=opt&id=' + id, '_blank', 'scrollBars=yes,resizable=no,toolbar=no,menubar=no,location=no,directories=no,width=400,height=290');
 578  };
 579  
 580  J.openThreadOptionsFrame = function(btn) {
 581    var id;
 582    
 583    if (this.threadOptsCnt) {
 584      this.closeThreadOptionsFrame();
 585    }
 586    
 587    id = btn.getAttribute('data-id');
 588    
 589    this.threadOptsCnt = document.createElement('div');
 590    this.threadOptsCnt.id = 'threadOpts';
 591    this.threadOptsCnt.className = 'extPanel reply';
 592    this.threadOptsCnt.setAttribute('data-trackpos', 'threadOpts-position');
 593    
 594    if (Config['threadOpts-position']) {
 595      this.threadOptsCnt.style.cssText = Config['threadOpts-position'];
 596    }
 597    else {
 598      this.threadOptsCnt.style.right = '0px';
 599      this.threadOptsCnt.style.top = '50px';
 600    }
 601    
 602    this.threadOptsCnt.innerHTML =
 603      '<div id="threadOptsHeader" class="drag postblock">Thread Options No.' + id
 604      + '<img alt="X" src="' + Main.icons.cross + '" id="threadOptsClose" '
 605      + 'class="extButton" title="Close Window"></div>'
 606      + '<iframe src="https://sys.' + $L.d(Main.board) + '/'
 607      + Main.board + '/admin?mode=admin&admin=opt&id=' + id
 608      + '&noheader=true" width="400" height="175" frameborder="0"></iframe>';
 609    
 610    document.body.appendChild(this.threadOptsCnt);
 611    
 612    window.addEventListener('message', J.onThreadOptsDone, false);
 613    document.addEventListener('keydown', J.onKeyDown, false);
 614    
 615    $.id('threadOptsClose').addEventListener('click', J.closeThreadOptionsFrame, false);
 616    Draggable.set($.id('threadOptsHeader'));
 617  };
 618  
 619  J.closeThreadOptionsFrame = function() {
 620    window.removeEventListener('message', J.onThreadOptsDone, false);
 621    document.removeEventListener('keydown', J.onKeyDown, false);
 622    Draggable.unset($.id('threadOptsHeader'));
 623    $.id('threadOptsClose').removeEventListener('click', J.closeThreadOptionsFrame, false);
 624    document.body.removeChild(J.threadOptsCnt);
 625    J.threadOptsCnt = null;
 626  };
 627  
 628  J.onThreadOptsDone = function(e) {
 629    if (J.threadOptsCnt && e.origin === 'https://sys.' + $L.d(Main.board) && e.data === 'done-threadopt') {
 630      J.closeThreadOptionsFrame();
 631    }
 632  };
 633  
 634  J.setFileSpoiler = function(t) {
 635    var xhr, pid, flag, el;
 636    
 637    pid = t.getAttribute('data-id');
 638    flag = t.getAttribute('data-flag');
 639    
 640    if (!pid) {
 641      return;
 642    }
 643    
 644    el = $.id('f' + pid);
 645    
 646    if (!flag) {
 647      flag = $.cls('imgspoiler', el.parentNode)[0] ? 0 : 1;
 648    }
 649    
 650    if (!el || el.hasAttribute('data-processing')) {
 651      return;
 652    }
 653    
 654    xhr = new XMLHttpRequest();
 655    xhr.open('GET', 'https://sys.' + $L.d(Main.board) + '/' + Main.board
 656      + '/admin.php?admin=spoiler&pid=' + pid + '&flag=' + flag, true);
 657    xhr.withCredentials = true;
 658    xhr.onload = J.onFileSpoilerLoad;
 659    xhr.onerror = J.onFileSpoilerError;
 660    xhr._pid = +pid;
 661    xhr._flag = +flag;
 662    
 663    Feedback.notify('Processing...', null);
 664    
 665    el.setAttribute('data-processing', '1');
 666    
 667    xhr.send(null);
 668  };
 669  
 670  J.onFileSpoilerLoad = function() {
 671    var el, el2;
 672    
 673    Feedback.hideMessage();
 674    
 675    if (this.responseText !== '1') {
 676      if (this.responseText === '-1') {
 677        Feedback.error('You are not logged in');
 678      }
 679      else {
 680        Feedback.error("Couldn't set spoiler flag for post No." + this._pid);
 681      }
 682      
 683      return;
 684    }
 685    
 686    if (!(el = $.id('f' + this._pid))) {
 687      return;
 688    }
 689    
 690    el.removeAttribute('data-processing');
 691    
 692    if (!(el = $.cls('fileThumb', el)[0])) {
 693      return;
 694    }
 695    
 696    if (this._flag) {
 697      $.addClass(el, 'imgspoiler');
 698      
 699      el2 = el.previousElementSibling;
 700      el2.setAttribute('title', el2.firstElementChild.textContent);
 701      
 702      if (!Config.revealSpoilers) {
 703        el = $.tag('img', el)[0];
 704        el.style.width = el.style.height = '100px';
 705        el.src = '//s.4cdn.org/image/spoiler-' + Main.board + '.png';
 706      }
 707    }
 708    else {
 709      if (!Config.revealSpoilers) {
 710        Parser.revealImageSpoiler(el);
 711      }
 712      $.removeClass(el, 'imgspoiler');
 713    }
 714  };
 715  
 716  J.onFileSpoilerError = function() {
 717    var el;
 718    
 719    if (!(el = $.id('f' + this._pid))) {
 720      return;
 721    }
 722    
 723    el.removeAttribute('data-processing');
 724    Feedback.error("Couldn't update the spoiler flag for post No." + this.pid);
 725  };
 726  
 727  /**
 728   * Multi
 729   */
 730  var Multi = {};
 731  
 732  Multi.exec = function(btn) {
 733    var pid, sel;
 734    
 735    if (UA.isOpera && typeof (sel = document.getSelection()) == 'string') {}
 736    else {
 737      sel = window.getSelection().toString();
 738    }
 739    
 740    if (sel) {
 741      window.open('https://' + J.teamSubDomain
 742        + '.4chan.org/search#{"comment":"' + sel.replace(/[\r\n]+/g, ' ') + '"}');
 743    }
 744    else {
 745      pid = btn.getAttribute('data-id');
 746      
 747      window.open('https://team.4chan.org/search?action=from_pid&board=' + Main.board
 748      + '&pid=' + pid);
 749    }
 750  };
 751  
 752  Multi.prompt = function (ip, pid) {
 753    var cnt, btn, link;
 754    
 755    cnt = $.id('pi' + pid);
 756    btn = $.cls('postMenuBtn', cnt)[0];
 757    
 758    link = document.createElement('a');
 759    link.href = 'https://' + J.teamSubDomain + '.4chan.org/search#{"ip":"' + ip + '"}';
 760    link.setAttribute('target', '_blank');
 761    link.className = 'post-ip';
 762    link.textContent = ip;
 763  
 764    cnt.insertBefore(link, btn);
 765  };
 766  
 767  /**
 768   * Admin tools
 769   */
 770  var AdminTools = {
 771    cacheTTL: 60000,
 772    autoRefreshDelay: 120000,
 773    autoRefreshTimeout: null
 774  };
 775  
 776  // FIXME, put it as a helper in extension.js
 777  AdminTools.initVisibilityAPI = function() {
 778    this.hidden = 'hidden';
 779    this.visibilitychange = 'visibilitychange';
 780    
 781    if (typeof document.hidden === 'undefined') {
 782      if ('mozHidden' in document) {
 783        this.hidden = 'mozHidden';
 784        this.visibilitychange = 'mozvisibilitychange';
 785      }
 786      else if ('webkitHidden' in document) {
 787        this.hidden = 'webkitHidden';
 788        this.visibilitychange = 'webkitvisibilitychange';
 789      }
 790      else if ('msHidden' in document) {
 791        this.hidden = 'msHidden';
 792        this.visibilitychange = 'msvisibilitychange';
 793      }
 794    }
 795    
 796    document.addEventListener(this.visibilitychange, this.onVisibilityChange, false);
 797  };
 798  
 799  J.initIconsCatalog = function() {
 800    var key, paths, url;
 801    
 802    Main.icons = {
 803      up: 'arrow_up.png',
 804      down: 'arrow_down.png',
 805      right: 'arrow_right.png',
 806      download: 'arrow_down2.png',
 807      refresh: 'refresh.png',
 808      cross: 'cross.png',
 809      gis: 'gis.png',
 810      iqdb: 'iqdb.png',
 811      minus: 'post_expand_minus.png',
 812      plus: 'post_expand_plus.png',
 813      rotate: 'post_expand_rotate.gif',
 814      quote: 'quote.png',
 815      report: 'report.png',
 816      notwatched: 'watch_thread_off.png',
 817      watched: 'watch_thread_on.png',
 818      help: 'question.png'
 819    };
 820    
 821    paths = {
 822      yotsuba_new: 'futaba/',
 823      futaba_new: 'futaba/',
 824      yotsuba_b_new: 'burichan/',
 825      burichan_new: 'burichan/',
 826      tomorrow: 'tomorrow/',
 827      photon: 'photon/'
 828    };
 829    
 830    url = '//s.4cdn.org/image/';
 831    
 832    if (window.devicePixelRatio >= 2) {
 833      for (key in Main.icons) {
 834        Main.icons[key] = Main.icons[key].replace('.', '@2x.');
 835      }
 836    }
 837    
 838    url += 'buttons/' + paths[Main.stylesheet];
 839    for (key in Main.icons) {
 840      Main.icons[key] = url + Main.icons[key];
 841    }
 842  };
 843  
 844  AdminTools.init = function () {
 845    var cnt, html;
 846    
 847    AdminTools.initVisibilityAPI();
 848    
 849    cnt = document.createElement('div');
 850    cnt.className = 'extPanel reply';
 851    cnt.id = 'adminToolbox';
 852    cnt.setAttribute('data-trackpos', 'AT-position');
 853  
 854    if (Config['AT-position']) {
 855      cnt.style.cssText = Config['AT-position'];
 856    } else {
 857      cnt.style.right = '10px';
 858      cnt.style.top = '380px';
 859    }
 860  
 861    cnt.style.position = Config.fixedAdminToolbox ? 'fixed' : '';
 862  
 863    html = '<div class="drag" id="atHeader">Moderator Tools'
 864      + '<img alt="Refresh" title="Refresh" src="' + Main.icons.refresh
 865      + '" id="atRefresh" data-cmd="at-refresh" class="pointer right"></div>'
 866      + '<h4><a href="https://' + J.reportsSubDomain + '.4chan.org/" target="_blank">Reports</a>: '
 867      + '<span title="Total" id="at-total">?</span> ('
 868      + '<span title="Illegal" id="at-illegal">?</span>)</h4>'
 869      + '<h4><a href="https://' + J.reportsSubDomain + '.4chan.org/?action=ban_requests" target="_blank">Ban Requests</a>: '
 870      + '<span id="at-banreqs">?</span> (<span title="Illegal" id="at-illegal-br">?</span>)</h4>'
 871      + '<h4><a href="https://' + J.teamSubDomain + '.4chan.org/appeals" target="_blank">Appeals</a>: '
 872      + '<span id="at-appeals">?</span> (<span title="4chan Pass Users" id="at-prio-appeals">?</span>)</h4>'
 873      + '<h4 id="at-msg-cnt"><a data-cmd="at-msg" href="https://' + J.reportsSubDomain
 874        + '.4chan.org/?action=staffmessages" target="_blank">Messages</a>: <span id="at-msg">?</span></h4>';
 875      
 876    if (Main.tid) {
 877      html += '<hr><h4><a href="javascript:void(0);" data-cmd="poster-id">Same Poster ID</a></h4>';
 878    }
 879  
 880    cnt.innerHTML = html;
 881    document.body.appendChild(cnt);
 882    AdminTools.refreshReportCount();
 883    
 884    Draggable.set($.id('atHeader'));
 885  };
 886  
 887  AdminTools.onVisibilityChange = function() {
 888    var self;
 889    
 890    self = AdminTools;
 891    
 892    if (document[AdminTools.hidden]) {
 893      clearInterval(self.autoRefreshTimeout);
 894      self.autoRefreshTimeout = null;
 895    }
 896    else {
 897      self.refreshReportCount();
 898      self.autoRefreshTimeout = setInterval(self.refreshReportCount, self.autoRefreshDelay);
 899    }
 900  };
 901  
 902  AdminTools.refreshReportCount = function(force) {
 903    var xhr, cache, msg_count;
 904    
 905    if (force !== true && (cache = localStorage.getItem('4chan-cache-rc'))) {
 906      cache = JSON.parse(cache);
 907      
 908      if (cache.ts > Date.now() - AdminTools.cacheTTL) {
 909        $.id('at-total').textContent = cache.data[0];
 910        $.id('at-illegal').textContent = cache.data[1];
 911        $.id('at-banreqs').textContent = cache.data[2];
 912        $.id('at-appeals').textContent = cache.data[3];
 913        $.id('at-illegal-br').textContent = cache.data[4] || 0;
 914        $.id('at-prio-appeals').textContent = cache.data[5] || 0;
 915        
 916        $.id('at-msg-cnt').style.display = cache.data[6] ? 'block' : '';
 917        $.id('at-msg').textContent = cache.data[6] || 0;
 918        
 919        return;
 920      }
 921    }
 922    
 923    xhr = new XMLHttpRequest();
 924    
 925    xhr.open('GET', 'https://' + J.reportsSubDomain + '.4chan.org/H429f6uIsUqU.php', true);
 926    
 927    xhr.withCredentials = true;
 928    
 929    xhr.onload = function () {
 930      var cache, resp, data;
 931      
 932      if (this.status == 200) {
 933        try {
 934          resp = JSON.parse(this.responseText);
 935        }
 936        catch (e) {
 937          console.log(e);
 938          return;
 939        }
 940        
 941        if (resp.status !== 'success') {
 942          console.log(resp.message); // FIXME, use global message
 943          return;
 944        }
 945        
 946        data = resp.data;
 947        
 948        msg_count = data.msg || 0;
 949        
 950        $.id('at-msg-cnt').style.display = msg_count ? 'block' : '';
 951        $.id('at-msg').textContent = msg_count;
 952        $.id('at-total').textContent = data.total;
 953        $.id('at-illegal').textContent = data.illegal;
 954        $.id('at-banreqs').textContent = data.banreqs;
 955        $.id('at-illegal-br').textContent = data.illegal_banreqs;
 956        $.id('at-appeals').textContent = data.appeals;
 957        $.id('at-prio-appeals').textContent = data.prio_appeals;
 958        
 959        cache = {
 960          ts: Date.now(),
 961          data: [
 962            data.total,
 963            data.illegal,
 964            data.banreqs,
 965            data.appeals,
 966            data.illegal_banreqs,
 967            data.prio_appeals,
 968            data.msg
 969          ]
 970        };
 971        
 972        cache = JSON.stringify(cache);
 973        
 974        localStorage.setItem('4chan-cache-rc', cache);
 975        
 976        document.dispatchEvent(new CustomEvent('4chanATUpdated'));
 977      }
 978      else {
 979        this.onerror();
 980      }
 981    };
 982    
 983    xhr.onerror = function () {
 984      console.log('Error while refreshing the report count (Status: ' + this.status + ').');
 985    };
 986    
 987    xhr.onloadend = function () {
 988      $.id('atRefresh').src = Main.icons.refresh;
 989    };
 990  
 991    $.id('atRefresh').src = Main.icons.rotate;
 992    
 993    xhr.send(null);
 994  };
 995  
 996  AdminTools.resetMsgCount = function() {
 997    var cache;
 998    
 999    $.id('at-msg').textContent = 0;
1000    
1001    if (cache = localStorage.getItem('4chan-cache-rc')) {
1002      cache = JSON.parse(cache);
1003      cache.data[6] = 0;
1004      cache = JSON.stringify(cache);
1005      localStorage.setItem('4chan-cache-rc', cache);
1006    }
1007  };
1008  
1009  /**
1010   * Click handler
1011   */
1012  J.onClick = function (e) {
1013    var t, cmd;
1014    
1015    if ((t = e.target) == document) {
1016      return;
1017    }
1018    
1019    if (cmd = t.getAttribute('data-cmd')) {
1020      switch (cmd) {
1021        case 'at-refresh':
1022          AdminTools.refreshReportCount(true);
1023          break;
1024        case 'delete-post':
1025        case 'delete-image':
1026          J.deletePost(t, (cmd === 'delete-image'));
1027          break;
1028        case 'force-archive':
1029          J.forceArchive(t);
1030          break;
1031        
1032        case 'open-delete-prompt':
1033          J.openDeletePrompt(t);
1034          break;
1035        
1036        case 'close-delete-prompt':
1037          J.closeDeletePrompt();
1038          break;
1039        
1040        case 'at-msg':
1041          AdminTools.resetMsgCount();
1042          break;
1043        
1044        case 'toggle-file-spoiler':
1045          J.setFileSpoiler(t);
1046          break;
1047        
1048        case 'prompt-spoiler':
1049          if (confirm('Toggle spoiler?')) {
1050            J.setFileSpoiler(t);
1051          }
1052          break;
1053        
1054        case 'thread-options':
1055          if (Config.inlinePopups) {
1056            J.openThreadOptionsFrame(t);
1057          }
1058          else {
1059            J.openThreadOptions(t);
1060          }
1061          break;
1062  
1063        case 'multi':
1064          e.preventDefault();
1065          Multi.exec(t);
1066          break;
1067        
1068        case 'get-md5':
1069          J.onGetMD5Click(t);
1070          break;
1071        
1072        case 'html-toggle':
1073          J.onHTMLToggle(t);
1074          break;
1075        
1076        case 'preview-html':
1077          e.preventDefault();
1078          J.onPreviewHTMLClick(t);
1079          break;
1080        
1081        case 'close-html-preview':
1082          J.closeHTMLPreview();
1083          break;
1084        
1085        case 'poster-id':
1086          J.loadSamePosters();
1087          break;
1088  
1089        case 'ban':
1090          if (Config.inlinePopups) {
1091            J.openBanFrame(t);
1092          }
1093          else {
1094            J.openBanWindow(t);
1095          }
1096          break;
1097      }
1098    }
1099  };
1100  
1101  J.onScroll = function () {
1102    var end;
1103  
1104    while (J.nextChunk.offsetTop < (document.documentElement.clientHeight + window.scrollY)) {
1105      end = J.nextChunkIndex + J.chunkSize;
1106      if (end >= J.postCount) {
1107        J.parseRange(J.nextChunkIndex, J.postCount);
1108        window.removeEventListener('scroll', J.onScroll, false);
1109        return false;
1110      }
1111      else {
1112        J.parseRange(J.nextChunkIndex, end);
1113      }
1114    }
1115  
1116    return true;
1117  };
1118  
1119  J.parseRange = function (start, end) {
1120    var i, j, posts;
1121  
1122    posts = document.getElementById('t' + Main.tid).getElementsByClassName('postInfo');
1123    
1124    for (i = start; i < end; ++i) {
1125      j = posts[i];
1126      
1127      if (!j) {
1128        break;
1129      }
1130      
1131      J.parsePost(j);
1132    }
1133    
1134    J.nextChunkIndex = i;
1135    J.nextChunk = posts[i];
1136  };
1137  
1138  J.onParsingDone = function(e) {
1139    var i, tid, offset, limit, posts;
1140    
1141    if (e) {
1142      tid = e.detail.threadId;
1143      offset = e.detail.offset;
1144      limit = e.detail.limit;
1145      posts = document.getElementById('t' + tid).getElementsByClassName('postInfo');
1146      
1147      if (J.sameIDActive) {
1148        J.loadSamePosters(posts[offset].id.slice(2));
1149      }
1150    }
1151    else {
1152      offset = 0;
1153      posts = document.getElementsByClassName('postInfo');
1154      limit = posts.length;
1155    }
1156    
1157    if (Config.useIconButtons) {
1158      for (i = offset; i < limit; ++i) {
1159        J.parsePost(posts[i]);
1160      }
1161    }
1162  };
1163  
1164  J.onPostMenuReady = function(e) {
1165    var elw, el, pid, menu, flag;
1166    
1167    pid = e.detail.postId;
1168    menu = e.detail.node;
1169    
1170    if (window.thread_archived && $.id('f' + pid)) {
1171      elw = document.createElement('li');
1172      elw.className = 'dd-admin';
1173      el = document.createElement('a');
1174      el.href = '#';
1175      el.setAttribute('data-cmd', 'get-md5');
1176      el.setAttribute('data-id', pid);
1177      el.textContent = 'File MD5';
1178      elw.appendChild(el);
1179      menu.appendChild(elw);
1180    }
1181    
1182    if (window.spoilers && (el = $.id('fT' + pid))) {
1183      flag = $.cls('imgspoiler', el.parentNode)[0] ? 0 : 1;
1184      elw = document.createElement('li');
1185      elw.className = 'dd-admin';
1186      el = document.createElement('a');
1187      el.setAttribute('data-cmd', 'toggle-file-spoiler');
1188      el.setAttribute('data-id', pid);
1189      el.setAttribute('data-flag', flag);
1190      el.textContent = (flag ? 'Set' : 'Unset') + ' Spoiler';
1191      elw.appendChild(el);
1192      menu.appendChild(elw);
1193    }
1194    
1195    if (Config.useIconButtons && !Main.hasMobileLayout) {
1196      return;
1197    }
1198    
1199    elw = document.createElement('li');
1200    elw.className = 'dd-admin';
1201    el = document.createElement('a');
1202    el.setAttribute('data-cmd', 'open-delete-prompt');
1203    el.setAttribute('data-id', pid);
1204    el.textContent = 'Delete';
1205    elw.appendChild(el);
1206    menu.appendChild(elw);
1207    
1208    if (window.thread_archived) {
1209      return;
1210    }
1211    
1212    elw = document.createElement('li');
1213    elw.className = 'dd-admin';
1214    el = document.createElement('a');
1215    el.setAttribute('data-cmd', 'ban');
1216    el.setAttribute('data-id', pid);
1217    el.textContent = 'Ban';
1218    elw.appendChild(el);
1219    menu.appendChild(elw);
1220    
1221    elw = document.createElement('li');
1222    elw.className = 'dd-admin';
1223    el = document.createElement('a');
1224    el.setAttribute('data-cmd', 'multi');
1225    el.setAttribute('data-id', pid);
1226    el.textContent = 'Search';
1227    elw.appendChild(el);
1228    menu.appendChild(elw);
1229    
1230    if (e.detail.isOP) {
1231      elw = document.createElement('li');
1232      elw.className = 'dd-admin';
1233      el = document.createElement('a');
1234      el.setAttribute('data-cmd', 'thread-options');
1235      el.setAttribute('data-id', pid);
1236      el.textContent = 'Thread options';
1237      elw.appendChild(el);
1238      menu.appendChild(elw);
1239    }
1240  };
1241  
1242  J.parsePost = function(postInfo) {
1243    var pid, html, cnt, tail;
1244    
1245    pid = postInfo.id.slice(2);
1246    
1247    html = '<img class="extButton" alt="X" data-cmd="open-delete-prompt" data-id="'
1248      + pid + '" src="' + Main.icons.cross
1249      + '" title="Delete">';
1250    
1251    if (window.spoilers && (el = $.id('fT' + pid))) {
1252      html += '<img class="extButton" alt="S" data-cmd="prompt-spoiler" data-id="'
1253      + pid + '" src="' + J.icons.spoiler
1254      + '" title="Toggle Spoiler">';
1255    }
1256    
1257    if (!window.thread_archived) {
1258      html += '<img class="extButton" alt="M" data-cmd="multi" data-id="'
1259      + pid + '" src="' + J.icons.multi
1260      + '" title="Display posts by this IP">'
1261      + '<img class="extButton" alt="B" data-cmd="ban" data-id="'
1262      + pid + '" src="' + J.icons.ban
1263      + '" title="Ban">';
1264    
1265      if ($.id('t' + pid)) {
1266        html += '<img class="extButton" alt="&gt;" data-cmd="thread-options" data-id="'
1267          + pid + '" src="' + J.icons.arrow_right + '" title="Thread Options">';
1268      }    
1269    }
1270    
1271    cnt = document.createElement('div');
1272    cnt.className = 'extControls';
1273    cnt.innerHTML = html;
1274    
1275    tail = postInfo.getElementsByClassName('postMenuBtn')[0];
1276    
1277    postInfo.insertBefore(cnt, tail);
1278  };
1279  
1280  J.displayJCount = function(jLink, jLinkBot, no, delta) {
1281    var msg;
1282    
1283    $.addClass(jLink, 'j-newposts');
1284    $.addClass(jLinkBot, 'j-newposts');
1285    jLink.setAttribute('data-no', no);
1286    jLinkBot.setAttribute('data-no', no);
1287    jLink.textContent = jLinkBot.textContent = 'j +' + delta;
1288    
1289    msg = delta + ' new post' + (delta > 1 ? 's' : '');
1290    
1291    Main.addTooltip(jLink, msg, 'j-tooltip');
1292    Main.addTooltip(jLinkBot, msg, 'j-tooltip-bot');
1293  };
1294  
1295  J.refreshJCount = function() {
1296    var stored, jLink, jLinkBot, xhr;
1297    
1298    jLink = $.id('j-link');
1299    jLinkBot = $.id('j-link-bot');
1300    
1301    if (!jLink || !jLinkBot) {
1302      return;
1303    }
1304    
1305    jLink = jLink.firstElementChild;
1306    jLinkBot = jLinkBot.firstElementChild;
1307    
1308    if (stored = localStorage.getItem('4chan-j-count')) {
1309      stored = JSON.parse(stored);
1310    }
1311    
1312    if (!stored || (Date.now() - stored.time) >= 10000) {
1313      xhr = new XMLHttpRequest();
1314      xhr.open('GET', 'https://sys.4chan.org/j/1mcQTXbjW5WO.php?&' + Date.now());
1315      xhr.withCredentials = true;
1316      xhr.onloadend = function() {
1317        var data, obj, delta;
1318        if (this.status == 200 || this.status == 304) {
1319          data = JSON.parse(this.responseText);
1320          if (!stored || Main.board == 'j') {
1321            obj = { time: Date.now(), no: data.no };
1322          }
1323          else if (data.no > stored.no) {
1324            delta = data.no - stored.no;
1325            J.displayJCount(jLink, jLinkBot, data.no, delta);
1326            obj = { time: Date.now(), no: stored.no, delta: delta };
1327          }
1328          if (obj) {
1329            localStorage.setItem('4chan-j-count', JSON.stringify(obj));
1330          }
1331        }
1332        else {
1333          console.log('Error: Could not load /j/ post count (Status: ' + this.status + ').');
1334        }
1335      };
1336      xhr.send(null);
1337    }
1338    else if (stored.delta) {
1339      J.displayJCount(jLink, jLinkBot, stored.no, stored.delta);
1340    }
1341  };
1342  
1343  J.clearJCount = function() {
1344    var obj, no, tt, ttbot;
1345    
1346    tt = $.id('j-tooltip');
1347    ttbot = $.id('j-tooltip-bot');
1348    
1349    if (!tt) {
1350      return;
1351    }
1352    
1353    no = this.getAttribute('data-no');
1354    obj = { time: Date.now(), no: no };
1355    localStorage.setItem('4chan-j-count', JSON.stringify(obj));
1356    
1357    tt.parentNode.removeChild(tt);
1358    ttbot.parentNode.removeChild(ttbot);
1359    
1360    setTimeout(function() {
1361      var nodes = $.cls('j-newposts');
1362      if (nodes[0]) {
1363        nodes[0].textContent = 'j';
1364        $.removeClass(nodes[0], 'j-newposts');
1365        nodes[0].textContent = 'j';
1366        $.removeClass(nodes[0], 'j-newposts');
1367      }
1368    }, 10);
1369  };
1370  
1371  J.hasFlag = function(flag) {
1372    return this.flags.indexOf(flag) != -1;
1373  };
1374  
1375  J.icons = {
1376    multi: 'multi.png',
1377    ban: 'ban.png',
1378    arrow_right: 'arrow_right.png',
1379    spoiler: 's.png'
1380  };
1381  
1382  J.initIcons = function () {
1383    var key, paths, url;
1384  
1385    paths = {
1386      yotsuba_new:'futaba/',
1387      futaba_new:'futaba/',
1388      yotsuba_b_new:'burichan/',
1389      burichan_new:'burichan/',
1390      tomorrow:'tomorrow/',
1391      photon:'photon/'
1392    };
1393  
1394    url = '//s.4cdn.org/image/buttons/' + paths[Main.stylesheet];
1395  
1396    if (window.devicePixelRatio >= 2) {
1397      for (key in J.icons) {
1398        J.icons[key] = J.icons[key].replace('.', '@2x.');
1399      }
1400    }
1401  
1402    for (key in J.icons) {
1403      J.icons[key] = url + J.icons[key];
1404    }
1405  };
1406  
1407  J.initNavLinks = function () {
1408    var el, txt, frag, fragBot, nav, navbot;
1409  
1410    nav = $.id('navtopright');
1411    navbot = $.id('navbotright');
1412  
1413    // [j] link
1414    el = document.createElement('span');
1415    el.id = 'j-link';
1416    el.innerHTML = '[<a href="https://sys.4chan.org/j/" title="Janitor &amp; Moderator Discussion">j</a>]';
1417    el.firstElementChild.addEventListener('mouseup', J.clearJCount, false);
1418    nav.parentNode.insertBefore(el, nav);
1419  
1420    // [j] bottom link
1421    el = el.cloneNode(true);
1422    el.id = 'j-link-bot';
1423    el.firstElementChild.addEventListener('mouseup', J.clearJCount, false);
1424    navbot.parentNode.insertBefore(el, navbot);
1425    
1426    J.refreshJCount();
1427    
1428    // Team link
1429    txt = nav.lastElementChild.previousSibling;
1430    frag = document.createDocumentFragment();
1431    frag.appendChild(document.createTextNode('] ['));
1432    el = document.createElement('a');
1433    el.textContent = 'Team';
1434    el.href = 'https://' + J.teamSubDomain + '.4chan.org/';
1435    el.setAttribute('target', '_blank');
1436    frag.appendChild(el);
1437    frag.appendChild(document.createTextNode('] ['));
1438    fragBot = frag.cloneNode(true);
1439    nav.replaceChild(frag, txt);
1440  
1441    // Team bottom link
1442    txt = navbot.lastElementChild.previousSibling;
1443    navbot.replaceChild(fragBot, txt);
1444    
1445    // Poster IDs
1446    if (Main.tid && Main.hasMobileLayout) {
1447      el = document.createElement('span');
1448      el.className = 'mobileib button redButton';
1449      el.innerHTML = '<label data-cmd="poster-id">Same Poster ID</label>';
1450      if (nav = $.cls('navLinks')[0]) {
1451        nav.appendChild(el);
1452      }
1453    }
1454  };
1455  
1456  J.initPostForm = function () {
1457    var form, el, field, cnt, cookie;
1458    
1459    form = $.id('postForm');
1460    
1461    if (!form && Main.tid && (cnt = $.cls('closed')[0])) {
1462      el = document.createElement('form');
1463      el.name = 'post';
1464      el.method = 'POST';
1465      el.enctype = 'multipart/form-data';
1466      el.action = 'https://sys.' + $L.d(Main.board) + '/' + Main.board + '/post';
1467      el.innerHTML = J.postFormHTML
1468        + '<input type="hidden" name="mode" value="regist">'
1469        + '<input type="hidden" name="resto" value="' + Main.tid + '">';
1470      
1471      cnt.parentNode.insertBefore(el, cnt);
1472      
1473      form = el.firstElementChild;
1474      
1475      QR.enabled = true;
1476    }
1477    
1478    if (form) {
1479      el = document.forms.post.name;
1480  
1481      if (el.type == 'hidden' && J.hasFlag('forcedanonname')) {
1482        field = document.createElement('tr');
1483        field.setAttribute('data-type', 'Name');
1484        field.innerHTML = '<td>Name</td><td><input name="name" type="text"></td>';
1485        if (cookie = Main.getCookie('4chan_name')) {
1486          field.lastChild.firstChild.value = cookie;
1487        }
1488        cnt = $.id('postForm').firstElementChild;
1489        cnt.insertBefore(field, cnt.firstElementChild);
1490        el.parentNode.removeChild(el);
1491      }
1492      
1493      if (J.hasFlag('html')) {
1494        el = document.createElement('tr');
1495        el.innerHTML = '<tr><td style="height: 20px;">Extra</td><td>[<label><input type="checkbox" data-cmd="html-toggle" name="html" value="1">HTML</label>] <span class="html-otp">[<label data-tip="2FA One-Time Password">OTP</label> <input type="text" autocomplete="off" name="otp" maxlength="6">] <a href="#" class="preview-html-btn" data-cmd="preview-html">Preview</a></span></td></tr>';
1496        form = form.firstElementChild;
1497        form.insertBefore(el, form.lastElementChild);
1498      }
1499    }
1500  };
1501  
1502  J.onHTMLToggle = function(t) {
1503    var el = $.cls('html-otp', t.parentNode.parentNode)[0];
1504    
1505    if (!el) {
1506      return;
1507    }
1508    
1509    el.style.display = t.checked ? 'inline' : '';
1510  };
1511  
1512  J.onPreviewHTMLClick = function(t) {
1513    var data, form, xhr;
1514    
1515    if (J.xhrs['html']) {
1516      return;
1517    }
1518    
1519    if (document.forms.qrPost) {
1520      form = document.forms.qrPost;
1521    }
1522    else {
1523      form = document.forms.post;
1524    }
1525    
1526    if (form.com.value === '') {
1527      return;
1528    }
1529    
1530    data = new FormData();
1531    data.append('com', form.com.value);
1532    
1533    xhr = new XMLHttpRequest();
1534    xhr.open('post', form.action + '?mode=preview_html', true);
1535    xhr.withCredentials = true;
1536    xhr.onload = J.onHTMLPReviewLoaded;
1537    xhr.onerror = J.onHTMLPReviewError;
1538    
1539    J.xhrs['html'] = xhr;
1540    
1541    $.addClass(t, 'disabled');
1542    
1543    xhr.send(data);
1544    
1545    Feedback.notify('Processing...', null);
1546  };
1547  
1548  J.resetHTMLPreviewBtn = function() {
1549    var nodes = $.cls('preview-html-btn');
1550    $.removeClass(nodes[0], 'disabled');
1551    nodes[1] && $.removeClass(nodes[1], 'disabled');
1552  };
1553  
1554  J.onHTMLPReviewLoaded = function() {
1555    var resp;
1556    
1557    J.xhrs['html'] = null;
1558    
1559    Feedback.hideMessage();
1560    
1561    J.resetHTMLPreviewBtn();
1562    
1563    try {
1564      resp = JSON.parse(this.responseText);
1565      
1566      if (resp.status == 'error') {
1567        Feedback.error(resp.message);
1568      }
1569    }
1570    catch (err) {
1571      Feedback.error('Something went wrong.');
1572    }
1573    
1574    J.buildHTMLPreview(resp.data);
1575  };
1576  
1577  J.onHTMLPReviewError = function() {
1578    J.xhrs['html'] = null;
1579    
1580    Feedback.hideMessage();
1581    
1582    J.resetHTMLPreviewBtn();
1583    
1584    console.log(this);
1585  };
1586  
1587  J.closeHTMLPreview = function() {
1588    var el;
1589    
1590    if (el = $.id('html-preview-cnt')) {
1591      el.parentNode.removeChild(el);
1592    }
1593    
1594    J.resetHTMLPreviewBtn();
1595  };
1596  
1597  J.buildHTMLPreview = function(html) {
1598    var el;
1599    
1600    J.closeHTMLPreview();
1601    
1602    el = document.createElement('div');
1603    el.id = 'html-preview-cnt';
1604    el.setAttribute('data-cmd', 'close-html-preview');
1605    el.innerHTML = '\
1606  <div class="extPanel reply"><div class="panelHeader">Preview HTML Post\
1607  <span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="close-html-preview" src="'
1608  + Main.icons.cross + '"></span></div>' + html + '</div>';
1609    
1610    document.body.appendChild(el);
1611  };
1612  
1613  J.onThreadMouseOver = function(e) {
1614    var t = e.target;
1615    
1616    if ($.hasClass(t, 'thumb')) {
1617      if (J.hasCatalogControls) {
1618        J.hideCatalogControls();
1619      }
1620      if (!$.hasClass(t.parentNode.parentNode, 'disabled')) {
1621        J.showCatalogControls(t);
1622      }
1623    }
1624  };
1625  /*
1626  J.onThreadMouseOut = function(e) {
1627    var t = e.target;
1628    
1629    if (J.hasCatalogControls && $.hasClass(t, 'thumb')) {
1630      J.hideCatalogControls();
1631    }
1632  };
1633  */
1634  J.showCatalogControls = function(t) {
1635    var el, id, cnt;
1636    
1637    id = t.getAttribute('data-id');
1638    
1639    el = document.createElement('div');
1640    el.id = 'cat-ctrl';
1641    el.className = J.stylesheet;
1642    el.innerHTML = '<span class="threadIcon deleteIcon" data-cmd="open-delete-prompt" data-id="'
1643      + id + '"></span><span class="threadIcon multiIcon" data-cmd="multi" data-id="'
1644      + id + '"></span><span class="threadIcon banIcon" data-cmd="ban" data-id="'
1645      + id + '"></span>';
1646    
1647    if (cnt = $.cls('threadIcons', t.parentNode.parentNode)[0]) {
1648      cnt.insertBefore(el, cnt.firstElementChild);
1649    }
1650    else {
1651      cnt = document.createElement('div');
1652      cnt.className = 'threadIcons';
1653      cnt.appendChild(el);
1654      t.parentNode.parentNode.insertBefore(cnt, t.parentNode.nextElementSibling);
1655    }
1656    
1657    J.hasCatalogControls = true;
1658  };
1659  
1660  J.hideCatalogControls = function() {
1661    var el = $.id('cat-ctrl');
1662    
1663    if (el) {
1664      el.parentNode.removeChild(el);
1665    }
1666    
1667    J.hasCatalogControls = false;
1668  };
1669  
1670  J.initCatalog = function() {
1671    var storage;
1672    
1673    window.Main = {
1674      board: location.pathname.split(/\//)[1]
1675    };
1676    
1677    Main.addTooltip = function(link, message, id) {
1678      var el, pos;
1679      
1680      el = document.createElement('div');
1681      el.className = 'click-me';
1682      if (id) {
1683        el.id = id;
1684      }
1685      el.innerHTML = message || 'Change your settings';
1686      link.parentNode.appendChild(el);
1687      
1688      pos = (link.offsetWidth - el.offsetWidth + link.offsetLeft - el.offsetLeft) / 2;
1689      el.style.marginLeft = pos + 'px';
1690      
1691      return el;
1692    };
1693    
1694    if (J.stylesheet = J.getCookie(window.style_group)) {
1695      J.stylesheet = J.stylesheet.toLowerCase().replace(/ /g, '_');
1696    }
1697    else {
1698      J.stylesheet =
1699        style_group == 'nws_style' ? 'yotsuba_new' : 'yotsuba_b_new';
1700    }
1701    
1702    Main.stylesheet = J.stylesheet;
1703    
1704    J.initIconsCatalog();
1705    
1706    J.addCss(); // fixme
1707    
1708    document.addEventListener('click', J.onClick, false);
1709    
1710    J.runCatalog();
1711  };
1712  
1713  J.runCatalog = function () {
1714    var threads;
1715    //J.addCss(); // fixme
1716    //document.removeEventListener('4chanMainInit', J.runCatalog, false);
1717    
1718    J.initNavLinks();
1719    
1720    if (!FC.hasMobileLayout) {
1721      AdminTools.init();
1722    }
1723    
1724    threads = $.id('threads');
1725    
1726    J.initCatAutoReload(true);
1727    
1728    document.addEventListener('keydown', J.onCatalogKeyDown, false);
1729    
1730    $.on(threads, 'mouseover', J.onThreadMouseOver);
1731    
1732    if (window.text_only) {
1733      document.addEventListener('4chanPostMenuReady', J.onPostMenuReady, false);
1734    }
1735    //$.on(threads, 'mouseout', J.onThreadMouseOut);
1736  };
1737  
1738  J.init = function () {
1739    var flags, ts;
1740    
1741    SettingsMenu.options['Moderator'] = {
1742      useIconButtons: [ 'Use icon buttons', 'Display old-style buttons instead of using drop-down' ],
1743      changeUpdateDelay:[ 'Reduce auto-update delay', 'Reduce the thread updater delay', true ],
1744      fixedAdminToolbox:[ 'Pin Moderator Tools to the page', 'Moderator Tools will scroll with you' ],
1745      inlinePopups: [ 'Inline admin panels', 'Open admin panels in browser window, instead of a popup' ],
1746      disableMngExt:[ 'Disable moderator extension', 'Completely disable the moderator extension (overrides any checked boxes)', true ]
1747    };
1748  
1749    if (Config.disableMngExt) {
1750      return;
1751    }
1752    
1753    if (flags = Main.getCookie('4chan_aflags')) {
1754      J.flags = flags.split(',');
1755    }
1756    
1757    J.addCss();
1758    
1759    if (Config.useIconButtons) {
1760      J.initIcons();
1761    }
1762    
1763    QR.noCooldown = QR.noCaptcha = true;
1764    //QR.comLength = window.comlen = 10000;
1765  
1766    document.addEventListener('click', J.onClick, false);
1767    document.addEventListener('DOMContentLoaded', J.run, false);
1768  };
1769  
1770  J.run = function() {
1771    var posts, el, nav;
1772  
1773    document.removeEventListener('DOMContentLoaded', J.run, false);
1774  
1775    J.initNavLinks();
1776    J.initPostForm();
1777    
1778    if (!Main.hasMobileLayout) {
1779      AdminTools.init();
1780    }
1781    
1782    if (Config.revealSpoilers) {
1783      $.addClass(document.body, 'reveal-img-spoilers');
1784    }
1785    
1786    if (Config.threadUpdater && Main.tid) {
1787      if (Config.changeUpdateDelay) {
1788        ThreadUpdater.delayIdHidden = 3;
1789        ThreadUpdater.delayRange = [ 5, 10, 15, 20, 30, 60 ];
1790        ThreadUpdater.apiUrlFilter = J.apiUrlFilter;
1791      }
1792    }
1793    
1794    if (Config.useIconButtons && !Main.hasMobileLayout) {
1795      if (Main.tid) {
1796        posts = document.getElementById('t' + Main.tid).getElementsByClassName('postInfo');
1797        J.postCount = posts.length;
1798        if (J.postCount > J.chunkSize) {
1799          J.nextChunk = posts[0];
1800          window.addEventListener('scroll', J.onScroll, false);
1801          J.onScroll();
1802        }
1803        else {
1804          J.onParsingDone();
1805        }
1806        
1807        nav = $.cls('navLinksBot')[0];
1808        el = document.createElement('span');
1809        el.id = 'threadOptsButtom';
1810        el.innerHTML = '[<a href="javascript:;" data-id="' + Main.tid + '" data-cmd="thread-options">Thread Options</a>]';
1811        nav.appendChild(el);
1812      }
1813      else {
1814        J.onParsingDone();
1815      }
1816      
1817      document.addEventListener('4chanParsingDone', J.onParsingDone, false);
1818      
1819      J.parserEventBound = true;
1820    }
1821    
1822    document.addEventListener('4chanPostMenuReady', J.onPostMenuReady, false);
1823    
1824    if (nav = $.id('boardSelectMobile')) {
1825      el = document.createElement('option');
1826      el.value = 'j';
1827      el.textContent = '/j/ - Janitors & Moderators';
1828      nav.insertBefore(el, nav.firstElementChild);
1829    }
1830  };
1831  
1832  J.getCookie = function(name) {
1833    var i, c, ca, key;
1834    
1835    key = name + "=";
1836    ca = document.cookie.split(';');
1837    
1838    for (i = 0; c = ca[i]; ++i) {
1839      while (c.charAt(0) == ' ') {
1840        c = c.substring(1, c.length);
1841      }
1842      if (c.indexOf(key) === 0) {
1843        return decodeURIComponent(c.substring(key.length, c.length));
1844      }
1845    }
1846    return null;
1847  };
1848  
1849  J.postFormHTML = '<table class="postForm" id="postForm"><tbody>\
1850  <tr data-type="Name"><td>Name</td><td>\
1851  <input name="name" type="text" tabindex="1" \
1852  placeholder="Anonymous"></td></tr><tr data-type="Options"><td>Options</td>\
1853  <td><input name="email" type="text" tabindex="2"><input type="submit" \
1854  value="Post" tabindex="6"></td></tr><tr data-type="Comment"><td>Comment</td>\
1855  <td><textarea name="com" cols="48" rows="4" tabindex="4" wrap="soft">\
1856  </textarea></td></tr><tr data-type="File"><td>File</td><td>\
1857  <input id="postFile" name="upfile" type="file" tabindex="7">\
1858  <span class="desktop">[<label><input type="checkbox" name="spoiler" value="on" \
1859  tabindex="8">Spoiler?</label>]</span></td></tr><tr><td></td></tr></tbody></table>';
1860  
1861  J.addCss = function () {
1862    var style, css;
1863    
1864    css = '\
1865  #adminToolbox {\
1866    max-width: 256px;\
1867    display: block;\
1868    position: absolute;\
1869    padding: 3px;\
1870  }\
1871  #adminToolbox h4 {\
1872    font-size: 12px;\
1873    margin: 2px 0 0;\
1874    padding: 0;\
1875    font-weight: normal;\
1876  }\
1877  #adminToolbox li {\
1878    list-style: none;\
1879  }\
1880  #adminToolbox ul {\
1881    padding: 0;\
1882    margin: 2px 0 0 10px;\
1883  }\
1884  #atHeader {\
1885    height: 17px;\
1886    font-weight: bold;\
1887    padding-bottom: 2px;\
1888  }\
1889  #atRefresh {\
1890    margin: -1px 0 0 3px;\
1891  }\
1892  .post-ip {\
1893    margin-left: 5px;\
1894  }\
1895  #delete-prompt > div {\
1896    text-align: center;\
1897  }\
1898  #watchList li:first-child {\
1899    margin-top: 3px;\
1900    padding-top: 2px;\
1901    border-top: 1px solid rgba(0, 0, 0, 0.20);\
1902  }\
1903  .photon #atHeader {\
1904    border-bottom: 1px solid #ccc;\
1905  }\
1906  .yotsuba_new #atHeader {\
1907    border-bottom: 1px solid #d9bfb7;\
1908  }\
1909  .yotsuba_b_new #atHeader {\
1910    border-bottom: 1px solid #b7c5d9;\
1911  }\
1912  .tomorrow #atHeader {\
1913    border-bottom: 1px solid #111;\
1914  }\
1915  #captchaFormPart {\
1916    display: none;\
1917  }\
1918  #at-prio-appeals {\
1919    color: blue;\
1920  }\
1921  #at-illegal-br,\
1922  #at-illegal {\
1923    color: red;\
1924  }\
1925  #at-msg-cnt {\
1926    display: none;\
1927  }\
1928  .j-newposts {\
1929    font-weight: bold !important;\
1930  }\
1931  #j-link,\
1932  #j-link-bot {\
1933    margin-right: 3px;\
1934    display: inline-block;\
1935    margin-left: 3px;\
1936  }\
1937  #adminToolbox hr {\
1938    margin: 2px 0;\
1939  }\
1940  #threadOptsClose,\
1941  #banReqClose {\
1942    float: right;\
1943  }\
1944  #threadOpts iframe,\
1945  #banReq iframe {\
1946    overflow: hidden;\
1947  }\
1948  #threadOpts,\
1949  #banReq {\
1950    display: block;\
1951    position: fixed;\
1952    padding: 2px;\
1953    font-size: 10pt;\
1954  }\
1955  #banReq {\
1956    height: 490px;\
1957  }\
1958  #threadOpts {\
1959    height: 194px;\
1960  }\
1961  #threadOptsHeader,\
1962  #banReqHeader {\
1963    text-align: center;\
1964    margin-bottom: 1px;\
1965    padding: 0;\
1966    height: 18px;\
1967    line-height: 18px;\
1968  }\
1969  #threadOptsButtom {\
1970    float: right;\
1971    margin-right: 4px;\
1972  }\
1973  .mobileExtControls {\
1974    float: right;\
1975    font-size: 11px;\
1976    margin-bottom: 3px;\
1977  }\
1978  .ws .mobileExtControls {\
1979    color: #34345C;\
1980  }\
1981  .nws .mobileExtControls {\
1982    color: #0000EE;\
1983  }\
1984  .reply .mobileExtControls {\
1985    margin-right: 5px;\
1986  }\
1987  .mobileExtControls span {\
1988    margin-left: 10px;\
1989  }\
1990  .mobileExtControls span:after {\
1991    content: "]";\
1992  }\
1993  .mobileExtControls span:before {\
1994    content: "[";\
1995  }\
1996  .nws .mobileExtControls span:after {\
1997    color: #800000;\
1998  }\
1999  .nws .mobileExtControls span:before {\
2000    color: #800000;\
2001  }\
2002  .ws .mobileExtControls span:after {\
2003    color: #000;\
2004  }\
2005  .ws .mobileExtControls span:before {\
2006    color: #000;\
2007  }\
2008  .m-dark .mobileExtControls {\
2009    color: #81a2be !important;\
2010  }\
2011  .m-dark .mobileExtControls span:after,\
2012  .m-dark .mobileExtControls span:before {\
2013    color: #1d1f21 !important;\
2014  }\
2015  #cat-ctrl {\
2016    display: inline-block;\
2017    margin-right: 2px;\
2018    margin-top: -1px;\
2019  }\
2020  #cat-ctrl .threadIcon {\
2021    cursor: pointer;\
2022  }\
2023  .disabled {\
2024    opacity: 0.5;\
2025  }\
2026  .burichan_new .deleteIcon,\
2027  .yotsuba_b_new .deleteIcon {\
2028    background-image: url("//s.4cdn.org/image/buttons/burichan/cross.png");\
2029  }\
2030  .burichan_new .banIcon,\
2031  .yotsuba_b_new .banIcon {\
2032    background-image: url("//s.4cdn.org/image/buttons/burichan/ban.png");\
2033  }\
2034  .burichan_new .fileIcon,\
2035  .yotsuba_b_new .fileIcon {\
2036    background-image: url("//s.4cdn.org/image/buttons/burichan/f.png");\
2037  }\
2038  .burichan_new .multiIcon,\
2039  .yotsuba_b_new .multiIcon {\
2040    background-image: url("//s.4cdn.org/image/buttons/burichan/multi.png");\
2041  }\
2042  .futaba_new .deleteIcon,\
2043  .yotsuba_new .deleteIcon {\
2044    background-image: url("//s.4cdn.org/image/buttons/futaba/cross.png");\
2045  }\
2046  .futaba_new .banIcon,\
2047  .yotsuba_new .banIcon {\
2048    background-image: url("//s.4cdn.org/image/buttons/futaba/ban.png");\
2049  }\
2050  .futaba_new .fileIcon,\
2051  .yotsuba_new .fileIcon {\
2052    background-image: url("//s.4cdn.org/image/buttons/futaba/f.png");\
2053  }\
2054  .futaba_new .multiIcon,\
2055  .yotsuba_new .multiIcon {\
2056    background-image: url("//s.4cdn.org/image/buttons/futaba/multi.png");\
2057  }\
2058  .photon .deleteIcon {\
2059    background-image: url("//s.4cdn.org/image/buttons/photon/cross.png");\
2060  }\
2061  .photon .banIcon {\
2062    background-image: url("//s.4cdn.org/image/buttons/photon/ban.png");\
2063  }\
2064  .photon .fileIcon {\
2065    background-image: url("//s.4cdn.org/image/buttons/photon/f.png");\
2066  }\
2067  .photon .multiIcon {\
2068    background-image: url("//s.4cdn.org/image/buttons/photon/multi.png");\
2069  }\
2070  .tomorrow .deleteIcon {\
2071    background-image: url("//s.4cdn.org/image/buttons/tomorrow/cross.png");\
2072  }\
2073  .tomorrow .banIcon {\
2074    background-image: url("//s.4cdn.org/image/buttons/tomorrow/ban.png");\
2075  }\
2076  .tomorrow .fileIcon {\
2077    background-image: url("//s.4cdn.org/image/buttons/tomorrow/f.png");\
2078  }\
2079  .tomorrow .multiIcon {\
2080    background-image: url("//s.4cdn.org/image/buttons/tomorrow/multi.png");\
2081  }\
2082  .dd-admin {\
2083    text-indent: 5px;\
2084  }\
2085  .dd-admin:before {\
2086    color: #FF0000;\
2087    content: "•";\
2088    left: -3px;\
2089    position: absolute;\
2090  }\
2091  .extPanel {\
2092    border: 1px solid rgba(0, 0, 0, 0.2);\
2093  }\
2094  .extPanel img.pointer { width: 18px; height: 18px }\
2095  .preview-html-btn { font-size: 11px; }\
2096  #html-preview-cnt .extPanel { width: 800px; margin-left: -400px; }\
2097  #html-preview-cnt {\
2098    position: fixed;\
2099    width: 100%;\
2100    height: 100%;\
2101    z-index: 9002;\
2102    top: 0;\
2103    left: 0;\
2104  }\
2105  #html-preview-cnt {\
2106    line-height: 14px;\
2107    font-size: 14px;\
2108    background-color: rgba(0, 0, 0, 0.25);\
2109  }\
2110  #html-preview-cnt:after {\
2111    display: inline-block;\
2112    height: 100%;\
2113    vertical-align: middle;\
2114    content: "";\
2115  }\
2116  #html-preview-cnt > div {\
2117    -moz-box-sizing: border-box;\
2118    box-sizing: border-box;\
2119    display: inline-block;\
2120    height: auto;\
2121    max-height: 100%;\
2122    position: relative;\
2123    width: 400px;\
2124    left: 50%;\
2125    margin-left: -200px;\
2126    overflow: auto;\
2127    box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);\
2128    vertical-align: middle;\
2129  }\
2130  .reveal-img-spoilers .imgspoiler::before {\
2131    content: " ";\
2132    width:0.75em;\
2133    height:0.75em;\
2134    border-radius: 0.5em;\
2135    position: absolute;\
2136    display: block;\
2137    background: red;\
2138    margin-top: 1px;\
2139    margin-left: 1px;\
2140    pointer-events: none;\
2141  }\
2142  .reveal-img-spoilers.is_catalog .imgspoiler::before { margin-top: 4px; margin-left: 12px;}\
2143  .reveal-img-spoilers .imgspoiler:hover::before { background: #fff; }\
2144  .html-otp { display: none; width: 50px; }\
2145  .html-otp input { width: 50px !important; height: 12px; }\
2146  .drag {\
2147    -moz-user-select: none !important;\
2148    cursor: move !important;\
2149  }' + (J.isCatalog ? '\
2150  .panelHeader .panelCtrl {\
2151    position: absolute;\
2152    right: 5px;\
2153    top: 5px;\
2154  }\
2155  .active-btn { border-bottom: 3px double; }\
2156  .UIPanel {\
2157    position: fixed;\
2158    width: 100%;\
2159    height: 100%;\
2160    top: 0;\
2161    left: 0;\
2162    z-index: 9000 !important;\
2163  }\
2164  .UIPanel {\
2165    line-height: 14px;\
2166    font-size: 14px;\
2167    background-color: rgba(0, 0, 0, 0.25);\
2168  }\
2169  .UIPanel:after {\
2170    display: inline-block;\
2171    height: 100%;\
2172    vertical-align: middle;\
2173    content: "";\
2174  }\
2175  .UIPanel > div {\
2176    -moz-box-sizing: border-box;\
2177    box-sizing: border-box;\
2178    display: inline-block;\
2179    height: auto;\
2180    max-height: 100%;\
2181    position: relative;\
2182    width: 400px;\
2183    left: 50%;\
2184    margin-left: -200px;\
2185    overflow: auto;\
2186    box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);\
2187    vertical-align: middle;\
2188  }\
2189  .UIPanel .extPanel {\
2190    padding: 2px;\
2191  }' : '');
2192  
2193    style = document.createElement('style');
2194    style.setAttribute('type', 'text/css');
2195    style.textContent = css;
2196    document.head.appendChild(style);
2197  };
2198  
2199  if (/https?:\/\/boards\.(?:4chan|4channel)\.org\/[a-z0-9]+\/catalog($|#.*$)/.test(location.href)) {
2200    J.isCatalog = true;
2201    J.initCatalog();
2202  }
2203  else {
2204    J.init();
2205    //J.run();
2206  }
2207  
2208  })();