/ js / janitor-unminified.js
janitor-unminified.js
   1  /**
   2   * Janitor Extension
   3   */
   4  
   5  (function() {
   6  /**
   7   * Admin tools
   8   */
   9  var AdminTools = {
  10    cacheTTL: 60000,
  11    autoRefreshDelay: 120000,
  12    autoRefreshTimeout: null
  13  };
  14  
  15  // FIXME, put it as a helper in extension.js
  16  AdminTools.initVisibilityAPI = function() {
  17    this.hidden = 'hidden';
  18    this.visibilitychange = 'visibilitychange';
  19    
  20    if (typeof document.hidden === 'undefined') {
  21      if ('mozHidden' in document) {
  22        this.hidden = 'mozHidden';
  23        this.visibilitychange = 'mozvisibilitychange';
  24      }
  25      else if ('webkitHidden' in document) {
  26        this.hidden = 'webkitHidden';
  27        this.visibilitychange = 'webkitvisibilitychange';
  28      }
  29      else if ('msHidden' in document) {
  30        this.hidden = 'msHidden';
  31        this.visibilitychange = 'msvisibilitychange';
  32      }
  33    }
  34    
  35    document.addEventListener(this.visibilitychange, this.onVisibilityChange, false);
  36  };
  37  
  38  AdminTools.init = function() {
  39    var cnt, html;
  40    
  41    AdminTools.initVisibilityAPI();
  42    
  43    cnt = document.createElement('div');
  44    cnt.className = 'extPanel reply';
  45    cnt.id = 'adminToolbox';
  46    cnt.setAttribute('data-trackpos', 'AT-position');
  47  
  48    if( Config['AT-position'] ) {
  49      cnt.style.cssText = Config['AT-position'];
  50    } else {
  51      cnt.style.right = '10px';
  52      cnt.style.top = '380px';
  53    }
  54  
  55    cnt.style.position = Config.fixedAdminToolbox ? 'fixed' : '';
  56  
  57    html = '<div class="drag" id="atHeader">Janitor Tools'
  58      + '<img alt="Refresh" title="Refresh" src="' + Main.icons.refresh
  59      + '" id="atRefresh" data-cmd="at-refresh" class="pointer right"></div>'
  60      + '<h4><a href="https://' + J.reportsSubDomain + '.4chan.org/" target="_blank">Reports</a>: '
  61      + '<span title="Total" id="at-total">?</span> ('
  62      + '<span title="Illegal" id="at-illegal">?</span>)</h4>'
  63      + '<h4 id="at-msg-cnt"><a data-cmd="at-msg" href="https://' + J.reportsSubDomain
  64        + '.4chan.org/?action=staffmessages" target="_blank">Messages</a>: <span id="at-msg">?</span></h4>';
  65  
  66    cnt.innerHTML = html;
  67    document.body.appendChild(cnt);
  68    AdminTools.refreshReportCount();
  69  
  70    Draggable.set($.id('atHeader'));
  71  };
  72  
  73  AdminTools.onVisibilityChange = function() {
  74    var self;
  75    
  76    self = AdminTools;
  77    
  78    if (document[AdminTools.hidden]) {
  79      clearInterval(self.autoRefreshTimeout);
  80      self.autoRefreshTimeout = null;
  81    }
  82    else {
  83      self.refreshReportCount();
  84      self.autoRefreshTimeout = setInterval(self.refreshReportCount, self.autoRefreshDelay);
  85    }
  86  };
  87  
  88  AdminTools.refreshReportCount = function(force) {
  89    var xhr, cache;
  90    
  91    if (force !== true && (cache = localStorage.getItem('4chan-cache-rc'))) {
  92      cache = JSON.parse(cache);
  93      
  94      if (cache.ts > Date.now() - AdminTools.cacheTTL) {
  95        $.id('at-total').textContent = cache.data[0];
  96        $.id('at-illegal').textContent = cache.data[1];
  97        
  98        $.id('at-msg-cnt').style.display = cache.data[2] ? 'block' : '';
  99        $.id('at-msg').textContent = cache.data[2] || 0;
 100        
 101        return;
 102      }
 103    }
 104    
 105    xhr = new XMLHttpRequest();
 106    
 107    xhr.open('GET', 'https://' + J.reportsSubDomain + '.4chan.org/H429f6uIsUqU.php', true);
 108    
 109    xhr.withCredentials = true;
 110    
 111    xhr.onload = function() {
 112      var cache, resp, data, msg_count;
 113      
 114      if (this.status == 200) {
 115        try {
 116          resp = JSON.parse(this.responseText);
 117        }
 118        catch (e) {
 119          console.log(e);
 120          return;
 121        }
 122        
 123        if (resp.status !== 'success') {
 124          console.log(resp.message); // FIXME, use global message
 125          return;
 126        }
 127        
 128        data = resp.data;
 129        
 130        msg_count = data.msg || 0;
 131        
 132        $.id('at-msg-cnt').style.display = msg_count ? 'block' : '';
 133        $.id('at-msg').textContent = msg_count;
 134        $.id('at-total').textContent = data.total;
 135        $.id('at-illegal').textContent = data.illegal;
 136        
 137        cache = {
 138          ts: Date.now(),
 139          data: [ data.total, data.illegal, msg_count ]
 140        };
 141        
 142        cache = JSON.stringify(cache);
 143        
 144        localStorage.setItem('4chan-cache-rc', cache);
 145        
 146        document.dispatchEvent(new CustomEvent('4chanATUpdated'));
 147      }
 148      else {
 149        this.onerror();
 150      }
 151    };
 152    
 153    xhr.onerror = function() {
 154      console.log('Error while refreshing the report count (Status: ' + this.status + ').');
 155    };
 156    
 157    xhr.onloadend = function() {
 158      $.id('atRefresh').src = Main.icons.refresh;
 159    };
 160  
 161    $.id('atRefresh').src = Main.icons.rotate;
 162    
 163    xhr.send(null);
 164  };
 165  
 166  AdminTools.resetMsgCount = function() {
 167    var cache;
 168    
 169    $.id('at-msg').textContent = 0;
 170    
 171    if (cache = localStorage.getItem('4chan-cache-rc')) {
 172      cache = JSON.parse(cache);
 173      cache.data[2] = 0;
 174      cache = JSON.stringify(cache);
 175      localStorage.setItem('4chan-cache-rc', cache);
 176    }
 177  };
 178  
 179  var J = {
 180    nextChunkIndex: 0,
 181    nextChunk: null,
 182    chunkSize: 100,
 183    
 184    reportsSubDomain: 'reports'
 185  };
 186  
 187  J.initIconsCatalog = function() {
 188    var key, paths, url;
 189    
 190    Main.icons = {
 191      up: 'arrow_up.png',
 192      down: 'arrow_down.png',
 193      right: 'arrow_right.png',
 194      download: 'arrow_down2.png',
 195      refresh: 'refresh.png',
 196      cross: 'cross.png',
 197      gis: 'gis.png',
 198      iqdb: 'iqdb.png',
 199      minus: 'post_expand_minus.png',
 200      plus: 'post_expand_plus.png',
 201      rotate: 'post_expand_rotate.gif',
 202      quote: 'quote.png',
 203      report: 'report.png',
 204      notwatched: 'watch_thread_off.png',
 205      watched: 'watch_thread_on.png',
 206      help: 'question.png'
 207    };
 208    
 209    paths = {
 210      yotsuba_new: 'futaba/',
 211      futaba_new: 'futaba/',
 212      yotsuba_b_new: 'burichan/',
 213      burichan_new: 'burichan/',
 214      tomorrow: 'tomorrow/',
 215      photon: 'photon/'
 216    };
 217    
 218    url = '//s.4cdn.org/image/';
 219    
 220    if (window.devicePixelRatio >= 2) {
 221      for (key in Main.icons) {
 222        Main.icons[key] = Main.icons[key].replace('.', '@2x.');
 223      }
 224    }
 225    
 226    url += 'buttons/' + paths[Main.stylesheet];
 227    for (key in Main.icons) {
 228      Main.icons[key] = url + Main.icons[key];
 229    }
 230  };
 231  
 232  J.apiUrlFilter = function(url) {
 233    return url + '?' + Math.round(Date.now() / 1000 / 3);
 234  };
 235  
 236  J.openDeletePrompt = function(id) {
 237    var html, cnt;
 238  
 239    id = id.getAttribute('data-id');
 240    html = '<div class="extPanel reply"><div class="panelHeader">Delete Post No.' + id
 241    + '<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="close-delete-prompt" src="'
 242    + Main.icons.cross + '"></a>'
 243    + '</span></div><span id="delete-prompt-inner">'
 244      + '<input type="button" value="Delete Post" tabindex="-1" data-cmd="delete-post" data-id="' + id + '"> '
 245      + '<input type="button" value="Delete Image Only" data-cmd="delete-image" data-id="' + id + '">';
 246  
 247    cnt = document.createElement('div');
 248    cnt.className = 'UIPanel';
 249    cnt.id = 'delete-prompt';
 250  
 251    cnt.innerHTML = html;
 252    cnt.addEventListener('click', J.closeDeletePrompt, false);
 253    document.body.appendChild(cnt);
 254    
 255    $.id('delete-prompt-inner').firstElementChild.focus();
 256  };
 257  
 258  J.closeDeletePrompt = function(e) {
 259    var prompt;
 260  
 261    if (!e || e.target.id == 'delete-prompt') {
 262      if (prompt = $.id('delete-prompt')) {
 263        prompt.removeEventListener('click', J.closeDeletePrompt, false);
 264        document.body.removeChild(prompt);
 265      }
 266    }
 267  };
 268  
 269  J.deletePost = function(btn, imageOnly) {
 270    var id, xhr, form, msg, el, url, mode, del, isOp;
 271  
 272    id = btn.getAttribute('data-id');
 273  
 274    isOp = $.id('t' + id);
 275  
 276    form = new FormData();
 277    msg = 'Delete Post No.';
 278    url = 'https://sys.' + $L.d(Main.board) + '/' + Main.board + '/post';
 279    mode = window.thread_archived ? 'arcdel' : 'usrdel';
 280  
 281    if(imageOnly) {
 282      msg = 'Delete Image No.';
 283      form.append('onlyimgdel', 'on');
 284    }
 285  
 286    form.append(id, 'delete');
 287    form.append('mode', mode);
 288    form.append('pwd', 'janitorise');
 289  
 290    (del = $.id('delete-prompt-inner')).textContent = 'Deleting...';
 291  
 292    xhr = new XMLHttpRequest();
 293    xhr.open('POST', url);
 294    xhr.withCredentials = true;
 295    xhr.onload = function() {
 296      btn.src = Main.icons.cross;
 297      if (this.status == 200) {
 298        if (this.responseText.indexOf('Updating') != -1) {
 299          if (!imageOnly) {
 300            if (id == Main.tid) {
 301              location.href = '//boards.' + $L.d(Main.board) + '/' + Main.board + '/';
 302              return;
 303            }
 304            else {
 305              if (isOp) {
 306                el = isOp.parentNode;
 307                el.removeChild(isOp.nextElementSibling);
 308                el.removeChild(isOp);
 309              }
 310              else {
 311                el = $.id('pc' + id);
 312                el.parentNode.removeChild(el);
 313              }
 314            }
 315          }
 316          else {
 317            el = $.id('f' + id);
 318            el.innerHTML = '<span class="fileThumb"><img alt="File deleted."'
 319              + ' src="//s.4cdn.org/image/filedeleted' + (isOp ? '' : '-res') + '.gif"></span>';
 320          }
 321  
 322          J.closeDeletePrompt();
 323        }
 324        else {
 325          del.textContent = 'Error: Post might have already been deleted, or is a sticky.';
 326        }
 327      }
 328      else {
 329        del.textContent = 'Error: Wrong status while deleting No.' + id + ' (Status: ' + this.status + ').';
 330      }
 331    };
 332    xhr.onerror = function() {
 333      del.textContent = 'Error: Error while deleting No.' + id + ' (Status: ' + this.status + ').';
 334    };
 335  
 336    xhr.send(form);
 337  };
 338  
 339  J.openBanReqWindow = function(btn)
 340  {
 341    var id;
 342  
 343    id = btn.getAttribute('data-id');
 344    window.open('https://sys.' + $L.d(Main.board) + '/' + Main.board + '/admin?mode=admin&admin=banreq&id=' + id, '_blank', 'scrollBars=yes,resizable=no,toolbar=no,menubar=no,location=no,directories=no,width=400,height=245');
 345  };
 346  
 347  J.openBanReqFrame = function(btn) {
 348    var id;
 349    
 350    if (this.banReqCnt) {
 351      this.close();
 352    }
 353    
 354    id = btn.getAttribute('data-id');
 355    
 356    this.banReqCnt = document.createElement('div');
 357    this.banReqCnt.id = 'banReq';
 358    this.banReqCnt.className = 'extPanel reply';
 359    this.banReqCnt.setAttribute('data-trackpos', 'banReq-position');
 360    
 361    if (Config['banReq-position']) {
 362      this.banReqCnt.style.cssText = Config['banReq-position'];
 363    }
 364    else {
 365      this.banReqCnt.style.right = '0px';
 366      this.banReqCnt.style.top = '50px';
 367    }
 368    
 369    this.banReqCnt.innerHTML =
 370      '<div id="banReqHeader" class="drag postblock">Ban Request No.' + id
 371      + '<img alt="X" src="' + Main.icons.cross + '" id="banReqClose" '
 372      + 'class="extButton" title="Close Window"></div>'
 373      + '<iframe src="https://sys.' + $L.d(Main.board) + '/'
 374      + Main.board + '/admin?mode=admin&admin=banreq&id=' + id
 375      + '&noheader=true" width="400" height="230" frameborder="0"></iframe>';
 376    
 377    document.body.appendChild(this.banReqCnt);
 378    
 379    window.addEventListener('message', J.onMessage, false);
 380    document.addEventListener('keydown', J.onKeyDown, false);
 381    
 382    $.id('banReqClose').addEventListener('click', J.closeBanReqFrame, false);
 383    Draggable.set($.id('banReqHeader'));
 384  };
 385  
 386  J.closeBanReqFrame = function() {
 387    window.removeEventListener('message', J.onMessage, false);
 388    document.removeEventListener('keydown', J.onKeyDown, false);
 389    Draggable.unset($.id('banReqHeader'));
 390    $.id('banReqClose').removeEventListener('click', J.closeBanReqFrame, false);
 391    document.body.removeChild(J.banReqCnt);
 392    J.banReqCnt = null;
 393  };
 394  
 395  J.processMessage = function(data) {
 396    if (!data) {
 397      return {};
 398    }
 399    
 400    data = data.split('-');
 401    
 402    return {
 403      cmd: data[0],
 404      type: data[1],
 405      id: data.slice(2).join('-')
 406    };
 407  };
 408  
 409  J.onKeyDown = function(e) {
 410    if (e.keyCode == 27 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
 411      J.closeBanReqFrame();
 412    }
 413  };
 414  
 415  J.onMessage = function(e) {
 416    var msg;
 417    
 418    if (e.origin !== 'https://sys.' + $L.d(Main.board)) {
 419      return;
 420    }
 421    
 422    msg = J.processMessage(e.data);
 423    
 424    if (msg.type !== 'ban') {
 425      return;
 426    }
 427    
 428    if (msg.cmd === 'done' || msg.cmd === 'cancel') {
 429      J.closeBanReqFrame();
 430    }
 431  };
 432  
 433  /**
 434   * Click handler
 435   */
 436  J.onClick = function(e) {
 437    var t, cmd;
 438  
 439    if ((t = e.target) == document) {
 440      return;
 441    }
 442  
 443    if (cmd = t.getAttribute('data-cmd')) {
 444      switch (cmd) {
 445        case 'at-refresh':
 446          AdminTools.refreshReportCount(true);
 447          break;
 448        case 'delete-post':
 449        case 'delete-image':
 450          J.deletePost(t, (cmd === 'delete-image'));
 451          break;
 452        case 'open-delete-prompt':
 453          J.openDeletePrompt(t);
 454          break;
 455        case 'close-delete-prompt':
 456          J.closeDeletePrompt();
 457          break;
 458        case 'open-banreq-prompt':
 459          if (Config.inlinePopups) {
 460            J.openBanReqFrame(t);
 461          }
 462          else {
 463            J.openBanReqWindow(t);
 464          }
 465          break;
 466        case 'at-msg':
 467          AdminTools.resetMsgCount();
 468          break;
 469        case 'toggle-file-spoiler':
 470          J.setFileSpoiler(t);
 471          break;
 472        
 473        case 'prompt-spoiler':
 474          if (confirm('Toggle spoiler?')) {
 475            J.setFileSpoiler(t);
 476          }
 477          break;
 478        
 479        case 'boardlist-open':
 480          J.openBoardList();
 481          break;
 482        case 'boardlist-close':
 483          J.closeBoardList();
 484          break;
 485        case 'boardlist-save':
 486          J.saveBoardList();
 487          J.closeBoardList();
 488          break;
 489      }
 490    }
 491  };
 492  
 493  J.onScroll = function() {
 494    var end;
 495    
 496    while (J.nextChunk.offsetTop < (document.documentElement.clientHeight + window.scrollY)) {
 497      end = J.nextChunkIndex + J.chunkSize;
 498      if (end >= J.postCount) {
 499        J.parseRange(J.nextChunkIndex, J.postCount);
 500        window.removeEventListener('scroll', J.onScroll, false);
 501        return false;
 502      }
 503      else {
 504        J.parseRange(J.nextChunkIndex, end);
 505      }
 506    }
 507  
 508    return true;
 509  };
 510  
 511  J.parseRange = function(start, end) {
 512    var i, j, posts;
 513  
 514    posts = document.getElementById('t' + Main.tid).getElementsByClassName('postInfo');
 515    
 516    for (i = start; i < end; ++i) {
 517      j = posts[i];
 518      
 519      if (!j) {
 520        break;
 521      }
 522      
 523      J.parsePost(j);
 524    }
 525    
 526    J.nextChunkIndex = i;
 527    J.nextChunk = posts[i];
 528  };
 529  
 530  J.onParsingDone = function(e) {
 531    var i, tid, offset, limit, posts;
 532    
 533    if (Config.useIconButtons) {
 534      if (e) {
 535        tid = e.detail.threadId;
 536        offset = e.detail.offset;
 537        limit = e.detail.limit;
 538        posts = document.getElementById('t' + tid).getElementsByClassName('postInfo');
 539      }
 540      else {
 541        offset = 0;
 542        posts = document.getElementsByClassName('postInfo');
 543        limit = posts.length;
 544      }
 545      
 546      for (i = offset; i < limit; ++i) {
 547        J.parsePost(posts[i]);
 548      }
 549    }
 550  };
 551  
 552  J.onPostMenuReady = function(e) {
 553    var el, pid, menu, flag;
 554    
 555    pid = e.detail.postId;
 556    menu = e.detail.node;
 557    
 558    el = document.createElement('li');
 559    el.className = 'dd-admin';
 560    el.setAttribute('data-cmd', 'open-delete-prompt');
 561    el.setAttribute('data-id', pid);
 562    el.textContent = 'Delete';
 563    menu.appendChild(el);
 564    
 565    if (window.spoilers && (el = $.id('fT' + pid))) {
 566      flag = $.cls('imgspoiler', el.parentNode)[0] ? 0 : 1;
 567      el = document.createElement('li');
 568      el.className = 'dd-admin';
 569      el.setAttribute('data-cmd', 'toggle-file-spoiler');
 570      el.setAttribute('data-id', pid);
 571      el.setAttribute('data-flag', flag);
 572      el.textContent = (flag ? 'Set' : 'Unset') + ' Spoiler';
 573      menu.appendChild(el);
 574    }
 575    
 576    if (window.thread_archived) {
 577      return;
 578    }
 579    
 580    el = document.createElement('li');
 581    el.className = 'dd-admin';
 582    el.setAttribute('data-cmd', 'open-banreq-prompt');
 583    el.setAttribute('data-id', pid);
 584    el.textContent = 'Ban request';
 585    menu.appendChild(el);
 586  };
 587  
 588  J.parsePost = function(postInfo) {
 589    var pid, html, cnt, tail;
 590  
 591    pid = postInfo.id.slice(2);
 592    
 593    html = '<img class="extButton" alt="X" data-cmd="open-delete-prompt" data-id="'
 594      + pid + '" src="' + Main.icons.cross
 595      + '" title="Delete">';
 596    
 597    if (window.spoilers && (el = $.id('fT' + pid))) {
 598      html += '<img class="extButton" alt="S" data-cmd="prompt-spoiler" data-id="'
 599      + pid + '" src="' + J.icons.spoiler
 600      + '" title="Toggle Spoiler">';
 601    }
 602    
 603    if (!window.thread_archived) {
 604      html += '<img class="extButton" alt="B" data-cmd="open-banreq-prompt" data-id="'
 605        + pid + '" src="' + J.icons.ban
 606        + '" title="Ban Request">';
 607    }
 608  
 609    cnt = document.createElement('div');
 610    cnt.className = 'extControls';
 611    cnt.innerHTML = html;
 612  
 613    tail = postInfo.getElementsByClassName('postMenuBtn')[0];
 614  
 615    postInfo.insertBefore(cnt, tail);
 616  };
 617  
 618  J.displayJCount = function(jLink, jLinkBot, no, delta) {
 619    var msg;
 620    
 621    $.addClass(jLink, 'j-newposts');
 622    $.addClass(jLinkBot, 'j-newposts');
 623    jLink.setAttribute('data-no', no);
 624    jLinkBot.setAttribute('data-no', no);
 625    jLink.textContent = jLinkBot.textContent = 'j +' + delta;
 626    
 627    msg = delta + ' new post' + (delta > 1 ? 's' : '');
 628    
 629    Main.addTooltip(jLink, msg, 'j-tooltip');
 630    Main.addTooltip(jLinkBot, msg, 'j-tooltip-bot');
 631  };
 632  
 633  J.refreshJCount = function() {
 634    var stored, jLink, jLinkBot, xhr;
 635    
 636    jLink = $.id('j-link');
 637    jLinkBot = $.id('j-link-bot');
 638    
 639    if (!jLink || !jLinkBot) {
 640      return;
 641    }
 642    
 643    jLink = jLink.firstElementChild;
 644    jLinkBot = jLinkBot.firstElementChild;
 645    
 646    if (stored = localStorage.getItem('4chan-j-count')) {
 647      stored = JSON.parse(stored);
 648    }
 649    
 650    if (!stored || (Date.now() - stored.time) >= 10000) {
 651      xhr = new XMLHttpRequest();
 652      xhr.open('GET', 'https://sys.4chan.org/j/1mcQTXbjW5WO.php?&' + Date.now());
 653      xhr.withCredentials = true;
 654      xhr.onloadend = function() {
 655        var data, obj, delta;
 656        if (this.status == 200 || this.status == 304) {
 657          data = JSON.parse(this.responseText);
 658          if (!stored || Main.board == 'j') {
 659            obj = { time: Date.now(), no: data.no };
 660          }
 661          else if (data.no > stored.no) {
 662            delta = data.no - stored.no;
 663            J.displayJCount(jLink, jLinkBot, data.no, delta);
 664            obj = { time: Date.now(), no: stored.no, delta: delta };
 665          }
 666          if (obj) {
 667            localStorage.setItem('4chan-j-count', JSON.stringify(obj));
 668          }
 669        }
 670        else {
 671          console.log('Error: Could not load /j/ post count (Status: ' + this.status + ').');
 672        }
 673      };
 674      xhr.send(null);
 675    }
 676    else if (stored.delta) {
 677      J.displayJCount(jLink, jLinkBot, stored.no, stored.delta);
 678    }
 679  };
 680  
 681  J.clearJCount = function() {
 682    var obj, no, tt, ttbot;
 683    
 684    tt = $.id('j-tooltip');
 685    ttbot = $.id('j-tooltip-bot');
 686    
 687    if (!tt) {
 688      return;
 689    }
 690    
 691    no = this.getAttribute('data-no');
 692    obj = { time: Date.now(), no: no };
 693    localStorage.setItem('4chan-j-count', JSON.stringify(obj));
 694    
 695    tt.parentNode.removeChild(tt);
 696    ttbot.parentNode.removeChild(ttbot);
 697    
 698    setTimeout(function() {
 699      var nodes = $.cls('j-newposts');
 700      if (nodes[0]) {
 701        nodes[0].textContent = 'j';
 702        $.removeClass(nodes[0], 'j-newposts');
 703        nodes[0].textContent = 'j';
 704        $.removeClass(nodes[0], 'j-newposts');
 705      }
 706    }, 10);
 707  };
 708  
 709  J.icons = {
 710    ban: 'ban.png',
 711    spoiler: 's.png'
 712  };
 713  
 714  J.initIcons = function() {
 715    var key, paths, url;
 716  
 717    paths = {
 718      yotsuba_new: 'futaba/',
 719      futaba_new: 'futaba/',
 720      yotsuba_b_new: 'burichan/',
 721      burichan_new: 'burichan/',
 722      tomorrow: 'tomorrow/',
 723      photon: 'photon/'
 724    };
 725  
 726    url = '//s.4cdn.org/image/buttons/' + paths[Main.stylesheet];
 727  
 728    if (window.devicePixelRatio >= 2) {
 729      for (key in J.icons) {
 730        J.icons[key] = J.icons[key].replace('.', '@2x.');
 731      }
 732    }
 733  
 734    for (key in J.icons) {
 735      J.icons[key] = url + J.icons[key];
 736    }
 737  };
 738  
 739  J.initNavLinks = function() {
 740    var el, nav, navbot;
 741  
 742    nav = $.id('navtopright');
 743    navbot = $.id('navbotright');
 744  
 745    // [j] link
 746    el = document.createElement('span');
 747    el.id = 'j-link';
 748    el.innerHTML = '[<a href="https://sys.4chan.org/j/" title="Janitor &amp; Moderator Discussion">j</a>]';
 749    el.firstElementChild.addEventListener('mouseup', J.clearJCount, false);
 750    nav.parentNode.insertBefore(el, nav);
 751  
 752    // [j] bottom link
 753    el = el.cloneNode(true);
 754    el.id = 'j-link-bot';
 755    el.firstElementChild.addEventListener('mouseup', J.clearJCount, false);
 756    navbot.parentNode.insertBefore(el, navbot);
 757    
 758    J.refreshJCount();
 759  };
 760  
 761  J.openBoardList = function() {
 762    var cnt;
 763  
 764    if ($.id('boardList')) {
 765      return;
 766    }
 767  
 768    cnt = document.createElement('div');
 769    cnt.id = 'boardList';
 770    cnt.className = 'UIPanel';
 771    cnt.setAttribute('data-cmd', 'boardlist-close');
 772    cnt.innerHTML = '\
 773  <div class="extPanel reply"><div class="panelHeader">Boards\
 774  <span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="boardlist-close" src="'
 775  + Main.icons.cross + '"></a></span></div>\
 776  <input placeholder="Example: jp tg mu or all" id="boardListBox" type="text" value="'
 777  + (localStorage.getItem('4chan-boardlist') || '') + '">\
 778  <div class="center"><button id="boardListSave" data-cmd="boardlist-save">Save</button></div>\
 779  </td></tr></tfoot></table></div>';
 780  
 781    document.body.appendChild(cnt);
 782    cnt.addEventListener('click', this.onClick, false);
 783  };
 784  
 785  J.saveBoardList = function() {
 786    var input;
 787  
 788    if (input = $.id('boardListBox')) {
 789      localStorage.setItem('4chan-boardlist', input.value);
 790    }
 791  };
 792  
 793  J.closeBoardList = function() {
 794    var cnt;
 795  
 796    if (cnt = $.id('boardList')) {
 797      cnt.removeEventListener('click', this.onClick, false);
 798      document.body.removeChild(cnt);
 799    }
 800  };
 801  
 802  J.setFileSpoiler = function(t) {
 803    var xhr, pid, flag, el;
 804    
 805    pid = t.getAttribute('data-id');
 806    flag = t.getAttribute('data-flag');
 807    
 808    if (!pid) {
 809      return;
 810    }
 811    
 812    el = $.id('f' + pid);
 813    
 814    if (!flag) {
 815      flag = $.cls('imgspoiler', el.parentNode)[0] ? 0 : 1;
 816    }
 817    
 818    if (!el || el.hasAttribute('data-processing')) {
 819      return;
 820    }
 821    
 822    xhr = new XMLHttpRequest();
 823    xhr.open('GET', 'https://sys.' + $L.d(Main.board) + '/' + Main.board
 824      + '/admin.php?admin=spoiler&pid=' + pid + '&flag=' + flag, true);
 825    xhr.withCredentials = true;
 826    xhr.onload = J.onFileSpoilerLoad;
 827    xhr.onerror = J.onFileSpoilerError;
 828    xhr._pid = +pid;
 829    xhr._flag = +flag;
 830    
 831    Feedback.notify('Processing...', null);
 832    
 833    el.setAttribute('data-processing', '1');
 834    
 835    xhr.send(null);
 836  };
 837  
 838  J.onFileSpoilerLoad = function() {
 839    var el, el2;
 840    
 841    Feedback.hideMessage();
 842    
 843    if (this.responseText !== '1') {
 844      if (this.responseText === '-1') {
 845        Feedback.error('You are not logged in');
 846      }
 847      else {
 848        Feedback.error("Couldn't set spoiler flag for post No." + this._pid);
 849      }
 850      
 851      return;
 852    }
 853    
 854    if (!(el = $.id('f' + this._pid))) {
 855      return;
 856    }
 857    
 858    el.removeAttribute('data-processing');
 859    
 860    if (!(el = $.cls('fileThumb', el)[0])) {
 861      return;
 862    }
 863    
 864    if (this._flag) {
 865      $.addClass(el, 'imgspoiler');
 866      
 867      el2 = el.previousElementSibling;
 868      el2.setAttribute('title', el2.firstElementChild.textContent);
 869      
 870      if (!Config.revealSpoilers) {
 871        el = $.tag('img', el)[0];
 872        el.style.width = el.style.height = '100px';
 873        el.src = '//s.4cdn.org/image/spoiler-' + Main.board + '.png';
 874      }
 875    }
 876    else {
 877      if (!Config.revealSpoilers) {
 878        Parser.revealImageSpoiler(el);
 879      }
 880      $.removeClass(el, 'imgspoiler');
 881    }
 882  };
 883  
 884  J.onFileSpoilerError = function() {
 885    var el;
 886    
 887    if (!(el = $.id('f' + this._pid))) {
 888      return;
 889    }
 890    el.removeAttribute('data-processing');
 891    Feedback.error("Couldn't update the spoiler flag for post No." + this.pid);
 892  };
 893  
 894  J.initCatalog = function() {
 895    var storage;
 896    
 897    window.Main = {
 898      board: location.pathname.split(/\//)[1]
 899    };
 900    
 901    window.Main.addTooltip = function(link, message, id) {
 902      var el, pos;
 903      
 904      el = document.createElement('div');
 905      el.className = 'click-me';
 906      if (id) {
 907        el.id = id;
 908      }
 909      el.innerHTML = message || 'Change your settings';
 910      link.parentNode.appendChild(el);
 911      
 912      pos = (link.offsetWidth - el.offsetWidth + link.offsetLeft - el.offsetLeft) / 2;
 913      el.style.marginLeft = pos + 'px';
 914      
 915      return el;
 916    };
 917    
 918    if (J.stylesheet = J.getCookie(window.style_group)) {
 919      J.stylesheet = J.stylesheet.toLowerCase().replace(/ /g, '_');
 920    }
 921    else {
 922      J.stylesheet =
 923        style_group == 'nws_style' ? 'yotsuba_new' : 'yotsuba_b_new';
 924    }
 925    
 926    Main.stylesheet = J.stylesheet;
 927    
 928    J.initIconsCatalog();
 929    
 930    J.addCss(); // fixme
 931    
 932    document.addEventListener('click', J.onClick, false);
 933    
 934    J.runCatalog();
 935  };
 936  
 937  J.runCatalog = function () {
 938    var threads;
 939    //J.addCss(); // fixme
 940    //document.removeEventListener('4chanMainInit', J.runCatalog, false);
 941    
 942    J.initNavLinks();
 943    
 944    if (!FC.hasMobileLayout) {
 945      AdminTools.init();
 946    }
 947    
 948    threads = $.id('threads');
 949    
 950    $.on(threads, 'mouseover', J.onThreadMouseOver);
 951    //$.on(threads, 'mouseout', J.onThreadMouseOut);
 952  };
 953  
 954  J.init = function() {
 955    var ts;
 956    
 957    Config.boardList = true;
 958  
 959    SettingsMenu.options['Janitor'] = {
 960      boardList: [ 'Janitor Boards [<a href="javascript:;" data-cmd="boardlist-open">Select</a>]', 'Select boards to enable janitor buttons on', true ],
 961      useIconButtons: [ 'Use icon buttons', 'Display old-style buttons instead of using drop-down' ],
 962      changeUpdateDelay: [ 'Reduce auto-update delay', 'Reduce the thread updater delay', true ],
 963      fixedAdminToolbox: [ 'Pin Janitor Tools to the page', 'Janitor Tools will scroll with you' ],
 964      inlinePopups: [ 'Inline ban request panel', 'Open ban request panel in browser window, instead of a popup' ],
 965      disableMngExt: [ 'Disable janitor extension', 'Completely disable the janitor extension (overrides any checked boxes)', true ]
 966    };
 967  
 968    if (Config.disableMngExt) {
 969      return;
 970    }
 971    
 972    J.addCss();
 973    
 974    if (Config.useIconButtons) {
 975      J.initIcons();
 976    }
 977    
 978    QR.noCooldown = QR.noCaptcha = true;
 979  
 980    document.addEventListener('click', J.onClick, false);
 981    document.addEventListener('DOMContentLoaded', J.run, false);
 982  };
 983  
 984  J.run = function() {
 985    var boards, posts, nav, el;
 986  
 987    document.removeEventListener('DOMContentLoaded', J.run, false);
 988  
 989    J.initNavLinks();
 990    
 991    if (!Main.hasMobileLayout) {
 992      AdminTools.init();
 993    }
 994    
 995    if (Config.revealSpoilers) {
 996      $.addClass(document.body, 'reveal-img-spoilers');
 997    }
 998    
 999    if (Config.threadUpdater && Main.tid) {
1000      if (Config.changeUpdateDelay) {
1001        ThreadUpdater.delayIdHidden = 3;
1002        ThreadUpdater.delayRange = [ 5, 10, 15, 20, 30, 60 ];
1003        ThreadUpdater.apiUrlFilter = J.apiUrlFilter;
1004      }
1005    }
1006    
1007    boards = localStorage.getItem('4chan-boardlist') || '';
1008    
1009    if (Main.board != 'j' && (boards == 'all' || boards.split(/[, ]+/).indexOf(Main.board) != -1)) {
1010      if (Config.useIconButtons && !Main.hasMobileLayout) {
1011        if (Main.tid) {
1012          posts = document.getElementById('t' + Main.tid).getElementsByClassName('postInfo');
1013          J.postCount = posts.length;
1014          if (J.postCount > J.chunkSize) {
1015            J.nextChunk = posts[0];
1016            window.addEventListener('scroll', J.onScroll, false);
1017            J.onScroll();
1018          }
1019          else {
1020            J.onParsingDone();
1021          }
1022        }
1023        else {
1024          J.onParsingDone();
1025        }
1026        
1027        document.addEventListener('4chanParsingDone', J.onParsingDone, false);
1028      }
1029      
1030      document.addEventListener('4chanPostMenuReady', J.onPostMenuReady, false);
1031    }
1032    
1033    if (nav = $.id('boardSelectMobile')) {
1034      el = document.createElement('option');
1035      el.value = 'j';
1036      el.textContent = '/j/ - Janitors & Moderators';
1037      nav.insertBefore(el, nav.firstElementChild);
1038    }
1039  };
1040  
1041  J.getCookie = function(name) {
1042    var i, c, ca, key;
1043    
1044    key = name + "=";
1045    ca = document.cookie.split(';');
1046    
1047    for (i = 0; c = ca[i]; ++i) {
1048      while (c.charAt(0) == ' ') {
1049        c = c.substring(1, c.length);
1050      }
1051      if (c.indexOf(key) === 0) {
1052        return decodeURIComponent(c.substring(key.length, c.length));
1053      }
1054    }
1055    return null;
1056  };
1057  
1058  J.addCss = function() {
1059    var style, css;
1060    
1061    css = '\
1062  #adminToolbox {\
1063    max-width: 256px;\
1064    display: block;\
1065    position: absolute;\
1066    padding: 3px;\
1067  }\
1068  #adminToolbox h4 {\
1069    font-size: 12px;\
1070    margin: 2px 0 0;\
1071    padding: 0;\
1072    font-weight: normal;\
1073  }\
1074  #adminToolbox li {\
1075    list-style: none;\
1076  }\
1077  #adminToolbox ul {\
1078    padding: 0;\
1079    margin: 2px 0 0 10px;\
1080  }\
1081  #atHeader {\
1082    height: 17px;\
1083    font-weight: bold;\
1084    padding-bottom: 2px;\
1085  }\
1086  #atRefresh {\
1087    margin: -1px 0 0 3px;\
1088  }\
1089  .post-ip {\
1090    margin-left: 5px;\
1091  }\
1092  #delete-prompt > div {\
1093    text-align: center;\
1094  }\
1095  #watchList li:first-child {\
1096    margin-top: 3px;\
1097    padding-top: 2px;\
1098    border-top: 1px solid rgba(0, 0, 0, 0.20);\
1099  }\
1100  .photon #atHeader {\
1101    border-bottom: 1px solid #ccc;\
1102  }\
1103  .yotsuba_new #atHeader {\
1104    border-bottom: 1px solid #d9bfb7;\
1105  }\
1106  .yotsuba_b_new #atHeader {\
1107    border-bottom: 1px solid #b7c5d9;\
1108  }\
1109  .tomorrow #atHeader {\
1110    border-bottom: 1px solid #111;\
1111  }\
1112  #at-illegal {\
1113    color: red;\
1114  }\
1115  #at-msg-cnt {\
1116    display: none;\
1117  }\
1118  .j-newposts {\
1119    font-weight: bold !important;\
1120  }\
1121  #j-link,\
1122  #j-link-bot {\
1123    margin-right: 3px;\
1124    display: inline-block;\
1125    margin-left: 3px;\
1126  }\
1127  #boardList input {\
1128    width: 385px;\
1129    margin: auto;\
1130    display: block;\
1131  }\
1132  #boardListSave {\
1133    margin-top: 5px;\
1134  }\
1135  #banReqClose {\
1136    float: right;\
1137  }\
1138  #banReq iframe {\
1139    overflow: hidden;\
1140  }\
1141  #banReq {\
1142    display: block;\
1143    position: fixed;\
1144    padding: 2px;\
1145    font-size: 10pt;\
1146    height: 250px;\
1147  }\
1148  #banReqHeader {\
1149    text-align: center;\
1150    margin-bottom: 1px;\
1151    padding: 0;\
1152    height: 18px;\
1153    line-height: 18px;\
1154  }\
1155  #captchaFormPart {\
1156    display: none;\
1157  }\
1158  .mobileExtControls {\
1159    float: right;\
1160    font-size: 11px;\
1161    margin-bottom: 3px;\
1162  }\
1163  .ws .mobileExtControls {\
1164    color: #34345C;\
1165  }\
1166  .nws .mobileExtControls {\
1167    color: #0000EE;\
1168  }\
1169  .reply .mobileExtControls {\
1170    margin-right: 5px;\
1171  }\
1172  .mobileExtControls span {\
1173    margin-left: 10px;\
1174  }\
1175  .mobileExtControls span:after {\
1176    content: "]";\
1177  }\
1178  .mobileExtControls span:before {\
1179    content: "[";\
1180  }\
1181  .nws .mobileExtControls span:after {\
1182    color: #800000;\
1183  }\
1184  .nws .mobileExtControls span:before {\
1185    color: #800000;\
1186  }\
1187  .ws .mobileExtControls span:after {\
1188    color: #000;\
1189  }\
1190  .ws .mobileExtControls span:before {\
1191    color: #000;\
1192  }\
1193  .m-dark .mobileExtControls,\
1194  .m-dark .mobileExtControls span:after,\
1195  .m-dark .mobileExtControls span:before {\
1196    color: #707070 !important;\
1197  }\
1198  .dd-admin {\
1199    text-indent: 5px;\
1200  }\
1201  .dd-admin:before {\
1202    color: #FF0000;\
1203    content: "•";\
1204    left: -3px;\
1205    position: absolute;\
1206  }\
1207  .extPanel {\
1208    border: 1px solid rgba(0, 0, 0, 0.2);\
1209  }\
1210  .extPanel img.pointer { width: 18px; height: 18px }\
1211  .drag {\
1212    -moz-user-select: none !important;\
1213    cursor: move !important;\
1214  }\
1215  .reveal-img-spoilers .imgspoiler::before {\
1216    content: " ";\
1217    width:0.75em;\
1218    height:0.75em;\
1219    border-radius: 0.5em;\
1220    position: absolute;\
1221    display: block;\
1222    background: red;\
1223    margin-top: 1px;\
1224    margin-left: 1px;\
1225    pointer-events: none;\
1226  }\
1227  .reveal-img-spoilers.is_catalog .imgspoiler::before { margin-top: 4px; margin-left: 12px;}\
1228  .reveal-img-spoilers .imgspoiler:hover::before { background: #fff; }';
1229  
1230    style = document.createElement('style');
1231    style.setAttribute('type', 'text/css');
1232    style.textContent = css;
1233    document.head.appendChild(style);
1234  };
1235  
1236  if (/https?:\/\/boards\.(?:4chan|4channel)\.org\/[a-z0-9]+\/catalog($|#.*$)/.test(location.href)) {
1237    J.initCatalog();
1238  }
1239  else {
1240    J.init();
1241    //J.run();
1242  }
1243  
1244  })();