Saturday, January 22, 2011

4chan X


// ==UserScript==
// @name           4chan x
// @namespace      aeosynth
// @description    Adds various features.
// @version        1.23.1
// @copyright      2009-2011 James Campos <james.r.campos@gmail.com>
// @license        MIT; http://en.wikipedia.org/wiki/Mit_license
// @include        http://boards.4chan.org/*
// @include        http://sys.4chan.org/*
// ==/UserScript==

/* LICENSE
 *
 * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/* HACKING
 *
 * 4chan x is written in CoffeeScript[1], and developed on github[2].
 *
 * [1]: http://jashkenas.github.com/coffee-script/
 * [2]: http://github.com/aeosynth/4chan-x
 */

/* CONTRIBUTORS
 *
 * Ongpot - sfw favicon
 * thisisanon - nsfw + 404 favicons
 * Anonymous - empty favicon
 * Seiba - chrome quick reply focusing
 * herpaderpderp - recaptcha fixes
 * wakimoko - recaptcha tab order http://userscripts.org/scripts/show/82657
 * All the people who've taken the time to write bug reports.
 *
 * Thank you.
 */

(function() {
  var $, $$, DAY, Dialog, a, arr, as, autoWatch, autohide, b, board, callback, changeCheckbox, changeValue, clearHidden, closeQR, config, cooldown, cutoff, d, delform, down, editSauce, el, expand, expandComment, expandThread, formSubmit, g, getConfig, getThread, getTime, hide, hideReply, hideThread, href, html, i, id, iframe, iframeLoad, imageClick, imageExpand, imageExpandClick, imageResize, imageThumb, imageToggle, imageType, imageTypeChange, img, inAfter, inBefore, input, inputs, keyModeInsert, keyModeNormal, keydown, keypress, l1, lastChecked, m, mv, n, navbotr, navtopr, nodeInserted, now, omitted, onloadComment, onloadThread, option, options, parseResponse, pathname, qrListener, qrText, quickReply, recaptcha, recaptchaListener, recaptchaReload, redirect, replace, replyNav, report, request, rm, scroll, scrollThread, show, showReply, showThread, slice, span, src, start, stopPropagation, temp, text, textContent, thread, threadF, threads, tn, tzOffset, up, updateAuto, updateCallback, updateFavicon, updateInterval, updateNow, updateTime, updateTitle, updateVerbose, updaterMake, watch, watchX, watcher, watcherUpdate, x, zeroPad, _, _base, _i, _j, _k, _l, _len, _len2, _len3, _len4, _len5, _len6, _len7, _m, _n, _ref, _ref2, _ref3, _ref4, _ref5, _ref6;
  var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __slice = Array.prototype.slice;
  config = {
    '404 Redirect': [true, 'Redirect dead threads'],
    'Anonymize': [false, 'Make everybody anonymous'],
    'Auto Watch': [true, 'Automatically watch threads that you start (Firefox only)'],
    'Comment Expansion': [true, 'Expand too long comments'],
    'Image Expansion': [true, 'Expand images'],
    'Keybinds': [false, 'Binds actions to keys'],
    'Localize Time': [true, 'Show times based on your timezone'],
    'Persistent QR': [false, 'Quick reply won\'t disappear after posting. Only in replies.'],
    'Post in Title': [true, 'Show the op\'s post in the tab title'],
    'Quick Reply': [true, 'Reply without leaving the page'],
    'Quick Report': [true, 'Add quick report buttons'],
    'Reply Hiding': [true, 'Hide single replies'],
    'Reply Navigation': [true, 'Navigate to the beginning / end of a thread'],
    'Restore IDs': [true, 'Check \'em'],
    'Sauce': [true, 'Add sauce to images'],
    'Show Stubs': [true, 'Of hidden threads / replies'],
    'Thread Expansion': [true, 'View all replies'],
    'Thread Hiding': [true, 'Hide entire threads'],
    'Thread Navigation': [true, 'Navigate to previous / next thread'],
    'Thread Updater': [true, 'Update threads'],
    'Thread Watcher': [true, 'Bookmark threads'],
    'Unread Count': [true, 'Show unread post count in tab title']
  };
  if (typeof GM_deleteValue === 'undefined') {
    window.GM_setValue = function(name, value) {
      value = (typeof value)[0] + value;
      return localStorage.setItem(name, value);
    };
    window.GM_getValue = function(name, defaultValue) {
      var type, value;
      if (!(value = localStorage.getItem(name))) {
        return defaultValue;
      }
      type = value[0];
      value = value.slice(1);
      switch (type) {
        case 'b':
          return value === 'true';
        case 'n':
          return Number(value);
        default:
          return value;
      }
    };
    window.GM_addStyle = function(css) {
      var style;
      style = document.createElement('style');
      style.type = 'text/css';
      style.textContent = css;
      return document.getElementsByTagName('head')[0].appendChild(style);
    };
    window.GM_openInTab = function(url) {
      return window.open(url, "_blank");
    };
  }
  GM_addStyle('\
    div.dialog {\
        border: 1px solid;\
    }\
    div.dialog > div.move {\
        cursor: move;\
    }\
    label, a {\
        cursor: pointer;\
    }\
');
  Dialog = (function() {
    function Dialog(id, position, html) {
      this.moveEnd = __bind(this.moveEnd, this);;
      this.moveMove = __bind(this.moveMove, this);;
      this.move = __bind(this.move, this);;      var el, left, top, _ref;
      this.el = el = document.createElement('div');
      el.className = 'reply dialog';
      el.innerHTML = html;
      el.id = id;
      switch (position) {
        case 'topleft':
          left = '0px';
          top = '0px';
          break;
        case 'topright':
          left = null;
          top = '0px';
          break;
        case 'bottomleft':
          left = '0px';
          top = null;
          break;
        case 'bottomright':
          left = null;
          top = null;
          break;
        case 'center':
          left = '50%';
          top = '25%';
      }
      left = GM_getValue("" + id + "Left", left);
      top = GM_getValue("" + id + "Top", top);
      if (left) {
        el.style.left = left;
      } else {
        el.style.right = '0px';
      }
      if (top) {
        el.style.top = top;
      } else {
        el.style.bottom = '0px';
      }
      $('div.move', el).addEventListener('mousedown', this.move, true);
      if ((_ref = $('div.move a[name=close]', el)) != null) {
        _ref.addEventListener('click', (function() {
          return rm(el);
        }), true);
      }
    }
    Dialog.prototype.move = function(e) {
      var el;
      el = this.el;
      this.dx = e.clientX - el.offsetLeft;
      this.dy = e.clientY - el.offsetTop;
      this.width = document.body.clientWidth - el.offsetWidth;
      this.height = document.body.clientHeight - el.offsetHeight;
      document.addEventListener('mousemove', this.moveMove, true);
      return document.addEventListener('mouseup', this.moveEnd, true);
    };
    Dialog.prototype.moveMove = function(e) {
      var bottom, el, left, right, top;
      el = this.el;
      left = e.clientX - this.dx;
      if (left < 20) {
        left = '0px';
      } else if (this.width - left < 20) {
        left = '';
      }
      right = left ? '' : '0px';
      el.style.left = left;
      el.style.right = right;
      top = e.clientY - this.dy;
      if (top < 20) {
        top = '0px';
      } else if (this.height - top < 20) {
        top = '';
      }
      bottom = top ? '' : '0px';
      el.style.top = top;
      return el.style.bottom = bottom;
    };
    Dialog.prototype.moveEnd = function() {
      var el, id;
      document.removeEventListener('mousemove', this.moveMove, true);
      document.removeEventListener('mouseup', this.moveEnd, true);
      el = this.el;
      id = el.id;
      GM_setValue("" + id + "Left", el.style.left);
      return GM_setValue("" + id + "Top", el.style.top);
    };
    return Dialog;
  })();
  d = document;
  g = null;
  $ = function(selector, root) {
    if (root == null) {
      root = d.body;
    }
    return root.querySelector(selector);
  };
  $$ = function(selector, root) {
    var node, result, _i, _len, _results;
    if (root == null) {
      root = d.body;
    }
    result = root.querySelectorAll(selector);
    _results = [];
    for (_i = 0, _len = result.length; _i < _len; _i++) {
      node = result[_i];
      _results.push(node);
    }
    return _results;
  };
  mv = function() {
    var child, children, parent, _i, _j, _len, _results;
    children = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), parent = arguments[_i++];
    _results = [];
    for (_j = 0, _len = children.length; _j < _len; _j++) {
      child = children[_j];
      _results.push(parent.appendChild(child));
    }
    return _results;
  };
  getConfig = function(name) {
    return GM_getValue(name, config[name][0]);
  };
  getTime = function() {
    return Math.floor(new Date().getTime() / 1000);
  };
  hide = function(el) {
    return el.style.display = 'none';
  };
  inAfter = function(root, el) {
    return root.parentNode.insertBefore(el, root.nextSibling);
  };
  inBefore = function(root, el) {
    return root.parentNode.insertBefore(el, root);
  };
  m = function(el, props) {
    var event, funk, key, l, val;
    if (l = props.listener) {
      delete props.listener;
      event = l[0], funk = l[1];
      el.addEventListener(event, funk, true);
    }
    for (key in props) {
      val = props[key];
      el[key] = val;
    }
    return el;
  };
  n = function(tag, props) {
    var el;
    el = d.createElement(tag);
    if (props) {
      m(el, props);
    }
    return el;
  };
  rm = function(el) {
    return el.parentNode.removeChild(el);
  };
  replace = function(root, el) {
    return root.parentNode.replaceChild(el, root);
  };
  show = function(el) {
    return el.style.display = '';
  };
  slice = function(arr, id) {
    var i, l, _results;
    i = 0;
    l = arr.length;
    _results = [];
    while (i < l) {
      if (id === arr[i].id) {
        arr.splice(i, 1);
        return arr;
      }
      _results.push(i++);
    }
    return _results;
  };
  tn = function(s) {
    return d.createTextNode(s);
  };
  x = function(path, root) {
    if (root == null) {
      root = d.body;
    }
    return d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
  };
  zeroPad = function(n) {
    if (n < 10) {
      return '0' + n;
    } else {
      return n;
    }
  };
  autohide = function() {
    var klass, qr;
    qr = $('#qr');
    klass = qr.className;
    if (klass.indexOf('auto') === -1) {
      klass += ' auto';
    } else {
      klass = klass.replace(' auto', '');
    }
    return qr.className = klass;
  };
  autoWatch = function() {
    var autoText;
    autoText = $('textarea', this).value.slice(0, 25);
    return GM_setValue('autoText', "/" + g.BOARD + "/ - " + autoText);
  };
  closeQR = function() {
    var div;
    div = this.parentNode.parentNode;
    return rm(div);
  };
  clearHidden = function() {
    GM_deleteValue("hiddenReplies/" + g.BOARD + "/");
    GM_deleteValue("hiddenThreads/" + g.BOARD + "/");
    this.value = "hidden: 0";
    g.hiddenReplies = [];
    return g.hiddenThreads = [];
  };
  cooldown = function() {
    var auto, seconds, submit;
    submit = $('#qr input[type=submit]');
    seconds = parseInt(submit.value);
    if (seconds === 0) {
      submit.disabled = false;
      submit.value = 'Submit';
      auto = submit.previousSibling.lastChild;
      if (auto.checked) {
        return $('#qr form').submit();
      }
    } else {
      submit.value = seconds - 1;
      return window.setTimeout(cooldown, 1000);
    }
  };
  editSauce = function() {
    var ta;
    ta = $('#options textarea');
    if (ta.style.display) {
      return show(ta);
    } else {
      return hide(ta);
    }
  };
  expandComment = function(e) {
    var a, href, r;
    e.preventDefault();
    a = this;
    href = a.getAttribute('href');
    r = new XMLHttpRequest();
    r.onload = function() {
      return onloadComment(this.responseText, a, href);
    };
    r.open('GET', href, true);
    r.send();
    return g.xhrs.push({
      r: r,
      id: href.match(/\d+/)[0]
    });
  };
  expandThread = function() {
    var id, num, prev, r, span, table, xhr, _i, _len, _ref;
    id = x('preceding-sibling::input[1]', this).name;
    span = this;
    if (span.textContent[0] === '-') {
      num = board === 'b' ? 3 : 5;
      table = x("following::br[@clear][1]/preceding::table[" + num + "]", span);
      while ((prev = table.previousSibling) && (prev.nodeName === 'TABLE')) {
        rm(prev);
      }
      span.textContent = span.textContent.replace('-', '+');
      return;
    }
    span.textContent = span.textContent.replace('+', 'X Loading...');
    _ref = g.xhrs;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      xhr = _ref[_i];
      if (xhr.id === id) {
        onloadThread(xhr.r.responseText, span);
        return;
      }
    }
    r = new XMLHttpRequest();
    r.onload = function() {
      return onloadThread(this.responseText, span);
    };
    r.open('GET', "res/" + id, true);
    r.send();
    return g.xhrs.push({
      r: r,
      id: id
    });
  };
  getThread = function() {
    var bottom, i, thread, threads, _len;
    threads = $$('div.thread');
    for (i = 0, _len = threads.length; i < _len; i++) {
      thread = threads[i];
      bottom = thread.getBoundingClientRect().bottom;
      if (bottom > 0) {
        return [thread, i];
      }
    }
  };
  formSubmit = function(e) {
    var recaptcha, span, _ref;
    if (span = this.nextSibling) {
      rm(span);
    }
    recaptcha = $('input[name=recaptcha_response_field]', this);
    if (recaptcha.value) {
      return (_ref = $('#qr input[title=autohide]:not(:checked)')) != null ? _ref.click() : void 0;
    } else {
      e.preventDefault();
      span = n('span', {
        className: 'error',
        textContent: 'You forgot to type in the verification.'
      });
      mv(span, this.parentNode);
      alert('You forgot to type in the verification.');
      return recaptcha.focus();
    }
  };
  hideReply = function(reply) {
    var a, div, name, p, table, trip, _ref;
    if (p = this.parentNode) {
      reply = p.nextSibling;
      g.hiddenReplies.push({
        id: reply.id,
        timestamp: getTime()
      });
      GM_setValue("hiddenReplies/" + g.BOARD + "/", JSON.stringify(g.hiddenReplies));
    }
    name = $('span.commentpostername', reply).textContent;
    trip = ((_ref = $('span.postertrip', reply)) != null ? _ref.textContent : void 0) || '';
    table = x('ancestor::table', reply);
    hide(table);
    if (getConfig('Show Stubs')) {
      a = n('a', {
        textContent: "[ + ] " + name + " " + trip,
        className: 'pointer',
        listener: ['click', showReply]
      });
      div = n('div');
      mv(a, div);
      return inBefore(table, div);
    }
  };
  hideThread = function(div) {
    var a, name, num, p, span, text, trip, _ref;
    if (p = this.parentNode) {
      div = p;
      g.hiddenThreads.push({
        id: div.id,
        timestamp: getTime()
      });
      GM_setValue("hiddenThreads/" + g.BOARD + "/", JSON.stringify(g.hiddenThreads));
    }
    hide(div);
    if (getConfig('Show Stubs')) {
      if (span = $('.omittedposts', div)) {
        num = Number(span.textContent.match(/\d+/)[0]);
      } else {
        num = 0;
      }
      num += $$('table', div).length;
      text = num === 1 ? "1 reply" : "" + num + " replies";
      name = $('span.postername', div).textContent;
      trip = ((_ref = $('span.postername + span.postertrip', div)) != null ? _ref.textContent : void 0) || '';
      a = n('a', {
        textContent: "[ + ] " + name + trip + " (" + text + ")",
        className: 'pointer',
        listener: ['click', showThread]
      });
      return inBefore(div, a);
    }
  };
  iframeLoad = function() {
    var auto, error, qr, span, submit, _ref, _ref2;
    if (g.iframe = !g.iframe) {
      return;
    }
    $('iframe').src = 'about:blank';
    qr = $('#qr');
    if (error = GM_getValue('error')) {
      span = n('span', {
        textContent: error,
        className: 'error'
      });
      mv(span, qr);
      if ((_ref = $('input[title=autohide]:checked', qr)) != null) {
        _ref.click();
      }
    } else if (g.REPLY && getConfig('Persistent QR')) {
      $('textarea', qr).value = '';
      $('input[name=recaptcha_response_field]', qr).value = '';
      submit = $('input[type=submit]', qr);
      submit.value = 30;
      submit.disabled = true;
      window.setTimeout(cooldown, 1000);
      auto = submit.previousSibling.lastChild;
      if (auto.checked) {
        if ((_ref2 = $('input[title=autohide]:checked', qr)) != null) {
          _ref2.click();
        }
      }
    } else {
      rm(qr);
    }
    return recaptchaReload();
  };
  imageClick = function(e) {
    if (e.shiftKey || e.altKey || e.ctrlKey) {
      return;
    }
    e.preventDefault();
    return imageToggle(this);
  };
  imageToggle = function(image) {
    var ch, cw, imageType, thumb;
    thumb = image.firstChild;
    cw = d.body.clientWidth;
    ch = d.body.clientHeight;
    imageType = $("#imageType").value;
    if (thumb.className === 'hide') {
      return imageThumb(thumb);
    } else {
      return imageExpand(thumb, cw, ch, imageType);
    }
  };
  imageTypeChange = function() {
    var ch, cw, image, imageType, images, _i, _len, _results;
    images = $$('img[md5] + img');
    cw = d.body.clientWidth;
    ch = d.body.clientHeight;
    imageType = this.value;
    _results = [];
    for (_i = 0, _len = images.length; _i < _len; _i++) {
      image = images[_i];
      _results.push(imageResize(cw, ch, imageType, image));
    }
    return _results;
  };
  imageExpandClick = function() {
    var ch, cw, imageType, thumb, thumbs, _i, _j, _len, _len2, _results, _results2;
    thumbs = $$('img[md5]');
    g.expand = this.checked;
    cw = d.body.clientWidth;
    ch = d.body.clientHeight;
    imageType = $("#imageType").value;
    if (this.checked) {
      _results = [];
      for (_i = 0, _len = thumbs.length; _i < _len; _i++) {
        thumb = thumbs[_i];
        _results.push(thumb.className !== 'hide' ? imageExpand(thumb, cw, ch, imageType) : void 0);
      }
      return _results;
    } else {
      _results2 = [];
      for (_j = 0, _len2 = thumbs.length; _j < _len2; _j++) {
        thumb = thumbs[_j];
        _results2.push(thumb.className === 'hide' ? imageThumb(thumb) : void 0);
      }
      return _results2;
    }
  };
  imageExpand = function(thumb, cw, ch, imageType) {
    var image, link;
    thumb.className = 'hide';
    link = thumb.parentNode;
    image = n('img', {
      src: link.href
    });
    link.appendChild(image);
    return imageResize(cw, ch, imageType, image);
  };
  imageResize = function(cw, ch, imageType, image) {
    var ih, iw, ratio, _, _ref;
    _ref = x("preceding::span[@class][1]/text()[2]", image).textContent.match(/(\d+)x(\d+)/), _ = _ref[0], iw = _ref[1], ih = _ref[2];
    iw = Number(iw);
    ih = Number(ih);
    switch (imageType) {
      case 'full':
        image.removeAttribute('style');
        return;
      case 'fit width':
        if (iw > cw) {
          image.style.width = '100%';
          image.style.margin = '0px';
        }
        break;
      case 'fit screen':
        ratio = Math.min(cw / iw, ch / ih);
        if (ratio < 1) {
          image.style.width = Math.floor(ratio * iw);
          return image.style.margin = '0px';
        }
    }
  };
  imageThumb = function(thumb) {
    thumb.className = '';
    return rm(thumb.nextSibling);
  };
  keydown = function(e) {
    var kc;
    kc = e.keyCode;
    g.keyCode = kc;
    return g.char = String.fromCharCode(kc);
  };
  keypress = function(e) {
    var _ref;
    if ((_ref = d.activeElement.nodeName) === 'TEXTAREA' || _ref === 'INPUT') {
      return keyModeInsert(e);
    } else {
      return keyModeNormal(e);
    }
  };
  keyModeInsert = function(e) {
    var char, kc, range, selEnd, selStart, ta, valEnd, valMid, valStart, value;
    kc = g.keyCode;
    char = g.char;
    if (kc === 27) {
      rm($('#qr'));
      return e.preventDefault();
    } else if (e.ctrlKey && char === "S") {
      ta = d.activeElement;
      if (ta.nodeName !== 'TEXTAREA') {
        return;
      }
      value = ta.value;
      selStart = ta.selectionStart;
      selEnd = ta.selectionEnd;
      valStart = value.slice(0, selStart) + '[spoiler]';
      valMid = value.slice(selStart, selEnd);
      valEnd = '[/spoiler]' + value.slice(selEnd);
      ta.value = valStart + valMid + valEnd;
      range = valStart.length + valMid.length;
      ta.setSelectionRange(range, range);
      return e.preventDefault();
    }
  };
  keyModeNormal = function(e) {
    var bot, char, hash, height, href, image, next, prev, qrLink, rect, replies, reply, root, sign, td, thread, top, watchButton, _i, _j, _len, _len2;
    if (e.ctrlKey || e.altKey) {
      return;
    }
    char = g.char;
    hash = location.hash;
    switch (char) {
      case "0":
        return location.pathname = "/" + g.BOARD;
      case "G":
        if (e.shiftKey) {
          return window.scrollTo(0, 99999);
        } else {
          window.scrollTo(0, 0);
          return location.hash = '';
        }
      case "I":
        if (g.REPLY) {
          if (!(qrLink = $('td.replyhl span[id] a:not(:first-child)'))) {
            qrLink = $("span[id^=nothread] a:not(:first-child)");
          }
        } else {
          thread = getThread()[0];
          if (!(qrLink = $('td.replyhl span[id] a:not(:first-child)', thread))) {
            qrLink = $("span#nothread" + thread.id + " a:not(:first-child)", thread);
          }
        }
        if (e.shiftKey) {
          return quickReply(qrLink);
        } else {
          return quickReply(qrLink, qrText(qrLink));
        }
      case "J":
        if (e.shiftKey) {
          if (!g.REPLY) {
            root = getThread()[0];
          }
          if (td = $('td.replyhl', root)) {
            td.className = 'reply';
            rect = td.getBoundingClientRect();
            if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
              next = x('following::td[@class="reply"]', td);
              rect = next.getBoundingClientRect();
              if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
                next.className = 'replyhl';
              }
              return;
            }
          }
          replies = $$('td.reply', root);
          for (_i = 0, _len = replies.length; _i < _len; _i++) {
            reply = replies[_i];
            top = reply.getBoundingClientRect().top;
            if (top > 0) {
              reply.className = 'replyhl';
              break;
            }
          }
        }
        break;
      case "K":
        if (e.shiftKey) {
          if (!g.REPLY) {
            root = getThread()[0];
          }
          if (td = $('td.replyhl', root)) {
            td.className = 'reply';
            rect = td.getBoundingClientRect();
            if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
              prev = x('preceding::td[@class="reply"][1]', td);
              rect = prev.getBoundingClientRect();
              if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
                prev.className = 'replyhl';
              }
              return;
            }
          }
          replies = $$('td.reply', root);
          replies.reverse();
          height = d.body.clientHeight;
          for (_j = 0, _len2 = replies.length; _j < _len2; _j++) {
            reply = replies[_j];
            bot = reply.getBoundingClientRect().bottom;
            if (bot < height) {
              reply.className = 'replyhl';
              break;
            }
          }
        }
        break;
      case "M":
        if (e.shiftKey) {
          return $("#imageExpand").click();
        } else {
          if (!g.REPLY) {
            root = getThread()[0];
          }
          if (!(image = $('td.replyhl span.filesize ~ a[target]', root))) {
            image = $('span.filesize ~ a[target]', root);
          }
          return imageToggle(image);
        }
      case "N":
        sign = e.shiftKey ? -1 : 1;
        return scrollThread(sign);
      case "O":
        href = $("" + hash + " ~ span[id] a:last-of-type").href;
        if (e.shiftKey) {
          return location.href = href;
        } else {
          return GM_openInTab(href);
        }
      case "U":
        return updateNow();
      case "W":
        root = g.REPLY ? null : getThread()[0];
        watchButton = $("span.filesize ~ img", root);
        return watch.call(watchButton);
    }
  };
  nodeInserted = function(e) {
    var callback, qr, target, _i, _len, _ref, _results;
    target = e.target;
    if (target.nodeName === 'TABLE') {
      _ref = g.callbacks;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        callback = _ref[_i];
        _results.push(callback(target));
      }
      return _results;
    } else if (target.id === 'recaptcha_challenge_field' && (qr = $('#qr'))) {
      $('#recaptcha_image img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + target.value;
      return $('#recaptcha_challenge_field', qr).value = target.value;
    }
  };
  onloadComment = function(responseText, a, href) {
    var bq, html, id, op, opbq, replies, reply, _, _i, _len, _ref, _ref2;
    _ref = href.match(/(\d+)#(\d+)/), _ = _ref[0], op = _ref[1], id = _ref[2];
    _ref2 = parseResponse(responseText), replies = _ref2[0], opbq = _ref2[1];
    if (id === op) {
      html = opbq.innerHTML;
    } else {
      for (_i = 0, _len = replies.length; _i < _len; _i++) {
        reply = replies[_i];
        if (reply.id === id) {
          html = $('blockquote', reply).innerHTML;
        }
      }
    }
    bq = x('ancestor::blockquote', a);
    return bq.innerHTML = html;
  };
  onloadThread = function(responseText, span) {
    var div, next, opbq, replies, reply, _i, _j, _len, _len2, _ref, _results, _results2;
    _ref = parseResponse(responseText), replies = _ref[0], opbq = _ref[1];
    span.textContent = span.textContent.replace('X Loading...', '- ');
    span.previousSibling.innerHTML = opbq.innerHTML;
    while ((next = span.nextSibling) && !next.clear) {
      rm(next);
    }
    if (next) {
      _results = [];
      for (_i = 0, _len = replies.length; _i < _len; _i++) {
        reply = replies[_i];
        _results.push(inBefore(next, x('ancestor::table', reply)));
      }
      return _results;
    } else {
      div = span.parentNode;
      _results2 = [];
      for (_j = 0, _len2 = replies.length; _j < _len2; _j++) {
        reply = replies[_j];
        _results2.push(mv(x('ancestor::table', reply), div));
      }
      return _results2;
    }
  };
  changeCheckbox = function() {
    return GM_setValue(this.name, this.checked);
  };
  changeValue = function() {
    return GM_setValue(this.name, this.value);
  };
  options = function() {
    var checked, description, div, hiddenNum, html, input, option, value, _i, _len, _ref;
    if (div = $('#options')) {
      rm(div);
      return;
    }
    hiddenNum = g.hiddenReplies.length + g.hiddenThreads.length;
    html = '<div class="move">Options <a name=close>X</a></div><div>';
    for (option in config) {
      value = config[option];
      description = value[1];
      checked = getConfig(option) ? "checked" : "";
      html += "<label title=\"" + description + "\">" + option + "<input " + checked + " name=\"" + option + "\" type=\"checkbox\"></label><br>";
    }
    html += "<div><a class=sauce>Flavors</a></div>";
    html += "<div><textarea style=\"display: none;\" name=flavors>" + (GM_getValue('flavors', g.flavors)) + "</textarea></div>";
    html += "<input type=\"button\" value=\"hidden: " + hiddenNum + "\"><br>";
    div = new Dialog('options', 'center', html).el;
    _ref = $$('input[type="checkbox"]', div);
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      input = _ref[_i];
      input.addEventListener('change', changeCheckbox, true);
    }
    $('a.sauce', div).addEventListener('click', editSauce, true);
    $('textarea', div).addEventListener('change', changeValue, true);
    $('input[type="button"]', div).addEventListener('click', clearHidden, true);
    return mv(div, d.body);
  };
  parseResponse = function(responseText) {
    var body, opbq, replies;
    body = n('body', {
      innerHTML: responseText
    });
    replies = $$('td.reply', body);
    opbq = $('blockquote', body);
    return [replies, opbq];
  };
  qrListener = function(e) {
    var link, text;
    e.preventDefault();
    link = e.target;
    text = qrText(link);
    return quickReply(link, text);
  };
  qrText = function(link) {
    var id, s, selection, text, _ref;
    text = '>>' + link.parentNode.id.match(/\d+$/)[0] + '\n';
    selection = window.getSelection();
    id = (_ref = x('preceding::span[@id][1]', selection.anchorNode)) != null ? _ref.id : void 0;
    if ((s = selection.toString()) && (id === link.parentNode.id)) {
      text += ">" + s;
    }
    return text;
  };
  quickReply = function(link, text) {
    var auto, autoBox, clone, form, html, input, qr, script, submit, textarea, xpath, _i, _len, _ref, _ref2;
    if (!(qr = $('#qr'))) {
      html = "<div class=move>Quick Reply <input type=checkbox title=autohide><a name=close title=close>X</a></div>";
      qr = new Dialog('qr', 'topleft', html).el;
      form = $('form[name=post]');
      clone = form.cloneNode(true);
      _ref = $$('script', clone);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        script = _ref[_i];
        rm(script);
      }
      m($('input[name=recaptcha_response_field]', clone), {
        listener: ['keydown', recaptchaListener]
      });
      m(clone, {
        listener: ['submit', formSubmit],
        target: 'iframe'
      });
      if (!g.REPLY) {
        xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]';
        input = n('input', {
          type: 'hidden',
          name: 'resto',
          value: x(xpath, link).name
        });
        mv(input, clone);
      } else if (getConfig('Persistent QR')) {
        submit = $('input[type=submit]', clone);
        auto = n('label', {
          textContent: 'Auto'
        });
        autoBox = n('input', {
          type: 'checkbox'
        });
        mv(autoBox, auto);
        inBefore(submit, auto);
      }
      mv(clone, qr);
      mv(qr, d.body);
    }
    if ((_ref2 = $('input[title=autohide]:checked', qr)) != null) {
      _ref2.click();
    }
    textarea = $('textarea', qr);
    textarea.focus();
    if (text) {
      return textarea.value += text;
    }
  };
  recaptchaListener = function(e) {
    if (e.keyCode === 8 && this.value === '') {
      return recaptchaReload();
    }
  };
  recaptchaReload = function() {
    return window.location = 'javascript:Recaptcha.reload()';
  };
  redirect = function() {
    var url;
    switch (g.BOARD) {
      case 'a':
      case 'g':
      case 'lit':
      case 'sci':
      case 'tv':
        url = "http://green-oval.net/cgi-board.pl/" + g.BOARD + "/thread/" + g.THREAD_ID;
        break;
      case 'cgl':
      case 'jp':
      case 'm':
      case 'tg':
        url = "http://archive.easymodo.net/cgi-board.pl/" + g.BOARD + "/thread/" + g.THREAD_ID;
        break;
      case '3':
      case 'adv':
      case 'an':
      case 'c':
      case 'ck':
      case 'co':
      case 'fa':
      case 'fit':
      case 'int':
      case 'k':
      case 'mu':
      case 'n':
      case 'o':
      case 'p':
      case 'po':
      case 'soc':
      case 'sp':
      case 'toy':
      case 'trv':
      case 'v':
      case 'vp':
      case 'x':
        url = "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
        break;
      default:
        url = "http://boards.4chan.org/" + g.BOARD;
    }
    return location.href = url;
  };
  replyNav = function() {
    var direction, op;
    if (g.REPLY) {
      return window.location = this.textContent === '▲' ? '#navtop' : '#navbot';
    } else {
      direction = this.textContent === '▲' ? 'preceding' : 'following';
      op = x("" + direction + "::span[starts-with(@id, 'nothread')][1]", this).id;
      return window.location = "#" + op;
    }
  };
  report = function() {
    var input;
    input = x('preceding-sibling::input[1]', this);
    input.click();
    $('input[value="Report"]').click();
    return input.click();
  };
  scrollThread = function(count) {
    var hash, idx, temp, thread, top, _ref;
    _ref = getThread(), thread = _ref[0], idx = _ref[1];
    top = thread.getBoundingClientRect().top;
    if (idx === 0 && top > 1) {
      idx = -1;
    }
    if (count < 0 && top < -1) {
      count++;
    }
    temp = idx + count;
    if (temp < 0) {
      hash = '';
    } else if (temp > 9) {
      hash = 'p9';
    } else {
      hash = "p" + temp;
    }
    return location.hash = hash;
  };
  showReply = function() {
    var div, id, table;
    div = this.parentNode;
    table = div.nextSibling;
    show(table);
    rm(div);
    id = $('td.reply, td.replyhl', table).id;
    slice(g.hiddenReplies, id);
    return GM_setValue("hiddenReplies/" + g.BOARD + "/", JSON.stringify(g.hiddenReplies));
  };
  showThread = function() {
    var div, id;
    div = this.nextSibling;
    show(div);
    hide(this);
    id = div.id;
    slice(g.hiddenThreads, id);
    return GM_setValue("hiddenThreads/" + g.BOARD + "/", JSON.stringify(g.hiddenThreads));
  };
  stopPropagation = function(e) {
    return e.stopPropagation();
  };
  threadF = function(current) {
    var a, div, hidden, id, _i, _len, _ref;
    div = n('div', {
      className: 'thread'
    });
    a = n('a', {
      textContent: '[ - ]',
      className: 'pointer',
      listener: ['click', hideThread]
    });
    mv(a, div);
    inBefore(current, div);
    while (!current.clear) {
      mv(current, div);
      current = div.nextSibling;
    }
    mv(current, div);
    current = div.nextSibling;
    id = $('input[value="delete"]', div).name;
    div.id = id;
    _ref = g.hiddenThreads;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      hidden = _ref[_i];
      if (id === hidden.id) {
        hideThread(div);
      }
    }
    current = current.nextSibling.nextSibling;
    if (current.nodeName !== 'CENTER') {
      return threadF(current);
    }
  };
  request = function(url, callback) {
    var r;
    r = new XMLHttpRequest();
    r.onload = callback;
    r.open('get', url, true);
    r.send();
    return r;
  };
  updateCallback = function() {
    var arr, body, count, id, input, l, replies, reply, root, s, table, timer, _i, _len, _ref;
    count = $('#updater #count');
    timer = $('#updater #timer');
    if (this.status === 404) {
      count.textContent = 404;
      count.className = 'error';
      timer.textContent = '';
      clearInterval(g.interval);
      _ref = $$('input[type=submit]');
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        input = _ref[_i];
        input.disabled = true;
        input.value = 404;
      }
      s = '';
      if (getConfig('Unread Count')) {
        s += "(" + g.replies.length + ") ";
      }
      s += "/" + g.BOARD + "/ - 404";
      d.title = s;
      g.dead = true;
      updateFavicon();
      return;
    }
    body = n('body', {
      innerHTML: this.responseText
    });
    replies = $$('td.reply', body);
    root = $('br[clear]');
    if (reply = $('td.reply, td.replyhl', root.previousElementSibling)) {
      id = Number(reply.id);
    } else {
      id = 0;
    }
    arr = [];
    while ((reply = replies.pop()) && (Number(reply.id > id))) {
      arr.push(reply);
    }
    if (g.verbose) {
      l = arr.length;
      count.textContent = "+" + l;
      count.className = l > 0 ? 'new' : '';
    }
    while (reply = arr.pop()) {
      table = x('ancestor::table', reply);
      inBefore(root, table);
    }
    return timer.textContent = -1 * GM_getValue('Interval', 10);
  };
  updateFavicon = function() {
    var clone, favicon, href, len;
    len = g.replies.length;
    if (g.dead) {
      if (len > 0) {
        href = g.favDeadHalo;
      } else {
        href = g.favDead;
      }
    } else {
      if (len > 0) {
        href = g.favHalo;
      } else {
        href = g.favDefault;
      }
    }
    favicon = $('link[rel="shortcut icon"]', d);
    clone = favicon.cloneNode(true);
    clone.href = href;
    return replace(favicon, clone);
  };
  updateTime = function() {
    var count, span, time;
    span = $('#updater #timer');
    time = Number(span.textContent);
    if (++time === 0) {
      return updateNow();
    } else if (time > 10) {
      time = 0;
      g.req.abort();
      updateNow();
      if (g.verbose) {
        count = $('#updater #count');
        count.textContent = 'retry';
        return count.className = '';
      }
    } else {
      return span.textContent = time;
    }
  };
  updateTitle = function() {
    var len;
    len = g.replies.length;
    d.title = d.title.replace(/\d+/, len);
    return updateFavicon();
  };
  updateAuto = function() {
    var span;
    span = $('#updater #timer');
    if (this.checked) {
      span.textContent = -1 * GM_getValue('Interval', 10);
      return g.interval = window.setInterval(updateTime, 1000);
    } else {
      span.textContent = 'Thread Updater';
      return clearInterval(g.interval);
    }
  };
  updateInterval = function() {
    var num, span;
    if (!(num = Number(this.value))) {
      num = 10;
    }
    this.value = num;
    GM_setValue('Interval', num);
    span = $('#updater #timer');
    if (0 > Number(span.textContent)) {
      return span.textContent = -1 * num;
    }
  };
  updateNow = function() {
    var url;
    url = location.href + '?' + new Date().getTime();
    g.req = request(url, updateCallback);
    return $("#updater #timer").textContent = 0;
  };
  updateVerbose = function() {
    var timer;
    g.verbose = this.checked;
    timer = $('#updater #timer');
    if (this.checked) {
      return timer.hidden = false;
    } else {
      timer.hidden = true;
      return $("#updater #count").textContent = 'Thread Updater';
    }
  };
  updaterMake = function() {
    var div, html, input, interval, name, _i, _len, _ref;
    html = "<div class=move><span id=count>Thread Updater</span> <span id=timer></span></div>";
    html += "<div><label>Verbose<input type=checkbox name=verbose></label></div>";
    html += "<div><label title=\"Make all threads auto update\">Auto Update Global<input type=checkbox name=autoG></label></div>";
    html += "<div><label title=\"Make this thread auto update\">Auto Update Local<input type=checkbox name=autoL></label></div>";
    html += "<div><label>Interval (s)<input type=text name=interval></label></div>";
    html += "<div><input type=button value='Update Now'></div>";
    div = new Dialog('updater', 'topright', html).el;
    _ref = $$('input[type=checkbox]', div);
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      input = _ref[_i];
      input.addEventListener('click', changeCheckbox, true);
      name = input.name;
      if (name === 'autoL') {
        input.checked = GM_getValue('autoG', true);
      } else {
        input.checked = GM_getValue(name, true);
      }
      switch (name) {
        case 'autoL':
          input.addEventListener('click', updateAuto, true);
          break;
        case 'verbose':
          input.addEventListener('click', updateVerbose, true);
      }
    }
    if (!(g.verbose = GM_getValue('verbose', true))) {
      $("#timer", div).hidden = true;
    }
    interval = $('input[name=interval]', div);
    interval.value = GM_getValue('Interval', 10);
    interval.addEventListener('change', updateInterval, true);
    $('input[type=button]', div).addEventListener('click', updateNow, true);
    d.body.appendChild(div);
    if (GM_getValue('autoG')) {
      return updateAuto.call($("input[name=autoL]", div));
    }
  };
  watch = function() {
    var id, text, _base, _name;
    id = this.nextSibling.name;
    if (this.src === g.favEmpty) {
      this.src = g.favDefault;
      text = ("/" + g.BOARD + "/ - ") + x('following-sibling::blockquote', this).textContent.slice(0, 25);
      (_base = g.watched)[_name = g.BOARD] || (_base[_name] = []);
      g.watched[g.BOARD].push({
        id: id,
        text: text
      });
    } else {
      this.src = g.fav

0 comments:

Post a Comment