Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on Aug 20th, 2012  |  syntax: None  |  size: 192.53 KB  |  hits: 24  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. // ==UserScript==
  2. // @name           4chan x
  3. // @version        2.34.2
  4. // @namespace      aeosynth
  5. // @description    Adds various features.
  6. // @copyright      2009-2011 James Campos <james.r.campos@gmail.com>
  7. // @copyright      2012 Nicolas Stepien <stepien.nicolas@gmail.com>
  8. // @license        MIT; http://en.wikipedia.org/wiki/Mit_license
  9. // @include        http://boards.4chan.org/*
  10. // @include        https://boards.4chan.org/*
  11. // @include        http://images.4chan.org/*
  12. // @include        https://images.4chan.org/*
  13. // @include        http://sys.4chan.org/*
  14. // @include        https://sys.4chan.org/*
  15. // @run-at         document-start
  16. // @updateURL      https://github.com/that4chanwolf/4chan-x/raw/stable/4chan_x.user.js
  17. // @downloadURL    https://github.com/that4chanwolf/4chan-x/raw/stable/4chan_x.user.js
  18. // @icon           http://mayhemydg.github.com/4chan-x/favicon.gif
  19. // ==/UserScript==
  20.  
  21. /* LICENSE
  22.  *
  23.  * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
  24.  * Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
  25.  * http://mayhemydg.github.com/4chan-x/
  26.  * 4chan X 2.34.2
  27.  *
  28.  * Permission is hereby granted, free of charge, to any person
  29.  * obtaining a copy of this software and associated documentation
  30.  * files (the "Software"), to deal in the Software without
  31.  * restriction, including without limitation the rights to use,
  32.  * copy, modify, merge, publish, distribute, sublicense, and/or sell
  33.  * copies of the Software, and to permit persons to whom the
  34.  * Software is furnished to do so, subject to the following
  35.  * conditions:
  36.  *
  37.  * The above copyright notice and this permission notice shall be
  38.  * included in all copies or substantial portions of the Software.
  39.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  40.  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  41.  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  42.  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  43.  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  44.  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  45.  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  46.  * OTHER DEALINGS IN THE SOFTWARE.
  47.  *
  48.  * HACKING
  49.  *
  50.  * 4chan X is written in CoffeeScript[1], and developed on GitHub[2].
  51.  *
  52.  * [1]: http://coffeescript.org/
  53.  * [2]: https://github.com/MayhemYDG/4chan-x
  54.  *
  55.  * CONTRIBUTORS
  56.  *
  57.  * noface - unique ID fixes
  58.  * desuwa - Firefox filename upload fix
  59.  * seaweed - bottom padding for image hover
  60.  * e000 - cooldown sanity check
  61.  * ahodesuka - scroll back when unexpanding images, file info formatting
  62.  * Shou- - pentadactyl fixes
  63.  * ferongr - new favicons
  64.  * xat- - new favicons
  65.  * Zixaphir - fix qr textarea - captcha-image gap
  66.  * Ongpot - sfw favicon
  67.  * thisisanon - nsfw + 404 favicons
  68.  * Anonymous - empty favicon
  69.  * Seiba - chrome quick reply focusing
  70.  * herpaderpderp - recaptcha fixes
  71.  * WakiMiko - recaptcha tab order http://userscripts.org/scripts/show/82657
  72.  * btmcsweeney - allow users to specify text for sauce links
  73.  *
  74.  * All the people who've taken the time to write bug reports.
  75.  *
  76.  * Thank you.
  77.  */
  78.  
  79. (function() {
  80.   var $, $$, Anonymize, ArchiveLink, AutoGif, Conf, Config, DeleteLink, DownloadLink, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, Get, ImageExpand, ImageHover, Keybinds, Main, Menu, Nav, Options, Prefetch, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteThreading, Quotify, Redirect, ReplyHiding, ReportLink, RevealSpoilers, Sauce, StrikethroughQuotes, ThreadHiding, ThreadStats, Time, TitlePost, UI, Unread, Updater, Watcher, d, g, log, _base;
  81.  
  82.   Config = {
  83.     main: {
  84.       Enhancing: {
  85.         '404 Redirect': [true, 'Redirect dead threads and images'],
  86.         'Keybinds': [true, 'Binds actions to keys'],
  87.         'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'],
  88.         'File Info Formatting': [true, 'Reformats the file information'],
  89.         'Comment Expansion': [true, 'Expand too long comments'],
  90.         'Thread Expansion': [true, 'View all replies'],
  91.         'Index Navigation': [true, 'Navigate to previous / next thread'],
  92.         'Rollover': [true, 'Index navigation will fallback to page navigation.'],
  93.         'Reply Navigation': [false, 'Navigate to top / bottom of thread']
  94.       },
  95.       Filtering: {
  96.         'Anonymize': [false, 'Make everybody anonymous'],
  97.         'Filter': [true, 'Self-moderation placebo'],
  98.         'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'],
  99.         'Reply Hiding': [true, 'Hide single replies'],
  100.         'Thread Hiding': [true, 'Hide entire threads'],
  101.         'Show Stubs': [true, 'Of hidden threads / replies']
  102.       },
  103.       Imaging: {
  104.         'Image Auto-Gif': [false, 'Animate gif thumbnails'],
  105.         'Image Expansion': [true, 'Expand images'],
  106.         'Image Hover': [false, 'Show full image on mouseover'],
  107.         'Sauce': [true, 'Add sauce to images'],
  108.         'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail'],
  109.         'Expand From Current': [false, 'Expand images from current position to thread end.'],
  110.         'Prefetch': [false, 'Prefetch images.']
  111.       },
  112.       Menu: {
  113.         'Menu': [true, 'Add a drop-down menu in posts.'],
  114.         'Report Link': [true, 'Add a report link to the menu.'],
  115.         'Delete Link': [true, 'Add post and image deletion links to the menu.'],
  116.         'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'],
  117.         'Archive Link': [true, 'Add an archive link to the menu.']
  118.       },
  119.       Monitoring: {
  120.         'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'],
  121.         'Unread Count': [true, 'Show unread post count in tab title'],
  122.         'Unread Favicon': [true, 'Show a different favicon when there are unread posts'],
  123.         'Post in Title': [true, 'Show the op\'s post in the tab title'],
  124.         'Thread Stats': [true, 'Display reply and image count'],
  125.         'Thread Watcher': [true, 'Bookmark threads'],
  126.         'Auto Watch': [true, 'Automatically watch threads that you start'],
  127.         'Auto Watch Reply': [false, 'Automatically watch threads that you reply to']
  128.       },
  129.       Posting: {
  130.         'Quick Reply': [true, 'Reply without leaving the page.'],
  131.         'Cooldown': [true, 'Prevent "flood detected" errors.'],
  132.         'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'],
  133.         'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'],
  134.         'Open Reply in New Tab': [false, 'Open replies in a new tab that are made from the main board.'],
  135.         'Remember QR size': [false, 'Remember the size of the Quick reply (Firefox only).'],
  136.         'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'],
  137.         'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'],
  138.         'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.'],
  139.         'Sage on /jp/': [true, 'Uses sage by default on /jp/']
  140.       },
  141.       Quoting: {
  142.         'Quote Backlinks': [true, 'Add quote backlinks'],
  143.         'OP Backlinks': [false, 'Add backlinks to the OP'],
  144.         'Quote Highlighting': [true, 'Highlight the previewed post'],
  145.         'Quote Inline': [true, 'Show quoted post inline on quote click'],
  146.         'Quote Preview': [true, 'Show quote content on hover'],
  147.         'Resurrect Quotes': [true, 'Linkify dead quotes to archives'],
  148.         'Indicate OP quote': [true, 'Add \'(OP)\' to OP quotes'],
  149.         'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes'],
  150.         'Forward Hiding': [true, 'Hide original posts of inlined backlinks'],
  151.         'Quote Threading': [false, 'Thread conversations']
  152.       }
  153.     },
  154.     filter: {
  155.       name: ['# Filter any namefags:', '#/^(?!Anonymous$)/'].join('\n'),
  156.       uniqueid: ['# Filter a specific ID:', '#/Txhvk1Tl/'].join('\n'),
  157.       tripcode: ['# Filter any tripfags', '#/^!/'].join('\n'),
  158.       mod: ['# Set a custom class for mods:', '#/Mod$/;highlight:mod;op:yes', '# Set a custom class for moot:', '#/Admin$/;highlight:moot;op:yes'].join('\n'),
  159.       email: ['# Filter any e-mails that are not `sage` on /a/ and /jp/:', '#/^(?!sage$)/;boards:a,jp'].join('\n'),
  160.       subject: ['# Filter Generals on /v/:', '#/general/i;boards:v;op:only'].join('\n'),
  161.       comment: ['# Filter Stallman copypasta on /g/:', '#/what you\'re refer+ing to as linux/i;boards:g'].join('\n'),
  162.       country: [''].join('\n'),
  163.       filename: [''].join('\n'),
  164.       dimensions: ['# Highlight potential wallpapers:', '#/1920x1080/;op:yes;highlight;top:no;boards:w,wg'].join('\n'),
  165.       filesize: [''].join('\n'),
  166.       md5: [''].join('\n')
  167.     },
  168.     sauces: ['http://iqdb.org/?url=$1', 'http://www.google.com/searchbyimage?image_url=$1', '#http://tineye.com/search?url=$1', '#http://saucenao.com/search.php?db=999&url=$1', '#http://3d.iqdb.org/?url=$1', '#http://regex.info/exif.cgi?imgurl=$2', '# uploaders:', '#http://imgur.com/upload?url=$2;text:Upload to imgur', '#http://omploader.org/upload?url1=$2;text:Upload to omploader', '# "View Same" in archives:', '#http://archive.foolz.us/search/image/$3/;text:View same on foolz', '#http://archive.foolz.us/$4/search/image/$3/;text:View same on foolz /$4/', '#https://archive.installgentoo.net/$4/image/$3;text:View same on installgentoo /$4/'].join('\n'),
  169.     time: '%m/%d/%y(%a)%H:%M',
  170.     backlink: '>>%id',
  171.     fileInfo: '%l (%p%s, %r)',
  172.     favicon: 'ferongr',
  173.     hotkeys: {
  174.       openQR: ['i', 'Open QR with post number inserted'],
  175.       openEmptyQR: ['I', 'Open QR without post number inserted'],
  176.       openOptions: ['ctrl+o', 'Open Options'],
  177.       close: ['Esc', 'Close Options or QR'],
  178.       spoiler: ['ctrl+s', 'Quick spoiler tags'],
  179.       code: ['alt+c', 'Quick code tags'],
  180.       sageru: ['alt+n', 'Sage keybind'],
  181.       submit: ['alt+s', 'Submit post'],
  182.       watch: ['w', 'Watch thread'],
  183.       update: ['u', 'Update now'],
  184.       unreadCountTo0: ['z', 'Reset unread status'],
  185.       threading: ['t', 'Toggle threading'],
  186.       expandImage: ['m', 'Expand selected image'],
  187.       expandAllImages: ['M', 'Expand all images'],
  188.       zero: ['0', 'Jump to page 0'],
  189.       nextPage: ['L', 'Jump to the next page'],
  190.       previousPage: ['H', 'Jump to the previous page'],
  191.       nextThread: ['n', 'See next thread'],
  192.       previousThread: ['p', 'See previous thread'],
  193.       expandThread: ['e', 'Expand thread'],
  194.       openThreadTab: ['o', 'Open thread in new tab'],
  195.       openThread: ['O', 'Open thread in current tab'],
  196.       nextReply: ['J', 'Select next reply'],
  197.       previousReply: ['K', 'Select previous reply'],
  198.       hide: ['x', 'Hide thread']
  199.     },
  200.     updater: {
  201.       checkbox: {
  202.         'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'],
  203.         'Scroll BG': [false, 'Scroll background tabs'],
  204.         'Verbose': [true, 'Show countdown timer, new post count'],
  205.         'Auto Update': [true, 'Automatically fetch new posts']
  206.       },
  207.       'Interval': 30,
  208.       'Max Interval': 10
  209.     }
  210.   };
  211.  
  212.   Conf = {};
  213.  
  214.   d = document;
  215.  
  216.   g = {};
  217.  
  218.   log = typeof (_base = console.log).bind === "function" ? _base.bind(console) : void 0;
  219.  
  220.   UI = {
  221.     dialog: function(id, position, html) {
  222.       var el, _ref;
  223.       el = d.createElement('div');
  224.       el.className = 'reply dialog';
  225.       el.innerHTML = html;
  226.       el.id = id;
  227.       el.style.cssText = localStorage.getItem("" + Main.namespace + id + ".position") || position;
  228.       if ((_ref = el.querySelector('.move')) != null) {
  229.         _ref.addEventListener('mousedown', UI.dragstart, false);
  230.       }
  231.       return el;
  232.     },
  233.     dragstart: function(e) {
  234.       var el, rect;
  235.       e.preventDefault();
  236.       UI.el = el = this.parentNode;
  237.       d.addEventListener('mousemove', UI.drag, false);
  238.       d.addEventListener('mouseup', UI.dragend, false);
  239.       rect = el.getBoundingClientRect();
  240.       UI.dx = e.clientX - rect.left;
  241.       UI.dy = e.clientY - rect.top;
  242.       UI.width = d.documentElement.clientWidth - rect.width;
  243.       return UI.height = d.documentElement.clientHeight - rect.height;
  244.     },
  245.     drag: function(e) {
  246.       var left, style, top;
  247.       left = e.clientX - UI.dx;
  248.       top = e.clientY - UI.dy;
  249.       left = left < 10 ? '0px' : UI.width - left < 10 ? null : left + 'px';
  250.       top = top < 10 ? '0px' : UI.height - top < 10 ? null : top + 'px';
  251.       style = UI.el.style;
  252.       style.left = left;
  253.       style.top = top;
  254.       style.right = left === null ? '0px' : null;
  255.       return style.bottom = top === null ? '0px' : null;
  256.     },
  257.     dragend: function() {
  258.       localStorage.setItem("" + Main.namespace + UI.el.id + ".position", UI.el.style.cssText);
  259.       d.removeEventListener('mousemove', UI.drag, false);
  260.       d.removeEventListener('mouseup', UI.dragend, false);
  261.       return delete UI.el;
  262.     },
  263.     hover: function(e) {
  264.       var clientHeight, clientWidth, clientX, clientY, height, style, top, _ref;
  265.       clientX = e.clientX, clientY = e.clientY;
  266.       style = UI.el.style;
  267.       _ref = d.documentElement, clientHeight = _ref.clientHeight, clientWidth = _ref.clientWidth;
  268.       height = UI.el.offsetHeight;
  269.       top = clientY - 120;
  270.       style.top = clientHeight <= height || top <= 0 ? '0px' : top + height >= clientHeight ? clientHeight - height + 'px' : top + 'px';
  271.       if (clientX <= clientWidth - 400) {
  272.         style.left = clientX + 45 + 'px';
  273.         return style.right = null;
  274.       } else {
  275.         style.left = null;
  276.         return style.right = clientWidth - clientX + 45 + 'px';
  277.       }
  278.     },
  279.     hoverend: function() {
  280.       $.rm(UI.el);
  281.       return delete UI.el;
  282.     }
  283.   };
  284.  
  285.   /*
  286.   loosely follows the jquery api:
  287.   http://api.jquery.com/
  288.   not chainable
  289.   */
  290.  
  291.  
  292.   $ = function(selector, root) {
  293.     if (root == null) {
  294.       root = d.body;
  295.     }
  296.     return root.querySelector(selector);
  297.   };
  298.  
  299.   $.extend = function(object, properties) {
  300.     var key, val;
  301.     for (key in properties) {
  302.       val = properties[key];
  303.       object[key] = val;
  304.     }
  305.   };
  306.  
  307.   $.extend($, {
  308.     NBSP: '\u00A0',
  309.     SECOND: 1000,
  310.     MINUTE: 1000 * 60,
  311.     HOUR: 1000 * 60 * 60,
  312.     DAY: 1000 * 60 * 60 * 24,
  313.     engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(),
  314.     ready: function(fc) {
  315.       var cb;
  316.       if (/interactive|complete/.test(d.readyState)) {
  317.         return setTimeout(fc);
  318.       }
  319.       cb = function() {
  320.         $.off(d, 'DOMContentLoaded', cb);
  321.         return fc();
  322.       };
  323.       return $.on(d, 'DOMContentLoaded', cb);
  324.     },
  325.     sync: function(key, cb) {
  326.       return $.on(window, 'storage', function(e) {
  327.         if (e.key === ("" + Main.namespace + key)) {
  328.           return cb(JSON.parse(e.newValue));
  329.         }
  330.       });
  331.     },
  332.     id: function(id) {
  333.       return d.getElementById(id);
  334.     },
  335.     formData: function(arg) {
  336.       var fd, key, val;
  337.       if (arg instanceof HTMLFormElement) {
  338.         fd = new FormData(arg);
  339.       } else {
  340.         fd = new FormData();
  341.         for (key in arg) {
  342.           val = arg[key];
  343.           if (val) {
  344.             fd.append(key, val);
  345.           }
  346.         }
  347.       }
  348.       return fd;
  349.     },
  350.     ajax: function(url, callbacks, opts) {
  351.       var form, headers, key, r, type, upCallbacks, val;
  352.       if (opts == null) {
  353.         opts = {};
  354.       }
  355.       type = opts.type, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form;
  356.       r = new XMLHttpRequest();
  357.       type || (type = form && 'post' || 'get');
  358.       r.open(type, url, true);
  359.       for (key in headers) {
  360.         val = headers[key];
  361.         r.setRequestHeader(key, val);
  362.       }
  363.       $.extend(r, callbacks);
  364.       $.extend(r.upload, upCallbacks);
  365.       r.send(form);
  366.       return r;
  367.     },
  368.     cache: function(url, cb) {
  369.       var req;
  370.       if (req = $.cache.requests[url]) {
  371.         if (req.readyState === 4) {
  372.           return cb.call(req);
  373.         } else {
  374.           return req.callbacks.push(cb);
  375.         }
  376.       } else {
  377.         req = $.ajax(url, {
  378.           onload: function() {
  379.             var _i, _len, _ref, _results;
  380.             _ref = this.callbacks;
  381.             _results = [];
  382.             for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  383.               cb = _ref[_i];
  384.               _results.push(cb.call(this));
  385.             }
  386.             return _results;
  387.           },
  388.           onabort: function() {
  389.             return delete $.cache.requests[url];
  390.           },
  391.           onerror: function() {
  392.             return delete $.cache.requests[url];
  393.           }
  394.         });
  395.         req.callbacks = [cb];
  396.         return $.cache.requests[url] = req;
  397.       }
  398.     },
  399.     cb: {
  400.       checked: function() {
  401.         $.set(this.name, this.checked);
  402.         return Conf[this.name] = this.checked;
  403.       },
  404.       value: function() {
  405.         $.set(this.name, this.value.trim());
  406.         return Conf[this.name] = this.value;
  407.       }
  408.     },
  409.     addStyle: function(css) {
  410.       var style;
  411.       style = $.el('style', {
  412.         textContent: css
  413.       });
  414.       $.add(d.head, style);
  415.       return style;
  416.     },
  417.     x: function(path, root) {
  418.       if (root == null) {
  419.         root = d.body;
  420.       }
  421.       return d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
  422.     },
  423.     X: function(path, root) {
  424.       if (root == null) {
  425.         root = d.body;
  426.       }
  427.       return d.evaluate(path, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  428.     },
  429.     addClass: function(el, className) {
  430.       return el.classList.add(className);
  431.     },
  432.     rmClass: function(el, className) {
  433.       return el.classList.remove(className);
  434.     },
  435.     rm: function(el) {
  436.       return el.parentNode.removeChild(el);
  437.     },
  438.     tn: function(s) {
  439.       return d.createTextNode(s);
  440.     },
  441.     nodes: function(nodes) {
  442.       var frag, node, _i, _len;
  443.       if (!(nodes instanceof Array)) {
  444.         return nodes;
  445.       }
  446.       frag = d.createDocumentFragment();
  447.       for (_i = 0, _len = nodes.length; _i < _len; _i++) {
  448.         node = nodes[_i];
  449.         frag.appendChild(node);
  450.       }
  451.       return frag;
  452.     },
  453.     add: function(parent, children) {
  454.       return parent.appendChild($.nodes(children));
  455.     },
  456.     prepend: function(parent, children) {
  457.       return parent.insertBefore($.nodes(children), parent.firstChild);
  458.     },
  459.     after: function(root, el) {
  460.       return root.parentNode.insertBefore($.nodes(el), root.nextSibling);
  461.     },
  462.     before: function(root, el) {
  463.       return root.parentNode.insertBefore($.nodes(el), root);
  464.     },
  465.     replace: function(root, el) {
  466.       return root.parentNode.replaceChild($.nodes(el), root);
  467.     },
  468.     el: function(tag, properties) {
  469.       var el;
  470.       el = d.createElement(tag);
  471.       if (properties) {
  472.         $.extend(el, properties);
  473.       }
  474.       return el;
  475.     },
  476.     on: function(el, events, handler) {
  477.       var event, _i, _len, _ref;
  478.       _ref = events.split(' ');
  479.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  480.         event = _ref[_i];
  481.         el.addEventListener(event, handler, false);
  482.       }
  483.     },
  484.     off: function(el, events, handler) {
  485.       var event, _i, _len, _ref;
  486.       _ref = events.split(' ');
  487.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  488.         event = _ref[_i];
  489.         el.removeEventListener(event, handler, false);
  490.       }
  491.     },
  492.     event: function(el, e) {
  493.       return el.dispatchEvent(e);
  494.     },
  495.     globalEval: function(code) {
  496.       var script;
  497.       script = $.el('script', {
  498.         textContent: "(" + code + ")()"
  499.       });
  500.       $.add(d.head, script);
  501.       return $.rm(script);
  502.     },
  503.     bytesToString: function(size) {
  504.       var unit;
  505.       unit = 0;
  506.       while (size >= 1024) {
  507.         size /= 1024;
  508.         unit++;
  509.       }
  510.       size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size);
  511.       return "" + size + " " + ['B', 'KB', 'MB', 'GB'][unit];
  512.     },
  513.     RandomAccessList: (function() {
  514.  
  515.       function _Class() {
  516.         this.first = null;
  517.         this.last = null;
  518.         this.length = 0;
  519.       }
  520.  
  521.       _Class.prototype.push = function(id, el) {
  522.         var item, last;
  523.         last = this.last;
  524.         this[id] = item = {
  525.           prev: last,
  526.           next: null,
  527.           el: el,
  528.           id: id
  529.         };
  530.         this.last = item;
  531.         if (last) {
  532.           last.next = item;
  533.         } else {
  534.           this.first = item;
  535.         }
  536.         return this.length++;
  537.       };
  538.  
  539.       _Class.prototype.shift = function() {
  540.         return this.rm(this.first.id);
  541.       };
  542.  
  543.       _Class.prototype.after = function(root, item) {
  544.         var next;
  545.         if (item.prev === root) {
  546.           return;
  547.         }
  548.         this.rmi(item);
  549.         next = root.next;
  550.         root.next = item;
  551.         item.prev = root;
  552.         item.next = next;
  553.         return next.prev = item;
  554.       };
  555.  
  556.       _Class.prototype.rm = function(id) {
  557.         var item;
  558.         item = this[id];
  559.         if (!item) {
  560.           return;
  561.         }
  562.         delete this[id];
  563.         this.length--;
  564.         return this.rmi(item);
  565.       };
  566.  
  567.       _Class.prototype.rmi = function(item) {
  568.         var next, prev;
  569.         prev = item.prev, next = item.next;
  570.         if (prev) {
  571.           prev.next = next;
  572.         } else {
  573.           this.first = next;
  574.         }
  575.         if (next) {
  576.           return next.prev = prev;
  577.         } else {
  578.           return this.last = prev;
  579.         }
  580.       };
  581.  
  582.       return _Class;
  583.  
  584.     })()
  585.   });
  586.  
  587.   $.cache.requests = {};
  588.  
  589.   $.extend($, typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null ? {
  590.     "delete": function(name) {
  591.       name = Main.namespace + name;
  592.       return GM_deleteValue(name);
  593.     },
  594.     get: function(name, defaultValue) {
  595.       var value;
  596.       name = Main.namespace + name;
  597.       if (value = GM_getValue(name)) {
  598.         return JSON.parse(value);
  599.       } else {
  600.         return defaultValue;
  601.       }
  602.     },
  603.     set: function(name, value) {
  604.       name = Main.namespace + name;
  605.       localStorage.setItem(name, JSON.stringify(value));
  606.       return GM_setValue(name, JSON.stringify(value));
  607.     },
  608.     open: function(url) {
  609.       return GM_openInTab(location.protocol + url, true);
  610.     }
  611.   } : {
  612.     "delete": function(name) {
  613.       return localStorage.removeItem(Main.namespace + name);
  614.     },
  615.     get: function(name, defaultValue) {
  616.       var value;
  617.       if (value = localStorage.getItem(Main.namespace + name)) {
  618.         return JSON.parse(value);
  619.       } else {
  620.         return defaultValue;
  621.       }
  622.     },
  623.     set: function(name, value) {
  624.       return localStorage.setItem(Main.namespace + name, JSON.stringify(value));
  625.     },
  626.     open: function(url) {
  627.       return window.open(location.protocol + url, '_blank');
  628.     }
  629.   });
  630.  
  631.   $$ = function(selector, root) {
  632.     if (root == null) {
  633.       root = d.body;
  634.     }
  635.     return Array.prototype.slice.call(root.querySelectorAll(selector));
  636.   };
  637.  
  638.   Filter = {
  639.     filters: {},
  640.     init: function() {
  641.       var boards, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4;
  642.       for (key in Config.filter) {
  643.         this.filters[key] = [];
  644.         _ref = Conf[key].split('\n');
  645.         for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  646.           filter = _ref[_i];
  647.           if (filter[0] === '#') {
  648.             continue;
  649.           }
  650.           if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) {
  651.             continue;
  652.           }
  653.           filter = filter.replace(regexp[0], '');
  654.           boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global';
  655.           if (boards !== 'global' && boards.split(',').indexOf(g.BOARD) === -1) {
  656.             continue;
  657.           }
  658.           try {
  659.             if (key === 'md5') {
  660.               regexp = regexp[1];
  661.             } else {
  662.               regexp = RegExp(regexp[1], regexp[2]);
  663.             }
  664.           } catch (e) {
  665.             alert(e.message);
  666.             continue;
  667.           }
  668.           op = ((_ref2 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref2[1] : void 0) || 'no';
  669.           stub = (function() {
  670.             var _ref3;
  671.             switch ((_ref3 = filter.match(/stub:(yes|no)/)) != null ? _ref3[1] : void 0) {
  672.               case 'yes':
  673.                 return true;
  674.               case 'no':
  675.                 return false;
  676.               default:
  677.                 return Conf['Show Stubs'];
  678.             }
  679.           })();
  680.           if (hl = /highlight/.test(filter)) {
  681.             hl = ((_ref3 = filter.match(/highlight:(\w+)/)) != null ? _ref3[1] : void 0) || 'filter_highlight';
  682.             top = ((_ref4 = filter.match(/top:(yes|no)/)) != null ? _ref4[1] : void 0) || 'yes';
  683.             top = top === 'yes';
  684.           }
  685.           this.filters[key].push(this.createFilter(regexp, op, stub, hl, top));
  686.         }
  687.         if (!this.filters[key].length) {
  688.           delete this.filters[key];
  689.         }
  690.       }
  691.       if (Object.keys(this.filters).length) {
  692.         return Main.callbacks.push(this.node);
  693.       }
  694.     },
  695.     createFilter: function(regexp, op, stub, hl, top) {
  696.       var settings, test;
  697.       test = typeof regexp === 'string' ? function(value) {
  698.         return regexp === value;
  699.       } : function(value) {
  700.         return regexp.test(value);
  701.       };
  702.       settings = {
  703.         hide: !hl,
  704.         stub: stub,
  705.         "class": hl,
  706.         top: top
  707.       };
  708.       return function(value, isOP) {
  709.         if (isOP && op === 'no' || !isOP && op === 'only') {
  710.           return false;
  711.         }
  712.         if (!test(value)) {
  713.           return false;
  714.         }
  715.         return settings;
  716.       };
  717.     },
  718.     node: function(post) {
  719.       var filter, isOP, key, result, root, value, _i, _len, _ref;
  720.       if (post.isInlined) {
  721.         return;
  722.       }
  723.       isOP = post.ID === post.threadID;
  724.       root = post.root;
  725.       for (key in Filter.filters) {
  726.         value = Filter[key](post);
  727.         if (value === false) {
  728.           continue;
  729.         }
  730.         _ref = Filter.filters[key];
  731.         for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  732.           filter = _ref[_i];
  733.           if (!(result = filter(value, isOP))) {
  734.             continue;
  735.           }
  736.           if (result.hide) {
  737.             if (isOP) {
  738.               if (!g.REPLY) {
  739.                 ThreadHiding.hide(root.parentNode, result.stub);
  740.               } else {
  741.                 continue;
  742.               }
  743.             } else {
  744.               ReplyHiding.hide(post.root, result.stub);
  745.             }
  746.             return;
  747.           }
  748.           $.addClass(root, result["class"]);
  749.         }
  750.       }
  751.     },
  752.     name: function(post) {
  753.       return $('.name', post.el).textContent;
  754.     },
  755.     uniqueid: function(post) {
  756.       var uid;
  757.       if (uid = $('.posteruid', post.el)) {
  758.         return uid.textContent.slice(5, -1);
  759.       }
  760.       return false;
  761.     },
  762.     tripcode: function(post) {
  763.       var trip;
  764.       if (trip = $('.postertrip', post.el)) {
  765.         return trip.textContent;
  766.       }
  767.       return false;
  768.     },
  769.     mod: function(post) {
  770.       var mod;
  771.       if (mod = $('.capcode', post.el)) {
  772.         return mod.textContent;
  773.       }
  774.       return false;
  775.     },
  776.     email: function(post) {
  777.       var mail;
  778.       if (mail = $('.useremail', post.el)) {
  779.         return decodeURIComponent(mail.href.slice(7));
  780.       }
  781.       return false;
  782.     },
  783.     subject: function(post) {
  784.       return $('.postInfo .subject', post.el).textContent || false;
  785.     },
  786.     comment: function(post) {
  787.       var data, i, nodes, text, _i, _ref;
  788.       text = [];
  789.       nodes = d.evaluate('.//br|.//text()', post.blockquote, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  790.       for (i = _i = 0, _ref = nodes.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  791.         text.push((data = nodes.snapshotItem(i).data) ? data : '\n');
  792.       }
  793.       return text.join('');
  794.     },
  795.     country: function(post) {
  796.       var flag;
  797.       if (flag = $('.countryFlag', post.el)) {
  798.         return flag.title;
  799.       }
  800.       return false;
  801.     },
  802.     filename: function(post) {
  803.       var file, fileInfo;
  804.       fileInfo = post.fileInfo;
  805.       if (fileInfo) {
  806.         if (file = $('.fileText > span', fileInfo)) {
  807.           return file.title;
  808.         } else {
  809.           return fileInfo.firstElementChild.dataset.filename;
  810.         }
  811.       }
  812.       return false;
  813.     },
  814.     dimensions: function(post) {
  815.       var fileInfo, match;
  816.       fileInfo = post.fileInfo;
  817.       if (fileInfo && (match = fileInfo.textContent.match(/\d+x\d+/))) {
  818.         return match[0];
  819.       }
  820.       return false;
  821.     },
  822.     filesize: function(post) {
  823.       var img;
  824.       img = post.img;
  825.       if (img) {
  826.         return img.alt;
  827.       }
  828.       return false;
  829.     },
  830.     md5: function(post) {
  831.       var img;
  832.       img = post.img;
  833.       if (img) {
  834.         return img.dataset.md5;
  835.       }
  836.       return false;
  837.     },
  838.     menuInit: function() {
  839.       var div, entry, type, _i, _len, _ref;
  840.       div = $.el('div', {
  841.         textContent: 'Filter'
  842.       });
  843.       entry = {
  844.         el: div,
  845.         open: function() {
  846.           return true;
  847.         },
  848.         children: []
  849.       };
  850.       _ref = [['Name', 'name'], ['Unique ID', 'uniqueid'], ['Tripcode', 'tripcode'], ['Admin/Mod', 'mod'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Country', 'country'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'md5']];
  851.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  852.         type = _ref[_i];
  853.         entry.children.push(Filter.createSubEntry(type[0], type[1]));
  854.       }
  855.       return Menu.addEntry(entry);
  856.     },
  857.     createSubEntry: function(text, type) {
  858.       var el, onclick, open;
  859.       el = $.el('a', {
  860.         href: 'javascript:;',
  861.         textContent: text
  862.       });
  863.       onclick = null;
  864.       open = function(post) {
  865.         var value;
  866.         value = Filter[type](post);
  867.         if (value === false) {
  868.           return false;
  869.         }
  870.         $.off(el, 'click', onclick);
  871.         onclick = function() {
  872.           var re, save, select, ta, tl;
  873.           re = type === 'md5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) {
  874.             if (c === '\n') {
  875.               return '\\n';
  876.             } else if (c === '\\') {
  877.               return '\\\\';
  878.             } else {
  879.               return "\\" + c;
  880.             }
  881.           });
  882.           re = type === 'md5' ? "/" + value + "/" : "/^" + re + "$/";
  883.           if (/\bop\b/.test(post["class"])) {
  884.             re += ';op:yes';
  885.           }
  886.           save = (save = $.get(type, '')) ? "" + save + "\n" + re : re;
  887.           $.set(type, save);
  888.           Options.dialog();
  889.           select = $('select[name=filter]', $.id('options'));
  890.           select.value = type;
  891.           $.event(select, new Event('change'));
  892.           $.id('filter_tab').checked = true;
  893.           ta = select.nextElementSibling;
  894.           tl = ta.textLength;
  895.           ta.setSelectionRange(tl, tl);
  896.           return ta.focus();
  897.         };
  898.         $.on(el, 'click', onclick);
  899.         return true;
  900.       };
  901.       return {
  902.         el: el,
  903.         open: open
  904.       };
  905.     }
  906.   };
  907.  
  908.   StrikethroughQuotes = {
  909.     init: function() {
  910.       return Main.callbacks.push(this.node);
  911.     },
  912.     node: function(post) {
  913.       var el, quote, show_stub, _i, _len, _ref;
  914.       if (post.isInlined) {
  915.         return;
  916.       }
  917.       _ref = post.quotes;
  918.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  919.         quote = _ref[_i];
  920.         if ((el = $.id(quote.hash.slice(1))) && el.hidden) {
  921.           $.addClass(quote, 'filtered');
  922.           if (Conf['Recursive Filtering']) {
  923.             show_stub = !!$.x('preceding-sibling::div[contains(@class,"stub")]', el);
  924.             ReplyHiding.hide(post.root, show_stub);
  925.           }
  926.         }
  927.       }
  928.     }
  929.   };
  930.  
  931.   ExpandComment = {
  932.     init: function() {
  933.       var a, _i, _len, _ref;
  934.       _ref = $$('.abbr');
  935.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  936.         a = _ref[_i];
  937.         $.on(a.firstElementChild, 'click', ExpandComment.expand);
  938.       }
  939.     },
  940.     expand: function(e) {
  941.       var a, replyID, threadID, _, _ref;
  942.       e.preventDefault();
  943.       _ref = this.href.match(/(\d+)#p(\d+)/), _ = _ref[0], threadID = _ref[1], replyID = _ref[2];
  944.       this.textContent = "Loading " + replyID + "...";
  945.       a = this;
  946.       return $.cache(this.pathname, function() {
  947.         return ExpandComment.parse(this, a, threadID, replyID);
  948.       });
  949.     },
  950.     parse: function(req, a, threadID, replyID) {
  951.       var doc, href, node, post, quote, quotes, _i, _len;
  952.       if (req.status !== 200) {
  953.         a.textContent = "" + req.status + " " + req.statusText;
  954.         return;
  955.       }
  956.       doc = d.implementation.createHTMLDocument('');
  957.       doc.documentElement.innerHTML = req.response;
  958.       node = d.importNode(doc.getElementById("m" + replyID), true);
  959.       quotes = node.getElementsByClassName('quotelink');
  960.       for (_i = 0, _len = quotes.length; _i < _len; _i++) {
  961.         quote = quotes[_i];
  962.         href = quote.getAttribute('href');
  963.         if (href[0] === '/') {
  964.           continue;
  965.         }
  966.         quote.href = "res/" + href;
  967.       }
  968.       post = {
  969.         blockquote: node,
  970.         threadID: threadID,
  971.         quotes: quotes,
  972.         backlinks: []
  973.       };
  974.       if (Conf['Resurrect Quotes']) {
  975.         Quotify.node(post);
  976.       }
  977.       if (Conf['Quote Preview']) {
  978.         QuotePreview.node(post);
  979.       }
  980.       if (Conf['Quote Inline']) {
  981.         QuoteInline.node(post);
  982.       }
  983.       if (Conf['Indicate OP quote']) {
  984.         QuoteOP.node(post);
  985.       }
  986.       if (Conf['Indicate Cross-thread Quotes']) {
  987.         QuoteCT.node(post);
  988.       }
  989.       $.replace(a.parentNode.parentNode, node);
  990.       return Main.prettify(node);
  991.     }
  992.   };
  993.  
  994.   ExpandThread = {
  995.     init: function() {
  996.       var a, span, _i, _len, _ref, _results;
  997.       _ref = $$('.summary');
  998.       _results = [];
  999.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1000.         span = _ref[_i];
  1001.         a = $.el('a', {
  1002.           textContent: "+ " + span.textContent,
  1003.           className: 'summary desktop',
  1004.           href: 'javascript:;'
  1005.         });
  1006.         $.on(a, 'click', function() {
  1007.           return ExpandThread.toggle(this.parentNode);
  1008.         });
  1009.         _results.push($.replace(span, a));
  1010.       }
  1011.       return _results;
  1012.     },
  1013.     toggle: function(thread) {
  1014.       var a, num, pathname, replies, reply, _i, _len;
  1015.       pathname = "/" + g.BOARD + "/res/" + thread.id.slice(1);
  1016.       a = $('.summary', thread);
  1017.       switch (a.textContent[0]) {
  1018.         case '+':
  1019.           a.textContent = a.textContent.replace('+', '× Loading...');
  1020.           $.cache(pathname, function() {
  1021.             return ExpandThread.parse(this, thread, a);
  1022.           });
  1023.           break;
  1024.         case 'X':
  1025.           a.textContent = a.textContent.replace('× Loading...', '+');
  1026.           $.cache.requests[pathname].abort();
  1027.           break;
  1028.         case '-':
  1029.           a.textContent = a.textContent.replace('-', '+');
  1030.           num = (function() {
  1031.             switch (g.BOARD) {
  1032.               case 'b':
  1033.               case 'vg':
  1034.                 return 3;
  1035.               case 't':
  1036.                 return 1;
  1037.               default:
  1038.                 return 5;
  1039.             }
  1040.           })();
  1041.           replies = $$('.replyContainer', thread);
  1042.           replies.splice(replies.length - num, num);
  1043.           for (_i = 0, _len = replies.length; _i < _len; _i++) {
  1044.             reply = replies[_i];
  1045.             $.rm(reply);
  1046.           }
  1047.       }
  1048.     },
  1049.     parse: function(req, thread, a) {
  1050.       var backlink, doc, href, id, link, nodes, post, quote, reply, threadID, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3;
  1051.       if (req.status !== 200) {
  1052.         a.textContent = "" + req.status + " " + req.statusText;
  1053.         $.off(a, 'click', ExpandThread.cb.toggle);
  1054.         return;
  1055.       }
  1056.       a.textContent = a.textContent.replace('× Loading...', '-');
  1057.       doc = d.implementation.createHTMLDocument('');
  1058.       doc.documentElement.innerHTML = req.response;
  1059.       threadID = thread.id.slice(1);
  1060.       nodes = [];
  1061.       _ref = $$('.replyContainer', doc);
  1062.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1063.         reply = _ref[_i];
  1064.         reply = d.importNode(reply, true);
  1065.         _ref1 = $$('.quotelink', reply);
  1066.         for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  1067.           quote = _ref1[_j];
  1068.           href = quote.getAttribute('href');
  1069.           if (href[0] === '/') {
  1070.             continue;
  1071.           }
  1072.           quote.href = "res/" + href;
  1073.         }
  1074.         id = reply.id.slice(2);
  1075.         link = $('a[title="Highlight this post"]', reply);
  1076.         link.href = "res/" + threadID + "#p" + id;
  1077.         link.nextSibling.href = "res/" + threadID + "#q" + id;
  1078.         nodes.push(reply);
  1079.       }
  1080.       _ref2 = $$('.summary ~ .replyContainer', a.parentNode);
  1081.       for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
  1082.         post = _ref2[_k];
  1083.         $.rm(post);
  1084.       }
  1085.       _ref3 = $$('.backlink', a.previousElementSibling);
  1086.       for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
  1087.         backlink = _ref3[_l];
  1088.         if (!$.id(backlink.hash.slice(1))) {
  1089.           $.rm(backlink);
  1090.         }
  1091.       }
  1092.       return $.after(a, nodes);
  1093.     }
  1094.   };
  1095.  
  1096.   ThreadHiding = {
  1097.     init: function() {
  1098.       var a, hiddenThreads, thread, _i, _len, _ref;
  1099.       hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
  1100.       _ref = $$('.thread');
  1101.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1102.         thread = _ref[_i];
  1103.         a = $.el('a', {
  1104.           className: 'hide_thread_button',
  1105.           innerHTML: '<span>[ - ]</span>',
  1106.           href: 'javascript:;'
  1107.         });
  1108.         $.on(a, 'click', ThreadHiding.cb);
  1109.         $.prepend(thread, a);
  1110.         if (thread.id.slice(1) in hiddenThreads) {
  1111.           ThreadHiding.hide(thread);
  1112.         }
  1113.       }
  1114.     },
  1115.     cb: function() {
  1116.       return ThreadHiding.toggle($.x('ancestor::div[parent::div[@class="board"]]', this));
  1117.     },
  1118.     toggle: function(thread) {
  1119.       var hiddenThreads, id;
  1120.       hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
  1121.       id = thread.id.slice(1);
  1122.       if (thread.hidden || /\bhidden_thread\b/.test(thread.firstChild.className)) {
  1123.         ThreadHiding.show(thread);
  1124.         delete hiddenThreads[id];
  1125.       } else {
  1126.         ThreadHiding.hide(thread);
  1127.         hiddenThreads[id] = Date.now();
  1128.       }
  1129.       return $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
  1130.     },
  1131.     hide: function(thread, show_stub) {
  1132.       var a, menuButton, num, opInfo, span, stub, text;
  1133.       if (show_stub == null) {
  1134.         show_stub = Conf['Show Stubs'];
  1135.       }
  1136.       if (!show_stub) {
  1137.         thread.hidden = true;
  1138.         thread.nextElementSibling.hidden = true;
  1139.         return;
  1140.       }
  1141.       if (/\bhidden_thread\b/.test(thread.firstChild.className)) {
  1142.         return;
  1143.       }
  1144.       num = 0;
  1145.       if (span = $('.summary', thread)) {
  1146.         num = Number(span.textContent.match(/\d+/));
  1147.       }
  1148.       num += $$('.opContainer ~ .replyContainer', thread).length;
  1149.       text = num === 1 ? '1 reply' : "" + num + " replies";
  1150.       opInfo = $('.desktop > .nameBlock', thread).textContent;
  1151.       stub = $.el('div', {
  1152.         className: 'hide_thread_button hidden_thread',
  1153.         innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>'
  1154.       });
  1155.       a = stub.firstChild;
  1156.       $.on(a, 'click', ThreadHiding.cb);
  1157.       $.add(a, $.tn("" + opInfo + " (" + text + ")"));
  1158.       if (Conf['Menu']) {
  1159.         menuButton = Menu.a.cloneNode(true);
  1160.         $.on(menuButton, 'click', Menu.toggle);
  1161.         $.add(stub, [$.tn(' '), menuButton]);
  1162.       }
  1163.       return $.prepend(thread, stub);
  1164.     },
  1165.     show: function(thread) {
  1166.       var stub;
  1167.       if (stub = $('.hidden_thread', thread)) {
  1168.         $.rm(stub);
  1169.       }
  1170.       thread.hidden = false;
  1171.       return thread.nextElementSibling.hidden = false;
  1172.     }
  1173.   };
  1174.  
  1175.   ReplyHiding = {
  1176.     init: function() {
  1177.       return Main.callbacks.push(this.node);
  1178.     },
  1179.     node: function(post) {
  1180.       var side;
  1181.       if (post.isInlined || post.ID === post.threadID) {
  1182.         return;
  1183.       }
  1184.       side = $('.sideArrows', post.root);
  1185.       $.addClass(side, 'hide_reply_button');
  1186.       side.innerHTML = '<a href="javascript:;"><span>[ - ]</span></a>';
  1187.       $.on(side.firstChild, 'click', ReplyHiding.toggle);
  1188.       if (post.ID in g.hiddenReplies) {
  1189.         return ReplyHiding.hide(post.root);
  1190.       }
  1191.     },
  1192.     toggle: function() {
  1193.       var button, id, quote, quotes, root, _i, _j, _len, _len1;
  1194.       button = this.parentNode;
  1195.       root = button.parentNode;
  1196.       id = root.id.slice(2);
  1197.       quotes = $$(".quotelink[href$='#p" + id + "'], .backlink[href$='#p" + id + "']");
  1198.       if (/\bstub\b/.test(button.className)) {
  1199.         ReplyHiding.show(root);
  1200.         for (_i = 0, _len = quotes.length; _i < _len; _i++) {
  1201.           quote = quotes[_i];
  1202.           $.rmClass(quote, 'filtered');
  1203.         }
  1204.         delete g.hiddenReplies[id];
  1205.       } else {
  1206.         ReplyHiding.hide(root);
  1207.         for (_j = 0, _len1 = quotes.length; _j < _len1; _j++) {
  1208.           quote = quotes[_j];
  1209.           $.addClass(quote, 'filtered');
  1210.         }
  1211.         g.hiddenReplies[id] = Date.now();
  1212.       }
  1213.       return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
  1214.     },
  1215.     hide: function(root, show_stub) {
  1216.       var a, el, menuButton, side, stub;
  1217.       if (show_stub == null) {
  1218.         show_stub = Conf['Show Stubs'];
  1219.       }
  1220.       side = $('.sideArrows', root);
  1221.       if (side.hidden) {
  1222.         return;
  1223.       }
  1224.       side.hidden = true;
  1225.       el = side.nextElementSibling;
  1226.       el.hidden = true;
  1227.       $.addClass(root, 'hidden');
  1228.       if (!show_stub) {
  1229.         return;
  1230.       }
  1231.       stub = $.el('div', {
  1232.         className: 'hide_reply_button stub',
  1233.         innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>'
  1234.       });
  1235.       a = stub.firstChild;
  1236.       $.on(a, 'click', ReplyHiding.toggle);
  1237.       $.add(a, $.tn($('.desktop > .nameBlock', el).textContent));
  1238.       if (Conf['Menu']) {
  1239.         menuButton = Menu.a.cloneNode(true);
  1240.         $.on(menuButton, 'click', Menu.toggle);
  1241.         $.add(stub, [$.tn(' '), menuButton]);
  1242.       }
  1243.       return $.prepend(root, stub);
  1244.     },
  1245.     show: function(root) {
  1246.       var stub;
  1247.       if (stub = $('.stub', root)) {
  1248.         $.rm(stub);
  1249.       }
  1250.       $('.sideArrows', root).hidden = false;
  1251.       $('.post', root).hidden = false;
  1252.       return $.rmClass(root, 'hidden');
  1253.     }
  1254.   };
  1255.  
  1256.   Menu = {
  1257.     entries: [],
  1258.     init: function() {
  1259.       this.a = $.el('a', {
  1260.         className: 'menu_button',
  1261.         href: 'javascript:;',
  1262.         innerHTML: '[<span></span>]'
  1263.       });
  1264.       this.el = $.el('div', {
  1265.         className: 'reply dialog',
  1266.         id: 'menu',
  1267.         tabIndex: 0
  1268.       });
  1269.       $.on(this.el, 'click', function(e) {
  1270.         return e.stopPropagation();
  1271.       });
  1272.       $.on(this.el, 'keydown', this.keybinds);
  1273.       $.on(d, 'AddMenuEntry', function(e) {
  1274.         return Menu.addEntry(e.detail);
  1275.       });
  1276.       return Main.callbacks.push(this.node);
  1277.     },
  1278.     node: function(post) {
  1279.       var a;
  1280.       if (post.isInlined && !post.isCrosspost) {
  1281.         a = $('.menu_button', post.el);
  1282.       } else {
  1283.         a = Menu.a.cloneNode(true);
  1284.         $.add($('.postInfo', post.el), [$.tn('\u00A0'), a]);
  1285.       }
  1286.       return $.on(a, 'click', Menu.toggle);
  1287.     },
  1288.     toggle: function(e) {
  1289.       var lastOpener, post;
  1290.       e.preventDefault();
  1291.       e.stopPropagation();
  1292.       if (Menu.el.parentNode) {
  1293.         lastOpener = Menu.lastOpener;
  1294.         Menu.close();
  1295.         if (lastOpener === this) {
  1296.           return;
  1297.         }
  1298.       }
  1299.       Menu.lastOpener = this;
  1300.       post = /\bhidden_thread\b/.test(this.parentNode.className) ? $.x('ancestor::div[parent::div[@class="board"]]/child::div[contains(@class,"opContainer")]', this) : $.x('ancestor::div[contains(@class,"postContainer")][1]', this);
  1301.       return Menu.open(this, Main.preParse(post));
  1302.     },
  1303.     open: function(button, post) {
  1304.       var bLeft, bRect, bTop, el, entry, funk, mRect, _i, _len, _ref;
  1305.       el = Menu.el;
  1306.       el.setAttribute('data-id', post.ID);
  1307.       el.setAttribute('data-rootid', post.root.id);
  1308.       funk = function(entry, parent) {
  1309.         var child, children, subMenu, _i, _len;
  1310.         children = entry.children;
  1311.         if (!entry.open(post)) {
  1312.           return;
  1313.         }
  1314.         $.add(parent, entry.el);
  1315.         if (!children) {
  1316.           return;
  1317.         }
  1318.         if (subMenu = $('.subMenu', entry.el)) {
  1319.           $.rm(subMenu);
  1320.         }
  1321.         subMenu = $.el('div', {
  1322.           className: 'reply dialog subMenu'
  1323.         });
  1324.         $.add(entry.el, subMenu);
  1325.         for (_i = 0, _len = children.length; _i < _len; _i++) {
  1326.           child = children[_i];
  1327.           funk(child, subMenu);
  1328.         }
  1329.       };
  1330.       _ref = Menu.entries;
  1331.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1332.         entry = _ref[_i];
  1333.         funk(entry, el);
  1334.       }
  1335.       Menu.focus($('.entry', Menu.el));
  1336.       $.on(d, 'click', Menu.close);
  1337.       $.add(d.body, el);
  1338.       mRect = el.getBoundingClientRect();
  1339.       bRect = button.getBoundingClientRect();
  1340.       bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top;
  1341.       bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left;
  1342.       el.style.top = bRect.top + bRect.height + mRect.height < d.documentElement.clientHeight ? bTop + bRect.height + 2 + 'px' : bTop - mRect.height - 2 + 'px';
  1343.       el.style.left = bRect.left + mRect.width < d.documentElement.clientWidth ? bLeft + 'px' : bLeft + bRect.width - mRect.width + 'px';
  1344.       return el.focus();
  1345.     },
  1346.     close: function() {
  1347.       var el, focused, _i, _len, _ref;
  1348.       el = Menu.el;
  1349.       $.rm(el);
  1350.       _ref = $$('.focused.entry', el);
  1351.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1352.         focused = _ref[_i];
  1353.         $.rmClass(focused, 'focused');
  1354.       }
  1355.       el.innerHTML = null;
  1356.       el.removeAttribute('style');
  1357.       delete Menu.lastOpener;
  1358.       delete Menu.focusedEntry;
  1359.       return $.off(d, 'click', Menu.close);
  1360.     },
  1361.     keybinds: function(e) {
  1362.       var el, next, subMenu;
  1363.       el = Menu.focusedEntry;
  1364.       switch (Keybinds.keyCode(e) || e.keyCode) {
  1365.         case 'Esc':
  1366.           Menu.lastOpener.focus();
  1367.           Menu.close();
  1368.           break;
  1369.         case 13:
  1370.         case 32:
  1371.           el.click();
  1372.           break;
  1373.         case 'Up':
  1374.           if (next = el.previousElementSibling) {
  1375.             Menu.focus(next);
  1376.           }
  1377.           break;
  1378.         case 'Down':
  1379.           if (next = el.nextElementSibling) {
  1380.             Menu.focus(next);
  1381.           }
  1382.           break;
  1383.         case 'Right':
  1384.           if ((subMenu = $('.subMenu', el)) && (next = subMenu.firstElementChild)) {
  1385.             Menu.focus(next);
  1386.           }
  1387.           break;
  1388.         case 'Left':
  1389.           if (next = $.x('parent::*[contains(@class,"subMenu")]/parent::*', el)) {
  1390.             Menu.focus(next);
  1391.           }
  1392.           break;
  1393.         default:
  1394.           return;
  1395.       }
  1396.       e.preventDefault();
  1397.       return e.stopPropagation();
  1398.     },
  1399.     focus: function(el) {
  1400.       var focused, _i, _len, _ref;
  1401.       if (focused = $.x('parent::*/child::*[contains(@class,"focused")]', el)) {
  1402.         $.rmClass(focused, 'focused');
  1403.       }
  1404.       _ref = $$('.focused', el);
  1405.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1406.         focused = _ref[_i];
  1407.         $.rmClass(focused, 'focused');
  1408.       }
  1409.       Menu.focusedEntry = el;
  1410.       return $.addClass(el, 'focused');
  1411.     },
  1412.     addEntry: function(entry) {
  1413.       var funk;
  1414.       funk = function(entry) {
  1415.         var child, children, el, _i, _len;
  1416.         el = entry.el, children = entry.children;
  1417.         $.addClass(el, 'entry');
  1418.         $.on(el, 'focus mouseover', function(e) {
  1419.           e.stopPropagation();
  1420.           return Menu.focus(this);
  1421.         });
  1422.         if (!children) {
  1423.           return;
  1424.         }
  1425.         $.addClass(el, 'hasSubMenu');
  1426.         for (_i = 0, _len = children.length; _i < _len; _i++) {
  1427.           child = children[_i];
  1428.           funk(child);
  1429.         }
  1430.       };
  1431.       funk(entry);
  1432.       return Menu.entries.push(entry);
  1433.     }
  1434.   };
  1435.  
  1436.   Keybinds = {
  1437.     init: function() {
  1438.       var node, _i, _len, _ref;
  1439.       _ref = $$('[accesskey]');
  1440.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1441.         node = _ref[_i];
  1442.         node.removeAttribute('accesskey');
  1443.       }
  1444.       return $.on(d, 'keydown', Keybinds.keydown);
  1445.     },
  1446.     keydown: function(e) {
  1447.       var key, link, o, target, thread;
  1448.       if (!(key = Keybinds.keyCode(e))) {
  1449.         return;
  1450.       }
  1451.       target = e.target;
  1452.       if (/TEXTAREA|INPUT/.test(target.nodeName)) {
  1453.         if (!((key === 'Esc') || (/\+/.test(key)))) {
  1454.           return;
  1455.         }
  1456.       }
  1457.       thread = Nav.getThread();
  1458.       switch (key) {
  1459.         case Conf.openQR:
  1460.           Keybinds.qr(thread, true);
  1461.           break;
  1462.         case Conf.openEmptyQR:
  1463.           Keybinds.qr(thread);
  1464.           break;
  1465.         case Conf.openOptions:
  1466.           if (!$.id('overlay')) {
  1467.             Options.dialog();
  1468.           }
  1469.           break;
  1470.         case Conf.close:
  1471.           if (o = $.id('overlay')) {
  1472.             Options.close.call(o);
  1473.           } else if (QR.el) {
  1474.             QR.close();
  1475.           }
  1476.           break;
  1477.         case Conf.submit:
  1478.           if (QR.el && !QR.status()) {
  1479.             QR.submit();
  1480.           }
  1481.           break;
  1482.         case Conf.spoiler:
  1483.           if (target.nodeName !== 'TEXTAREA') {
  1484.             return;
  1485.           }
  1486.           Keybinds.tags('spoiler', target);
  1487.           break;
  1488.         case Conf.code:
  1489.           if (target.nodeName !== 'TEXTAREA') {
  1490.             return;
  1491.           }
  1492.           Keybinds.tags('code', target);
  1493.           break;
  1494.         case Conf.sageru:
  1495.           $("[name=email]", QR.el).value = "sage";
  1496.           QR.selected.email = "sage";
  1497.           break;
  1498.         case Conf.watch:
  1499.           Watcher.toggle(thread);
  1500.           break;
  1501.         case Conf.update:
  1502.           Updater.updateReset();
  1503.           break;
  1504.         case Conf.unreadCountTo0:
  1505.           Unread.replies = new $.RandomAccessList;
  1506.           Unread.update(true);
  1507.           break;
  1508.         case Conf.threading:
  1509.           QuoteThreading["public"].toggle();
  1510.           break;
  1511.         case Conf.expandImage:
  1512.           Keybinds.img(thread);
  1513.           break;
  1514.         case Conf.expandAllImages:
  1515.           Keybinds.img(thread, true);
  1516.           break;
  1517.         case Conf.zero:
  1518.           window.location = "/" + g.BOARD + "/0#delform";
  1519.           break;
  1520.         case Conf.nextPage:
  1521.           if (link = $('link[rel=next]', d.head)) {
  1522.             window.location = link.href + '#delform';
  1523.           }
  1524.           break;
  1525.         case Conf.previousPage:
  1526.           if (link = $('link[rel=prev]', d.head)) {
  1527.             window.location = link.href + '#delform';
  1528.           }
  1529.           break;
  1530.         case Conf.nextThread:
  1531.           if (g.REPLY) {
  1532.             return;
  1533.           }
  1534.           Nav.scroll(+1);
  1535.           break;
  1536.         case Conf.previousThread:
  1537.           if (g.REPLY) {
  1538.             return;
  1539.           }
  1540.           Nav.scroll(-1);
  1541.           break;
  1542.         case Conf.expandThread:
  1543.           ExpandThread.toggle(thread);
  1544.           break;
  1545.         case Conf.openThread:
  1546.           Keybinds.open(thread);
  1547.           break;
  1548.         case Conf.openThreadTab:
  1549.           Keybinds.open(thread, true);
  1550.           break;
  1551.         case Conf.nextReply:
  1552.           Keybinds.hl(+1, thread);
  1553.           break;
  1554.         case Conf.previousReply:
  1555.           Keybinds.hl(-1, thread);
  1556.           break;
  1557.         case Conf.hide:
  1558.           if (/\bthread\b/.test(thread.className)) {
  1559.             ThreadHiding.toggle(thread);
  1560.           }
  1561.           break;
  1562.         default:
  1563.           return;
  1564.       }
  1565.       return e.preventDefault();
  1566.     },
  1567.     keyCode: function(e) {
  1568.       var c, kc, key;
  1569.       key = (function() {
  1570.         switch (kc = e.keyCode) {
  1571.           case 8:
  1572.             return '';
  1573.           case 27:
  1574.             return 'Esc';
  1575.           case 37:
  1576.             return 'Left';
  1577.           case 38:
  1578.             return 'Up';
  1579.           case 39:
  1580.             return 'Right';
  1581.           case 40:
  1582.             return 'Down';
  1583.           case 48:
  1584.           case 49:
  1585.           case 50:
  1586.           case 51:
  1587.           case 52:
  1588.           case 53:
  1589.           case 54:
  1590.           case 55:
  1591.           case 56:
  1592.           case 57:
  1593.           case 65:
  1594.           case 66:
  1595.           case 67:
  1596.           case 68:
  1597.           case 69:
  1598.           case 70:
  1599.           case 71:
  1600.           case 72:
  1601.           case 73:
  1602.           case 74:
  1603.           case 75:
  1604.           case 76:
  1605.           case 77:
  1606.           case 78:
  1607.           case 79:
  1608.           case 80:
  1609.           case 81:
  1610.           case 82:
  1611.           case 83:
  1612.           case 84:
  1613.           case 85:
  1614.           case 86:
  1615.           case 87:
  1616.           case 88:
  1617.           case 89:
  1618.           case 90:
  1619.             c = String.fromCharCode(kc);
  1620.             if (e.shiftKey) {
  1621.               return c;
  1622.             } else {
  1623.               return c.toLowerCase();
  1624.             }
  1625.             break;
  1626.           default:
  1627.             return null;
  1628.         }
  1629.       })();
  1630.       if (key) {
  1631.         if (e.altKey) {
  1632.           key = 'alt+' + key;
  1633.         }
  1634.         if (e.ctrlKey) {
  1635.           key = 'ctrl+' + key;
  1636.         }
  1637.         if (e.metaKey) {
  1638.           key = 'meta+' + key;
  1639.         }
  1640.       }
  1641.       return key;
  1642.     },
  1643.     tags: function(tag, ta) {
  1644.       var range, selEnd, selStart, value;
  1645.       value = ta.value;
  1646.       selStart = ta.selectionStart;
  1647.       selEnd = ta.selectionEnd;
  1648.       ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd);
  1649.       range = ("[" + tag + "]").length + selEnd;
  1650.       ta.setSelectionRange(range, range);
  1651.       return $.event(ta, new Event('input'));
  1652.     },
  1653.     img: function(thread, all) {
  1654.       var thumb;
  1655.       if (all) {
  1656.         return $.id('imageExpand').click();
  1657.       } else {
  1658.         thumb = $('img[data-md5]', $('.post.highlight', thread) || thread);
  1659.         return ImageExpand.toggle(thumb.parentNode);
  1660.       }
  1661.     },
  1662.     qr: function(thread, quote) {
  1663.       if (quote) {
  1664.         QR.quote.call($('a[title="Quote this post"]', $('.post.highlight', thread) || thread));
  1665.       } else {
  1666.         QR.open();
  1667.       }
  1668.       return $('textarea', QR.el).focus();
  1669.     },
  1670.     open: function(thread, tab) {
  1671.       var id, url;
  1672.       id = thread.id.slice(1);
  1673.       url = "//boards.4chan.org/" + g.BOARD + "/res/" + id;
  1674.       if (tab) {
  1675.         return $.open(url);
  1676.       } else {
  1677.         return location.href = url;
  1678.       }
  1679.     },
  1680.     hl: function(delta, thread) {
  1681.       var axis, next, post, rect, replies, reply, _i, _len;
  1682.       if (post = $('.reply.highlight', thread)) {
  1683.         $.rmClass(post, 'highlight');
  1684.         rect = post.getBoundingClientRect();
  1685.         if (rect.bottom >= 0 && rect.top <= d.documentElement.clientHeight) {
  1686.           axis = delta === +1 ? 'following' : 'preceding';
  1687.           next = $.x(axis + '::div[contains(@class,"post reply")][1]', post);
  1688.           if (!next) {
  1689.             return;
  1690.           }
  1691.           if (!(g.REPLY || $.x('ancestor::div[parent::div[@class="board"]]', next) === thread)) {
  1692.             return;
  1693.           }
  1694.           rect = next.getBoundingClientRect();
  1695.           if (rect.top < 0 || rect.bottom > d.documentElement.clientHeight) {
  1696.             next.scrollIntoView(delta === -1);
  1697.           }
  1698.           this.focus(next);
  1699.           return;
  1700.         }
  1701.       }
  1702.       replies = $$('.reply', thread);
  1703.       if (delta === -1) {
  1704.         replies.reverse();
  1705.       }
  1706.       for (_i = 0, _len = replies.length; _i < _len; _i++) {
  1707.         reply = replies[_i];
  1708.         rect = reply.getBoundingClientRect();
  1709.         if (delta === +1 && rect.top >= 0 || delta === -1 && rect.bottom <= d.documentElement.clientHeight) {
  1710.           this.focus(reply);
  1711.           return;
  1712.         }
  1713.       }
  1714.     },
  1715.     focus: function(post) {
  1716.       $.addClass(post, 'highlight');
  1717.       return post.focus();
  1718.     }
  1719.   };
  1720.  
  1721.   Nav = {
  1722.     init: function() {
  1723.       var next, prev, span;
  1724.       span = $.el('span', {
  1725.         id: 'navlinks'
  1726.       });
  1727.       prev = $.el('a', {
  1728.         textContent: 'â–²',
  1729.         href: 'javascript:;'
  1730.       });
  1731.       next = $.el('a', {
  1732.         textContent: 'â–¼',
  1733.         href: 'javascript:;'
  1734.       });
  1735.       $.on(prev, 'click', this.prev);
  1736.       $.on(next, 'click', this.next);
  1737.       $.add(span, [prev, $.tn(' '), next]);
  1738.       return $.add(d.body, span);
  1739.     },
  1740.     prev: function() {
  1741.       if (g.REPLY) {
  1742.         return window.scrollTo(0, 0);
  1743.       } else {
  1744.         return Nav.scroll(-1);
  1745.       }
  1746.     },
  1747.     next: function() {
  1748.       if (g.REPLY) {
  1749.         return window.scrollTo(0, d.body.scrollHeight);
  1750.       } else {
  1751.         return Nav.scroll(+1);
  1752.       }
  1753.     },
  1754.     getThread: function(full) {
  1755.       var bottom, i, rect, thread, _i, _len, _ref;
  1756.       Nav.threads = $$('.thread:not(.hidden)');
  1757.       _ref = Nav.threads;
  1758.       for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
  1759.         thread = _ref[i];
  1760.         rect = thread.getBoundingClientRect();
  1761.         bottom = rect.bottom;
  1762.         if (bottom > 0) {
  1763.           if (full) {
  1764.             return [thread, i, rect];
  1765.           }
  1766.           return thread;
  1767.         }
  1768.       }
  1769.       return $('.board');
  1770.     },
  1771.     scroll: function(delta) {
  1772.       var i, link, rect, thread, top, _ref, _ref1;
  1773.       _ref = Nav.getThread(true), thread = _ref[0], i = _ref[1], rect = _ref[2];
  1774.       top = rect.top;
  1775.       if (!((delta === -1 && Math.ceil(top) < 0) || (delta === +1 && top > 1))) {
  1776.         i += delta;
  1777.       }
  1778.       if (Conf['Rollover']) {
  1779.         if (i === -1) {
  1780.           if (link = $('link[rel=prev]', d.head)) {
  1781.             window.location = link.href + '#delform';
  1782.           } else {
  1783.             window.location = "/" + g.BOARD + "/0#delform";
  1784.           }
  1785.           return;
  1786.         }
  1787.         if ((delta === +1) && ((i === Nav.threads.length) || (innerHeight + pageYOffset === d.body.scrollHeight))) {
  1788.           if (link = $('link[rel=next]', d.head)) {
  1789.             window.location = link.href + '#delform';
  1790.             return;
  1791.           }
  1792.         }
  1793.       }
  1794.       top = (_ref1 = Nav.threads[i]) != null ? _ref1.getBoundingClientRect().top : void 0;
  1795.       return window.scrollBy(0, top);
  1796.     }
  1797.   };
  1798.  
  1799.   QR = {
  1800.     init: function() {
  1801.       var link;
  1802.       if (!$.id('postForm')) {
  1803.         return;
  1804.       }
  1805.       Main.callbacks.push(this.node);
  1806.       if (Conf['Hide Original Post Form']) {
  1807.         link = $.el('h1', {
  1808.           innerHTML: "<a href=javascript:;>" + (g.REPLY ? 'Reply to Thread' : 'Start a Thread') + "</a>"
  1809.         });
  1810.         $.on(link.firstChild, 'click', function() {
  1811.           QR.open();
  1812.           if (!g.REPLY) {
  1813.             $('select', QR.el).value = 'new';
  1814.           }
  1815.           return $('textarea', QR.el).focus();
  1816.         });
  1817.         $.before($.id('postForm'), link);
  1818.       }
  1819.       if (Conf['Persistent QR']) {
  1820.         QR.dialog();
  1821.         if (Conf['Auto Hide QR']) {
  1822.           QR.hide();
  1823.         }
  1824.       }
  1825.       $.on(d, 'dragover', QR.dragOver);
  1826.       $.on(d, 'drop', QR.dropFile);
  1827.       return $.on(d, 'dragstart dragend', QR.drag);
  1828.     },
  1829.     node: function(post) {
  1830.       return $.on($('a[title="Quote this post"]', post.el), 'click', QR.quote);
  1831.     },
  1832.     open: function() {
  1833.       if (QR.el) {
  1834.         QR.el.hidden = false;
  1835.         return QR.unhide();
  1836.       } else {
  1837.         return QR.dialog();
  1838.       }
  1839.     },
  1840.     close: function() {
  1841.       var i, spoiler, _i, _len, _ref;
  1842.       QR.el.hidden = true;
  1843.       QR.abort();
  1844.       d.activeElement.blur();
  1845.       $.rmClass(QR.el, 'dump');
  1846.       _ref = QR.replies;
  1847.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1848.         i = _ref[_i];
  1849.         QR.replies[0].rm();
  1850.       }
  1851.       QR.cooldown.auto = false;
  1852.       QR.status();
  1853.       QR.resetFileInput();
  1854.       if (!Conf['Remember Spoiler'] && (spoiler = $.id('spoiler')).checked) {
  1855.         spoiler.click();
  1856.       }
  1857.       return QR.cleanError();
  1858.     },
  1859.     hide: function() {
  1860.       d.activeElement.blur();
  1861.       $.addClass(QR.el, 'autohide');
  1862.       return $.id('autohide').checked = true;
  1863.     },
  1864.     unhide: function() {
  1865.       $.rmClass(QR.el, 'autohide');
  1866.       return $.id('autohide').checked = false;
  1867.     },
  1868.     toggleHide: function() {
  1869.       return this.checked && QR.hide() || QR.unhide();
  1870.     },
  1871.     error: function(err) {
  1872.       var el;
  1873.       el = $('.warning', QR.el);
  1874.       if (typeof err === 'string') {
  1875.         el.textContent = err;
  1876.       } else {
  1877.         el.innerHTML = null;
  1878.         $.add(el, err);
  1879.       }
  1880.       QR.open();
  1881.       if (/captcha|verification/i.test(el.textContent)) {
  1882.         $('[autocomplete]', QR.el).focus();
  1883.       }
  1884.       if (d.hidden || d.oHidden || d.mozHidden || d.webkitHidden) {
  1885.         return alert(el.textContent);
  1886.       }
  1887.     },
  1888.     cleanError: function() {
  1889.       return $('.warning', QR.el).textContent = null;
  1890.     },
  1891.     status: function(data) {
  1892.       var disabled, input, value;
  1893.       if (data == null) {
  1894.         data = {};
  1895.       }
  1896.       if (!QR.el) {
  1897.         return;
  1898.       }
  1899.       if (g.dead) {
  1900.         value = 404;
  1901.         disabled = true;
  1902.         QR.cooldown.auto = false;
  1903.       }
  1904.       value = QR.cooldown.seconds || data.progress || value;
  1905.       input = QR.status.input;
  1906.       input.value = QR.cooldown.auto && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit';
  1907.       return input.disabled = disabled || false;
  1908.     },
  1909.     cooldown: {
  1910.       init: function() {
  1911.         if (!Conf['Cooldown']) {
  1912.           return;
  1913.         }
  1914.         QR.cooldown.start($.get("/" + g.BOARD + "/cooldown", 0));
  1915.         return $.sync("/" + g.BOARD + "/cooldown", QR.cooldown.start);
  1916.       },
  1917.       start: function(timeout) {
  1918.         var seconds;
  1919.         seconds = Math.floor((timeout - Date.now()) / 1000);
  1920.         return QR.cooldown.count(seconds);
  1921.       },
  1922.       set: function(seconds) {
  1923.         if (!Conf['Cooldown']) {
  1924.           return;
  1925.         }
  1926.         QR.cooldown.count(seconds);
  1927.         return $.set("/" + g.BOARD + "/cooldown", Date.now() + seconds * $.SECOND);
  1928.       },
  1929.       count: function(seconds) {
  1930.         if (!((0 <= seconds && seconds <= 60))) {
  1931.           return;
  1932.         }
  1933.         setTimeout(QR.cooldown.count, 1000, seconds - 1);
  1934.         QR.cooldown.seconds = seconds;
  1935.         if (seconds === 0) {
  1936.           $["delete"]("/" + g.BOARD + "/cooldown");
  1937.           if (QR.cooldown.auto) {
  1938.             QR.submit();
  1939.           }
  1940.         }
  1941.         return QR.status();
  1942.       }
  1943.     },
  1944.     quote: function(e) {
  1945.       var caretPos, id, range, s, sel, ta, text, _ref;
  1946.       if (e != null) {
  1947.         e.preventDefault();
  1948.       }
  1949.       QR.open();
  1950.       if (!g.REPLY) {
  1951.         $('select', QR.el).value = $.x('ancestor::div[parent::div[@class="board"]]', this).id.slice(1);
  1952.       }
  1953.       id = this.previousSibling.hash.slice(2);
  1954.       text = ">>" + id + "\n";
  1955.       sel = window.getSelection();
  1956.       if ((s = sel.toString()) && id === ((_ref = $.x('ancestor-or-self::blockquote', sel.anchorNode)) != null ? _ref.id.match(/\d+$/)[0] : void 0)) {
  1957.         if ($.engine === 'presto') {
  1958.           s = d.getSelection();
  1959.         }
  1960.         s = s.replace(/\n/g, '\n>');
  1961.         text += ">" + s + "\n";
  1962.       }
  1963.       ta = $('textarea', QR.el);
  1964.       caretPos = ta.selectionStart;
  1965.       ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd);
  1966.       ta.focus();
  1967.       range = caretPos + text.length;
  1968.       if ($.engine === 'presto') {
  1969.         range += text.match(/\n/g).length;
  1970.       }
  1971.       ta.setSelectionRange(range, range);
  1972.       return $.event(ta, new Event('input'));
  1973.     },
  1974.     characterCount: function() {
  1975.       var count, counter;
  1976.       counter = QR.charaCounter;
  1977.       count = this.textLength;
  1978.       counter.textContent = count;
  1979.       counter.hidden = count < 1000;
  1980.       return (count > 1500 ? $.addClass : $.rmClass)(counter, 'warning');
  1981.     },
  1982.     drag: function(e) {
  1983.       var i;
  1984.       i = e.type === 'dragstart' ? 'off' : 'on';
  1985.       $[i](d, 'dragover', QR.dragOver);
  1986.       return $[i](d, 'drop', QR.dropFile);
  1987.     },
  1988.     dragOver: function(e) {
  1989.       e.preventDefault();
  1990.       return e.dataTransfer.dropEffect = 'copy';
  1991.     },
  1992.     dropFile: function(e) {
  1993.       if (!e.dataTransfer.files.length) {
  1994.         return;
  1995.       }
  1996.       e.preventDefault();
  1997.       QR.open();
  1998.       QR.fileInput.call(e.dataTransfer);
  1999.       return $.addClass(QR.el, 'dump');
  2000.     },
  2001.     fileInput: function() {
  2002.       var file, _i, _len, _ref;
  2003.       QR.cleanError();
  2004.       if (this.files.length === 1) {
  2005.         file = this.files[0];
  2006.         if (file.size > this.max) {
  2007.           QR.error('File too large.');
  2008.           QR.resetFileInput();
  2009.         } else if (-1 === QR.mimeTypes.indexOf(file.type)) {
  2010.           QR.error('Unsupported file type.');
  2011.           QR.resetFileInput();
  2012.         } else {
  2013.           QR.selected.setFile(file);
  2014.         }
  2015.         return;
  2016.       }
  2017.       _ref = this.files;
  2018.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  2019.         file = _ref[_i];
  2020.         if (file.size > this.max) {
  2021.           QR.error("File " + file.name + " is too large.");
  2022.           break;
  2023.         } else if (-1 === QR.mimeTypes.indexOf(file.type)) {
  2024.           QR.error("" + file.name + ": Unsupported file type.");
  2025.           break;
  2026.         }
  2027.         if (!QR.replies[QR.replies.length - 1].file) {
  2028.           QR.replies[QR.replies.length - 1].setFile(file);
  2029.         } else {
  2030.           new QR.reply().setFile(file);
  2031.         }
  2032.       }
  2033.       $.addClass(QR.el, 'dump');
  2034.       return QR.resetFileInput();
  2035.     },
  2036.     resetFileInput: function() {
  2037.       var clone, input;
  2038.       input = $('[type=file]', QR.el);
  2039.       input.value = null;
  2040.       if ($.engine !== 'presto') {
  2041.         return;
  2042.       }
  2043.       clone = $.el('input', {
  2044.         type: 'file',
  2045.         accept: input.accept,
  2046.         max: input.max,
  2047.         multiple: input.multiple,
  2048.         size: input.size,
  2049.         title: input.title
  2050.       });
  2051.       $.on(clone, 'change', QR.fileInput);
  2052.       $.on(clone, 'click', function(e) {
  2053.         if (e.shiftKey) {
  2054.           return QR.selected.rmFile() || e.preventDefault();
  2055.         }
  2056.       });
  2057.       return $.replace(input, clone);
  2058.     },
  2059.     replies: [],
  2060.     reply: (function() {
  2061.  
  2062.       function _Class() {
  2063.         var persona, prev,
  2064.           _this = this;
  2065.         prev = QR.replies[QR.replies.length - 1];
  2066.         persona = $.get('QR.persona', {});
  2067.         this.name = prev ? prev.name : persona.name || null;
  2068.         this.email = prev && !/^sage$/.test(prev.email) ? prev.email : Conf['Sage on /jp/'] && g.BOARD === 'jp' ? 'sage' : persona.email || null;
  2069.         this.sub = prev && Conf['Remember Subject'] ? prev.sub : Conf['Remember Subject'] ? persona.sub : null;
  2070.         this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
  2071.         this.com = null;
  2072.         this.el = $.el('a', {
  2073.           className: 'thumbnail',
  2074.           draggable: true,
  2075.           href: 'javascript:;',
  2076.           innerHTML: '<a class=remove>X</a><label hidden><input type=checkbox> Spoiler</label><span></span>'
  2077.         });
  2078.         $('input', this.el).checked = this.spoiler;
  2079.         $.on(this.el, 'click', function() {
  2080.           return _this.select();
  2081.         });
  2082.         $.on($('.remove', this.el), 'click', function(e) {
  2083.           e.stopPropagation();
  2084.           return _this.rm();
  2085.         });
  2086.         $.on($('label', this.el), 'click', function(e) {
  2087.           return e.stopPropagation();
  2088.         });
  2089.         $.on($('input', this.el), 'change', function(e) {
  2090.           _this.spoiler = e.target.checked;
  2091.           if (_this.el.id === 'selected') {
  2092.             return $.id('spoiler').checked = _this.spoiler;
  2093.           }
  2094.         });
  2095.         $.before($('#addReply', QR.el), this.el);
  2096.         $.on(this.el, 'dragstart', this.dragStart);
  2097.         $.on(this.el, 'dragenter', this.dragEnter);
  2098.         $.on(this.el, 'dragleave', this.dragLeave);
  2099.         $.on(this.el, 'dragover', this.dragOver);
  2100.         $.on(this.el, 'dragend', this.dragEnd);
  2101.         $.on(this.el, 'drop', this.drop);
  2102.         QR.replies.push(this);
  2103.       }
  2104.  
  2105.       _Class.prototype.setFile = function(file) {
  2106.         var fileUrl, img, url,
  2107.           _this = this;
  2108.         this.file = file;
  2109.         this.el.title = "" + file.name + " (" + ($.bytesToString(file.size)) + ")";
  2110.         if (QR.spoiler) {
  2111.           $('label', this.el).hidden = false;
  2112.         }
  2113.         if (!/^image/.test(file.type)) {
  2114.           this.el.style.backgroundImage = null;
  2115.           return;
  2116.         }
  2117.         url = window.URL || window.webkitURL;
  2118.         if (typeof url.revokeObjectURL === "function") {
  2119.           url.revokeObjectURL(this.url);
  2120.         }
  2121.         fileUrl = url.createObjectURL(file);
  2122.         img = $.el('img');
  2123.         $.on(img, 'load', function() {
  2124.           var bb, c, data, i, l, s, ui8a, _i;
  2125.           s = 90 * 3;
  2126.           if (img.height < s || img.width < s) {
  2127.             _this.url = fileUrl;
  2128.             _this.el.style.backgroundImage = "url(" + _this.url + ")";
  2129.             return;
  2130.           }
  2131.           if (img.height <= img.width) {
  2132.             img.width = s / img.height * img.width;
  2133.             img.height = s;
  2134.           } else {
  2135.             img.height = s / img.width * img.height;
  2136.             img.width = s;
  2137.           }
  2138.           c = $.el('canvas');
  2139.           c.height = img.height;
  2140.           c.width = img.width;
  2141.           c.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
  2142.           data = atob(c.toDataURL().split(',')[1]);
  2143.           l = data.length;
  2144.           ui8a = new Uint8Array(l);
  2145.           for (i = _i = 0; 0 <= l ? _i < l : _i > l; i = 0 <= l ? ++_i : --_i) {
  2146.             ui8a[i] = data.charCodeAt(i);
  2147.           }
  2148.           bb = new (window.MozBlobBuilder || window.WebKitBlobBuilder)();
  2149.           bb.append(ui8a.buffer);
  2150.           _this.url = url.createObjectURL(bb.getBlob('image/png'));
  2151.           _this.el.style.backgroundImage = "url(" + _this.url + ")";
  2152.           return typeof url.revokeObjectURL === "function" ? url.revokeObjectURL(fileUrl) : void 0;
  2153.         });
  2154.         return img.src = fileUrl;
  2155.       };
  2156.  
  2157.       _Class.prototype.rmFile = function() {
  2158.         var _base1;
  2159.         QR.resetFileInput();
  2160.         delete this.file;
  2161.         this.el.title = null;
  2162.         this.el.style.backgroundImage = null;
  2163.         if (QR.spoiler) {
  2164.           $('label', this.el).hidden = true;
  2165.         }
  2166.         return typeof (_base1 = window.URL || window.webkitURL).revokeObjectURL === "function" ? _base1.revokeObjectURL(this.url) : void 0;
  2167.       };
  2168.  
  2169.       _Class.prototype.select = function() {
  2170.         var data, rectEl, rectList, _i, _len, _ref, _ref1;
  2171.         if ((_ref = QR.selected) != null) {
  2172.           _ref.el.id = null;
  2173.         }
  2174.         QR.selected = this;
  2175.         this.el.id = 'selected';
  2176.         rectEl = this.el.getBoundingClientRect();
  2177.         rectList = this.el.parentNode.getBoundingClientRect();
  2178.         this.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
  2179.         _ref1 = ['name', 'email', 'sub', 'com'];
  2180.         for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
  2181.           data = _ref1[_i];
  2182.           $("[name=" + data + "]", QR.el).value = this[data];
  2183.         }
  2184.         QR.characterCount.call($('textarea', QR.el));
  2185.         return $('#spoiler', QR.el).checked = this.spoiler;
  2186.       };
  2187.  
  2188.       _Class.prototype.dragStart = function() {
  2189.         return $.addClass(this, 'drag');
  2190.       };
  2191.  
  2192.       _Class.prototype.dragEnter = function() {
  2193.         return $.addClass(this, 'over');
  2194.       };
  2195.  
  2196.       _Class.prototype.dragLeave = function() {
  2197.         return $.rmClass(this, 'over');
  2198.       };
  2199.  
  2200.       _Class.prototype.dragOver = function(e) {
  2201.         e.preventDefault();
  2202.         return e.dataTransfer.dropEffect = 'move';
  2203.       };
  2204.  
  2205.       _Class.prototype.drop = function() {
  2206.         var el, index, newIndex, oldIndex, reply;
  2207.         el = $('.drag', this.parentNode);
  2208.         index = function(el) {
  2209.           return Array.prototype.slice.call(el.parentNode.children).indexOf(el);
  2210.         };
  2211.         oldIndex = index(el);
  2212.         newIndex = index(this);
  2213.         if (oldIndex < newIndex) {
  2214.           $.after(this, el);
  2215.         } else {
  2216.           $.before(this, el);
  2217.         }
  2218.         reply = QR.replies.splice(oldIndex, 1)[0];
  2219.         return QR.replies.splice(newIndex, 0, reply);
  2220.       };
  2221.  
  2222.       _Class.prototype.dragEnd = function() {
  2223.         var el;
  2224.         $.rmClass(this, 'drag');
  2225.         if (el = $('.over', this.parentNode)) {
  2226.           return $.rmClass(el, 'over');
  2227.         }
  2228.       };
  2229.  
  2230.       _Class.prototype.rm = function() {
  2231.         var index, _base1;
  2232.         QR.resetFileInput();
  2233.         $.rm(this.el);
  2234.         index = QR.replies.indexOf(this);
  2235.         if (QR.replies.length === 1) {
  2236.           new QR.reply().select();
  2237.         } else if (this.el.id === 'selected') {
  2238.           (QR.replies[index - 1] || QR.replies[index + 1]).select();
  2239.         }
  2240.         QR.replies.splice(index, 1);
  2241.         if (typeof (_base1 = window.URL || window.webkitURL).revokeObjectURL === "function") {
  2242.           _base1.revokeObjectURL(this.url);
  2243.         }
  2244.         return delete this;
  2245.       };
  2246.  
  2247.       return _Class;
  2248.  
  2249.     })(),
  2250.     captcha: {
  2251.       init: function() {
  2252.         var _this = this;
  2253.         if (!(QR.captchaIsEnabled = !!$.id('captchaFormPart'))) {
  2254.           return;
  2255.         }
  2256.         if ($.id('recaptcha_challenge_field_holder')) {
  2257.           return this.ready();
  2258.         } else {
  2259.           this.onready = function() {
  2260.             return _this.ready();
  2261.           };
  2262.           return $.on($.id('recaptcha_widget_div'), 'DOMNodeInserted', this.onready);
  2263.         }
  2264.       },
  2265.       ready: function() {
  2266.         var _this = this;
  2267.         if (this.challenge = $.id('recaptcha_challenge_field_holder')) {
  2268.           $.off($.id('recaptcha_widget_div'), 'DOMNodeInserted', this.onready);
  2269.           delete this.onready;
  2270.         } else {
  2271.           return;
  2272.         }
  2273.         $.after($('.textarea', QR.el), $.el('div', {
  2274.           className: 'captchaimg',
  2275.           title: 'Reload',
  2276.           innerHTML: '<img>'
  2277.         }));
  2278.         $.after($('.captchaimg', QR.el), $.el('div', {
  2279.           className: 'captchainput',
  2280.           innerHTML: '<input title=Verification class=field autocomplete=off size=1>'
  2281.         }));
  2282.         this.img = $('.captchaimg > img', QR.el);
  2283.         this.input = $('.captchainput > input', QR.el);
  2284.         $.on(this.img.parentNode, 'click', this.reload);
  2285.         $.on(this.input, 'keydown', this.keydown);
  2286.         $.on(this.challenge, 'DOMNodeInserted', function() {
  2287.           return _this.load();
  2288.         });
  2289.         $.sync('captchas', function(arr) {
  2290.           return _this.count(arr.length);
  2291.         });
  2292.         this.count($.get('captchas', []).length);
  2293.         return this.reload();
  2294.       },
  2295.       save: function() {
  2296.         var captcha, captchas, response;
  2297.         if (!(response = this.input.value)) {
  2298.           return;
  2299.         }
  2300.         captchas = $.get('captchas', []);
  2301.         while ((captcha = captchas[0]) && captcha.time < Date.now()) {
  2302.           captchas.shift();
  2303.         }
  2304.         captchas.push({
  2305.           challenge: this.challenge.firstChild.value,
  2306.           response: response,
  2307.           time: this.timeout
  2308.         });
  2309.         $.set('captchas', captchas);
  2310.         this.count(captchas.length);
  2311.         return this.reload();
  2312.       },
  2313.       load: function() {
  2314.         var challenge;
  2315.         this.timeout = Date.now() + 4 * $.MINUTE;
  2316.         challenge = this.challenge.firstChild.value;
  2317.         this.img.alt = challenge;
  2318.         this.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge;
  2319.         return this.input.value = null;
  2320.       },
  2321.       count: function(count) {
  2322.         this.input.placeholder = (function() {
  2323.           switch (count) {
  2324.             case 0:
  2325.               return 'Verification (Shift + Enter to cache)';
  2326.             case 1:
  2327.               return 'Verification (1 cached captcha)';
  2328.             default:
  2329.               return "Verification (" + count + " cached captchas)";
  2330.           }
  2331.         })();
  2332.         return this.input.alt = count;
  2333.       },
  2334.       reload: function(focus) {
  2335.         window.location = 'javascript:Recaptcha.reload("t")';
  2336.         if (focus) {
  2337.           return QR.captcha.input.focus();
  2338.         }
  2339.       },
  2340.       keydown: function(e) {
  2341.         var c;
  2342.         c = QR.captcha;
  2343.         if (e.keyCode === 8 && !c.input.value) {
  2344.           c.reload();
  2345.         } else if (e.keyCode === 13 && e.shiftKey) {
  2346.           c.save();
  2347.         } else {
  2348.           return;
  2349.         }
  2350.         return e.preventDefault();
  2351.       }
  2352.     },
  2353.     dialog: function() {
  2354.       var fileInput, id, mimeTypes, name, spoiler, ta, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
  2355.       QR.el = UI.dialog('qr', 'top:0;right:0;', '\
  2356. <div class=move>\
  2357.   Quick Reply <input type=checkbox id=autohide title=Auto-hide>\
  2358.   <span> <a class=close title=Close>×</a></span>\
  2359. </div>\
  2360. <form>\
  2361.   <div><input id=dump type=button title="Dump list" value=+ class=field><input name=name title=Name placeholder=Name class=field size=1><input name=email title=E-mail placeholder=E-mail class=field size=1><input name=sub title=Subject placeholder=Subject class=field size=1></div>\
  2362.   <div id=replies><div><a id=addReply href=javascript:; title="Add a reply">+</a></div></div>\
  2363.   <div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span></div>\
  2364.   <div><input type=file title="Shift+Click to remove the selected file." multiple size=16><input type=submit></div>\
  2365.   <label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image</label>\
  2366.   <div class=warning></div>\
  2367. </form>');
  2368.       if (Conf['Remember QR size'] && $.engine === 'gecko') {
  2369.         $.on(ta = $('textarea', QR.el), 'mouseup', function() {
  2370.           return $.set('QR.size', this.style.cssText);
  2371.         });
  2372.         ta.style.cssText = $.get('QR.size', '');
  2373.       }
  2374.       mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace(/\w+/g, function(type) {
  2375.         switch (type) {
  2376.           case 'jpg':
  2377.             return 'image/jpeg';
  2378.           case 'pdf':
  2379.             return 'application/pdf';
  2380.           case 'swf':
  2381.             return 'application/x-shockwave-flash';
  2382.           default:
  2383.             return "image/" + type;
  2384.         }
  2385.       });
  2386.       QR.mimeTypes = mimeTypes.split(', ');
  2387.       QR.mimeTypes.push('');
  2388.       fileInput = $('input[type=file]', QR.el);
  2389.       fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
  2390.       if ($.engine !== 'presto') {
  2391.         fileInput.accept = mimeTypes;
  2392.       }
  2393.       QR.spoiler = !!$('input[name=spoiler]');
  2394.       spoiler = $('#spoilerLabel', QR.el);
  2395.       spoiler.hidden = !QR.spoiler;
  2396.       QR.charaCounter = $('#charCount', QR.el);
  2397.       ta = $('textarea', QR.el);
  2398.       if (!g.REPLY) {
  2399.         threads = '<option value=new>New thread</option>';
  2400.         _ref = $$('.thread');
  2401.         for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  2402.           thread = _ref[_i];
  2403.           id = thread.id.slice(1);
  2404.           threads += "<option value=" + id + ">Thread " + id + "</option>";
  2405.         }
  2406.         $.prepend($('.move > span', QR.el), $.el('select', {
  2407.           innerHTML: threads,
  2408.           title: 'Create a new thread / Reply to a thread'
  2409.         }));
  2410.         $.on($('select', QR.el), 'mousedown', function(e) {
  2411.           return e.stopPropagation();
  2412.         });
  2413.       }
  2414.       $.on($('#autohide', QR.el), 'change', QR.toggleHide);
  2415.       $.on($('.close', QR.el), 'click', QR.close);
  2416.       $.on($('#dump', QR.el), 'click', function() {
  2417.         return QR.el.classList.toggle('dump');
  2418.       });
  2419.       $.on($('#addReply', QR.el), 'click', function() {
  2420.         return new QR.reply().select();
  2421.       });
  2422.       $.on($('form', QR.el), 'submit', QR.submit);
  2423.       $.on(ta, 'input', function() {
  2424.         return QR.selected.el.lastChild.textContent = this.value;
  2425.       });
  2426.       $.on(ta, 'input', QR.characterCount);
  2427.       $.on(fileInput, 'change', QR.fileInput);
  2428.       $.on(fileInput, 'click', function(e) {
  2429.         if (e.shiftKey) {
  2430.           return QR.selected.rmFile() || e.preventDefault();
  2431.         }
  2432.       });
  2433.       $.on(spoiler.firstChild, 'change', function() {
  2434.         return $('input', QR.selected.el).click();
  2435.       });
  2436.       $.on($('.warning', QR.el), 'click', QR.cleanError);
  2437.       new QR.reply().select();
  2438.       _ref1 = ['name', 'email', 'sub', 'com'];
  2439.       for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  2440.         name = _ref1[_j];
  2441.         $.on($("[name=" + name + "]", QR.el), 'input', function() {
  2442.           var _ref2;
  2443.           QR.selected[this.name] = this.value;
  2444.           if (QR.cooldown.auto && QR.selected === QR.replies[0] && (0 < (_ref2 = QR.cooldown.seconds) && _ref2 < 6)) {
  2445.             return QR.cooldown.auto = false;
  2446.           }
  2447.         });
  2448.       }
  2449.       QR.status.input = $('input[type=submit]', QR.el);
  2450.       QR.status();
  2451.       QR.cooldown.init();
  2452.       QR.captcha.init();
  2453.       $.add(d.body, QR.el);
  2454.       return $.event(QR.el, new CustomEvent('QRDialogCreation', {
  2455.         bubbles: true
  2456.       }));
  2457.     },
  2458.     submit: function(e) {
  2459.       var callbacks, captcha, captchas, challenge, err, m, opts, post, reply, response, threadID;
  2460.       if (e != null) {
  2461.         e.preventDefault();
  2462.       }
  2463.       if (QR.cooldown.seconds) {
  2464.         QR.cooldown.auto = !QR.cooldown.auto;
  2465.         QR.status();
  2466.         return;
  2467.       }
  2468.       QR.abort();
  2469.       reply = QR.replies[0];
  2470.       threadID = g.THREAD_ID || $('select', QR.el).value;
  2471.       if (!(threadID === 'new' && reply.file || threadID !== 'new' && (reply.com || reply.file))) {
  2472.         err = 'No file selected.';
  2473.       } else if (QR.captchaIsEnabled) {
  2474.         captchas = $.get('captchas', []);
  2475.         while ((captcha = captchas[0]) && captcha.time < Date.now()) {
  2476.           captchas.shift();
  2477.         }
  2478.         if (captcha = captchas.shift()) {
  2479.           challenge = captcha.challenge;
  2480.           response = captcha.response;
  2481.         } else {
  2482.           challenge = QR.captcha.img.alt;
  2483.           if (response = QR.captcha.input.value) {
  2484.             QR.captcha.reload();
  2485.           }
  2486.         }
  2487.         $.set('captchas', captchas);
  2488.         QR.captcha.count(captchas.length);
  2489.         if (!response) {
  2490.           err = 'No valid captcha.';
  2491.         }
  2492.       }
  2493.       if (err) {
  2494.         QR.cooldown.auto = false;
  2495.         QR.status();
  2496.         QR.error(err);
  2497.         return;
  2498.       }
  2499.       QR.cleanError();
  2500.       QR.cooldown.auto = QR.replies.length > 1;
  2501.       if (Conf['Auto Hide QR'] && !QR.cooldown.auto) {
  2502.         QR.hide();
  2503.       }
  2504.       if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) {
  2505.         d.activeElement.blur();
  2506.       }
  2507.       QR.status({
  2508.         progress: '...'
  2509.       });
  2510.       post = {
  2511.         resto: threadID,
  2512.         name: reply.name,
  2513.         email: reply.email,
  2514.         sub: reply.sub,
  2515.         com: reply.com,
  2516.         upfile: reply.file,
  2517.         spoiler: reply.spoiler,
  2518.         mode: 'regist',
  2519.         pwd: (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value,
  2520.         recaptcha_challenge_field: challenge,
  2521.         recaptcha_response_field: response + ' '
  2522.       };
  2523.       callbacks = {
  2524.         onload: function() {
  2525.           return QR.response(this.response);
  2526.         },
  2527.         onerror: function() {
  2528.           QR.status();
  2529.           return QR.error($.el('a', {
  2530.             href: '//www.4chan.org/banned',
  2531.             target: '_blank',
  2532.             textContent: 'Connection error, or you are banned.'
  2533.           }));
  2534.         }
  2535.       };
  2536.       opts = {
  2537.         form: $.formData(post),
  2538.         upCallbacks: {
  2539.           onload: function() {
  2540.             return QR.status({
  2541.               progress: '...'
  2542.             });
  2543.           },
  2544.           onprogress: function(e) {
  2545.             return QR.status({
  2546.               progress: "" + (Math.round(e.loaded / e.total * 100)) + "%"
  2547.             });
  2548.           }
  2549.         }
  2550.       };
  2551.       return QR.ajax = $.ajax($.id('postForm').parentNode.action, callbacks, opts);
  2552.     },
  2553.     response: function(html) {
  2554.       var bs, doc, err, msg, persona, postID, reply, threadID, _, _ref;
  2555.       doc = d.implementation.createHTMLDocument('');
  2556.       doc.documentElement.innerHTML = html;
  2557.       if (doc.title === '4chan - Banned') {
  2558.         bs = $$('b', doc);
  2559.         err = $.el('span', {
  2560.           innerHTML: /^You were issued a warning/.test($('.boxcontent', doc).textContent.trim()) ? "You were issued a warning on " + bs[0].innerHTML + " as " + bs[3].innerHTML + ".<br>Warning reason: " + bs[1].innerHTML : "You are banned! ;_;<br>Please click <a href=//www.4chan.org/banned target=_blank>HERE</a> to see the reason."
  2561.         });
  2562.       } else if (msg = doc.getElementById('errmsg')) {
  2563.         err = msg.textContent;
  2564.         if (msg.firstChild.tagName) {
  2565.           err = msg.firstChild;
  2566.           err.target = '_blank';
  2567.         }
  2568.       } else if (!(msg = $('b', doc))) {
  2569.         err = 'Connection error with sys.4chan.org.';
  2570.       }
  2571.       if (err) {
  2572.         if (/captcha|verification/i.test(err) || err === 'Connection error with sys.4chan.org.') {
  2573.           QR.cooldown.auto = !!$.get('captchas', []).length;
  2574.           QR.cooldown.set(2);
  2575.         } else {
  2576.           QR.cooldown.auto = false;
  2577.         }
  2578.         QR.status();
  2579.         QR.error(err);
  2580.         return;
  2581.       }
  2582.       reply = QR.replies[0];
  2583.       persona = $.get('QR.persona', {});
  2584.       persona = {
  2585.         name: reply.name,
  2586.         email: /^sage$/.test(reply.email) ? persona.email : reply.email,
  2587.         sub: Conf['Remember Subject'] ? reply.sub : null
  2588.       };
  2589.       $.set('QR.persona', persona);
  2590.       _ref = msg.lastChild.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref[0], threadID = _ref[1], postID = _ref[2];
  2591.       $.event(QR.el, new CustomEvent('QRPostSuccessful', {
  2592.         bubbles: true,
  2593.         detail: {
  2594.           threadID: threadID,
  2595.           postID: postID
  2596.         }
  2597.       }));
  2598.       if (threadID === '0') {
  2599.         location.pathname = "/" + g.BOARD + "/res/" + postID;
  2600.       } else {
  2601.         QR.cooldown.auto = QR.replies.length > 1;
  2602.         QR.cooldown.set(/sage/i.test(reply.email) ? 60 : 30);
  2603.         if (Conf['Open Reply in New Tab'] && !g.REPLY && !QR.cooldown.auto) {
  2604.           $.open("//boards.4chan.org/" + g.BOARD + "/res/" + threadID + "#p" + postID);
  2605.         }
  2606.       }
  2607.       if (Conf['Persistent QR'] || QR.cooldown.auto) {
  2608.         reply.rm();
  2609.       } else {
  2610.         QR.close();
  2611.       }
  2612.       QR.status();
  2613.       return QR.resetFileInput();
  2614.     },
  2615.     abort: function() {
  2616.       var _ref;
  2617.       if ((_ref = QR.ajax) != null) {
  2618.         _ref.abort();
  2619.       }
  2620.       delete QR.ajax;
  2621.       return QR.status();
  2622.     }
  2623.   };
  2624.  
  2625.   Options = {
  2626.     init: function() {
  2627.       var a, el, settings, _i, _len, _ref;
  2628.       _ref = ['navtopr', 'navbotr'];
  2629.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  2630.         settings = _ref[_i];
  2631.         a = $.el('a', {
  2632.           href: 'javascript:;',
  2633.           className: 'settingsWindowLink',
  2634.           textContent: '4chan X Settings'
  2635.         });
  2636.         $.on(a, 'click', Options.dialog);
  2637.         el = $.id(settings).firstElementChild;
  2638.         el.hidden = true;
  2639.         $.before(el, a);
  2640.       }
  2641.       if (!$.get('firstrun')) {
  2642.         $.set('firstrun', true);
  2643.         return Options.dialog();
  2644.       }
  2645.     },
  2646.     dialog: function() {
  2647.       var arr, back, checked, description, dialog, favicon, fileInfo, filter, hiddenNum, hiddenThreads, indicator, indicators, input, key, left, li, obj, overlay, sauce, time, top, tr, ul, _i, _len, _ref, _ref1, _ref2;
  2648.       dialog = $.el('div', {
  2649.         id: 'options',
  2650.         className: 'reply dialog',
  2651.         innerHTML: '<div id=optionsbar>\
  2652.   <div id=credits>\
  2653.     <a target=_blank href=http://that4chanwolf.github.com/4chan-x/>4chan X</a>\
  2654.     | <a target=_blank href=https://raw.github.com/that4chanwolf/4chan-x/master/changelog>' + Main.version + '</a>\
  2655.     | <a target=_blank href=http://that4chanwolf.github.com/4chan-x/#bug-report>Issues</a>\
  2656.   </div>\
  2657.   <div>\
  2658.     <label for=main_tab>Main</label>\
  2659.     | <label for=filter_tab>Filter</label>\
  2660.     | <label for=sauces_tab>Sauce</label>\
  2661.     | <label for=rice_tab>Rice</label>\
  2662.     | <label for=keybinds_tab>Keybinds</label>\
  2663.   </div>\
  2664. </div>\
  2665. <hr>\
  2666. <div id=content>\
  2667.   <input type=radio name=tab hidden id=main_tab checked>\
  2668.   <div></div>\
  2669.   <input type=radio name=tab hidden id=sauces_tab>\
  2670.   <div>\
  2671.     <div class=warning><code>Sauce</code> is disabled.</div>\
  2672.     Lines starting with a <code>#</code> will be ignored.<br>\
  2673.     You can specify a certain display text by appending <code>;text:[text]</code> to the url.\
  2674.     <ul>These parameters will be replaced by their corresponding values:\
  2675.       <li>$1: Thumbnail url.</li>\
  2676.       <li>$2: Full image url.</li>\
  2677.       <li>$3: MD5 hash.</li>\
  2678.       <li>$4: Current board.</li>\
  2679.     </ul>\
  2680.     <textarea name=sauces id=sauces class=field></textarea>\
  2681.   </div>\
  2682.   <input type=radio name=tab hidden id=filter_tab>\
  2683.   <div>\
  2684.     <div class=warning><code>Filter</code> is disabled.</div>\
  2685.     <select name=filter>\
  2686.       <option value=guide>Guide</option>\
  2687.       <option value=name>Name</option>\
  2688.       <option value=uniqueid>Unique ID</option>\
  2689.       <option value=tripcode>Tripcode</option>\
  2690.       <option value=mod>Admin/Mod</option>\
  2691.       <option value=email>E-mail</option>\
  2692.       <option value=subject>Subject</option>\
  2693.       <option value=comment>Comment</option>\
  2694.       <option value=country>Country</option>\
  2695.       <option value=filename>Filename</option>\
  2696.       <option value=dimensions>Image dimensions</option>\
  2697.       <option value=filesize>Filesize</option>\
  2698.       <option value=md5>Image MD5 (uses exact string matching, not regular expressions)</option>\
  2699.     </select>\
  2700.   </div>\
  2701.   <input type=radio name=tab hidden id=rice_tab>\
  2702.   <div>\
  2703.     <div class=warning><code>Quote Backlinks</code> are disabled.</div>\
  2704.     <ul>\
  2705.       Backlink formatting\
  2706.       <li><input name=backlink class=field> : <span id=backlinkPreview></span></li>\
  2707.     </ul>\
  2708.     <div class=warning><code>Time Formatting</code> is disabled.</div>\
  2709.     <ul>\
  2710.       Time formatting\
  2711.       <li><input name=time class=field> : <span id=timePreview></span></li>\
  2712.       <li>Supported <a href=http://en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</li>\
  2713.       <li>Day: %a, %A, %d, %e</li>\
  2714.       <li>Month: %m, %b, %B</li>\
  2715.       <li>Year: %y</li>\
  2716.       <li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li>\
  2717.       <li>Minutes: %M</li>\
  2718.       <li>Seconds: %S</li>\
  2719.     </ul>\
  2720.     <div class=warning><code>File Info Formatting</code> is disabled.</div>\
  2721.     <ul>\
  2722.       File Info Formatting\
  2723.       <li><input name=fileInfo class=field> : <span id=fileInfoPreview class=fileText></span></li>\
  2724.       <li>Link (with original file name): %l (lowercase L, truncated), %L (untruncated)</li>\
  2725.       <li>Original file name: %n (Truncated), %N (Untruncated)</li>\
  2726.       <li>Spoiler indicator: %p</li>\
  2727.       <li>Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)</li>\
  2728.       <li>Resolution: %r (Displays PDF on /po/, for PDFs)</li>\
  2729.     </ul>\
  2730.     <div class=warning><code>Unread Favicon</code> is disabled.</div>\
  2731.     Unread favicons<br>\
  2732.     <select name=favicon>\
  2733.       <option value=ferongr>ferongr</option>\
  2734.       <option value=xat->xat-</option>\
  2735.       <option value=Mayhem>Mayhem</option>\
  2736.       <option value=Original>Original</option>\
  2737.     </select>\
  2738.     <span></span>\
  2739.   </div>\
  2740.   <input type=radio name=tab hidden id=keybinds_tab>\
  2741.   <div>\
  2742.     <div class=warning><code>Keybinds</code> are disabled.</div>\
  2743.     <div>Allowed keys: Ctrl, Alt, Meta, a-z, A-Z, 0-9, Up, Down, Right, Left.</div>\
  2744.     <table><tbody>\
  2745.       <tr><th>Actions</th><th>Keybinds</th></tr>\
  2746.     </tbody></table>\
  2747.   </div>\
  2748. </div>'
  2749.       });
  2750.       _ref = Config.main;
  2751.       for (key in _ref) {
  2752.         obj = _ref[key];
  2753.         ul = $.el('ul', {
  2754.           textContent: key
  2755.         });
  2756.         for (key in obj) {
  2757.           arr = obj[key];
  2758.           checked = $.get(key, Conf[key]) ? 'checked' : '';
  2759.           description = arr[1];
  2760.           li = $.el('li', {
  2761.             innerHTML: "<label><input type=checkbox name=\"" + key + "\" " + checked + ">" + key + "</label><span class=description>: " + description + "</span>"
  2762.           });
  2763.           $.on($('input', li), 'click', $.cb.checked);
  2764.           $.add(ul, li);
  2765.         }
  2766.         $.add($('#main_tab + div', dialog), ul);
  2767.       }
  2768.       hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
  2769.       hiddenNum = Object.keys(g.hiddenReplies).length + Object.keys(hiddenThreads).length;
  2770.       li = $.el('li', {
  2771.         innerHTML: "<button>hidden: " + hiddenNum + "</button> <span class=description>: Forget all hidden posts. Useful if you accidentally hide a post and have \"Show Stubs\" disabled."
  2772.       });
  2773.       $.on($('button', li), 'click', Options.clearHidden);
  2774.       $.add($('ul:nth-child(2)', dialog), li);
  2775.       filter = $('select[name=filter]', dialog);
  2776.       $.on(filter, 'change', Options.filter);
  2777.       sauce = $('#sauces', dialog);
  2778.       sauce.value = $.get(sauce.name, Conf[sauce.name]);
  2779.       $.on(sauce, 'change', $.cb.value);
  2780.       (back = $('[name=backlink]', dialog)).value = $.get('backlink', Conf['backlink']);
  2781.       (time = $('[name=time]', dialog)).value = $.get('time', Conf['time']);
  2782.       (fileInfo = $('[name=fileInfo]', dialog)).value = $.get('fileInfo', Conf['fileInfo']);
  2783.       $.on(back, 'input', $.cb.value);
  2784.       $.on(back, 'input', Options.backlink);
  2785.       $.on(time, 'input', $.cb.value);
  2786.       $.on(time, 'input', Options.time);
  2787.       $.on(fileInfo, 'input', $.cb.value);
  2788.       $.on(fileInfo, 'input', Options.fileInfo);
  2789.       favicon = $('select[name=favicon]', dialog);
  2790.       favicon.value = $.get('favicon', Conf['favicon']);
  2791.       $.on(favicon, 'change', $.cb.value);
  2792.       $.on(favicon, 'change', Options.favicon);
  2793.       _ref1 = Config.hotkeys;
  2794.       for (key in _ref1) {
  2795.         arr = _ref1[key];
  2796.         tr = $.el('tr', {
  2797.           innerHTML: "<td>" + arr[1] + "</td><td><input name=" + key + " class=field></td>"
  2798.         });
  2799.         input = $('input', tr);
  2800.         input.value = $.get(key, Conf[key]);
  2801.         $.on(input, 'keydown', Options.keybind);
  2802.         $.add($('#keybinds_tab + div tbody', dialog), tr);
  2803.       }
  2804.       indicators = {};
  2805.       _ref2 = $$('.warning', dialog);
  2806.       for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
  2807.         indicator = _ref2[_i];
  2808.         key = indicator.firstChild.textContent;
  2809.         indicator.hidden = $.get(key, Conf[key]);
  2810.         indicators[key] = indicator;
  2811.         $.on($("[name='" + key + "']", dialog), 'click', function() {
  2812.           return indicators[this.name].hidden = this.checked;
  2813.         });
  2814.       }
  2815.       overlay = $.el('div', {
  2816.         id: 'overlay'
  2817.       });
  2818.       $.on(overlay, 'click', Options.close);
  2819.       $.add(d.body, overlay);
  2820.       dialog.style.visibility = 'hidden';
  2821.       $.add(d.body, dialog);
  2822.       left = (window.innerWidth - dialog.getBoundingClientRect().width) / 2 + window.pageXOffset;
  2823.       top = (window.innerHeight - dialog.getBoundingClientRect().height) / 2 + window.pageYOffset;
  2824.       if (left < 0) {
  2825.         left = 0;
  2826.       }
  2827.       if (top < 0) {
  2828.         top = 0;
  2829.       }
  2830.       dialog.style.left = left + 'px';
  2831.       dialog.style.top = top + 'px';
  2832.       dialog.style.visibility = 'visible';
  2833.       Options.filter.call(filter);
  2834.       Options.backlink.call(back);
  2835.       Options.time.call(time);
  2836.       Options.fileInfo.call(fileInfo);
  2837.       return Options.favicon.call(favicon);
  2838.     },
  2839.     close: function() {
  2840.       $.rm(this.nextSibling);
  2841.       return $.rm(this);
  2842.     },
  2843.     clearHidden: function() {
  2844.       $["delete"]("hiddenReplies/" + g.BOARD + "/");
  2845.       $["delete"]("hiddenThreads/" + g.BOARD + "/");
  2846.       this.textContent = "hidden: 0";
  2847.       return g.hiddenReplies = {};
  2848.     },
  2849.     keybind: function(e) {
  2850.       var key;
  2851.       if (e.keyCode === 9) {
  2852.         return;
  2853.       }
  2854.       e.preventDefault();
  2855.       e.stopPropagation();
  2856.       if ((key = Keybinds.keyCode(e)) == null) {
  2857.         return;
  2858.       }
  2859.       this.value = key;
  2860.       return $.cb.value.call(this);
  2861.     },
  2862.     filter: function() {
  2863.       var el, name, ta;
  2864.       el = this.nextSibling;
  2865.       if ((name = this.value) !== 'guide') {
  2866.         ta = $.el('textarea', {
  2867.           name: name,
  2868.           className: 'field',
  2869.           value: $.get(name, Conf[name])
  2870.         });
  2871.         $.on(ta, 'change', $.cb.value);
  2872.         $.replace(el, ta);
  2873.         return;
  2874.       }
  2875.       if (el) {
  2876.         $.rm(el);
  2877.       }
  2878.       return $.after(this, $.el('article', {
  2879.         innerHTML: '<p>Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>\
  2880.   Lines starting with a <code>#</code> will be ignored.<br>\
  2881.   For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.</p>\
  2882.   <ul>You can use these settings with each regular expression, separate them with semicolons:\
  2883.     <li>\
  2884.       Per boards, separate them with commas. It is global if not specified.<br>\
  2885.       For example: <code>boards:a,jp;</code>.\
  2886.     </li>\
  2887.     <li>\
  2888.       Filter OPs only along with their threads (`only`), replies only (`no`, this is default), or both (`yes`).<br>\
  2889.       For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.\
  2890.     </li>\
  2891.     <li>\
  2892.       Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>\
  2893.       For example: <code>stub:yes;</code> or <code>stub:no;</code>.\
  2894.     </li>\
  2895.     <li>\
  2896.       Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>\
  2897.       For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.\
  2898.     </li>\
  2899.     <li>\
  2900.       Highlighted OPs will have their threads put on top of board pages by default.<br>\
  2901.       For example: <code>top:yes;</code> or <code>top:no;</code>.\
  2902.     </li>\
  2903.   </ul>'
  2904.       }));
  2905.     },
  2906.     time: function() {
  2907.       Time.foo();
  2908.       Time.date = new Date();
  2909.       return $.id('timePreview').textContent = Time.funk(Time);
  2910.     },
  2911.     backlink: function() {
  2912.       return $.id('backlinkPreview').textContent = Conf['backlink'].replace(/%id/, '123456789');
  2913.     },
  2914.     fileInfo: function() {
  2915.       FileInfo.data = {
  2916.         link: 'javascript:;',
  2917.         spoiler: true,
  2918.         size: '276',
  2919.         unit: 'KB',
  2920.         resolution: '1280x720',
  2921.         fullname: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
  2922.         shortname: 'd9bb2efc98dd0df141a94399ff5880(...).jpg'
  2923.       };
  2924.       FileInfo.setFormats();
  2925.       return $.id('fileInfoPreview').innerHTML = FileInfo.funk(FileInfo);
  2926.     },
  2927.     favicon: function() {
  2928.       Favicon["switch"]();
  2929.       Unread.update(true);
  2930.       return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">";
  2931.     }
  2932.   };
  2933.  
  2934.   Updater = {
  2935.     init: function() {
  2936.       var checkbox, checked, dialog, html, input, name, title, type, _i, _len, _ref;
  2937.       html = "<div class=move><span id=count></span> <span id=timer>-" + Conf['Interval'] + "</span></div>";
  2938.       checkbox = Config.updater.checkbox;
  2939.       for (name in checkbox) {
  2940.         title = checkbox[name][1];
  2941.         checked = Conf[name] ? 'checked' : '';
  2942.         html += "<div><label title='" + title + "'>" + name + "<input name='" + name + "' type=checkbox " + checked + "></label></div>";
  2943.       }
  2944.       checked = Conf['Auto Update'] ? 'checked' : '';
  2945.       html += " <div><label title='Controls whether *this* thread automatically updates or not'>Auto Update This<input name='Auto Update This' type=checkbox " + checked + "></label></div>     <div><label>Interval (s)<input name=Interval value=" + Conf['Interval'] + " class=field size=4></label></div>   <div><input value='Update Now' type=button></div>";
  2946.       dialog = UI.dialog('updater', 'bottom: 0; right: 0;', html);
  2947.       this.count = $('#count', dialog);
  2948.       this.timer = $('#timer', dialog);
  2949.       this.thread = $.id("t" + g.THREAD_ID);
  2950.       this.lastPost = this.thread.lastElementChild;
  2951.       _ref = $$('input', dialog);
  2952.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  2953.         input = _ref[_i];
  2954.         type = input.type, name = input.name;
  2955.         if (type === 'checkbox') {
  2956.           $.on(input, 'click', $.cb.checked);
  2957.         }
  2958.         switch (name) {
  2959.           case 'Scroll BG':
  2960.             $.on(input, 'click', this.cb.scrollBG);
  2961.             this.cb.scrollBG.call(input);
  2962.             break;
  2963.           case 'Verbose':
  2964.             $.on(input, 'click', this.cb.verbose);
  2965.             this.cb.verbose.call(input);
  2966.             break;
  2967.           case 'Auto Update This':
  2968.             $.on(input, 'click', this.cb.autoUpdate);
  2969.             this.cb.autoUpdate.call(input);
  2970.             Conf[input.name] = input.checked;
  2971.             break;
  2972.           case 'Interval':
  2973.             $.on(input, 'input', this.cb.interval);
  2974.             break;
  2975.           case 'Update Now':
  2976.             $.on(input, 'click', this.update);
  2977.         }
  2978.       }
  2979.       $.add(d.body, dialog);
  2980.       this.retryCoef = 10;
  2981.       this.lastModified = 0;
  2982.       return $.on(d, 'QRPostSuccessful', this.cb.post);
  2983.     },
  2984.     cb: {
  2985.       post: function() {
  2986.         if (!Conf['Auto Update This']) {
  2987.           return;
  2988.         }
  2989.         Updater.unsuccessfulFetchCount = 0;
  2990.         return setTimeout(Updater.update, 100);
  2991.       },
  2992.       visibility: function() {
  2993.         var state;
  2994.         state = d.visibilityState || d.oVisibilityState || d.mozVisibilityState || d.webkitVisibilityState;
  2995.         if (state !== 'visible') {
  2996.           return;
  2997.         }
  2998.         Updater.unsuccessfulFetchCount = 0;
  2999.         if (Updater.timer.textContent < -Conf['Interval']) {
  3000.           return Updater.timer.textContent = -Updater.getInterval();
  3001.         }
  3002.       },
  3003.       interval: function() {
  3004.         var val;
  3005.         val = parseInt(this.value, 10);
  3006.         this.value = val > 0 ? val : 30;
  3007.         $.cb.value.call(this);
  3008.         return Updater.timer.textContent = -Updater.getInterval();
  3009.       },
  3010.       maxInterval: function() {
  3011.         var val;
  3012.         val = parseInt(this.value, 10);
  3013.         this.value = val > 180 ? val : 180;
  3014.         return $.cb.value.call(this);
  3015.       },
  3016.       verbose: function() {
  3017.         if (Conf['Verbose']) {
  3018.           Updater.count.textContent = '+0';
  3019.           return Updater.timer.hidden = false;
  3020.         } else {
  3021.           $.extend(Updater.count, {
  3022.             className: '',
  3023.             textContent: 'Thread Updater'
  3024.           });
  3025.           return Updater.timer.hidden = true;
  3026.         }
  3027.       },
  3028.       autoUpdate: function() {
  3029.         if (Conf['Auto Update This'] = this.checked) {
  3030.           return Updater.timeoutID = setTimeout(Updater.timeout, 1000);
  3031.         } else {
  3032.           return clearTimeout(Updater.timeoutID);
  3033.         }
  3034.       },
  3035.       scrollBG: function() {
  3036.         return Updater.scrollBG = this.checked ? function() {
  3037.           return true;
  3038.         } : function() {
  3039.           return !(d.hidden || d.oHidden || d.mozHidden || d.webkitHidden);
  3040.         };
  3041.       },
  3042.       update: function() {
  3043.         var count, doc, id, lastPost, nodes, reply, scroll, _i, _len, _ref;
  3044.         if (this.status === 404) {
  3045.           Updater.timer.textContent = '';
  3046.           Updater.count.textContent = 404;
  3047.           Updater.count.className = 'warning';
  3048.           clearTimeout(Updater.timeoutID);
  3049.           g.dead = true;
  3050.           if (Conf['Unread Count']) {
  3051.             Unread.title = Unread.title.match(/^.+-/)[0] + ' 404';
  3052.           } else {
  3053.             d.title = d.title.match(/^.+-/)[0] + ' 404';
  3054.           }
  3055.           Unread.update(true);
  3056.           QR.abort();
  3057.           return;
  3058.         }
  3059.         if (this.status !== 200 && this.status !== 304) {
  3060.           Updater.retryCoef += 10 * (Updater.retryCoef < 120);
  3061.           if (Conf['Verbose']) {
  3062.             Updater.count.textContent = this.statusText;
  3063.             Updater.count.className = 'warning';
  3064.           }
  3065.           return;
  3066.         }
  3067.         Updater.retryCoef = 10;
  3068.         Updater.timer.textContent = "-" + Conf['Interval'];
  3069.         /*
  3070.               Status Code 304: Not modified
  3071.               By sending the `If-Modified-Since` header we get a proper status code, and no response.
  3072.               This saves bandwidth for both the user and the servers, avoid unnecessary computation,
  3073.               and won't load images and scripts when parsing the response.
  3074.         */
  3075.  
  3076.         if (this.status === 304) {
  3077.           if (Conf['Verbose']) {
  3078.             Updater.count.textContent = '+0';
  3079.             Updater.count.className = null;
  3080.           }
  3081.           return;
  3082.         }
  3083.         Updater.lastModified = this.getResponseHeader('Last-Modified');
  3084.         doc = d.implementation.createHTMLDocument('');
  3085.         doc.documentElement.innerHTML = this.response;
  3086.         lastPost = Updater.lastPost;
  3087.         id = lastPost.id.slice(2);
  3088.         nodes = [];
  3089.         _ref = $$('.replyContainer', doc).reverse();
  3090.         for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3091.           reply = _ref[_i];
  3092.           if (reply.id.slice(2) <= id) {
  3093.             break;
  3094.           }
  3095.           nodes.push(reply);
  3096.         }
  3097.         count = nodes.length;
  3098.         if (Conf['Verbose']) {
  3099.           Updater.count.textContent = "+" + count;
  3100.           Updater.count.className = count ? 'new' : null;
  3101.         }
  3102.         if (lastPost = nodes[0]) {
  3103.           Updater.lastPost = lastPost;
  3104.         }
  3105.         if (!count) {
  3106.           return;
  3107.         }
  3108.         Updater.unsuccessfulFetchCount = 0;
  3109.         Updater.timer.textContent = -Updater.getInterval();
  3110.         scroll = Conf['Scrolling'] && Updater.scrollBG() && Updater.thread.getBoundingClientRect().bottom - d.documentElement.clientHeight < 25;
  3111.         $.add(Updater.thread, nodes.reverse());
  3112.         if (scroll) {
  3113.           return lastPost.scrollIntoView();
  3114.         }
  3115.       }
  3116.     },
  3117.     getInterval: function() {
  3118.       var max, min, now;
  3119.       min = +Conf['Interval'];
  3120.       max = +Conf['Max Interval'];
  3121.       now = 1 * Math.pow(2, this.unsuccessfulFetchCount);
  3122.       if (min > now) {
  3123.         return min;
  3124.       } else if (max < now) {
  3125.         return max;
  3126.       } else {
  3127.         return now;
  3128.       }
  3129.     },
  3130.     timeout: function() {
  3131.       var n;
  3132.       Updater.timeoutID = setTimeout(Updater.timeout, 1000);
  3133.       n = 1 + Number(Updater.timer.textContent);
  3134.       if (n === 0) {
  3135.         return Updater.update();
  3136.       } else if (n === Updater.retryCoef) {
  3137.         Updater.retryCoef += 10 * (Updater.retryCoef < 120);
  3138.         return Updater.retry();
  3139.       } else {
  3140.         return Updater.timer.textContent = n;
  3141.       }
  3142.     },
  3143.     update: function() {
  3144.       var url, _ref;
  3145.       Updater.timer.textContent = 0;
  3146.       if ((_ref = Updater.request) != null) {
  3147.         _ref.abort();
  3148.       }
  3149.       url = location.pathname + '?' + Date.now();
  3150.       return Updater.request = $.ajax(url, {
  3151.         onload: Updater.cb.update
  3152.       }, {
  3153.         headers: {
  3154.           'If-Modified-Since': Updater.lastModified
  3155.         }
  3156.       });
  3157.     },
  3158.     updateReset: function() {
  3159.       Updater.unsuccessfulFetchCount = 0;
  3160.       return Updater.update();
  3161.     }
  3162.   };
  3163.  
  3164.   Watcher = {
  3165.     init: function() {
  3166.       var favicon, html, input, _i, _len, _ref;
  3167.       html = '<div class=move>Thread Watcher</div>';
  3168.       this.dialog = UI.dialog('watcher', 'top: 50px; left: 0px;', html);
  3169.       $.add(d.body, this.dialog);
  3170.       _ref = $$('.op input');
  3171.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3172.         input = _ref[_i];
  3173.         favicon = $.el('img', {
  3174.           className: 'favicon'
  3175.         });
  3176.         $.on(favicon, 'click', this.cb.toggle);
  3177.         $.before(input, favicon);
  3178.       }
  3179.       if (g.THREAD_ID === $.get('autoWatch', 0)) {
  3180.         this.watch(g.THREAD_ID);
  3181.         $["delete"]('autoWatch');
  3182.       } else {
  3183.         this.refresh();
  3184.       }
  3185.       $.on(d, 'QRPostSuccessful', this.cb.post);
  3186.       return $.sync('watched', this.refresh);
  3187.     },
  3188.     refresh: function(watched) {
  3189.       var board, div, favicon, id, link, nodes, props, watchedBoard, x, _i, _j, _len, _len1, _ref, _ref1, _ref2;
  3190.       watched || (watched = $.get('watched', {}));
  3191.       nodes = [];
  3192.       for (board in watched) {
  3193.         _ref = watched[board];
  3194.         for (id in _ref) {
  3195.           props = _ref[id];
  3196.           x = $.el('a', {
  3197.             textContent: '×',
  3198.             href: 'javascript:;'
  3199.           });
  3200.           $.on(x, 'click', Watcher.cb.x);
  3201.           link = $.el('a', props);
  3202.           link.title = link.textContent;
  3203.           div = $.el('div');
  3204.           $.add(div, [x, $.tn(' '), link]);
  3205.           nodes.push(div);
  3206.         }
  3207.       }
  3208.       _ref1 = $$('div:not(.move)', Watcher.dialog);
  3209.       for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
  3210.         div = _ref1[_i];
  3211.         $.rm(div);
  3212.       }
  3213.       $.add(Watcher.dialog, nodes);
  3214.       watchedBoard = watched[g.BOARD] || {};
  3215.       _ref2 = $$('.favicon');
  3216.       for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
  3217.         favicon = _ref2[_j];
  3218.         id = favicon.nextSibling.name;
  3219.         if (id in watchedBoard) {
  3220.           favicon.src = Favicon["default"];
  3221.         } else {
  3222.           favicon.src = Favicon.empty;
  3223.         }
  3224.       }
  3225.     },
  3226.     cb: {
  3227.       toggle: function() {
  3228.         return Watcher.toggle(this.parentNode);
  3229.       },
  3230.       x: function() {
  3231.         var thread;
  3232.         thread = this.nextElementSibling.pathname.split('/');
  3233.         return Watcher.unwatch(thread[3], thread[1]);
  3234.       },
  3235.       post: function(e) {
  3236.         var postID, threadID, _ref;
  3237.         _ref = e.detail, postID = _ref.postID, threadID = _ref.threadID;
  3238.         if (threadID === '0') {
  3239.           if (Conf['Auto Watch']) {
  3240.             return $.set('autoWatch', postID);
  3241.           }
  3242.         } else if (Conf['Auto Watch Reply']) {
  3243.           return Watcher.watch(threadID);
  3244.         }
  3245.       }
  3246.     },
  3247.     toggle: function(thread) {
  3248.       var id;
  3249.       id = $('.favicon + input', thread).name;
  3250.       return Watcher.watch(id) || Watcher.unwatch(id, g.BOARD);
  3251.     },
  3252.     unwatch: function(id, board) {
  3253.       var watched;
  3254.       watched = $.get('watched', {});
  3255.       delete watched[board][id];
  3256.       $.set('watched', watched);
  3257.       return Watcher.refresh();
  3258.     },
  3259.     watch: function(id) {
  3260.       var thread, watched, _name;
  3261.       thread = $.id("t" + id);
  3262.       if ($('.favicon', thread).src === Favicon["default"]) {
  3263.         return false;
  3264.       }
  3265.       watched = $.get('watched', {});
  3266.       watched[_name = g.BOARD] || (watched[_name] = {});
  3267.       watched[g.BOARD][id] = {
  3268.         href: "/" + g.BOARD + "/res/" + id,
  3269.         textContent: Get.title(thread)
  3270.       };
  3271.       $.set('watched', watched);
  3272.       Watcher.refresh();
  3273.       return true;
  3274.     }
  3275.   };
  3276.  
  3277.   Anonymize = {
  3278.     init: function() {
  3279.       return Main.callbacks.push(this.node);
  3280.     },
  3281.     node: function(post) {
  3282.       var name, parent, trip;
  3283.       if (post.isInlined && !post.isCrosspost) {
  3284.         return;
  3285.       }
  3286.       name = $('.postInfo .name', post.el);
  3287.       name.textContent = 'Anonymous';
  3288.       if ((trip = name.nextElementSibling) && trip.className === 'postertrip') {
  3289.         $.rm(trip);
  3290.       }
  3291.       if ((parent = name.parentNode).className === 'useremail' && !/^mailto:sage$/i.test(parent.href)) {
  3292.         return $.replace(parent, name);
  3293.       }
  3294.     }
  3295.   };
  3296.  
  3297.   Sauce = {
  3298.     init: function() {
  3299.       var link, _i, _len, _ref;
  3300.       if (g.BOARD === 'f') {
  3301.         return;
  3302.       }
  3303.       this.links = [];
  3304.       _ref = Conf['sauces'].split('\n');
  3305.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3306.         link = _ref[_i];
  3307.         if (link[0] === '#') {
  3308.           continue;
  3309.         }
  3310.         this.links.push(this.createSauceLink(link.trim()));
  3311.       }
  3312.       if (!this.links.length) {
  3313.         return;
  3314.       }
  3315.       return Main.callbacks.push(this.node);
  3316.     },
  3317.     createSauceLink: function(link) {
  3318.       var domain, el, href, m;
  3319.       link = link.replace(/(\$\d)/g, function(parameter) {
  3320.         switch (parameter) {
  3321.           case '$1':
  3322.             return "' + (isArchived ? img.firstChild.src : 'http://thumbs.4chan.org' + img.pathname.replace(/src(\\/\\d+).+$/, 'thumb$1s.jpg')) + '";
  3323.           case '$2':
  3324.             return "' + img.href + '";
  3325.           case '$3':
  3326.             return "' + encodeURIComponent(img.firstChild.dataset.md5) + '";
  3327.           case '$4':
  3328.             return g.BOARD;
  3329.           default:
  3330.             return parameter;
  3331.         }
  3332.       });
  3333.       domain = (m = link.match(/;text:(.+)$/)) ? m[1] : link.match(/(\w+)\.\w+\//)[1];
  3334.       href = link.replace(/;text:.+$/, '');
  3335.       href = Function('img', 'isArchived', "return '" + href + "'");
  3336.       el = $.el('a', {
  3337.         target: '_blank',
  3338.         textContent: domain
  3339.       });
  3340.       return function(img, isArchived) {
  3341.         var a;
  3342.         a = el.cloneNode(true);
  3343.         a.href = href(img, isArchived);
  3344.         return a;
  3345.       };
  3346.     },
  3347.     node: function(post) {
  3348.       var img, link, nodes, _i, _len, _ref;
  3349.       img = post.img;
  3350.       if (post.isInlined && !post.isCrosspost || !img) {
  3351.         return;
  3352.       }
  3353.       img = img.parentNode;
  3354.       nodes = [];
  3355.       _ref = Sauce.links;
  3356.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3357.         link = _ref[_i];
  3358.         nodes.push($.tn($.NBSP), link(img, post.isArchived));
  3359.       }
  3360.       return $.add(post.fileInfo, nodes);
  3361.     }
  3362.   };
  3363.  
  3364.   RevealSpoilers = {
  3365.     init: function() {
  3366.       return Main.callbacks.push(this.node);
  3367.     },
  3368.     node: function(post) {
  3369.       var img, s;
  3370.       img = post.img;
  3371.       if (!(img && /^Spoiler/.test(img.alt)) || post.isInlined && !post.isCrosspost || post.isArchived) {
  3372.         return;
  3373.       }
  3374.       img.removeAttribute('style');
  3375.       s = img.style;
  3376.       s.maxHeight = s.maxWidth = /\bop\b/.test(post["class"]) ? '250px' : '125px';
  3377.       return img.src = "//thumbs.4chan.org" + (img.parentNode.pathname.replace(/src(\/\d+).+$/, 'thumb$1s.jpg'));
  3378.     }
  3379.   };
  3380.  
  3381.   Time = {
  3382.     init: function() {
  3383.       Time.foo();
  3384.       return Main.callbacks.push(this.node);
  3385.     },
  3386.     node: function(post) {
  3387.       var node;
  3388.       if (post.isInlined && !post.isCrosspost) {
  3389.         return;
  3390.       }
  3391.       node = $('.postInfo > .dateTime', post.el);
  3392.       Time.date = new Date(node.dataset.utc * 1000);
  3393.       return node.textContent = Time.funk(Time);
  3394.     },
  3395.     foo: function() {
  3396.       var code;
  3397.       code = Conf['time'].replace(/%([A-Za-z])/g, function(s, c) {
  3398.         if (c in Time.formatters) {
  3399.           return "' + Time.formatters." + c + "() + '";
  3400.         } else {
  3401.           return s;
  3402.         }
  3403.       });
  3404.       return Time.funk = Function('Time', "return '" + code + "'");
  3405.     },
  3406.     day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
  3407.     month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
  3408.     zeroPad: function(n) {
  3409.       if (n < 10) {
  3410.         return '0' + n;
  3411.       } else {
  3412.         return n;
  3413.       }
  3414.     },
  3415.     formatters: {
  3416.       a: function() {
  3417.         return Time.day[Time.date.getDay()].slice(0, 3);
  3418.       },
  3419.       A: function() {
  3420.         return Time.day[Time.date.getDay()];
  3421.       },
  3422.       b: function() {
  3423.         return Time.month[Time.date.getMonth()].slice(0, 3);
  3424.       },
  3425.       B: function() {
  3426.         return Time.month[Time.date.getMonth()];
  3427.       },
  3428.       d: function() {
  3429.         return Time.zeroPad(Time.date.getDate());
  3430.       },
  3431.       e: function() {
  3432.         return Time.date.getDate();
  3433.       },
  3434.       H: function() {
  3435.         return Time.zeroPad(Time.date.getHours());
  3436.       },
  3437.       I: function() {
  3438.         return Time.zeroPad(Time.date.getHours() % 12 || 12);
  3439.       },
  3440.       k: function() {
  3441.         return Time.date.getHours();
  3442.       },
  3443.       l: function() {
  3444.         return Time.date.getHours() % 12 || 12;
  3445.       },
  3446.       m: function() {
  3447.         return Time.zeroPad(Time.date.getMonth() + 1);
  3448.       },
  3449.       M: function() {
  3450.         return Time.zeroPad(Time.date.getMinutes());
  3451.       },
  3452.       p: function() {
  3453.         if (Time.date.getHours() < 12) {
  3454.           return 'AM';
  3455.         } else {
  3456.           return 'PM';
  3457.         }
  3458.       },
  3459.       P: function() {
  3460.         if (Time.date.getHours() < 12) {
  3461.           return 'am';
  3462.         } else {
  3463.           return 'pm';
  3464.         }
  3465.       },
  3466.       S: function() {
  3467.         return Time.zeroPad(Time.date.getSeconds());
  3468.       },
  3469.       y: function() {
  3470.         return Time.date.getFullYear() - 2000;
  3471.       }
  3472.     }
  3473.   };
  3474.  
  3475.   FileInfo = {
  3476.     init: function() {
  3477.       if (g.BOARD === 'f') {
  3478.         return;
  3479.       }
  3480.       this.setFormats();
  3481.       return Main.callbacks.push(this.node);
  3482.     },
  3483.     node: function(post) {
  3484.       var alt, node, span;
  3485.       if (post.isInlined && !post.isCrosspost || !post.fileInfo) {
  3486.         return;
  3487.       }
  3488.       node = post.fileInfo.firstElementChild;
  3489.       alt = post.img.alt;
  3490.       span = $('span', node);
  3491.       FileInfo.data = {
  3492.         link: post.img.parentNode.href,
  3493.         spoiler: /^Spoiler/.test(alt),
  3494.         size: alt.match(/\d+\.?\d*/)[0],
  3495.         unit: alt.match(/\w+$/)[0],
  3496.         resolution: span.previousSibling.textContent.match(/\d+x\d+|PDF/)[0],
  3497.         fullname: span.title,
  3498.         shortname: span.textContent
  3499.       };
  3500.       node.setAttribute('data-filename', span.title);
  3501.       return node.innerHTML = FileInfo.funk(FileInfo);
  3502.     },
  3503.     setFormats: function() {
  3504.       var code;
  3505.       code = Conf['fileInfo'].replace(/%([BKlLMnNprs])/g, function(s, c) {
  3506.         if (c in FileInfo.formatters) {
  3507.           return "' + f.formatters." + c + "() + '";
  3508.         } else {
  3509.           return s;
  3510.         }
  3511.       });
  3512.       return this.funk = Function('f', "return '" + code + "'");
  3513.     },
  3514.     convertUnit: function(unitT) {
  3515.       var i, size, unitF, units;
  3516.       size = this.data.size;
  3517.       unitF = this.data.unit;
  3518.       if (unitF !== unitT) {
  3519.         units = ['B', 'KB', 'MB'];
  3520.         i = units.indexOf(unitF) - units.indexOf(unitT);
  3521.         if (unitT === 'B') {
  3522.           unitT = 'Bytes';
  3523.         }
  3524.         if (i > 0) {
  3525.           while (i-- > 0) {
  3526.             size *= 1024;
  3527.           }
  3528.         } else if (i < 0) {
  3529.           while (i++ < 0) {
  3530.             size /= 1024;
  3531.           }
  3532.         }
  3533.         if (size < 1 && size.toString().length > size.toFixed(2).length) {
  3534.           size = size.toFixed(2);
  3535.         }
  3536.       }
  3537.       return "" + size + " " + unitT;
  3538.     },
  3539.     formatters: {
  3540.       l: function() {
  3541.         return "<a href=" + FileInfo.data.link + " target=_blank>" + (this.n()) + "</a>";
  3542.       },
  3543.       L: function() {
  3544.         return "<a href=" + FileInfo.data.link + " target=_blank>" + (this.N()) + "</a>";
  3545.       },
  3546.       n: function() {
  3547.         if (FileInfo.data.fullname === FileInfo.data.shortname) {
  3548.           return FileInfo.data.fullname;
  3549.         } else {
  3550.           return "<span class=fntrunc>" + FileInfo.data.shortname + "</span><span class=fnfull>" + FileInfo.data.fullname + "</span>";
  3551.         }
  3552.       },
  3553.       N: function() {
  3554.         return FileInfo.data.fullname;
  3555.       },
  3556.       p: function() {
  3557.         if (FileInfo.data.spoiler) {
  3558.           return 'Spoiler, ';
  3559.         } else {
  3560.           return '';
  3561.         }
  3562.       },
  3563.       s: function() {
  3564.         return "" + FileInfo.data.size + " " + FileInfo.data.unit;
  3565.       },
  3566.       B: function() {
  3567.         return FileInfo.convertUnit('B');
  3568.       },
  3569.       K: function() {
  3570.         return FileInfo.convertUnit('KB');
  3571.       },
  3572.       M: function() {
  3573.         return FileInfo.convertUnit('MB');
  3574.       },
  3575.       r: function() {
  3576.         return FileInfo.data.resolution;
  3577.       }
  3578.     }
  3579.   };
  3580.  
  3581.   Get = {
  3582.     post: function(board, threadID, postID, root, cb) {
  3583.       var post, url;
  3584.       if (board === g.BOARD && (post = $.id("pc" + postID))) {
  3585.         $.add(root, Get.cleanPost(post.cloneNode(true)));
  3586.         return;
  3587.       }
  3588.       root.textContent = "Loading post No." + postID + "...";
  3589.       if (threadID) {
  3590.         return $.cache("/" + board + "/res/" + threadID, function() {
  3591.           return Get.parsePost(this, board, threadID, postID, root, cb);
  3592.         });
  3593.       } else if (url = Redirect.post(board, postID)) {
  3594.         return $.cache(url, function() {
  3595.           return Get.parseArchivedPost(this, board, postID, root, cb);
  3596.         });
  3597.       }
  3598.     },
  3599.     parsePost: function(req, board, threadID, postID, root, cb) {
  3600.       var doc, href, link, pc, quote, status, url, _i, _len, _ref;
  3601.       status = req.status;
  3602.       if (status !== 200) {
  3603.         if (url = Redirect.post(board, postID)) {
  3604.           $.cache(url, function() {
  3605.             return Get.parseArchivedPost(this, board, postID, root, cb);
  3606.           });
  3607.         } else {
  3608.           root.textContent = status === 404 ? "Thread No." + threadID + " has not been found." : "Error " + req.status + ": " + req.statusText + ".";
  3609.         }
  3610.         return;
  3611.       }
  3612.       doc = d.implementation.createHTMLDocument('');
  3613.       doc.documentElement.innerHTML = req.response;
  3614.       if (!(pc = doc.getElementById("pc" + postID))) {
  3615.         if (url = Redirect.post(board, postID)) {
  3616.           $.cache(url, function() {
  3617.             return Get.parseArchivedPost(this, board, postID, root, cb);
  3618.           });
  3619.         } else {
  3620.           root.textContent = "Post No." + postID + " has not been found.";
  3621.         }
  3622.         return;
  3623.       }
  3624.       pc = Get.cleanPost(d.importNode(pc, true));
  3625.       _ref = $$('.quotelink', pc);
  3626.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3627.         quote = _ref[_i];
  3628.         href = quote.getAttribute('href');
  3629.         if (href[0] === '/') {
  3630.           continue;
  3631.         }
  3632.         quote.href = "/" + board + "/res/" + href;
  3633.       }
  3634.       link = $('a[title="Highlight this post"]', pc);
  3635.       link.href = "/" + board + "/res/" + threadID + "#p" + postID;
  3636.       link.nextSibling.href = "/" + board + "/res/" + threadID + "#q" + postID;
  3637.       $.replace(root.firstChild, pc);
  3638.       if (cb) {
  3639.         return cb();
  3640.       }
  3641.     },
  3642.     parseArchivedPost: function(req, board, postID, root, cb) {
  3643.       var bq, br, capcode, data, email, file, filename, filesize, isOP, name, nameBlock, pc, pi, piM, span, spoiler, subject, threadID, threshold, thumb_src, timestamp, trip;
  3644.       data = JSON.parse(req.response);
  3645.       $.addClass(root, 'archivedPost');
  3646.       if (data.error) {
  3647.         root.textContent = data.error;
  3648.         return;
  3649.       }
  3650.       threadID = data.thread_num;
  3651.       isOP = postID === threadID;
  3652.       name = data.name, trip = data.trip, timestamp = data.timestamp;
  3653.       subject = data.title;
  3654.       piM = $.el('div', {
  3655.         id: "pim" + postID,
  3656.         className: 'postInfoM mobile',
  3657.         innerHTML: "<span class=nameBlock><span class=name></span><br><span class=subject></span></span><span class='dateTime postNum' data-utc=" + timestamp + ">" + data.fourchan_date + "<br><em></em><a href='/" + board + "/res/" + threadID + "#p" + postID + "'>No.</a><a href='/" + board + "/res/" + threadID + "#q" + postID + "'>" + postID + "</a></span>"
  3658.       });
  3659.       $('.name', piM).textContent = name;
  3660.       $('.subject', piM).textContent = subject;
  3661.       br = $('br', piM);
  3662.       if (trip) {
  3663.         $.before(br, [
  3664.           $.tn(' '), $.el('span', {
  3665.             className: 'postertrip',
  3666.             textContent: trip
  3667.           })
  3668.         ]);
  3669.       }
  3670.       capcode = data.capcode;
  3671.       if (capcode !== 'N') {
  3672.         $.addClass(br.parentNode, capcode === 'A' ? 'capcodeAdmin' : 'capcodeMod');
  3673.         $.before(br, [
  3674.           $.tn(' '), $.el('strong', {
  3675.             className: 'capcode',
  3676.             textContent: capcode === 'A' ? '## Admin' : '## Mod'
  3677.           }), $.tn(' '), $.el('img', {
  3678.             src: capcode === 'A' ? '//static.4chan.org/image/adminicon.gif' : '//static.4chan.org/image/modicon.gif',
  3679.             alt: capcode === 'A' ? 'This user is the 4chan Administrator.' : 'This user is a 4chan Moderator.',
  3680.             title: capcode === 'A' ? 'This user is the 4chan Administrator.' : 'This user is a 4chan Moderator.',
  3681.             className: 'identityIcon'
  3682.           })
  3683.         ]);
  3684.       }
  3685.       pi = $.el('div', {
  3686.         id: "pi" + postID,
  3687.         className: 'postInfo desktop',
  3688.         innerHTML: "<input type=checkbox name=" + postID + " value=delete> <span class=subject></span> <span class=nameBlock></span> <span class=dateTime data-utc=" + timestamp + ">data.fourchan_date</span> <span class='postNum desktop'><a href='/" + board + "/res/" + threadID + "#p" + postID + "' title='Highlight this post'>No.</a><a href='/" + board + "/res/" + threadID + "#q" + postID + "' title='Quote this post'>" + postID + "</a>" + (isOP ? ' &nbsp; ' : '') + "</span> "
  3689.       });
  3690.       $('.subject', pi).textContent = subject;
  3691.       nameBlock = $('.nameBlock', pi);
  3692.       if (data.email) {
  3693.         email = $.el('a', {
  3694.           className: 'useremail',
  3695.           href: "mailto:" + data.email
  3696.         });
  3697.         $.add(nameBlock, email);
  3698.         nameBlock = email;
  3699.       }
  3700.       $.add(nameBlock, $.el('span', {
  3701.         className: 'name',
  3702.         textContent: data.name
  3703.       }));
  3704.       if (trip) {
  3705.         $.add(nameBlock, [
  3706.           $.tn(' '), $.el('span', {
  3707.             className: 'postertrip',
  3708.             textContent: trip
  3709.           })
  3710.         ]);
  3711.       }
  3712.       if (capcode !== 'N') {
  3713.         $.add(nameBlock, [
  3714.           $.tn(' '), $.el('strong', {
  3715.             className: capcode === 'A' ? 'capcode capcodeAdmin' : 'capcode',
  3716.             textContent: capcode === 'A' ? '## Admin' : '## Mod'
  3717.           })
  3718.         ]);
  3719.         nameBlock = $('.nameBlock', pi);
  3720.         $.addClass(nameBlock, capcode === 'A' ? 'capcodeAdmin' : 'capcodeMod');
  3721.         $.add(nameBlock, [
  3722.           $.tn(' '), $.el('img', {
  3723.             src: capcode === 'A' ? '//static.4chan.org/image/adminicon.gif' : '//static.4chan.org/image/modicon.gif',
  3724.             alt: capcode === 'A' ? 'This user is the 4chan Administrator.' : 'This user is a 4chan Moderator.',
  3725.             title: capcode === 'A' ? 'This user is the 4chan Administrator.' : 'This user is a 4chan Moderator.',
  3726.             className: 'identityIcon'
  3727.           })
  3728.         ]);
  3729.       }
  3730.       bq = $.el('blockquote', {
  3731.         id: "m" + postID,
  3732.         className: 'postMessage',
  3733.         textContent: data.comment
  3734.       });
  3735.       bq.innerHTML = bq.innerHTML.replace(/\n|\[\/?b\]|\[\/?spoiler\]|\[\/?code\]|\[\/?moot\]|\[\/?banned\]/g, function(text) {
  3736.         switch (text) {
  3737.           case '\n':
  3738.             return '<br>';
  3739.           case '[b]':
  3740.             return '<b>';
  3741.           case '[/b]':
  3742.             return '</b>';
  3743.           case '[spoiler]':
  3744.             return '<span class=spoiler>';
  3745.           case '[/spoiler]':
  3746.             return '</span>';
  3747.           case '[code]':
  3748.             return '<pre class=prettyprint>';
  3749.           case '[/code]':
  3750.             return '</pre>';
  3751.           case '[moot]':
  3752.             return '<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">';
  3753.           case '[/moot]':
  3754.             return '</div>';
  3755.           case '[banned]':
  3756.             return '<b style="color: red;">';
  3757.           case '[/banned]':
  3758.             return '</b>';
  3759.         }
  3760.       });
  3761.       bq.innerHTML = bq.innerHTML.replace(/(^|>)(&gt;[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3');
  3762.       pc = $.el('div', {
  3763.         id: "pc" + postID,
  3764.         className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
  3765.         innerHTML: "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + "'></div>"
  3766.       });
  3767.       $.add(pc.firstChild, [piM, pi, bq]);
  3768.       if (filename = data.media_filename) {
  3769.         file = $.el('div', {
  3770.           id: "f" + postID,
  3771.           className: 'file'
  3772.         });
  3773.         spoiler = data.spoiler === '1';
  3774.         filesize = $.bytesToString(data.media_size);
  3775.         $.add(file, $.el('div', {
  3776.           className: 'fileInfo',
  3777.           innerHTML: "<span id=fT" + postID + " class=fileText>File: <a href='" + (data.media_link || data.remote_media_link) + "' target=_blank>" + data.media_orig + "</a>-(" + (spoiler ? 'Spoiler Image, ' : '') + filesize + ", " + data.media_w + "x" + data.media_h + ", <span title></span>)</span>"
  3778.         }));
  3779.         span = $('span[title]', file);
  3780.         span.title = filename;
  3781.         threshold = isOP ? 40 : 30;
  3782.         span.textContent = filename.replace(/\.\w+$/, '').length > threshold ? "" + filename.slice(0, threshold - 5) + "(...)" + (filename.match(/\.\w+$/)) : filename;
  3783.         thumb_src = data.media_status === 'available' ? "src=" + data.thumb_link : '';
  3784.         $.add(file, $.el('a', {
  3785.           className: spoiler ? 'fileThumb imgspoiler' : 'fileThumb',
  3786.           href: data.media_link || data.remote_media_link,
  3787.           target: '_blank',
  3788.           innerHTML: "<img " + thumb_src + " alt='" + (data.media_status !== 'available' ? "Error: " + data.media_status + ", " : '') + (spoiler ? 'Spoiler Image, ' : '') + filesize + "' data-md5=" + data.media_hash + " style='height: " + data.preview_h + "px; width: " + data.preview_w + "px;'>"
  3789.         }));
  3790.         $.after((isOP ? piM : pi), file);
  3791.       }
  3792.       $.replace(root.firstChild, Get.cleanPost(pc));
  3793.       if (cb) {
  3794.         return cb();
  3795.       }
  3796.     },
  3797.     cleanPost: function(root) {
  3798.       var child, el, els, inline, inlined, now, post, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2;
  3799.       post = $('.post', root);
  3800.       _ref = Array.prototype.slice.call(root.childNodes);
  3801.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3802.         child = _ref[_i];
  3803.         if (child !== post) {
  3804.           $.rm(child);
  3805.         }
  3806.       }
  3807.       _ref1 = $$('.inline', post);
  3808.       for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  3809.         inline = _ref1[_j];
  3810.         $.rm(inline);
  3811.       }
  3812.       _ref2 = $$('.inlined', post);
  3813.       for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
  3814.         inlined = _ref2[_k];
  3815.         $.rmClass(inlined, 'inlined');
  3816.       }
  3817.       now = Date.now();
  3818.       els = $$('[id]', root);
  3819.       els.push(root);
  3820.       for (_l = 0, _len3 = els.length; _l < _len3; _l++) {
  3821.         el = els[_l];
  3822.         el.id = "" + now + "_" + el.id;
  3823.       }
  3824.       $.rmClass(root, 'forwarded');
  3825.       $.rmClass(root, 'qphl');
  3826.       $.rmClass(post, 'highlight');
  3827.       $.rmClass(post, 'qphl');
  3828.       root.hidden = post.hidden = false;
  3829.       return root;
  3830.     },
  3831.     title: function(thread) {
  3832.       var el, op, span;
  3833.       op = $('.op', thread);
  3834.       el = $('.subject', op);
  3835.       if (!el.textContent) {
  3836.         el = $('blockquote', op);
  3837.         if (!el.textContent) {
  3838.           el = $('.nameBlock', op);
  3839.         }
  3840.       }
  3841.       span = $.el('span', {
  3842.         innerHTML: el.innerHTML.replace(/<br>/g, ' ')
  3843.       });
  3844.       return "/" + g.BOARD + "/ - " + (span.textContent.trim());
  3845.     }
  3846.   };
  3847.  
  3848.   TitlePost = {
  3849.     init: function() {
  3850.       return d.title = Get.title();
  3851.     }
  3852.   };
  3853.  
  3854.   QuoteBacklink = {
  3855.     init: function() {
  3856.       var format;
  3857.       format = Conf['backlink'].replace(/%id/g, "' + id + '");
  3858.       this.funk = Function('id', "return '" + format + "'");
  3859.       return Main.callbacks.push(this.node);
  3860.     },
  3861.     node: function(post) {
  3862.       var a, container, el, link, qid, quote, quotes, _i, _len, _ref;
  3863.       if (post.isInlined) {
  3864.         return;
  3865.       }
  3866.       quotes = {};
  3867.       _ref = post.quotes;
  3868.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3869.         quote = _ref[_i];
  3870.         if (qid = quote.hash.slice(2)) {
  3871.           quotes[qid] = true;
  3872.         }
  3873.       }
  3874.       a = $.el('a', {
  3875.         href: "/" + g.BOARD + "/res/" + post.threadID + "#p" + post.ID,
  3876.         className: post.el.hidden ? 'filtered backlink' : 'backlink',
  3877.         textContent: QuoteBacklink.funk(post.ID)
  3878.       });
  3879.       for (qid in quotes) {
  3880.         if (!(el = $.id("pi" + qid)) || !Conf['OP Backlinks'] && /\bop\b/.test(el.parentNode.className)) {
  3881.           continue;
  3882.         }
  3883.         link = a.cloneNode(true);
  3884.         if (Conf['Quote Preview']) {
  3885.           $.on(link, 'mouseover', QuotePreview.mouseover);
  3886.         }
  3887.         if (Conf['Quote Inline']) {
  3888.           $.on(link, 'click', QuoteInline.toggle);
  3889.         } else {
  3890.           link.setAttribute('onclick', "replyhl('" + post.ID + "');");
  3891.         }
  3892.         if (!(container = $.id("blc" + qid))) {
  3893.           container = $.el('span', {
  3894.             className: 'container',
  3895.             id: "blc" + qid
  3896.           });
  3897.           $.add(el, container);
  3898.         }
  3899.         $.add(container, [$.tn(' '), link]);
  3900.       }
  3901.     }
  3902.   };
  3903.  
  3904.   QuoteInline = {
  3905.     init: function() {
  3906.       return Main.callbacks.push(this.node);
  3907.     },
  3908.     node: function(post) {
  3909.       var quote, _i, _j, _len, _len1, _ref, _ref1;
  3910.       _ref = post.quotes;
  3911.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3912.         quote = _ref[_i];
  3913.         if (!(quote.hash || /\bdeadlink\b/.test(quote.className))) {
  3914.           continue;
  3915.         }
  3916.         quote.removeAttribute('onclick');
  3917.         $.on(quote, 'click', QuoteInline.toggle);
  3918.       }
  3919.       _ref1 = post.backlinks;
  3920.       for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  3921.         quote = _ref1[_j];
  3922.         $.on(quote, 'click', QuoteInline.toggle);
  3923.       }
  3924.     },
  3925.     toggle: function(e) {
  3926.       var id;
  3927.       if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
  3928.         return;
  3929.       }
  3930.       e.preventDefault();
  3931.       id = this.dataset.id || this.hash.slice(2);
  3932.       if (/\binlined\b/.test(this.className)) {
  3933.         QuoteInline.rm(this, id);
  3934.       } else {
  3935.         if ($.x("ancestor::div[contains(@id,'p" + id + "')]", this)) {
  3936.           return;
  3937.         }
  3938.         QuoteInline.add(this, id);
  3939.       }
  3940.       return this.classList.toggle('inlined');
  3941.     },
  3942.     add: function(q, id) {
  3943.       var board, el, inline, isBacklink, path, postID, root, threadID;
  3944.       if (q.host === 'boards.4chan.org') {
  3945.         path = q.pathname.split('/');
  3946.         board = path[1];
  3947.         threadID = path[3];
  3948.         postID = id;
  3949.       } else {
  3950.         board = q.dataset.board;
  3951.         threadID = 0;
  3952.         postID = q.dataset.id;
  3953.       }
  3954.       el = board === g.BOARD ? $.id("p" + postID) : false;
  3955.       inline = $.el('div', {
  3956.         id: "i" + postID,
  3957.         className: el ? 'inline' : 'inline crosspost'
  3958.       });
  3959.       root = (isBacklink = /\bbacklink\b/.test(q.className)) ? q.parentNode : $.x('ancestor-or-self::*[parent::blockquote][1]', q);
  3960.       $.after(root, inline);
  3961.       Get.post(board, threadID, postID, inline);
  3962.       if (!el) {
  3963.         return;
  3964.       }
  3965.       if (isBacklink && Conf['Forward Hiding']) {
  3966.         $.addClass(el.parentNode, 'forwarded');
  3967.         ++el.dataset.forwarded || (el.dataset.forwarded = 1);
  3968.       }
  3969.       if (Unread.replies && postID in Unread.replies) {
  3970.         Unread.replies.rm(postID);
  3971.         return Unread.update(true);
  3972.       }
  3973.     },
  3974.     rm: function(q, id) {
  3975.       var div, inlined, _i, _len, _ref;
  3976.       div = $.x("following::div[@id='i" + id + "']", q);
  3977.       $.rm(div);
  3978.       if (!Conf['Forward Hiding']) {
  3979.         return;
  3980.       }
  3981.       _ref = $$('.backlink.inlined', div);
  3982.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  3983.         inlined = _ref[_i];
  3984.         div = $.id(inlined.hash.slice(1));
  3985.         if (!--div.dataset.forwarded) {
  3986.           $.rmClass(div.parentNode, 'forwarded');
  3987.         }
  3988.       }
  3989.       if (/\bbacklink\b/.test(q.className)) {
  3990.         div = $.id("p" + id);
  3991.         if (!--div.dataset.forwarded) {
  3992.           return $.rmClass(div.parentNode, 'forwarded');
  3993.         }
  3994.       }
  3995.     }
  3996.   };
  3997.  
  3998.   QuotePreview = {
  3999.     init: function() {
  4000.       return Main.callbacks.push(this.node);
  4001.     },
  4002.     node: function(post) {
  4003.       var quote, _i, _j, _len, _len1, _ref, _ref1;
  4004.       _ref = post.quotes;
  4005.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  4006.         quote = _ref[_i];
  4007.         if (quote.hash || /\bdeadlink\b/.test(quote.className)) {
  4008.           $.on(quote, 'mouseover', QuotePreview.mouseover);
  4009.         }
  4010.       }
  4011.       _ref1 = post.backlinks;
  4012.       for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  4013.         quote = _ref1[_j];
  4014.         $.on(quote, 'mouseover', QuotePreview.mouseover);
  4015.       }
  4016.     },
  4017.     mouseover: function(e) {
  4018.       var board, el, path, postID, qp, quote, quoterID, threadID, _i, _len, _ref;
  4019.       if (/\binlined\b/.test(this.className)) {
  4020.         return;
  4021.       }
  4022.       if (qp = $.id('qp')) {
  4023.         if (qp === UI.el) {
  4024.           delete UI.el;
  4025.         }
  4026.         $.rm(qp);
  4027.       }
  4028.       if (UI.el) {
  4029.         return;
  4030.       }
  4031.       if (this.host === 'boards.4chan.org') {
  4032.         path = this.pathname.split('/');
  4033.         board = path[1];
  4034.         threadID = path[3];
  4035.         postID = this.hash.slice(2);
  4036.       } else {
  4037.         board = this.dataset.board;
  4038.         threadID = 0;
  4039.         postID = this.dataset.id;
  4040.       }
  4041.       qp = UI.el = $.el('div', {
  4042.         id: 'qp',
  4043.         className: 'reply dialog'
  4044.       });
  4045.       UI.hover(e);
  4046.       $.add(d.body, qp);
  4047.       if (board === g.BOARD) {
  4048.         el = $.id("p" + postID);
  4049.       }
  4050.       Get.post(board, threadID, postID, qp, function() {
  4051.         var bq, img, post;
  4052.         bq = $('blockquote', qp);
  4053.         Main.prettify(bq);
  4054.         post = {
  4055.           el: qp,
  4056.           blockquote: bq,
  4057.           isArchived: /\barchivedPost\b/.test(qp.className)
  4058.         };
  4059.         if (img = $('img[data-md5]', qp)) {
  4060.           post.fileInfo = img.parentNode.previousElementSibling;
  4061.           post.img = img;
  4062.         }
  4063.         if (Conf['Reveal Spoilers']) {
  4064.           RevealSpoilers.node(post);
  4065.         }
  4066.         if (Conf['Image Auto-Gif']) {
  4067.           AutoGif.node(post);
  4068.         }
  4069.         if (Conf['Time Formatting']) {
  4070.           Time.node(post);
  4071.         }
  4072.         if (Conf['File Info Formatting']) {
  4073.           FileInfo.node(post);
  4074.         }
  4075.         if (Conf['Resurrect Quotes']) {
  4076.           return Quotify.node(post);
  4077.         }
  4078.       });
  4079.       $.on(this, 'mousemove', UI.hover);
  4080.       $.on(this, 'mouseout click', QuotePreview.mouseout);
  4081.       if (!el) {
  4082.         return;
  4083.       }
  4084.       if (Conf['Quote Highlighting']) {
  4085.         if (/\bop\b/.test(el.className)) {
  4086.           $.addClass(el.parentNode, 'qphl');
  4087.         } else {
  4088.           $.addClass(el, 'qphl');
  4089.         }
  4090.       }
  4091.       quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0];
  4092.       _ref = $$('.quotelink, .backlink', qp);
  4093.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  4094.         quote = _ref[_i];
  4095.         if (quote.hash.slice(2) === quoterID) {
  4096.           $.addClass(quote, 'forwardlink');
  4097.         }
  4098.       }
  4099.     },
  4100.     mouseout: function(e) {
  4101.       var el;
  4102.       UI.hoverend();
  4103.       if (el = $.id(this.hash.slice(1))) {
  4104.         $.rmClass(el, 'qphl');
  4105.         $.rmClass(el.parentNode, 'qphl');
  4106.       }
  4107.       $.off(this, 'mousemove', UI.hover);
  4108.       return $.off(this, 'mouseout click', QuotePreview.mouseout);
  4109.     }
  4110.   };
  4111.  
  4112.   QuoteOP = {
  4113.     init: function() {
  4114.       return Main.callbacks.push(this.node);
  4115.     },
  4116.     node: function(post) {
  4117.       var quote, _i, _len, _ref;
  4118.       if (post.isInlined && !post.isCrosspost) {
  4119.         return;
  4120.       }
  4121.       _ref = post.quotes;
  4122.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  4123.         quote = _ref[_i];
  4124.         if (quote.hash.slice(2) === post.threadID) {
  4125.           $.add(quote, $.tn($.NBSP + '(OP)'));
  4126.         }
  4127.       }
  4128.     }
  4129.   };
  4130.  
  4131.   QuoteCT = {
  4132.     init: function() {
  4133.       return Main.callbacks.push(this.node);
  4134.     },
  4135.     node: function(post) {
  4136.       var path, quote, _i, _len, _ref;
  4137.       if (post.isInlined && !post.isCrosspost) {
  4138.         return;
  4139.       }
  4140.       _ref = post.quotes;
  4141.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  4142.         quote = _ref[_i];
  4143.         if (!quote.hash) {
  4144.           continue;
  4145.         }
  4146.         path = quote.pathname.split('/');
  4147.         if (path[1] === g.BOARD && path[3] !== post.threadID) {
  4148.           $.add(quote, $.tn($.NBSP + '(Cross-thread)'));
  4149.         }
  4150.       }
  4151.     }
  4152.   };
  4153.  
  4154.   Quotify = {
  4155.     init: function() {
  4156.       return Main.callbacks.push(this.node);
  4157.     },
  4158.     node: function(post) {
  4159.       var a, board, data, i, id, index, m, node, nodes, quote, quotes, snapshot, text, _i, _j, _len, _ref;
  4160.       if (post.isInlined && !post.isCrosspost) {
  4161.         return;
  4162.       }
  4163.       snapshot = $.X('.//text()[not(parent::a)]', post.blockquote);
  4164.       for (i = _i = 0, _ref = snapshot.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  4165.         node = snapshot.snapshotItem(i);
  4166.         data = node.data;
  4167.         if (!(quotes = data.match(/>>(>\/[a-z\d]+\/)?\d+/g))) {
  4168.           continue;
  4169.         }
  4170.         nodes = [];
  4171.         for (_j = 0, _len = quotes.length; _j < _len; _j++) {
  4172.           quote = quotes[_j];
  4173.           index = data.indexOf(quote);
  4174.           if (text = data.slice(0, index)) {
  4175.             nodes.push($.tn(text));
  4176.           }
  4177.           id = quote.match(/\d+$/)[0];
  4178.           board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : $('a[title="Highlight this post"]', post.el).pathname.split('/')[1];
  4179.           nodes.push(a = $.el('a', {
  4180.             textContent: "" + quote + $.NBSP + "(Dead)"
  4181.           }));
  4182.           if (board === g.BOARD && $.id("p" + id)) {
  4183.             a.href = "#p" + id;
  4184.             a.className = 'quotelink';
  4185.             a.setAttribute('onclick', "replyhl('" + id + "');");
  4186.           } else {
  4187.             a.href = Redirect.thread(board, 0, id);
  4188.             a.className = 'deadlink';
  4189.             a.target = '_blank';
  4190.             if (Redirect.post(board, id)) {
  4191.               $.addClass(a, 'quotelink');
  4192.               a.setAttribute('data-board', board);
  4193.               a.setAttribute('data-id', id);
  4194.             }
  4195.           }
  4196.           data = data.slice(index + quote.length);
  4197.         }
  4198.         if (data) {
  4199.           nodes.push($.tn(data));
  4200.         }
  4201.         $.replace(node, nodes);
  4202.       }
  4203.     }
  4204.   };
  4205.  
  4206.   QuoteThreading = {
  4207.     init: function() {
  4208.       var controls, form, input;
  4209.       if (!(Conf['Unread Count'] || Conf['Unread Favicon'])) {
  4210.         return;
  4211.       }
  4212.       Main.callbacks.push(this.node);
  4213.       this.enabled = true;
  4214.       controls = $.el('span', {
  4215.         innerHTML: '<label>Threading<input id=threadingControl type=checkbox checked></label>'
  4216.       });
  4217.       input = $('input', controls);
  4218.       $.on(input, 'change', QuoteThreading.toggle);
  4219.       form = $('#delform');
  4220.       return $.prepend(form, controls);
  4221.     },
  4222.     node: function(post) {
  4223.       var ID, keys, pEl, pid, preply, qid, qreply, qroot, quote, quotes, replies, reply, threadContainer, uniq, _i, _len;
  4224.       if (post.isInlined || !QuoteThreading.enabled) {
  4225.         return;
  4226.       }
  4227.       quotes = post.quotes, ID = post.ID;
  4228.       replies = Unread.replies;
  4229.       if (!(reply = replies[ID])) {
  4230.         return;
  4231.       }
  4232.       uniq = {};
  4233.       for (_i = 0, _len = quotes.length; _i < _len; _i++) {
  4234.         quote = quotes[_i];
  4235.         qid = quote.hash.slice(2);
  4236.         if (!(qid < ID)) {
  4237.           continue;
  4238.         }
  4239.         if (qid in replies) {
  4240.           uniq[qid] = true;
  4241.         }
  4242.       }
  4243.       keys = Object.keys(uniq);
  4244.       if (keys.length !== 1) {
  4245.         return;
  4246.       }
  4247.       qid = keys[0];
  4248.       qreply = replies[qid];
  4249.       qroot = qreply.el.parentNode;
  4250.       threadContainer = qroot.nextSibling;
  4251.       if ((threadContainer != null ? threadContainer.className : void 0) !== 'threadContainer') {
  4252.         threadContainer = $.el('div', {
  4253.           className: 'threadContainer'
  4254.         });
  4255.         $.after(qroot, threadContainer);
  4256.       }
  4257.       $.add(threadContainer, reply.el.parentNode);
  4258.       pEl = $.x('preceding::div[contains(@class,"post reply")][1]/parent::div', reply.el.parentNode);
  4259.       pid = pEl.id.slice(2);
  4260.       preply = replies[pid];
  4261.       return replies.after(preply, reply);
  4262.     },
  4263.     toggle: function() {
  4264.       var container, containers, node, nodes, replies, reply, thread, _i, _j, _k, _len, _len1, _len2;
  4265.       Main.disconnect();
  4266.       Unread.replies = new $.RandomAccessList;
  4267.       thread = $('.thread');
  4268.       replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
  4269.       QuoteThreading.enabled = this.checked;
  4270.       if (this.checked) {
  4271.         nodes = (function() {
  4272.           var _i, _len, _results;
  4273.           _results = [];
  4274.           for (_i = 0, _len = replies.length; _i < _len; _i++) {
  4275.             reply = replies[_i];
  4276.             _results.push(Main.preParse(reply));
  4277.           }
  4278.           return _results;
  4279.         })();
  4280.         for (_i = 0, _len = nodes.length; _i < _len; _i++) {
  4281.           node = nodes[_i];
  4282.           Unread.node(node);
  4283.         }
  4284.         Unread.scroll();
  4285.         for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {
  4286.           node = nodes[_j];
  4287.           QuoteThreading.node(node);
  4288.         }
  4289.       } else {
  4290.         replies.sort(function(a, b) {
  4291.           var aID, bID;
  4292.           aID = Number(a.id.slice(2));
  4293.           bID = Number(b.id.slice(2));
  4294.           return aID - bID;
  4295.         });
  4296.         $.add(thread, replies);
  4297.         containers = $$('.threadContainer', thread);
  4298.         for (_k = 0, _len2 = containers.length; _k < _len2; _k++) {
  4299.           container = containers[_k];
  4300.           $.rm(container);
  4301.         }
  4302.         Unread.update(true);
  4303.       }
  4304.       return Main.observe();
  4305.     },
  4306.     "public": {
  4307.       toggle: function() {
  4308.         var control;
  4309.         control = $.id('threadingControl');
  4310.         control.checked = !control.checked;
  4311.         return QuoteThreading.toggle.call(control);
  4312.       }
  4313.     }
  4314.   };
  4315.  
  4316.   DeleteLink = {
  4317.     init: function() {
  4318.       var aImage, aPost, children, div;
  4319.       div = $.el('div', {
  4320.         className: 'delete_link',
  4321.         textContent: 'Delete'
  4322.       });
  4323.       aPost = $.el('a', {
  4324.         className: 'delete_post',
  4325.         href: 'javascript:;'
  4326.       });
  4327.       aImage = $.el('a', {
  4328.         className: 'delete_image',
  4329.         href: 'javascript:;'
  4330.       });
  4331.       children = [];
  4332.       children.push({
  4333.         el: aPost,
  4334.         open: function() {
  4335.           aPost.textContent = 'Post';
  4336.           $.on(aPost, 'click', DeleteLink["delete"]);
  4337.           return true;
  4338.         }
  4339.       });
  4340.       children.push({
  4341.         el: aImage,
  4342.         open: function(post) {
  4343.           if (!post.img) {
  4344.             return false;
  4345.           }
  4346.           aImage.textContent = 'Image';
  4347.           $.on(aImage, 'click', DeleteLink["delete"]);
  4348.           return true;
  4349.         }
  4350.       });
  4351.       Menu.addEntry({
  4352.         el: div,
  4353.         open: function(post) {
  4354.           var node, seconds;
  4355.           if (post.isArchived) {
  4356.             return false;
  4357.           }
  4358.           node = div.firstChild;
  4359.           if (seconds = DeleteLink.cooldown[post.ID]) {
  4360.             node.textContent = "Delete (" + seconds + ")";
  4361.             DeleteLink.cooldown.el = node;
  4362.           } else {
  4363.             node.textContent = 'Delete';
  4364.             delete DeleteLink.cooldown.el;
  4365.           }
  4366.           return true;
  4367.         },
  4368.         children: children
  4369.       });
  4370.       return $.on(d, 'QRPostSuccessful', this.cooldown.start);
  4371.     },
  4372.     "delete": function() {
  4373.       var board, form, id, m, menu, pwd, self;
  4374.       menu = $.id('menu');
  4375.       id = menu.dataset.id;
  4376.       if (DeleteLink.cooldown[id]) {
  4377.         return;
  4378.       }
  4379.       $.off(this, 'click', DeleteLink["delete"]);
  4380.       this.textContent = 'Deleting...';
  4381.       pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $.id('delPassword').value;
  4382.       board = $('a[title="Highlight this post"]', $.id(menu.dataset.rootid)).pathname.split('/')[1];
  4383.       self = this;
  4384.       form = {
  4385.         mode: 'usrdel',
  4386.         onlyimgdel: /\bdelete_image\b/.test(this.className),
  4387.         pwd: pwd
  4388.       };
  4389.       form[id] = 'delete';
  4390.       return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + board + "/"), {
  4391.         onload: function() {
  4392.           return DeleteLink.load(self, this.response);
  4393.         },
  4394.         onerror: function() {
  4395.           return DeleteLink.error(self);
  4396.         }
  4397.       }, {
  4398.         form: $.formData(form)
  4399.       });
  4400.     },
  4401.     load: function(self, html) {
  4402.       var doc, msg, s;
  4403.       doc = d.implementation.createHTMLDocument('');
  4404.       doc.documentElement.innerHTML = html;
  4405.       if (doc.title === '4chan - Banned') {
  4406.         s = 'Banned!';
  4407.       } else if (msg = doc.getElementById('errmsg')) {
  4408.         s = msg.textContent;
  4409.         $.on(self, 'click', DeleteLink["delete"]);
  4410.       } else {
  4411.         s = 'Deleted';
  4412.       }
  4413.       return self.textContent = s;
  4414.     },
  4415.     error: function(self) {
  4416.       self.textContent = 'Connection error, please retry.';
  4417.       return $.on(self, 'click', DeleteLink["delete"]);
  4418.     },
  4419.     cooldown: {
  4420.       start: function(e) {
  4421.         return DeleteLink.cooldown.count(e.detail.postID, 30);
  4422.       },
  4423.       count: function(postID, seconds) {
  4424.         var el;
  4425.         if (!((0 <= seconds && seconds <= 30))) {
  4426.           return;
  4427.         }
  4428.         setTimeout(DeleteLink.cooldown.count, 1000, postID, seconds - 1);
  4429.         el = DeleteLink.cooldown.el;
  4430.         if (seconds === 0) {
  4431.           if (el != null) {
  4432.             el.textContent = 'Delete';
  4433.           }
  4434.           delete DeleteLink.cooldown[postID];
  4435.           delete DeleteLink.cooldown.el;
  4436.           return;
  4437.         }
  4438.         if (el != null) {
  4439.           el.textContent = "Delete (" + seconds + ")";
  4440.         }
  4441.         return DeleteLink.cooldown[postID] = seconds;
  4442.       }
  4443.     }
  4444.   };
  4445.  
  4446.   ReportLink = {
  4447.     init: function() {
  4448.       var a;
  4449.       a = $.el('a', {
  4450.         className: 'report_link',
  4451.         href: 'javascript:;',
  4452.         textContent: 'Report this post'
  4453.       });
  4454.       $.on(a, 'click', this.report);
  4455.       return Menu.addEntry({
  4456.         el: a,
  4457.         open: function(post) {
  4458.           return post.isArchived === false;
  4459.         }
  4460.       });
  4461.     },
  4462.     report: function() {
  4463.       var a, id, set, url;
  4464.       a = $('a[title="Highlight this post"]', $.id(this.parentNode.dataset.rootid));
  4465.       url = "//sys.4chan.org/" + (a.pathname.split('/')[1]) + "/imgboard.php?mode=report&no=" + this.parentNode.dataset.id;
  4466.       id = Date.now();
  4467.       set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200";
  4468.       return window.open(url, id, set);
  4469.     }
  4470.   };
  4471.  
  4472.   DownloadLink = {
  4473.     init: function() {
  4474.       var a;
  4475.       if ($.el('a').download === void 0) {
  4476.         return;
  4477.       }
  4478.       a = $.el('a', {
  4479.         className: 'download_link',
  4480.         textContent: 'Download file'
  4481.       });
  4482.       return Menu.addEntry({
  4483.         el: a,
  4484.         open: function(post) {
  4485.           var fileText;
  4486.           if (!post.img) {
  4487.             return false;
  4488.           }
  4489.           a.href = post.img.parentNode.href;
  4490.           fileText = post.fileInfo.firstElementChild;
  4491.           a.download = Conf['File Info Formatting'] ? fileText.dataset.filename : $('span', fileText).title;
  4492.           return true;
  4493.         }
  4494.       });
  4495.     }
  4496.   };
  4497.  
  4498.   ArchiveLink = {
  4499.     init: function() {
  4500.       var a;
  4501.       a = $.el('a', {
  4502.         className: 'archive_link',
  4503.         target: '_blank',
  4504.         textContent: 'Archived post'
  4505.       });
  4506.       return Menu.addEntry({
  4507.         el: a,
  4508.         open: function(post) {
  4509.           var href, path;
  4510.           path = $('a[title="Highlight this post"]', post.el).pathname.split('/');
  4511.           if ((href = Redirect.thread(path[1], path[3], post.ID)) === ("//boards.4chan.org/" + path[1] + "/")) {
  4512.             return false;
  4513.           }
  4514.           a.href = href;
  4515.           return true;
  4516.         }
  4517.       });
  4518.     }
  4519.   };
  4520.  
  4521.   ThreadStats = {
  4522.     init: function() {
  4523.       var dialog;
  4524.       dialog = UI.dialog('stats', 'bottom: 0; left: 0;', '<div class=move><span id=postcount>0</span> / <span id=imagecount>0</span></div>');
  4525.       dialog.className = 'dialog';
  4526.       $.add(d.body, dialog);
  4527.       this.posts = this.images = 0;
  4528.       this.imgLimit = (function() {
  4529.         switch (g.BOARD) {
  4530.           case 'a':
  4531.           case 'b':
  4532.           case 'v':
  4533.           case 'co':
  4534.           case 'mlp':
  4535.             return 251;
  4536.           case 'vg':
  4537.             return 501;
  4538.           default:
  4539.             return 151;
  4540.         }
  4541.       })();
  4542.       return Main.callbacks.push(this.node);
  4543.     },
  4544.     node: function(post) {
  4545.       var imgcount;
  4546.       if (post.isInlined) {
  4547.         return;
  4548.       }
  4549.       $.id('postcount').textContent = ++ThreadStats.posts;
  4550.       if (!post.img) {
  4551.         return;
  4552.       }
  4553.       imgcount = $.id('imagecount');
  4554.       imgcount.textContent = ++ThreadStats.images;
  4555.       if (ThreadStats.images > ThreadStats.imgLimit) {
  4556.         return $.addClass(imgcount, 'warning');
  4557.       }
  4558.     }
  4559.   };
  4560.  
  4561.   Unread = {
  4562.     init: function() {
  4563.       this.replies = new $.RandomAccessList;
  4564.       this.title = d.title;
  4565.       $.on(d, 'QRPostSuccessful', this.post);
  4566.       this.update();
  4567.       $.on(window, 'scroll', Unread.scroll);
  4568.       return Main.callbacks.push(this.node);
  4569.     },
  4570.     foresee: [],
  4571.     post: function(e) {
  4572.       return Unread.foresee.push(e.detail.postID);
  4573.     },
  4574.     node: function(post) {
  4575.       var el, index, replies;
  4576.       el = post.el;
  4577.       if ((index = Unread.foresee.indexOf(post.ID)) !== -1) {
  4578.         Unread.foresee.splice(index, 1);
  4579.         return;
  4580.       }
  4581.       if (el.hidden || /\bop\b/.test(post["class"]) || post.isInlined) {
  4582.         return;
  4583.       }
  4584.       replies = Unread.replies;
  4585.       replies.push(post.ID, el);
  4586.       return Unread.update(replies.length === 1);
  4587.     },
  4588.     scroll: function() {
  4589.       var bottom, first, height, replies, update;
  4590.       height = d.documentElement.clientHeight;
  4591.       replies = Unread.replies;
  4592.       first = replies.first;
  4593.       update = false;
  4594.       while (first) {
  4595.         bottom = first.el.getBoundingClientRect().bottom;
  4596.         if (bottom > height) {
  4597.           break;
  4598.         }
  4599.         update = true;
  4600.         replies.shift();
  4601.         first = replies.first;
  4602.       }
  4603.       if (!update) {
  4604.         return;
  4605.       }
  4606.       return Unread.update(replies.length === 0);
  4607.     },
  4608.     setTitle: function(count) {
  4609.       return d.title = "(" + count + ") " + Unread.title;
  4610.     },
  4611.     update: function(updateFavicon) {
  4612.       var count;
  4613.       if (!g.REPLY) {
  4614.         return;
  4615.       }
  4616.       count = this.replies.length;
  4617.       if (Conf['Unread Count']) {
  4618.         this.setTitle(count);
  4619.       }
  4620.       if (!(Conf['Unread Favicon'] && updateFavicon)) {
  4621.         return;
  4622.       }
  4623.       if ($.engine === 'presto') {
  4624.         $.rm(Favicon.el);
  4625.       }
  4626.       Favicon.el.href = g.dead ? count ? Favicon.unreadDead : Favicon.dead : count ? Favicon.unread : Favicon["default"];
  4627.       if (g.dead) {
  4628.         $.addClass(Favicon.el, 'dead');
  4629.       } else {
  4630.         $.rmClass(Favicon.el, 'dead');
  4631.       }
  4632.       if (count) {
  4633.         $.addClass(Favicon.el, 'unread');
  4634.       } else {
  4635.         $.rmClass(Favicon.el, 'unread');
  4636.       }
  4637.       if ($.engine !== 'webkit') {
  4638.         return $.add(d.head, Favicon.el);
  4639.       }
  4640.     }
  4641.   };
  4642.  
  4643.   Favicon = {
  4644.     init: function() {
  4645.       var href;
  4646.       this.el = $('link[rel="shortcut icon"]', d.head);
  4647.       this.el.type = 'image/x-icon';
  4648.       href = this.el.href;
  4649.       this.SFW = /ws.ico$/.test(href);
  4650.       this["default"] = href;
  4651.       return this["switch"]();
  4652.     },
  4653.     "switch": function() {
  4654.       switch (Conf['favicon']) {
  4655.         case 'ferongr':
  4656.           this.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
  4657.           this.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
  4658.           this.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
  4659.           break;
  4660.         case 'xat-':
  4661.           this.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC';
  4662.           this.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg==';
  4663.           this.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII=';
  4664.           break;
  4665.         case 'Mayhem':
  4666.           this.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg==';
  4667.           this.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC';
  4668.           this.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII=';
  4669.           break;
  4670.         case 'Original':
  4671.           this.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
  4672.           this.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
  4673.           this.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
  4674.       }
  4675.       return this.unread = this.SFW ? this.unreadSFW : this.unreadNSFW;
  4676.     },
  4677.     empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
  4678.     dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
  4679.   };
  4680.  
  4681.   Redirect = {
  4682.     image: function(board, filename) {
  4683.       switch (board) {
  4684.         case 'a':
  4685.         case 'jp':
  4686.         case 'm':
  4687.         case 'sp':
  4688.         case 'tg':
  4689.         case 'vg':
  4690.         case 'wsg':
  4691.           return "//archive.foolz.us/" + board + "/full_image/" + filename;
  4692.         case 'u':
  4693.           return "//nsfw.foolz.us/" + board + "/full_image/" + filename;
  4694.       }
  4695.     },
  4696.     post: function(board, postID) {
  4697.       switch (board) {
  4698.         case 'a':
  4699.         case 'co':
  4700.         case 'jp':
  4701.         case 'm':
  4702.         case 'sp':
  4703.         case 'tg':
  4704.         case 'tv':
  4705.         case 'v':
  4706.         case 'vg':
  4707.         case 'wsg':
  4708.         case 'dev':
  4709.         case 'foolz':
  4710.           return "//archive.foolz.us/api/chan/post/board/" + board + "/num/" + postID + "/format/json";
  4711.         case 'u':
  4712.         case 'kuku':
  4713.           return "//nsfw.foolz.us/api/chan/post/board/" + board + "/num/" + postID + "/format/json";
  4714.       }
  4715.     },
  4716.     thread: function(board, threadID, postID) {
  4717.       var path, url;
  4718.       if (postID) {
  4719.         postID = postID.match(/\d+/)[0];
  4720.       }
  4721.       path = threadID ? "" + board + "/thread/" + threadID : "" + board + "/post/" + postID;
  4722.       switch (board) {
  4723.         case 'a':
  4724.         case 'co':
  4725.         case 'jp':
  4726.         case 'm':
  4727.         case 'sp':
  4728.         case 'tg':
  4729.         case 'tv':
  4730.         case 'v':
  4731.         case 'vg':
  4732.         case 'wsg':
  4733.         case 'dev':
  4734.         case 'foolz':
  4735.           url = "//archive.foolz.us/" + path + "/";
  4736.           if (threadID && postID) {
  4737.             url += "#" + postID;
  4738.           }
  4739.           break;
  4740.         case 'u':
  4741.         case 'kuku':
  4742.           url = "//nsfw.foolz.us/" + path + "/";
  4743.           if (threadID && postID) {
  4744.             url += "#" + postID;
  4745.           }
  4746.           break;
  4747.         case 'ck':
  4748.         case 'jp':
  4749.         case 'lit':
  4750.           url = "//fuuka.warosu.org/" + path;
  4751.           if (threadID && postID) {
  4752.             url += "#p" + postID;
  4753.           }
  4754.           break;
  4755.         case 'diy':
  4756.         case 'g':
  4757.         case 'sci':
  4758.           url = "//archive.installgentoo.net/" + path;
  4759.           if (threadID && postID) {
  4760.             url += "#p" + postID;
  4761.           }
  4762.           break;
  4763.         case 'cgl':
  4764.         case 'mu':
  4765.         case 'soc':
  4766.         case 'w':
  4767.           url = "//archive.rebeccablacktech.com/" + path;
  4768.           if (threadID && postID) {
  4769.             url += "#p" + postID;
  4770.           }
  4771.           break;
  4772.         case 'an':
  4773.         case 'fit':
  4774.         case 'k':
  4775.         case 'r9k':
  4776.         case 'toy':
  4777.         case 'x':
  4778.           url = "http://archive.maidlab.jp/" + path;
  4779.           if (threadID && postID) {
  4780.             url += "#p" + postID;
  4781.           }
  4782.           break;
  4783.         case 'e':
  4784.           url = "https://md401.homelinux.net/4chan/cgi-board.pl/" + path;
  4785.           if (threadID && postID) {
  4786.             url += "#p" + postID;
  4787.           }
  4788.           break;
  4789.         default:
  4790.           if (threadID) {
  4791.             url = "//boards.4chan.org/" + board + "/";
  4792.           }
  4793.       }
  4794.       return url || null;
  4795.     }
  4796.   };
  4797.  
  4798.   ImageHover = {
  4799.     init: function() {
  4800.       return Main.callbacks.push(this.node);
  4801.     },
  4802.     node: function(post) {
  4803.       if (!post.img) {
  4804.         return;
  4805.       }
  4806.       return $.on(post.img, 'mouseover', ImageHover.mouseover);
  4807.     },
  4808.     mouseover: function() {
  4809.       var el;
  4810.       if (el = $.id('ihover')) {
  4811.         if (el === UI.el) {
  4812.           delete UI.el;
  4813.         }
  4814.         $.rm(el);
  4815.       }
  4816.       if (UI.el) {
  4817.         return;
  4818.       }
  4819.       el = UI.el = $.el('img', {
  4820.         id: 'ihover',
  4821.         src: this.parentNode.href
  4822.       });
  4823.       $.add(d.body, el);
  4824.       $.on(el, 'load', ImageHover.load);
  4825.       $.on(el, 'error', ImageHover.error);
  4826.       $.on(this, 'mousemove', UI.hover);
  4827.       return $.on(this, 'mouseout', ImageHover.mouseout);
  4828.     },
  4829.     load: function() {
  4830.       var style;
  4831.       if (!this.parentNode) {
  4832.         return;
  4833.       }
  4834.       style = this.style;
  4835.       return UI.hover({
  4836.         clientX: -45 + parseInt(style.left),
  4837.         clientY: 120 + parseInt(style.top)
  4838.       });
  4839.     },
  4840.     error: function() {
  4841.       var src, timeoutID, url,
  4842.         _this = this;
  4843.       src = this.src.split('/');
  4844.       if (!(src[2] === 'images.4chan.org' && (url = Redirect.image(src[3], src[5])))) {
  4845.         if (g.dead) {
  4846.           return;
  4847.         }
  4848.         url = "//images.4chan.org/" + src[3] + "/src/" + src[5];
  4849.       }
  4850.       if ($.engine !== 'webkit' && url.split('/')[2] === 'images.4chan.org') {
  4851.         return;
  4852.       }
  4853.       timeoutID = setTimeout((function() {
  4854.         return _this.src = url;
  4855.       }), 3000);
  4856.       if ($.engine !== 'webkit' || url.split('/')[2] !== 'images.4chan.org') {
  4857.         return;
  4858.       }
  4859.       return $.ajax(url, {
  4860.         onreadystatechange: (function() {
  4861.           if (this.status === 404) {
  4862.             return clearTimeout(timeoutID);
  4863.           }
  4864.         })
  4865.       }, {
  4866.         type: 'head'
  4867.       });
  4868.     },
  4869.     mouseout: function() {
  4870.       UI.hoverend();
  4871.       $.off(this, 'mousemove', UI.hover);
  4872.       return $.off(this, 'mouseout', ImageHover.mouseout);
  4873.     }
  4874.   };
  4875.  
  4876.   AutoGif = {
  4877.     init: function() {
  4878.       var _ref;
  4879.       if ((_ref = g.BOARD) === 'gif' || _ref === 'wsg') {
  4880.         return;
  4881.       }
  4882.       return Main.callbacks.push(this.node);
  4883.     },
  4884.     node: function(post) {
  4885.       var gif, img, src;
  4886.       img = post.img;
  4887.       if (post.el.hidden || !img) {
  4888.         return;
  4889.       }
  4890.       src = img.parentNode.href;
  4891.       if (/gif$/.test(src) && !/spoiler/.test(img.src)) {
  4892.         gif = $.el('img');
  4893.         $.on(gif, 'load', function() {
  4894.           return img.src = src;
  4895.         });
  4896.         return gif.src = src;
  4897.       }
  4898.     }
  4899.   };
  4900.  
  4901.   Prefetch = {
  4902.     init: function() {
  4903.       return this.dialog();
  4904.     },
  4905.     dialog: function() {
  4906.       var controls, first, input;
  4907.       controls = $.el('label', {
  4908.         id: 'prefetch',
  4909.         innerHTML: "Prefetch Images<input type=checkbox id=prefetch>"
  4910.       });
  4911.       input = $('input', controls);
  4912.       $.on(input, 'change', Prefetch.change);
  4913.       first = $.id('delform').firstElementChild;
  4914.       if (first.id === 'imgControls') {
  4915.         return $.after(first, controls);
  4916.       } else {
  4917.         return $.before(first, controls);
  4918.       }
  4919.     },
  4920.     change: function() {
  4921.       var img, thumb, _i, _len, _ref;
  4922.       $.off(this, 'change', Prefetch.change);
  4923.       _ref = $$('a.fileThumb');
  4924.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  4925.         thumb = _ref[_i];
  4926.         img = $.el('img', {
  4927.           src: thumb.href
  4928.         });
  4929.       }
  4930.       return Main.callbacks.push(Prefetch.node);
  4931.     },
  4932.     node: function(post) {
  4933.       var img;
  4934.       img = post.img;
  4935.       if (!img) {
  4936.         return;
  4937.       }
  4938.       return $.el('img', {
  4939.         src: img.parentNode.href
  4940.       });
  4941.     }
  4942.   };
  4943.  
  4944.   ImageExpand = {
  4945.     init: function() {
  4946.       Main.callbacks.push(this.node);
  4947.       return this.dialog();
  4948.     },
  4949.     node: function(post) {
  4950.       var a, img;
  4951.       img = post.img;
  4952.       if (!post.img) {
  4953.         return;
  4954.       }
  4955.       a = post.img.parentNode;
  4956.       $.on(a, 'click', ImageExpand.cb.toggle);
  4957.       if (img.alt.match(/^Spoiler/)) {
  4958.         console.log("spoilered: " + post);
  4959.       }
  4960.       if (ImageExpand.on && !post.el.hidden && (img.alt.match(/^Spoiler/ !== true))) {
  4961.         return ImageExpand.expand(post.img);
  4962.       }
  4963.     },
  4964.     cb: {
  4965.       toggle: function(e) {
  4966.         if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
  4967.           return;
  4968.         }
  4969.         e.preventDefault();
  4970.         return ImageExpand.toggle(this);
  4971.       },
  4972.       all: function() {
  4973.         var i, thumb, thumbs, _i, _j, _k, _len, _len1, _len2, _ref;
  4974.         ImageExpand.on = this.checked;
  4975.         if (ImageExpand.on) {
  4976.           thumbs = $$('img[data-md5]');
  4977.           if (Conf['Expand From Current']) {
  4978.             for (i = _i = 0, _len = thumbs.length; _i < _len; i = ++_i) {
  4979.               thumb = thumbs[i];
  4980.               if (thumb.getBoundingClientRect().top > 0) {
  4981.                 break;
  4982.               }
  4983.             }
  4984.             thumbs = thumbs.slice(i);
  4985.           }
  4986.           for (_j = 0, _len1 = thumbs.length; _j < _len1; _j++) {
  4987.             thumb = thumbs[_j];
  4988.             ImageExpand.expand(thumb);
  4989.           }
  4990.         } else {
  4991.           _ref = $$('img[data-md5][hidden]');
  4992.           for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) {
  4993.             thumb = _ref[_k];
  4994.             ImageExpand.contract(thumb);
  4995.           }
  4996.         }
  4997.       },
  4998.       typeChange: function() {
  4999.         var klass;
  5000.         switch (this.value) {
  5001.           case 'full':
  5002.             klass = '';
  5003.             break;
  5004.           case 'fit width':
  5005.             klass = 'fitwidth';
  5006.             break;
  5007.           case 'fit height':
  5008.             klass = 'fitheight';
  5009.             break;
  5010.           case 'fit screen':
  5011.             klass = 'fitwidth fitheight';
  5012.         }
  5013.         $.id('delform').className = klass;
  5014.         if (/\bfitheight\b/.test(klass)) {
  5015.           $.on(window, 'resize', ImageExpand.resize);
  5016.           if (!ImageExpand.style) {
  5017.             ImageExpand.style = $.addStyle('');
  5018.           }
  5019.           return ImageExpand.resize();
  5020.         } else if (ImageExpand.style) {
  5021.           return $.off(window, 'resize', ImageExpand.resize);
  5022.         }
  5023.       }
  5024.     },
  5025.     toggle: function(a) {
  5026.       var rect, thumb;
  5027.       thumb = a.firstChild;
  5028.       if (thumb.hidden) {
  5029.         rect = a.getBoundingClientRect();
  5030.         if ($.engine === 'webkit') {
  5031.           if (rect.top < 0) {
  5032.             d.body.scrollTop += rect.top - 42;
  5033.           }
  5034.           if (rect.left < 0) {
  5035.             d.body.scrollLeft += rect.left;
  5036.           }
  5037.         } else {
  5038.           if (rect.top < 0) {
  5039.             d.documentElement.scrollTop += rect.top - 42;
  5040.           }
  5041.           if (rect.left < 0) {
  5042.             d.documentElement.scrollLeft += rect.left;
  5043.           }
  5044.         }
  5045.         return ImageExpand.contract(thumb);
  5046.       } else {
  5047.         return ImageExpand.expand(thumb);
  5048.       }
  5049.     },
  5050.     contract: function(thumb) {
  5051.       thumb.hidden = false;
  5052.       thumb.nextSibling.hidden = true;
  5053.       return $.rmClass(thumb.parentNode.parentNode.parentNode, 'image_expanded');
  5054.     },
  5055.     expand: function(thumb, url) {
  5056.       var a, img;
  5057.       if ($.x('ancestor-or-self::*[@hidden]', thumb)) {
  5058.         return;
  5059.       }
  5060.       thumb.hidden = true;
  5061.       $.addClass(thumb.parentNode.parentNode.parentNode, 'image_expanded');
  5062.       if (img = thumb.nextSibling) {
  5063.         img.hidden = false;
  5064.         return;
  5065.       }
  5066.       a = thumb.parentNode;
  5067.       img = $.el('img', {
  5068.         src: url || a.href
  5069.       });
  5070.       $.on(img, 'error', ImageExpand.error);
  5071.       return $.add(a, img);
  5072.     },
  5073.     error: function() {
  5074.       var src, thumb, timeoutID, url;
  5075.       thumb = this.previousSibling;
  5076.       ImageExpand.contract(thumb);
  5077.       $.rm(this);
  5078.       src = this.src.split('/');
  5079.       if (!(src[2] === 'images.4chan.org' && (url = Redirect.image(src[3], src[5])))) {
  5080.         if (g.dead) {
  5081.           return;
  5082.         }
  5083.         url = "//images.4chan.org/" + src[3] + "/src/" + src[5];
  5084.       }
  5085.       if ($.engine !== 'webkit' && url.split('/')[2] === 'images.4chan.org') {
  5086.         return;
  5087.       }
  5088.       timeoutID = setTimeout(ImageExpand.expand, 10000, thumb, url);
  5089.       if ($.engine !== 'webkit' || url.split('/')[2] !== 'images.4chan.org') {
  5090.         return;
  5091.       }
  5092.       return $.ajax(url, {
  5093.         onreadystatechange: (function() {
  5094.           if (this.status === 404) {
  5095.             return clearTimeout(timeoutID);
  5096.           }
  5097.         })
  5098.       }, {
  5099.         type: 'head'
  5100.       });
  5101.     },
  5102.     dialog: function() {
  5103.       var controls, imageType, select;
  5104.       controls = $.el('span', {
  5105.         id: 'imgControls',
  5106.         innerHTML: "<select id=imageType name=imageType><option value=full>Full</option><option value='fit width'>Fit Width</option><option value='fit height'>Fit Height</option value='fit screen'><option value='fit screen'>Fit Screen</option></select><label>Expand Images<input type=checkbox id=imageExpand></label>"
  5107.       });
  5108.       imageType = $.get('imageType', 'full');
  5109.       select = $('select', controls);
  5110.       select.value = imageType;
  5111.       ImageExpand.cb.typeChange.call(select);
  5112.       $.on(select, 'change', $.cb.value);
  5113.       $.on(select, 'change', ImageExpand.cb.typeChange);
  5114.       $.on($('input', controls), 'click', ImageExpand.cb.all);
  5115.       return $.prepend($.id('delform'), controls);
  5116.     },
  5117.     resize: function() {
  5118.       return ImageExpand.style.textContent = ".fitheight img[data-md5] + img {max-height:" + d.documentElement.clientHeight + "px;}";
  5119.     }
  5120.   };
  5121.  
  5122.   Main = {
  5123.     init: function() {
  5124.       var key, path, pathname, temp, val;
  5125.       Main.flatten(null, Config);
  5126.       for (key in Conf) {
  5127.         val = Conf[key];
  5128.         Conf[key] = $.get(key, val);
  5129.       }
  5130.       path = location.pathname;
  5131.       pathname = path.slice(1).split('/');
  5132.       g.BOARD = pathname[0], temp = pathname[1];
  5133.       if (temp === 'res') {
  5134.         g.REPLY = true;
  5135.         g.THREAD_ID = pathname[2];
  5136.       }
  5137.       switch (location.hostname) {
  5138.         case 'sys.4chan.org':
  5139.           if (/report/.test(location.search)) {
  5140.             $.ready(function() {
  5141.               return $.on($.id('recaptcha_response_field'), 'keydown', function(e) {
  5142.                 if (e.keyCode === 8 && !e.target.value) {
  5143.                   return window.location = 'javascript:Recaptcha.reload()';
  5144.                 }
  5145.               });
  5146.             });
  5147.           }
  5148.           return;
  5149.         case 'images.4chan.org':
  5150.           $.ready(function() {
  5151.             var url;
  5152.             if (/^4chan - 404/.test(d.title) && Conf['404 Redirect']) {
  5153.               path = location.pathname.split('/');
  5154.               url = Redirect.image(path[1], path[3]);
  5155.               if (url) {
  5156.                 return location.href = url;
  5157.               }
  5158.             }
  5159.           });
  5160.           return;
  5161.       }
  5162.       Main.pruneHidden();
  5163.       if (Conf['Quick Reply'] && Conf['Hide Original Post Form']) {
  5164.         Main.css += '#postForm { display: none; }';
  5165.       }
  5166.       if (Conf['Recursive Filtering']) {
  5167.         Main.css += '.hidden + .threadContainer { display: none; }';
  5168.       }
  5169.       Main.addStyle();
  5170.       if (Conf['Filter']) {
  5171.         Filter.init();
  5172.       }
  5173.       if (Conf['Reply Hiding']) {
  5174.         ReplyHiding.init();
  5175.       }
  5176.       if (Conf['Filter'] || Conf['Reply Hiding']) {
  5177.         StrikethroughQuotes.init();
  5178.       }
  5179.       if (Conf['Anonymize']) {
  5180.         Anonymize.init();
  5181.       }
  5182.       if (Conf['Time Formatting']) {
  5183.         Time.init();
  5184.       }
  5185.       if (Conf['File Info Formatting']) {
  5186.         FileInfo.init();
  5187.       }
  5188.       if (Conf['Sauce']) {
  5189.         Sauce.init();
  5190.       }
  5191.       if (Conf['Reveal Spoilers']) {
  5192.         RevealSpoilers.init();
  5193.       }
  5194.       if (Conf['Image Auto-Gif']) {
  5195.         AutoGif.init();
  5196.       }
  5197.       if (Conf['Image Hover']) {
  5198.         ImageHover.init();
  5199.       }
  5200.       if (Conf['Menu']) {
  5201.         Menu.init();
  5202.         if (Conf['Report Link']) {
  5203.           ReportLink.init();
  5204.         }
  5205.         if (Conf['Delete Link']) {
  5206.           DeleteLink.init();
  5207.         }
  5208.         if (Conf['Filter']) {
  5209.           Filter.menuInit();
  5210.         }
  5211.         if (Conf['Download Link']) {
  5212.           DownloadLink.init();
  5213.         }
  5214.         if (Conf['Archive Link']) {
  5215.           ArchiveLink.init();
  5216.         }
  5217.       }
  5218.       if (Conf['Resurrect Quotes']) {
  5219.         Quotify.init();
  5220.       }
  5221.       if (Conf['Quote Inline']) {
  5222.         QuoteInline.init();
  5223.       }
  5224.       if (Conf['Quote Preview']) {
  5225.         QuotePreview.init();
  5226.       }
  5227.       if (Conf['Quote Backlinks']) {
  5228.         QuoteBacklink.init();
  5229.       }
  5230.       if (Conf['Indicate OP quote']) {
  5231.         QuoteOP.init();
  5232.       }
  5233.       if (Conf['Indicate Cross-thread Quotes']) {
  5234.         QuoteCT.init();
  5235.       }
  5236.       return $.ready(Main.ready);
  5237.     },
  5238.     ready: function() {
  5239.       var a, board, nav, node, nodes, now, _i, _j, _len, _len1, _ref, _ref1;
  5240.       if (/^4chan - 404/.test(d.title)) {
  5241.         if (Conf['404 Redirect'] && /^\d+$/.test(g.THREAD_ID)) {
  5242.           location.href = Redirect.thread(g.BOARD, g.THREAD_ID, location.hash);
  5243.         }
  5244.         return;
  5245.       }
  5246.       if (!$.id('navtopr')) {
  5247.         return;
  5248.       }
  5249.       $.addClass(d.body, $.engine);
  5250.       $.addClass(d.body, 'fourchan_x');
  5251.       _ref = ['boardNavDesktop', 'boardNavDesktopFoot'];
  5252.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  5253.         nav = _ref[_i];
  5254.         if (a = $("a[href$='/" + g.BOARD + "/']", $.id(nav))) {
  5255.           $.addClass(a, 'current');
  5256.         }
  5257.       }
  5258.       now = Date.now();
  5259.       Favicon.init();
  5260.       Options.init();
  5261.       if (Conf['Quick Reply']) {
  5262.         QR.init();
  5263.       }
  5264.       if (Conf['Image Expansion']) {
  5265.         ImageExpand.init();
  5266.       }
  5267.       if (Conf['Thread Watcher']) {
  5268.         Watcher.init();
  5269.       }
  5270.       if (Conf['Keybinds']) {
  5271.         Keybinds.init();
  5272.       }
  5273.       if (g.REPLY) {
  5274.         if (Conf['Prefetch']) {
  5275.           Prefetch.init();
  5276.         }
  5277.         if (Conf['Thread Updater']) {
  5278.           Updater.init();
  5279.         }
  5280.         if (Conf['Thread Stats']) {
  5281.           ThreadStats.init();
  5282.         }
  5283.         if (Conf['Reply Navigation']) {
  5284.           Nav.init();
  5285.         }
  5286.         if (Conf['Post in Title']) {
  5287.           TitlePost.init();
  5288.         }
  5289.         if (Conf['Unread Count'] || Conf['Unread Favicon']) {
  5290.           Unread.init();
  5291.         }
  5292.         if (Conf['Quote Threading']) {
  5293.           QuoteThreading.init();
  5294.         }
  5295.       } else {
  5296.         if (Conf['Thread Hiding']) {
  5297.           ThreadHiding.init();
  5298.         }
  5299.         if (Conf['Thread Expansion']) {
  5300.           ExpandThread.init();
  5301.         }
  5302.         if (Conf['Comment Expansion']) {
  5303.           ExpandComment.init();
  5304.         }
  5305.         if (Conf['Index Navigation']) {
  5306.           Nav.init();
  5307.         }
  5308.       }
  5309.       board = $('.board');
  5310.       nodes = [];
  5311.       _ref1 = $$('.postContainer', board);
  5312.       for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  5313.         node = _ref1[_j];
  5314.         nodes.push(Main.preParse(node));
  5315.       }
  5316.       Main.node(nodes, true);
  5317.       Main.prettify = Main._prettify;
  5318.       return Main.observe();
  5319.     },
  5320.     observe: function() {
  5321.       var MutationObserver, board, observer;
  5322.       board = $('.board');
  5323.       if (MutationObserver = window.WebKitMutationObserver || window.MozMutationObserver || window.OMutationObserver || window.MutationObserver) {
  5324.         Main.observer2 = observer = new MutationObserver(Main.observer);
  5325.         return observer.observe(board, {
  5326.           childList: true,
  5327.           subtree: true
  5328.         });
  5329.       } else {
  5330.         return $.on(board, 'DOMNodeInserted', Main.listener);
  5331.       }
  5332.     },
  5333.     disconnect: function() {
  5334.       var board;
  5335.       if (Main.observer2) {
  5336.         return Main.observer2.disconnect();
  5337.       } else {
  5338.         board = $('.board');
  5339.         return $.off(board, 'DOMNodeInserted', Main.listener);
  5340.       }
  5341.     },
  5342.     pruneHidden: function() {
  5343.       var cutoff, hiddenThreads, id, now, timestamp, _ref;
  5344.       now = Date.now();
  5345.       g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {});
  5346.       if ($.get('lastChecked', 0) < now - 1 * $.DAY) {
  5347.         $.set('lastChecked', now);
  5348.         cutoff = now - 7 * $.DAY;
  5349.         hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
  5350.         for (id in hiddenThreads) {
  5351.           timestamp = hiddenThreads[id];
  5352.           if (timestamp < cutoff) {
  5353.             delete hiddenThreads[id];
  5354.           }
  5355.         }
  5356.         _ref = g.hiddenReplies;
  5357.         for (id in _ref) {
  5358.           timestamp = _ref[id];
  5359.           if (timestamp < cutoff) {
  5360.             delete g.hiddenReplies[id];
  5361.           }
  5362.         }
  5363.         $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
  5364.         return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
  5365.       }
  5366.     },
  5367.     flatten: function(parent, obj) {
  5368.       var key, val;
  5369.       if (obj instanceof Array) {
  5370.         Conf[parent] = obj[0];
  5371.       } else if (typeof obj === 'object') {
  5372.         for (key in obj) {
  5373.           val = obj[key];
  5374.           Main.flatten(key, val);
  5375.         }
  5376.       } else {
  5377.         Conf[parent] = obj;
  5378.       }
  5379.     },
  5380.     addStyle: function() {
  5381.       $.off(d, 'DOMNodeInserted', Main.addStyle);
  5382.       if (d.head) {
  5383.         return $.addStyle(Main.css);
  5384.       } else {
  5385.         return $.on(d, 'DOMNodeInserted', Main.addStyle);
  5386.       }
  5387.     },
  5388.     message: function(e) {
  5389.       var version;
  5390.       version = e.data.version;
  5391.       if (version && version !== Main.version && confirm('An updated version of 4chan X is available, would you like to install it now?')) {
  5392.         return window.location = "https://raw.github.com/aeosynth/4chan-x/" + version + "/4chan_x.user.js";
  5393.       }
  5394.     },
  5395.     preParse: function(node) {
  5396.       var el, img, parentClass, post;
  5397.       parentClass = node.parentNode.className;
  5398.       el = $('.post', node);
  5399.       post = {
  5400.         root: node,
  5401.         el: el,
  5402.         "class": el.className,
  5403.         ID: el.id.match(/\d+$/)[0],
  5404.         threadID: g.THREAD_ID || $.x('ancestor::div[parent::div[@class="board"]]', node).id.match(/\d+$/)[0],
  5405.         isArchived: /\barchivedPost\b/.test(parentClass),
  5406.         isInlined: /\binline\b/.test(parentClass),
  5407.         isCrosspost: /\bcrosspost\b/.test(parentClass),
  5408.         blockquote: el.lastElementChild,
  5409.         quotes: el.getElementsByClassName('quotelink'),
  5410.         backlinks: el.getElementsByClassName('backlink'),
  5411.         fileInfo: false,
  5412.         img: false
  5413.       };
  5414.       if (img = $('img[data-md5]', el)) {
  5415.         post.fileInfo = img.parentNode.previousElementSibling;
  5416.         post.img = img;
  5417.       }
  5418.       Main.prettify(post.blockquote);
  5419.       return post;
  5420.     },
  5421.     node: function(nodes, notify) {
  5422.       var callback, node, _i, _j, _len, _len1, _ref;
  5423.       _ref = Main.callbacks;
  5424.       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  5425.         callback = _ref[_i];
  5426.         try {
  5427.           for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {
  5428.             node = nodes[_j];
  5429.             callback(node);
  5430.           }
  5431.         } catch (err) {
  5432.           if (notify) {
  5433.             alert("4chan X has experienced an error. You can help by sending this snippet to:\nhttps://github.com/aeosynth/4chan-x/issues\n\n" + Main.version + "\n" + window.location + "\n" + navigator.userAgent + "\n\n" + err.stack);
  5434.           }
  5435.         }
  5436.       }
  5437.     },
  5438.     observer: function(mutations) {
  5439.       var addedNode, mutation, nodes, _i, _j, _len, _len1, _ref;
  5440.       nodes = [];
  5441.       for (_i = 0, _len = mutations.length; _i < _len; _i++) {
  5442.         mutation = mutations[_i];
  5443.         _ref = mutation.addedNodes;
  5444.         for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
  5445.           addedNode = _ref[_j];
  5446.           if (/\bpostContainer\b/.test(addedNode.className) && addedNode.parentNode.className !== 'threadContainer') {
  5447.             nodes.push(Main.preParse(addedNode));
  5448.           }
  5449.         }
  5450.       }
  5451.       if (nodes.length) {
  5452.         return Main.node(nodes);
  5453.       }
  5454.     },
  5455.     listener: function(e) {
  5456.       var target;
  5457.       target = e.target;
  5458.       if (/\bpostContainer\b/.test(target.className) && target.parentNode.className !== 'threadContainer') {
  5459.         return Main.node([Main.preParse(target)]);
  5460.       }
  5461.     },
  5462.     prettify: function() {},
  5463.     _prettify: function(bq) {
  5464.       var code;
  5465.       switch (g.BOARD) {
  5466.         case 'g':
  5467.           code = function() {
  5468.             var pre, _i, _len, _ref;
  5469.             _ref = document.getElementById('_id_').getElementsByClassName('prettyprint');
  5470.             for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  5471.               pre = _ref[_i];
  5472.               pre.innerHTML = prettyPrintOne(pre.innerHTML.replace(/\s/g, '&nbsp;'));
  5473.             }
  5474.           };
  5475.           break;
  5476.         case 'sci':
  5477.           code = function() {
  5478.             jsMath.Process(document.getElementById('_id_'));
  5479.           };
  5480.           break;
  5481.         default:
  5482.           return;
  5483.       }
  5484.       return $.globalEval(("" + code).replace('_id_', bq.id));
  5485.     },
  5486.     namespace: '4chan_x.',
  5487.     version: '2.34.1',
  5488.     callbacks: [],
  5489.     css: '\
  5490. /* dialog styling */\
  5491. .dialog.reply {\
  5492.   display: block;\
  5493.   border: 1px solid rgba(0,0,0,.25);\
  5494.   padding: 0;\
  5495. }\
  5496. .move {\
  5497.   cursor: move;\
  5498. }\
  5499. label, .favicon {\
  5500.   cursor: pointer;\
  5501. }\
  5502. a[href="javascript:;"] {\
  5503.   text-decoration: none;\
  5504. }\
  5505. .warning {\
  5506.   color: red;\
  5507. }\
  5508. \
  5509. .hide_thread_button:not(.hidden_thread) {\
  5510.   float: left;\
  5511. }\
  5512. \
  5513. .thread > .hidden_thread ~ *,\
  5514. [hidden],\
  5515. #content > [name=tab]:not(:checked) + div,\
  5516. #updater:not(:hover) > :not(.move),\
  5517. .autohide:not(:hover) > form,\
  5518. #qp input, .forwarded {\
  5519.   display: none !important;\
  5520. }\
  5521. \
  5522. .menu_button {\
  5523.   display: inline-block;\
  5524. }\
  5525. .menu_button > span {\
  5526.   border-top:   .5em solid;\
  5527.   border-right: .3em solid transparent;\
  5528.   border-left:  .3em solid transparent;\
  5529.   display: inline-block;\
  5530.   margin: 2px;\
  5531.   vertical-align: middle;\
  5532. }\
  5533. #menu {\
  5534.   position: absolute;\
  5535.   outline: none;\
  5536. }\
  5537. .entry {\
  5538.   border-bottom: 1px solid rgba(0, 0, 0, .25);\
  5539.   cursor: pointer;\
  5540.   display: block;\
  5541.   outline: none;\
  5542.   padding: 3px 7px;\
  5543.   position: relative;\
  5544.   text-decoration: none;\
  5545.   white-space: nowrap;\
  5546. }\
  5547. .entry:last-child {\
  5548.   border: none;\
  5549. }\
  5550. .focused.entry {\
  5551.   background: rgba(255, 255, 255, .33);\
  5552. }\
  5553. .entry.hasSubMenu {\
  5554.   padding-right: 1.5em;\
  5555. }\
  5556. .hasSubMenu::after {\
  5557.   content: "";\
  5558.   border-left:   .5em solid;\
  5559.   border-top:    .3em solid transparent;\
  5560.   border-bottom: .3em solid transparent;\
  5561.   display: inline-block;\
  5562.   margin: .3em;\
  5563.   position: absolute;\
  5564.   right: 3px;\
  5565. }\
  5566. .hasSubMenu:not(.focused) > .subMenu {\
  5567.   display: none;\
  5568. }\
  5569. .subMenu {\
  5570.   position: absolute;\
  5571.   left: 100%;\
  5572.   top: 0;\
  5573.   margin-top: -1px;\
  5574. }\
  5575. \
  5576. h1 {\
  5577.   text-align: center;\
  5578. }\
  5579. #qr > .move {\
  5580.   min-width: 300px;\
  5581.   overflow: hidden;\
  5582.   box-sizing: border-box;\
  5583.   -moz-box-sizing: border-box;\
  5584.   padding: 0 2px;\
  5585. }\
  5586. #qr > .move > span {\
  5587.   float: right;\
  5588. }\
  5589. #autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning {\
  5590.   cursor: pointer;\
  5591. }\
  5592. #qr select,\
  5593. #qr > form {\
  5594.   margin: 0;\
  5595. }\
  5596. #dump {\
  5597.   background: -webkit-linear-gradient(#EEE, #CCC);\
  5598.   background: -moz-linear-gradient(#EEE, #CCC);\
  5599.   background: -o-linear-gradient(#EEE, #CCC);\
  5600.   background: linear-gradient(#EEE, #CCC);\
  5601.   width: 10%;\
  5602.   padding: -moz-calc(1px) 0 2px;\
  5603. }\
  5604. #dump:hover, #dump:focus {\
  5605.   background: -webkit-linear-gradient(#FFF, #DDD);\
  5606.   background: -moz-linear-gradient(#FFF, #DDD);\
  5607.   background: -o-linear-gradient(#FFF, #DDD);\
  5608.   background: linear-gradient(#FFF, #DDD);\
  5609. }\
  5610. #dump:active, .dump #dump:not(:hover):not(:focus) {\
  5611.   background: -webkit-linear-gradient(#CCC, #DDD);\
  5612.   background: -moz-linear-gradient(#CCC, #DDD);\
  5613.   background: -o-linear-gradient(#CCC, #DDD);\
  5614.   background: linear-gradient(#CCC, #DDD);\
  5615. }\
  5616. #qr:not(.dump) #replies, .dump > form > label {\
  5617.   display: none;\
  5618. }\
  5619. #replies {\
  5620.   display: block;\
  5621.   height: 100px;\
  5622.   position: relative;\
  5623.   -webkit-user-select: none;\
  5624.   -moz-user-select: none;\
  5625.   -o-user-select: none;\
  5626.   user-select: none;\
  5627. }\
  5628. #replies > div {\
  5629.   counter-reset: thumbnails;\
  5630.   top: 0; right: 0; bottom: 0; left: 0;\
  5631.   margin: 0; padding: 0;\
  5632.   overflow: hidden;\
  5633.   position: absolute;\
  5634.   white-space: pre;\
  5635. }\
  5636. #replies > div:hover {\
  5637.   bottom: -10px;\
  5638.   overflow-x: auto;\
  5639.   z-index: 1;\
  5640. }\
  5641. .thumbnail {\
  5642.   background-color: rgba(0,0,0,.2) !important;\
  5643.   background-position: 50% 20% !important;\
  5644.   background-size: cover !important;\
  5645.   border: 1px solid #666;\
  5646.   box-sizing: border-box;\
  5647.   -moz-box-sizing: border-box;\
  5648.   cursor: move;\
  5649.   display: inline-block;\
  5650.   height: 90px; width: 90px;\
  5651.   margin: 5px; padding: 2px;\
  5652.   opacity: .5;\
  5653.   outline: none;\
  5654.   overflow: hidden;\
  5655.   position: relative;\
  5656.   text-shadow: 0 1px 1px #000;\
  5657.   -webkit-transition: opacity .25s ease-in-out;\
  5658.   -moz-transition: opacity .25s ease-in-out;\
  5659.   -o-transition: opacity .25s ease-in-out;\
  5660.   transition: opacity .25s ease-in-out;\
  5661.   vertical-align: top;\
  5662. }\
  5663. .thumbnail:hover, .thumbnail:focus {\
  5664.   opacity: .9;\
  5665. }\
  5666. .thumbnail#selected {\
  5667.   opacity: 1;\
  5668. }\
  5669. .thumbnail::before {\
  5670.   counter-increment: thumbnails;\
  5671.   content: counter(thumbnails);\
  5672.   color: #FFF;\
  5673.   font-weight: 700;\
  5674.   padding: 3px;\
  5675.   position: absolute;\
  5676.   top: 0;\
  5677.   right: 0;\
  5678.   text-shadow: 0 0 3px #000, 0 0 8px #000;\
  5679. }\
  5680. .thumbnail.drag {\
  5681.   box-shadow: 0 0 10px rgba(0,0,0,.5);\
  5682. }\
  5683. .thumbnail.over {\
  5684.   border-color: #FFF;\
  5685. }\
  5686. .thumbnail > span {\
  5687.   color: #FFF;\
  5688. }\
  5689. .remove {\
  5690.   background: none;\
  5691.   color: #E00;\
  5692.   font-weight: 700;\
  5693.   padding: 3px;\
  5694. }\
  5695. .remove:hover::after {\
  5696.   content: " Remove";\
  5697. }\
  5698. .thumbnail > label {\
  5699.   background: rgba(0,0,0,.5);\
  5700.   color: #FFF;\
  5701.   right: 0; bottom: 0; left: 0;\
  5702.   position: absolute;\
  5703.   text-align: center;\
  5704. }\
  5705. .thumbnail > label > input {\
  5706.   margin: 0;\
  5707. }\
  5708. #addReply {\
  5709.   color: #333;\
  5710.   font-size: 3.5em;\
  5711.   line-height: 100px;\
  5712. }\
  5713. #addReply:hover, #addReply:focus {\
  5714.   color: #000;\
  5715. }\
  5716. .field {\
  5717.   border: 1px solid #CCC;\
  5718.   box-sizing: border-box;\
  5719.   -moz-box-sizing: border-box;\
  5720.   color: #333;\
  5721.   font: 13px sans-serif;\
  5722.   margin: 0;\
  5723.   padding: 2px 4px 3px;\
  5724.   -webkit-transition: color .25s, border .25s;\
  5725.   -moz-transition: color .25s, border .25s;\
  5726.   -o-transition: color .25s, border .25s;\
  5727.   transition: color .25s, border .25s;\
  5728. }\
  5729. .field:-moz-placeholder,\
  5730. .field:hover:-moz-placeholder {\
  5731.   color: #AAA;\
  5732. }\
  5733. .field:hover, .field:focus {\
  5734.   border-color: #999;\
  5735.   color: #000;\
  5736.   outline: none;\
  5737. }\
  5738. #qr > form > div:first-child > .field:not(#dump) {\
  5739.   width: 30%;\
  5740. }\
  5741. #qr textarea.field {\
  5742.   display: -webkit-box;\
  5743.   min-height: 120px;\
  5744.   min-width: 100%;\
  5745. }\
  5746. #.textarea {\
  5747. #  position: relative;\
  5748. #}\
  5749. #charCount {\
  5750.   color: #000;\
  5751.   background: hsla(0, 0%, 100%, .5);\
  5752.   position: absolute;\
  5753.   top: 100%;\
  5754.   right: 0;\
  5755. }\
  5756. #charCount.warning {\
  5757.   color: red;\
  5758. }\
  5759. .captchainput > .field {\
  5760.   min-width: 100%;\
  5761. }\
  5762. .captchaimg {\
  5763.   background: #FFF;\
  5764.   outline: 1px solid #CCC;\
  5765.   outline-offset: -1px;\
  5766.   text-align: center;\
  5767. }\
  5768. .captchaimg > img {\
  5769.   display: block;\
  5770.   height: 57px;\
  5771.   width: 300px;\
  5772. }\
  5773. #qr [type=file] {\
  5774.   margin: 1px 0;\
  5775.   width: 70%;\
  5776. }\
  5777. #qr [type=submit] {\
  5778.   margin: 1px 0;\
  5779.   padding: 1px; /* not Gecko */\
  5780.   padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */\
  5781.   width: 30%;\
  5782. }\
  5783. \
  5784. .fileText:hover .fntrunc,\
  5785. .fileText:not(:hover) .fnfull {\
  5786.   display: none;\
  5787. }\
  5788. .fitwidth img[data-md5] + img {\
  5789.   max-width: 100%;\
  5790. }\
  5791. .gecko  .fitwidth img[data-md5] + img,\
  5792. .presto .fitwidth img[data-md5] + img {\
  5793.   width: 100%;\
  5794. }\
  5795. \
  5796. #qr, #qp, #updater, #stats, #ihover, #overlay, #navlinks {\
  5797.   position: fixed;\
  5798. }\
  5799. \
  5800. #ihover {\
  5801.   max-height: 97%;\
  5802.   max-width: 75%;\
  5803.   padding-bottom: 18px;\
  5804. }\
  5805. \
  5806. #navlinks {\
  5807.   font-size: 16px;\
  5808.   top: 25px;\
  5809.   right: 5px;\
  5810. }\
  5811. \
  5812. #overlay {\
  5813.   top: 0;\
  5814.   right: 0;\
  5815.   width: 100%;\
  5816.   height: 100%;\
  5817.   background: rgba(0,0,0,.5);\
  5818.   z-index: 1;\
  5819. }\
  5820. #options {\
  5821.   z-index: 2;\
  5822.   position: absolute;\
  5823.   display: inline-block;\
  5824.   padding: 5px;\
  5825.   text-align: left;\
  5826.   vertical-align: middle;\
  5827.   width: 600px;\
  5828.   max-width: 100%;\
  5829.   height: 500px;\
  5830.   max-height: 100%;\
  5831. }\
  5832. #credits {\
  5833.   float: right;\
  5834. }\
  5835. #options ul {\
  5836.   padding: 0;\
  5837. }\
  5838. #options article li {\
  5839.   margin: 10px 0 10px 2em;\
  5840. }\
  5841. #options code {\
  5842.   background: hsla(0, 0%, 100%, .5);\
  5843.   color: #000;\
  5844.   padding: 0 1px;\
  5845. }\
  5846. #options label {\
  5847.   text-decoration: underline;\
  5848. }\
  5849. #content {\
  5850.   overflow: auto;\
  5851.   position: absolute;\
  5852.   top: 2.5em;\
  5853.   right: 5px;\
  5854.   bottom: 5px;\
  5855.   left: 5px;\
  5856. }\
  5857. #content textarea {\
  5858.   font-family: monospace;\
  5859.   min-height: 350px;\
  5860.   resize: vertical;\
  5861.   width: 100%;\
  5862. }\
  5863. \
  5864. #updater {\
  5865.   text-align: right;\
  5866. }\
  5867. #updater:not(:hover) {\
  5868.   border: none;\
  5869.   background: transparent;\
  5870. }\
  5871. #updater input[type=number] {\
  5872.   width: 4em;\
  5873. }\
  5874. .new {\
  5875.   background: lime;\
  5876. }\
  5877. \
  5878. #watcher {\
  5879.   padding-bottom: 5px;\
  5880.   position: absolute;\
  5881.   overflow: hidden;\
  5882.   white-space: nowrap;\
  5883. }\
  5884. #watcher:not(:hover) {\
  5885.   max-height: 220px;\
  5886. }\
  5887. #watcher > div {\
  5888.   max-width: 200px;\
  5889.   overflow: hidden;\
  5890.   padding-left: 5px;\
  5891.   padding-right: 5px;\
  5892.   text-overflow: ellipsis;\
  5893. }\
  5894. #watcher > .move {\
  5895.   padding-top: 5px;\
  5896.   text-decoration: underline;\
  5897. }\
  5898. \
  5899. #qp {\
  5900.   padding: 2px 2px 5px;\
  5901. }\
  5902. #qp .post {\
  5903.   border: none;\
  5904.   margin: 0;\
  5905.   padding: 0;\
  5906. }\
  5907. #qp img {\
  5908.   max-height: 300px;\
  5909.   max-width: 500px;\
  5910. }\
  5911. .qphl {\
  5912.   outline: 2px solid rgba(216, 94, 49, .7);\
  5913. }\
  5914. .inlined {\
  5915.   opacity: .5;\
  5916. }\
  5917. .inline {\
  5918.   background-color: rgba(255, 255, 255, 0.15);\
  5919.   border: 1px solid rgba(128, 128, 128, 0.5);\
  5920.   display: table;\
  5921.   margin: 2px;\
  5922.   padding: 2px;\
  5923. }\
  5924. .inline .post {\
  5925.   background: none;\
  5926.   border: none;\
  5927.   margin: 0;\
  5928.   padding: 0;\
  5929. }\
  5930. div.opContainer {\
  5931.   display: block !important;\
  5932. }\
  5933. .opContainer.filter_highlight {\
  5934.   box-shadow: inset 5px 0 rgba(255,0,0,0.5);\
  5935. }\
  5936. .filter_highlight > .reply {\
  5937.   box-shadow: -5px 0 rgba(255,0,0,0.5);\
  5938. }\
  5939. .filtered {\
  5940.   text-decoration: underline line-through;\
  5941. }\
  5942. .quotelink.forwardlink,\
  5943. .backlink.forwardlink {\
  5944.   text-decoration: none;\
  5945.   border-bottom: 1px dashed;\
  5946. }\
  5947. \
  5948. .threadContainer {\
  5949.   margin-left: 20px;\
  5950.   border-left: 1px solid black;\
  5951. }\
  5952. .stub ~ * {\
  5953.   display: none !important;\
  5954. }\
  5955. '
  5956.   };
  5957.  
  5958.   Main.init();
  5959.  
  5960. }).call(this);