someone_

4chanX 2.39.6

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