// Generated by CoffeeScript
// ==UserScript==
// @name         4chan X beta
// @version      1.9.3.8
// @minGMVer     1.14
// @minFFVer     26
// @namespace    4chan-X
// @description  Cross-browser userscript for maximum lurking on 4chan.
// @license      MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE 
// @match        *://boards.4chan.org/*
// @match        *://sys.4chan.org/*
// @match        *://a.4cdn.org/*
// @match        *://i.4cdn.org/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// @updateURL 	 https://ccd0.github.io/4chan-x/builds/4chan-X-beta.meta.js
// @downloadURL  https://ccd0.github.io/4chan-x/builds/4chan-X-beta.user.js
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAF5JREFUeNrtkTESABAQxPD/R6tsE2dUGYUtFJvLDKf93KevHJAjpBorAQWSBIKqFASC4G0pCAkm4GfaEvgYXl0T6HBaE97f0vmnfYHbZOMLZCx9ISdKWwjOWZSC8GYm4SUGwfYgqI4AAAAASUVORK5CYII=
// ==/UserScript==

/*
* 4chan X - Version 1.9.3.8
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
*
* Appchan X Copyright © 2013-2014 Zixaphir <zixaphirmoxphar@gmail.com>
* http://zixaphir.github.io/appchan-x/ 
* 4chan x Copyright © 2009-2011 James Campos <james.r.campos@gmail.com>
* https://github.com/aeosynth/4chan-x
* 4chan x Copyright © 2012-2014 Nicolas Stepien <stepien.nicolas@gmail.com>
* https://4chan-x.just-believe.in/
* 4chan x Copyright © 2013-2014 Jordan Bates <saudrapsmann@gmail.com>
* http://seaweedchan.github.io/4chan-x/
* 4chan x Copyright © 2012-2013 ihavenoface
* http://ihavenoface.github.io/4chan-x/
* 4chan SS Copyright © 2011-2013 Ahodesuka
* https://github.com/ahodesuka/4chan-Style-Script/ 
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Contributors:
* aeosynth
* mayhemydg
* noface
* !K.WeEabo0o
* blaise
* that4chanwolf
* desuwa
* seaweed
* e000
* ahodesuka
* Shou
* ferongr
* xat
* Ongpot
* thisisanon
* Anonymous
* Seiba
* herpaderpderp
* WakiMiko
* btmcsweeney
* AppleBloom
* detharonil
*
* All the people who've taken the time to write bug reports.
*
* Thank you.
*/

/*
* Contains data from external sources:
*
* audio/beep.wav from http://freesound.org/people/pierrecartoons1979/sounds/90112/
*   cc-by-nc-3.0
*
* 4chan/4chan-JS (https://github.com/4chan/4chan-JS)
*   Copyright (c) 2012-2013, 4chan LLC
*   All rights reserved.
*
*   license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*/

'use strict';

(function() {
  var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, c, d, doc, g,
    __slice = [].slice,
    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  Array.prototype.indexOf = function(val, i) {
    var len;
    i || (i = 0);
    len = this.length;
    while (i < len) {
      if (this[i] === val) {
        return i;
      }
      i++;
    }
    return -1;
  };

  __indexOf = [].indexOf;

  Config = {
    main: {
      'Miscellaneous': {
        'JSON Navigation': [true, 'Replace the board index with a dynamically generated one supporting searching, sorting, and infinite scrolling.'],
        'Catalog Links': [true, 'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'],
        'External Catalog': [false, 'Link to external catalog instead of the internal one.'],
        'QR Shortcut': [false, 'Adds a small [QR] link in the header.'],
        'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'],
        'Desktop Notifications': [false, 'Enables desktop notifications across various 4chan X features.'],
        '404 Redirect': [true, 'Redirect dead threads and images to the archives.'],
        'Except Archives from Encryption': [false, 'Permit loading content from, and warningless redirects to, HTTP-only archives from HTTPS pages.'],
        'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
        'Time Formatting': [true, 'Localize and format timestamps.'],
        'Relative Post Dates': [true, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'],
        'Comment Expansion': [true, 'Add buttons to expand too long comments.'],
        'File Info Formatting': [true, 'Reformat the file information.'],
        'Thread Expansion': [true, 'Add buttons to expand threads.'],
        'Index Navigation': [false, 'Add buttons to navigate between threads.'],
        'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'],
        'Custom Board Titles': [true, 'Allow editing of the board title and subtitle by ctrl+clicking them'],
        'Persistent Custom Board Titles': [false, 'Force custom board titles to be persistent, even if moot updates the board titles.'],
        'Show Updated Notifications': [true, 'Show notifications when 4chan X is successfully updated.'],
        'Color User IDs': [false, 'Assign unique colors to user IDs on boards that use them'],
        'Remove Spoilers': [false, 'Remove all spoilers in text.'],
        'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'],
        'Show Support Message': [true, 'Warn if your browser or configuration is unsupported and may cause 4chan X to not operate correctly.'],
        'Normalize URL': [true, 'Rewrite the URL of the current page, removing stubs and changing /res/ to /thread/.']
      },
      'Linkification': {
        'Linkify': [true, 'Convert text into links where applicable.'],
        'Embedding': [true, 'Embed supported services.'],
        'Auto-embed': [false, 'Auto-embed Linkify Embeds.'],
        'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists']
      },
      'Filtering': {
        'Anonymize': [false, 'Make everyone Anonymous.'],
        'Filter': [true, 'Self-moderation placebo.'],
        'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'],
        'Thread Hiding Buttons': [false, 'Add buttons to hide entire threads.'],
        'Reply Hiding Buttons': [false, 'Add buttons to hide single replies.'],
        'Filtered Backlinks': [true, 'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.'],
        'Stubs': [true, 'Show stubs of hidden threads / replies.']
      },
      'Images and Videos': {
        'Image Expansion': [true, 'Expand images / videos.'],
        'Image Hover': [true, 'Show full image / video on mouseover.'],
        'Gallery': [true, 'Adds a simple and cute image gallery.'],
        'PDF in Gallery': [false, 'Show PDF files in gallery.'],
        'Sauce': [true, 'Add sauce links to images.'],
        'Reveal Spoiler Thumbnails': [false, 'Replace spoiler thumbnails with the original image.'],
        'Replace GIF': [false, 'Replace gif thumbnails with the actual image.'],
        'Replace JPG': [false, 'Replace jpg thumbnails with the actual image.'],
        'Replace PNG': [false, 'Replace png thumbnails with the actual image.'],
        'Replace WEBM': [false, 'Replace webm thumbnails with the actual webm video. Probably will degrade browser performance ;)'],
        'Image Prefetching': [false, 'Add link in header menu to turn on image preloading.'],
        'Fappe Tyme': [false, 'Hide posts without images when header menu item is checked. *hint* *hint*'],
        'Werk Tyme': [false, 'Hide all post images when header menu item is checked.'],
        'Autoplay': [true, 'Videos begin playing immediately when opened.'],
        'Show Controls': [true, 'Show controls on videos expanded inline. Turn this off if you want to contract videos by clicking on them.'],
        'Loop in New Tab': [true, 'Loop videos opened in their own tabs.']
      },
      'Menu': {
        'Menu': [true, 'Add a drop-down menu to posts.'],
        'Report Link': [true, 'Add a report link to the menu.'],
        'Thread Hiding Link': [true, 'Add a link to hide entire threads.'],
        'Reply Hiding Link': [true, 'Add a link to hide single replies.'],
        'Delete Link': [true, 'Add post and image deletion links to the menu.'],
        'Download Link': [true, 'Add a download with original filename link to the menu.'],
        'Archive Link': [true, 'Add an archive link to the menu.']
      },
      'Monitoring': {
        'Thread Updater': [true, 'Fetch and insert new replies. Has more options in its own dialog.'],
        'Unread Count': [true, 'Show the unread posts count in the tab title.'],
        'Hide Unread Count at (0)': [false, 'Hide the unread posts count in the tab title when it reaches 0.'],
        'Unread Favicon': [true, 'Show a different favicon when there are unread posts.'],
        'Unread Line': [true, 'Show a line to distinguish read posts from unread ones.'],
        'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.'],
        'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'],
        'Thread Stats': [true, 'Display reply and image count.'],
        'Page Count in Stats': [true, 'Display the page count in the thread stats as well.'],
        'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'],
        'Thread Watcher': [true, 'Bookmark threads.'],
        'Toggleable Thread Watcher': [true, 'Adds a shortcut for the thread watcher, hides the watcher by default, and makes it scroll with the page.']
      },
      'Posting': {
        'Quick Reply': [true, 'All-in-one form to reply, create threads, automate dumping and more.'],
        'Persistent QR': [true, 'The Quick reply won\'t disappear after posting.'],
        'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'],
        'Open Post in New Tab': [true, 'Open new threads or replies to a thread from the index in a new tab.'],
        'Remember QR Size': [false, 'Remember the size of the Quick reply.'],
        'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'],
        'Show New Thread Option in Threads': [false, 'Show the option to post a new thread from inside a thread.'],
        'Show Name and Subject': [false, 'Show the classic name, email, and subject fields in the QR, even when 4chan doesn\'t use them all.'],
        'Hide Original Post Form': [true, 'Hide the normal post form.'],
        'Cooldown': [true, 'Indicate the remaining time before posting again.'],
        'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'],
        'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'],
        'Auto-load captcha': [false, 'Automatically load the captcha when you open a thread, and reload it after you post.'],
        'Bottom QR Link': [true, 'Places a link on the bottom of threads to open the QR.']
      },
      'Quote Links': {
        'Quote Backlinks': [true, 'Add quote backlinks.'],
        'OP Backlinks': [true, 'Add backlinks to the OP.'],
        'Quote Inlining': [true, 'Inline quoted post on click.'],
        'Quote Hash Navigation': [false, 'Include an extra link after quotes for autoscrolling to quoted posts.'],
        'Forward Hiding': [true, 'Hide original posts of inlined backlinks.'],
        'Quote Previewing': [true, 'Show quoted post on hover.'],
        'Quote Highlighting': [true, 'Highlight the previewed post.'],
        'Resurrect Quotes': [true, 'Link dead quotes to the archives.'],
        'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'],
        'Quoted Title': [false, 'Change the page title to reflect you\'ve been quoted.'],
        'Highlight Posts Quoting You': [false, 'Highlights any posts that contain a quote to your post.'],
        'Highlight Own Posts': [false, 'Highlights own posts if Mark Quotes of You is enabled.'],
        'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'],
        'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'],
        'Quote Threading': [false, 'Thread conversations']
      }
    },
    imageExpansion: {
      'Fit width': [false, ''],
      'Fit height': [false, ''],
      'Scroll into view': [true, 'Scroll down when expanding images to bring the full image into view.'],
      'Expand spoilers': [true, 'Expand all images along with spoilers.'],
      'Expand videos': [true, 'Expand all images also expands videos.'],
      'Expand from here': [false, 'Expand all images only from current position to thread end.'],
      'Advance on contract': [false, 'Advance to next post when contracting an expanded image.']
    },
    gallery: {
      'Hide Thumbnails': [false],
      'Fit Width': [true],
      'Fit Height': [true],
      'Scroll to Post': [true],
      'Slide Delay': [5.0]
    },
    threadWatcher: {
      'Current Board': [false, 'Only show watched threads from the current board.'],
      'Auto Watch': [true, 'Automatically watch threads you start.'],
      'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'],
      'Auto Prune': [false, 'Automatically prune dead threads.'],
      'Show Unread Count': [true, 'Show number of unread posts in watched threads.']
    },
    filter: {
      name: "# Filter any namefags:\n#/^(?!Anonymous$)/",
      uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/",
      tripcode: "# Filter any tripfag\n#/^!/",
      capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for moot:\n#/Admin$/;highlight:moot;op:yes",
      subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only",
      comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g",
      flag: '',
      filename: '',
      dimensions: "# Highlight potential wallpapers:\n#/1920x1080/;op:yes;highlight;top:no;boards:w,wg",
      filesize: '',
      MD5: ''
    },
    sauces: "https://www.google.com/searchbyimage?image_url=%TURL\nhttp://iqdb.org/?url=%TURL\n#//tineye.com/search?url=%TURL\n#//saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#//imgur.com/upload?url=%URL;text:Upload to imgur\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/;boards:a,biz,c,co,diy,gd,int,jp,m,out,po,sci,sp,tg,tv,vg,vp,vr,wsg\n#https://rbt.asia/%board/image/%MD5;text:View same on RBT /%board/;boards:cgl,con,g,mu,w\n# Search with full image only for image file types:\n#https://www.google.com/searchbyimage?image_url=%URL;types:gif,jpg,png\n#https://www.google.com/searchbyimage?image_url=%TURL;types:webm,pdf",
    FappeT: {
      fappe: false,
      werk: false
    },
    'Custom CSS': false,
    Index: {
      'Index Mode': 'paged',
      'Index Sort': 'bump',
      'Show Replies': true,
      'Anchor Hidden Threads': true,
      'Refreshed Navigation': false
    },
    Header: {
      'Fixed Header': true,
      'Header auto-hide': false,
      'Header auto-hide on scroll': false,
      'Bottom Header': false,
      'Centered links': false,
      'Header catalog links': false,
      'Bottom Board List': true,
      'Shortcut Icons': true,
      'Custom Board Navigation': true
    },
    boardnav: "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]",
    QR: {
      'QR.personas': "#options:\"sage\";boards:jp;always"
    },
    time: '%m/%d/%y(%a)%H:%M:%S',
    backlink: '>>%id',
    fileInfo: '%L (%p%s, %r)',
    favicon: 'ferongr',
    usercss: '',
    hotkeys: {
      'Toggle board list': ['Ctrl+b', 'Toggle the full board list.'],
      'Toggle header': ['Shift+h', 'Toggle the auto-hide option of the header.'],
      'Open empty QR': ['q', 'Open QR without post number inserted.'],
      'Open QR': ['Shift+q', 'Open QR with post number inserted.'],
      'Open settings': ['Alt+o', 'Open Settings.'],
      'Close': ['Esc', 'Close Settings, Notifications or QR.'],
      'Spoiler tags': ['Ctrl+s', 'Insert spoiler tags.'],
      'Code tags': ['Alt+c', 'Insert code tags.'],
      'Eqn tags': ['Alt+e', 'Insert eqn tags.'],
      'Math tags': ['Alt+m', 'Insert math tags.'],
      'Toggle sage': ['Alt+s', 'Toggle sage in options field.'],
      'Submit QR': ['Ctrl+Enter', 'Submit post.'],
      'Watch': ['w', 'Watch thread.'],
      'Update': ['r', 'Update the thread / refresh the index.'],
      'Expand image': ['Shift+e', 'Expand selected image.'],
      'Expand images': ['e', 'Expand all images.'],
      'Open Gallery': ['g', 'Opens the gallery.'],
      'fappeTyme': ['f', 'Toggle Fappe Tyme.'],
      'werkTyme': ['Shift+w', 'Toggle Werk Tyme.'],
      'Front page': ['1', 'Jump to front page.'],
      'Open front page': ['Shift+1', 'Open front page in a new tab.'],
      'Next page': ['Ctrl+Right', 'Jump to the next page.'],
      'Previous page': ['Ctrl+Left', 'Jump to the previous page.'],
      'Open catalog': ['Shift+c', 'Open the catalog of the current board.'],
      'Search form': ['Ctrl+Alt+s', 'Focus the search field on the board index.'],
      'Next thread': ['Ctrl+Down', 'See next thread.'],
      'Previous thread': ['Ctrl+Up', 'See previous thread.'],
      'Expand thread': ['Ctrl+e', 'Expand thread.'],
      'Open thread': ['o', 'Open thread in current tab.'],
      'Open thread tab': ['Shift+o', 'Open thread in new tab.'],
      'Next reply': ['j', 'Select next reply.'],
      'Previous reply': ['k', 'Select previous reply.'],
      'Deselect reply': ['Shift+d', 'Deselect reply.'],
      'Hide': ['x', 'Hide thread.'],
      'Previous Post Quoting You': ['Alt+Up', 'Scroll to the previous post that quotes you.'],
      'Next Post Quoting You': ['Alt+Down', 'Scroll to the next post that quotes you.']
    },
    updater: {
      checkbox: {
        'Beep': [false, 'Beep on new post to completely read thread.'],
        'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'],
        'Bottom Scroll': [false, 'Always scroll to the bottom, not the first new post. Useful for event threads.'],
        'Scroll BG': [false, 'Auto-scroll background tabs.'],
        'Auto Update': [true, 'Automatically fetch new posts.'],
        'Optional Increase': [false, 'Increase the intervals between updates on threads without new posts.']
      },
      'Interval': 30
    }
  };

  Conf = {};

  c = console;

  d = document;

  doc = d.documentElement;

  g = {
    VERSION: '1.9.3.8',
    NAMESPACE: '4chan X.',
    NAME: '4chan X',
    FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
    CHANGELOG: 'https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md',
    boards: {}
  };

  E = function(text) {
    return (text + '').replace(/[&"'<>]/g, function(x) {
      return {
        '&': '&amp;',
        "'": '&#039;',
        '"': '&quot;',
        '<': '&lt;',
        '>': '&gt;'
      }[x];
    });
  };

  $ = function(selector, root) {
    if (root == null) {
      root = d.body;
    }
    return root.querySelector(selector);
  };

  $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));

  $.id = function(id) {
    return d.getElementById(id);
  };

  $.ready = function(fc) {
    var cb;
    if (d.readyState !== 'loading') {
      $.queueTask(fc);
      return;
    }
    cb = function() {
      $.off(d, 'DOMContentLoaded', cb);
      return fc();
    };
    return $.on(d, 'DOMContentLoaded', cb);
  };

  $.formData = function(form) {
    var fd, key, val;
    if (form instanceof HTMLFormElement) {
      return new FormData(form);
    }
    fd = new FormData();
    for (key in form) {
      val = form[key];
      if (val) {
        if (typeof val === 'object' && 'newName' in val) {
          fd.append(key, val, val.newName);
        } else {
          fd.append(key, val);
        }
      }
    }
    return fd;
  };

  $.extend = function(object, properties) {
    var key, val;
    for (key in properties) {
      val = properties[key];
      object[key] = val;
    }
  };

  $.ajax = (function() {
    var blockedError, blockedURLs, lastModified;
    lastModified = {};
    blockedURLs = {};
    blockedError = function(url) {
      var message;
      if (blockedURLs[url]) {
        return;
      }
      blockedURLs[url] = true;
      message = $.el('div', {
        innerHTML: E(g.NAME) + " was blocked from loading the following URL:<br><span></span><br>[<a href=\"" + E(g.FAQ) + "#why-was-4chan-x-blocked-from-loading-a-url\" target=\"_blank\">More info</a>]"
      });
      $('span', message).textContent = (/^\/\//.test(url) ? location.protocol : '') + url;
      return new Notice('error', message, 30, function() {
        return delete blockedURLs[url];
      });
    };
    return function(url, options, extra) {
      var err, form, r, type, upCallbacks, whenModified;
      if (extra == null) {
        extra = {};
      }
      type = extra.type, whenModified = extra.whenModified, upCallbacks = extra.upCallbacks, form = extra.form;
      r = new XMLHttpRequest();
      type || (type = form && 'post' || 'get');
      try {
        r.open(type, url, true);
      } catch (_error) {
        err = _error;
        blockedError(url);
        return typeof options.onerror === "function" ? options.onerror() : void 0;
      }
      if (whenModified) {
        if (url in lastModified) {
          r.setRequestHeader('If-Modified-Since', lastModified[url]);
        }
        $.on(r, 'load', function() {
          return lastModified[url] = r.getResponseHeader('Last-Modified');
        });
      }
      if (/\.json$/.test(url)) {
        r.responseType = 'json';
      }
      $.extend(r, options);
      $.extend(r.upload, upCallbacks);
      r.send(form);
      return r;
    };
  })();

  (function() {
    var reqs;
    reqs = {};
    $.cache = function(url, cb, options) {
      var err, req, rm;
      if (req = reqs[url]) {
        if (req.readyState === 4) {
          cb.call(req, req.evt);
        } else {
          req.callbacks.push(cb);
        }
        return;
      }
      rm = function() {
        return delete reqs[url];
      };
      try {
        req = $.ajax(url, options);
      } catch (_error) {
        err = _error;
        return;
      }
      $.on(req, 'load', function(e) {
        var _i, _len, _ref;
        _ref = this.callbacks;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          cb = _ref[_i];
          cb.call(this, e);
        }
        this.evt = e;
        this.cached = true;
        return delete this.callbacks;
      });
      $.on(req, 'abort error', rm);
      req.callbacks = [cb];
      return reqs[url] = req;
    };
    return $.cleanCache = function(testf) {
      var url;
      for (url in reqs) {
        if (testf(url)) {
          delete reqs[url];
        }
      }
    };
  })();

  $.cb = {
    checked: function() {
      $.set(this.name, this.checked);
      return Conf[this.name] = this.checked;
    },
    value: function() {
      $.set(this.name, this.value.trim());
      return Conf[this.name] = this.value;
    }
  };

  $.asap = function(test, cb) {
    if (test()) {
      return cb();
    } else {
      return setTimeout($.asap, 25, test, cb);
    }
  };

  $.addStyle = function(css, id) {
    var style;
    style = $.el('style', {
      id: id,
      textContent: css
    });
    $.asap((function() {
      return d.head;
    }), function() {
      return $.add(d.head, style);
    });
    return style;
  };

  $.x = function(path, root) {
    root || (root = d.body);
    return d.evaluate(path, root, null, 8, null).singleNodeValue;
  };

  $.X = function(path, root) {
    root || (root = d.body);
    return d.evaluate(path, root, null, 7, null);
  };

  $.addClass = function() {
    var className, classNames, el, _i, _len;
    el = arguments[0], classNames = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
    for (_i = 0, _len = classNames.length; _i < _len; _i++) {
      className = classNames[_i];
      el.classList.add(className);
    }
  };

  $.rmClass = function() {
    var className, classNames, el, _i, _len;
    el = arguments[0], classNames = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
    for (_i = 0, _len = classNames.length; _i < _len; _i++) {
      className = classNames[_i];
      el.classList.remove(className);
    }
  };

  $.toggleClass = function(el, className) {
    return el.classList.toggle(className);
  };

  $.hasClass = function(el, className) {
    return __indexOf.call(el.classList, className) >= 0;
  };

  $.rm = function(el) {
    return el.remove();
  };

  $.rmAll = function(root) {
    return root.textContent = null;
  };

  $.tn = function(s) {
    return d.createTextNode(s);
  };

  $.frag = function() {
    return d.createDocumentFragment();
  };

  $.nodes = function(nodes) {
    var frag, node, _i, _len;
    if (!(nodes instanceof Array)) {
      return nodes;
    }
    frag = $.frag();
    for (_i = 0, _len = nodes.length; _i < _len; _i++) {
      node = nodes[_i];
      frag.appendChild(node);
    }
    return frag;
  };

  $.add = function(parent, el) {
    return parent.appendChild($.nodes(el));
  };

  $.prepend = function(parent, el) {
    return parent.insertBefore($.nodes(el), parent.firstChild);
  };

  $.after = function(root, el) {
    return root.parentNode.insertBefore($.nodes(el), root.nextSibling);
  };

  $.before = function(root, el) {
    return root.parentNode.insertBefore($.nodes(el), root);
  };

  $.replace = function(root, el) {
    return root.parentNode.replaceChild($.nodes(el), root);
  };

  $.el = function(tag, properties) {
    var el;
    el = d.createElement(tag);
    if (properties) {
      $.extend(el, properties);
    }
    return el;
  };

  $.on = function(el, events, handler) {
    var event, _i, _len, _ref;
    _ref = events.split(' ');
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      event = _ref[_i];
      el.addEventListener(event, handler, false);
    }
  };

  $.off = function(el, events, handler) {
    var event, _i, _len, _ref;
    _ref = events.split(' ');
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      event = _ref[_i];
      el.removeEventListener(event, handler, false);
    }
  };

  $.event = function(event, detail, root) {
    if (root == null) {
      root = d;
    }
    if ((detail != null) && typeof cloneInto === 'function') {
      detail = cloneInto(detail, document.defaultView);
    }
    return root.dispatchEvent(new CustomEvent(event, {
      bubbles: true,
      detail: detail
    }));
  };

  $.open = GM_openInTab;

  $.debounce = function(wait, fn) {
    var args, exec, lastCall, that, timeout;
    lastCall = 0;
    timeout = null;
    that = null;
    args = null;
    exec = function() {
      lastCall = Date.now();
      return fn.apply(that, args);
    };
    return function() {
      args = arguments;
      that = this;
      if (lastCall < Date.now() - wait) {
        return exec();
      }
      clearTimeout(timeout);
      return timeout = setTimeout(exec, wait);
    };
  };

  $.queueTask = (function() {
    var execTask, taskChannel, taskQueue;
    taskQueue = [];
    execTask = function() {
      var args, func, task;
      task = taskQueue.shift();
      func = task[0];
      args = Array.prototype.slice.call(task, 1);
      return func.apply(func, args);
    };
    if (window.MessageChannel) {
      taskChannel = new MessageChannel();
      taskChannel.port1.onmessage = execTask;
      return function() {
        taskQueue.push(arguments);
        return taskChannel.port2.postMessage(null);
      };
    } else {
      return function() {
        taskQueue.push(arguments);
        return setTimeout(execTask, 0);
      };
    }
  })();

  $.globalEval = function(code) {
    var script;
    script = $.el('script', {
      textContent: code
    });
    $.add(d.head || doc, script);
    return $.rm(script);
  };

  $.bytesToString = function(size) {
    var unit;
    unit = 0;
    while (size >= 1024) {
      size /= 1024;
      unit++;
    }
    size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size);
    return "" + size + " " + ['B', 'KB', 'MB', 'GB'][unit];
  };

  $.minmax = function(value, min, max) {
    return (value < min ? min : value > max ? max : value);
  };

  $.item = function(key, val) {
    var item;
    item = {};
    item[key] = val;
    return item;
  };

  $.syncing = {};

  $.sync = (function() {
    $.on(window, 'storage', function(_arg) {
      var cb, key, newValue;
      key = _arg.key, newValue = _arg.newValue;
      if (cb = $.syncing[key]) {
        return cb(JSON.parse(newValue), key);
      }
    });
    return function(key, cb) {
      return $.syncing[g.NAMESPACE + key] = cb;
    };
  })();

  $["delete"] = function(keys) {
    var key, _i, _len;
    if (!(keys instanceof Array)) {
      keys = [keys];
    }
    for (_i = 0, _len = keys.length; _i < _len; _i++) {
      key = keys[_i];
      key = g.NAMESPACE + key;
      localStorage.removeItem(key);
      GM_deleteValue(key);
    }
  };

  $.get = function(key, val, cb) {
    var items;
    if (typeof cb === 'function') {
      items = $.item(key, val);
    } else {
      items = key;
      cb = val;
    }
    return $.queueTask(function() {
      for (key in items) {
        if (val = GM_getValue(g.NAMESPACE + key)) {
          items[key] = JSON.parse(val);
        }
      }
      return cb(items);
    });
  };

  $.set = (function() {
    var set;
    set = function(key, val) {
      key = g.NAMESPACE + key;
      val = JSON.stringify(val);
      if (key in $.syncing) {
        localStorage.setItem(key, val);
      }
      return GM_setValue(key, val);
    };
    return function(keys, val) {
      var key;
      if (typeof keys === 'string') {
        set(keys, val);
        return;
      }
      for (key in keys) {
        val = keys[key];
        set(key, val);
      }
    };
  })();

  $.clear = function(cb) {
    $["delete"](GM_listValues().map(function(key) {
      return key.replace(g.NAMESPACE, '');
    }));
    return typeof cb === "function" ? cb() : void 0;
  };

  $$ = function(selector, root) {
    if (root == null) {
      root = d.body;
    }
    return __slice.call(root.querySelectorAll(selector));
  };

  Callbacks = (function() {
    function Callbacks(type) {
      this.type = type;
      this.keys = [];
    }

    Callbacks.prototype.push = function(_arg) {
      var cb, name;
      name = _arg.name, cb = _arg.cb;
      if (!this[name]) {
        this.keys.push(name);
      }
      return this[name] = cb;
    };

    Callbacks.prototype.execute = function(node) {
      var err, errors, name, _i, _len, _ref;
      _ref = this.keys;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        name = _ref[_i];
        try {
          this[name].call(node);
        } catch (_error) {
          err = _error;
          if (!errors) {
            errors = [];
          }
          errors.push({
            message: ['"', name, '" crashed on node ', this.type, ' No.', node.ID, ' (', node.board, ').'].join(''),
            error: err
          });
        }
      }
      if (errors) {
        return Main.handleErrors(errors);
      }
    };

    return Callbacks;

  })();

  Board = (function() {
    Board.prototype.toString = function() {
      return this.ID;
    };

    function Board(ID) {
      this.ID = ID;
      this.threads = new SimpleDict;
      this.posts = new SimpleDict;
      g.boards[this] = this;
    }

    return Board;

  })();

  Thread = (function() {
    Thread.callbacks = new Callbacks('Thread');

    Thread.prototype.toString = function() {
      return this.ID;
    };

    function Thread(ID, board) {
      this.ID = ID;
      this.board = board;
      this.fullID = "" + this.board + "." + this.ID;
      this.posts = new SimpleDict;
      this.isSticky = false;
      this.isClosed = false;
      this.postLimit = false;
      this.fileLimit = false;
      g.threads.push(this.fullID, board.threads.push(this, this));
    }

    Thread.prototype.setPage = function(pageNum) {
      var icon, info;
      info = this.OP.nodes.info;
      if (!(icon = $('.page-num', info))) {
        icon = $.el('span', {
          className: 'page-num'
        });
        $.after($('a[title="Reply to this post"]', info), [$.tn(' '), icon]);
      }
      icon.title = "This thread is on page " + pageNum + " in the original index.";
      return icon.textContent = "[" + pageNum + "]";
    };

    Thread.prototype.setStatus = function(type, status) {
      var icon, name, root, typeLC;
      name = "is" + type;
      if (this[name] === status) {
        return;
      }
      this[name] = status;
      if (!this.OP) {
        return;
      }
      typeLC = type.toLowerCase();
      if (!status) {
        $.rm($("." + typeLC + "Icon", this.OP.nodes.info));
        return;
      }
      icon = $.el('img', {
        src: "//s.4cdn.org/image/" + typeLC + (window.devicePixelRatio >= 2 ? '@2x' : '') + ".gif",
        alt: type,
        title: type,
        className: "" + typeLC + "Icon retina"
      });
      root = type === 'Closed' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || $('[title="Reply to this post"]', this.OP.nodes.info);
      return $.after(root, [$.tn(' '), icon]);
    };

    Thread.prototype.kill = function() {
      this.isDead = true;
      return this.timeOfDeath = Date.now();
    };

    Thread.prototype.collect = function() {
      this.posts.forEach(function(post) {
        return post.collect();
      });
      g.threads.rm(this.fullID);
      return this.board.threads.rm(this);
    };

    return Thread;

  })();

  Post = (function() {
    Post.callbacks = new Callbacks('Post');

    Post.prototype.toString = function() {
      return this.ID;
    };

    function Post(root, thread, board, that) {
      var capcode, date, email, flag, info, name, post, subject, tripcode, uniqueID;
      this.thread = thread;
      this.board = board;
      if (that == null) {
        that = {};
      }
      this.ID = +root.id.slice(2);
      this.fullID = "" + this.board + "." + this.ID;
      if (that.isOriginalMarkup) {
        this.cleanup(root);
      }
      post = $('.post', root);
      info = $('.postInfo', post);
      this.nodes = {
        root: root,
        post: post,
        info: info,
        comment: $('.postMessage', post),
        links: [],
        quotelinks: [],
        backlinks: info.getElementsByClassName('backlink')
      };
      if (!(this.isReply = $.hasClass(post, 'reply'))) {
        this.thread.OP = this;
        this.thread.isSticky = !!$('.stickyIcon', info);
        this.thread.isClosed = !!$('.closedIcon', info);
      }
      this.info = {};
      if (subject = $('.subject', info)) {
        this.nodes.subject = subject;
        this.info.subject = subject.textContent;
      }
      if (name = $('.name', info)) {
        this.nodes.name = name;
        this.info.name = name.textContent;
      }
      if (email = $('.useremail', info)) {
        this.nodes.email = email;
        this.info.email = decodeURIComponent(email.href.slice(7));
      }
      if (tripcode = $('.postertrip', info)) {
        this.nodes.tripcode = tripcode;
        this.info.tripcode = tripcode.textContent;
      }
      if (uniqueID = $('.posteruid', info)) {
        this.nodes.uniqueID = uniqueID;
        this.info.uniqueID = uniqueID.firstElementChild.textContent;
      }
      if (capcode = $('.capcode.hand', info)) {
        this.nodes.capcode = capcode;
        this.info.capcode = capcode.textContent.replace('## ', '');
      }
      if (flag = $('.flag, .countryFlag', info)) {
        this.nodes.flag = flag;
        this.info.flag = flag.title;
      }
      if (date = $('.dateTime', info)) {
        this.nodes.date = date;
        this.info.date = new Date(date.dataset.utc * 1000);
      }
      this.parseComment();
      this.parseQuotes();
      this.parseFile(that);
      this.clones = [];
      g.posts.push(this.fullID, thread.posts.push(this, board.posts.push(this, this)));
      if (that.isArchived) {
        this.kill();
      }
    }

    Post.prototype.parseComment = function() {
      var bq, node, spoilers, _i, _len, _ref;
      this.nodes.comment.normalize();
      bq = this.nodes.comment.cloneNode(true);
      _ref = $$('.abbr, .exif, b', bq);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        $.rm(node);
      }
      this.info.comment = this.nodesToText(bq);
      spoilers = $$('s', bq);
      return this.info.commentSpoilered = (function() {
        var _j, _len1;
        if (spoilers.length) {
          for (_j = 0, _len1 = spoilers.length; _j < _len1; _j++) {
            node = spoilers[_j];
            $.replace(node, $.tn('[spoiler]'));
          }
          return this.nodesToText(bq);
        } else {
          return this.info.comment;
        }
      }).call(this);
    };

    Post.prototype.nodesToText = function(bq) {
      var i, node, nodes, text;
      text = "";
      nodes = $.X('.//br|.//text()', bq);
      i = 0;
      while (node = nodes.snapshotItem(i++)) {
        text += node.data || '\n';
      }
      return text.trim().replace(/\s+$/gm, '');
    };

    Post.prototype.parseQuotes = function() {
      var quotelink, _i, _len, _ref;
      this.quotes = [];
      _ref = $$(':not(pre) > .quotelink', this.nodes.comment);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quotelink = _ref[_i];
        this.parseQuote(quotelink);
      }
    };

    Post.prototype.parseQuote = function(quotelink) {
      var fullID, match;
      if (!(match = quotelink.href.match(/boards\.4chan\.org\/([^\/]+)\/(?:res|thread)\/\d+(?:\/[^#]*)?#p(\d+)$/))) {
        return;
      }
      this.nodes.quotelinks.push(quotelink);
      if (this.isClone) {
        return;
      }
      fullID = "" + match[1] + "." + match[2];
      if (__indexOf.call(this.quotes, fullID) < 0) {
        return this.quotes.push(fullID);
      }
    };

    Post.prototype.parseFile = function(that) {
      var anchor, fileEl, fileText, nameNode, size, thumb, unit;
      if (!((fileEl = $('.file', this.nodes.post)) && (thumb = $('img[data-md5]', fileEl)))) {
        return;
      }
      anchor = thumb.parentNode;
      fileText = fileEl.firstElementChild;
      this.file = {
        text: fileText,
        thumb: thumb,
        URL: anchor.href,
        size: thumb.alt.match(/[\d.]+\s\w+/)[0],
        MD5: thumb.dataset.md5,
        isSpoiler: $.hasClass(anchor, 'imgspoiler')
      };
      size = +this.file.size.match(/[\d.]+/)[0];
      unit = ['B', 'KB', 'MB', 'GB'].indexOf(this.file.size.match(/\w+$/)[0]);
      while (unit-- > 0) {
        size *= 1024;
      }
      this.file.sizeInBytes = size;
      this.file.thumbURL = "" + location.protocol + "//t.4cdn.org/" + this.board + "/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg";
      this.file.isImage = /(jpg|png|gif)$/i.test(this.file.URL);
      this.file.isVideo = /webm$/i.test(this.file.URL);
      nameNode = $('a', fileText);
      if (this.file.isImage || this.file.isVideo) {
        this.file.dimensions = nameNode.nextSibling.textContent.match(/\d+x\d+/)[0];
      }
      return this.file.name = fileText.title || nameNode.title || nameNode.textContent;
    };

    Post.prototype.cleanup = function(root) {
      var node, _i, _j, _len, _len1, _ref, _ref1;
      _ref = $$('.mobile', root);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        $.rm(node);
      }
      _ref1 = $$('.desktop', root);
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        node = _ref1[_j];
        $.rmClass(node, 'desktop');
      }
    };

    Post.prototype.kill = function(file, now) {
      var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1;
      now || (now = new Date());
      if (file) {
        if (this.file.isDead) {
          return;
        }
        this.file.isDead = true;
        this.file.timeOfDeath = now;
        $.addClass(this.nodes.root, 'deleted-file');
      } else {
        if (this.isDead) {
          return;
        }
        this.isDead = true;
        this.timeOfDeath = now;
        $.addClass(this.nodes.root, 'deleted-post');
      }
      if (!(strong = $('strong.warning', this.nodes.info))) {
        strong = $.el('strong', {
          className: 'warning',
          textContent: this.isReply ? '[Deleted]' : '[Dead]'
        });
        $.after($('input', this.nodes.info), strong);
      }
      strong.textContent = file ? '[File deleted]' : '[Deleted]';
      if (this.isClone) {
        return;
      }
      _ref = this.clones;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        clone = _ref[_i];
        clone.kill(file, now);
      }
      if (file) {
        return;
      }
      _ref1 = Get.allQuotelinksLinkingTo(this);
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        quotelink = _ref1[_j];
        if (!(!$.hasClass(quotelink, 'deadlink'))) {
          continue;
        }
        quotelink.textContent = quotelink.textContent + '\u00A0(Dead)';
        $.addClass(quotelink, 'deadlink');
      }
    };

    Post.prototype.resurrect = function() {
      var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1;
      delete this.isDead;
      delete this.timeOfDeath;
      $.rmClass(this.nodes.root, 'deleted-post');
      strong = $('strong.warning', this.nodes.info);
      if (this.file && this.file.isDead) {
        strong.textContent = '[File deleted]';
      } else {
        $.rm(strong);
      }
      if (this.isClone) {
        return;
      }
      _ref = this.clones;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        clone = _ref[_i];
        clone.resurrect();
      }
      _ref1 = Get.allQuotelinksLinkingTo(this);
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        quotelink = _ref1[_j];
        if ($.hasClass(quotelink, 'deadlink')) {
          quotelink.textContent = quotelink.textContent.replace('\u00A0(Dead)', '');
          $.rmClass(quotelink, 'deadlink');
        }
      }
    };

    Post.prototype.collect = function() {
      this.kill();
      g.posts.rm(this.fullID);
      this.thread.posts.rm(this);
      return this.board.posts.rm(this);
    };

    Post.prototype.addClone = function(context, contractThumb) {
      return new Clone(this, context, contractThumb);
    };

    Post.prototype.rmClone = function(index) {
      var clone, _i, _len, _ref;
      this.clones.splice(index, 1);
      _ref = this.clones.slice(index);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        clone = _ref[_i];
        clone.nodes.root.dataset.clone = index++;
      }
    };

    return Post;

  })();

  Clone = (function(_super) {
    __extends(Clone, _super);

    function Clone(origin, context, contractThumb) {
      var file, info, inline, inlined, key, nodes, post, root, val, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3, _ref4;
      this.origin = origin;
      this.context = context;
      _ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply'];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        key = _ref[_i];
        this[key] = origin[key];
      }
      nodes = origin.nodes;
      root = contractThumb ? this.cloneWithoutVideo(nodes.root) : nodes.root.cloneNode(true);
      post = $('.post', root);
      info = $('.postInfo', post);
      this.nodes = {
        root: root,
        post: post,
        info: info,
        comment: $('.postMessage', post),
        quotelinks: [],
        backlinks: info.getElementsByClassName('backlink')
      };
      _ref1 = $$('.inline', post);
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        inline = _ref1[_j];
        $.rm(inline);
      }
      _ref2 = $$('.inlined', post);
      for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
        inlined = _ref2[_k];
        $.rmClass(inlined, 'inlined');
      }
      root.hidden = false;
      $.rmClass(root, 'forwarded');
      $.rmClass(post, 'highlight');
      if (nodes.subject) {
        this.nodes.subject = $('.subject', info);
      }
      if (nodes.name) {
        this.nodes.name = $('.name', info);
      }
      if (nodes.email) {
        this.nodes.email = $('.useremail', info);
      }
      if (nodes.tripcode) {
        this.nodes.tripcode = $('.postertrip', info);
      }
      if (nodes.uniqueID) {
        this.nodes.uniqueID = $('.posteruid', info);
      }
      if (nodes.capcode) {
        this.nodes.capcode = $('.capcode', info);
      }
      if (nodes.flag) {
        this.nodes.flag = $('.countryFlag', info);
      }
      if (nodes.date) {
        this.nodes.date = $('.dateTime', info);
      }
      this.parseQuotes();
      if (origin.file) {
        this.file = {};
        _ref3 = origin.file;
        for (key in _ref3) {
          val = _ref3[key];
          this.file[key] = val;
        }
        file = $('.file', post);
        this.file.text = file.firstElementChild;
        this.file.thumb = $('.fileThumb > [data-md5]', file);
        this.file.fullImage = $('.full-image', file);
        this.file.videoControls = $('.video-controls', this.file.text);
        if (contractThumb) {
          ImageExpand.contract(this);
        }
        if ((_ref4 = this.file.fullImage) != null) {
          _ref4.removeAttribute('id');
        }
        delete file.isHovered;
      }
      if (origin.isDead) {
        this.isDead = true;
      }
      this.isClone = true;
      root.dataset.clone = origin.clones.push(this) - 1;
    }

    Clone.prototype.cloneWithoutVideo = function(node) {
      var child, clone, _i, _len, _ref;
      if (node.tagName === 'VIDEO' && !node.dataset.md5) {
        return [];
      } else if (node.nodeType === Node.ELEMENT_NODE && $('video', node)) {
        clone = node.cloneNode(false);
        _ref = node.childNodes;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          child = _ref[_i];
          $.add(clone, this.cloneWithoutVideo(child));
        }
        return clone;
      } else {
        return node.cloneNode(true);
      }
    };

    return Clone;

  })(Post);

  DataBoard = (function() {
    DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads'];

    function DataBoard(key, sync, dontClean) {
      var init;
      this.key = key;
      this.onSync = __bind(this.onSync, this);
      this.data = Conf[key];
      $.sync(key, this.onSync);
      if (!dontClean) {
        this.clean();
      }
      if (!sync) {
        return;
      }
      init = (function(_this) {
        return function() {
          $.off(d, '4chanXInitFinished', init);
          return _this.sync = sync;
        };
      })(this);
      $.on(d, '4chanXInitFinished', init);
    }

    DataBoard.prototype.save = function() {
      return $.set(this.key, this.data);
    };

    DataBoard.prototype["delete"] = function(_arg) {
      var boardID, postID, threadID, _ref;
      boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID;
      if (postID) {
        if (!((_ref = this.data.boards[boardID]) != null ? _ref[threadID] : void 0)) {
          return;
        }
        delete this.data.boards[boardID][threadID][postID];
        this.deleteIfEmpty({
          boardID: boardID,
          threadID: threadID
        });
      } else if (threadID) {
        if (!this.data.boards[boardID]) {
          return;
        }
        delete this.data.boards[boardID][threadID];
        this.deleteIfEmpty({
          boardID: boardID
        });
      } else {
        delete this.data.boards[boardID];
      }
      return this.save();
    };

    DataBoard.prototype.deleteIfEmpty = function(_arg) {
      var boardID, threadID;
      boardID = _arg.boardID, threadID = _arg.threadID;
      if (threadID) {
        if (!Object.keys(this.data.boards[boardID][threadID]).length) {
          delete this.data.boards[boardID][threadID];
          return this.deleteIfEmpty({
            boardID: boardID
          });
        }
      } else if (!Object.keys(this.data.boards[boardID]).length) {
        return delete this.data.boards[boardID];
      }
    };

    DataBoard.prototype.set = function(_arg) {
      var boardID, postID, threadID, val, _base, _base1, _base2;
      boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, val = _arg.val;
      if (postID !== void 0) {
        ((_base = ((_base1 = this.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[postID] = val;
      } else if (threadID !== void 0) {
        ((_base2 = this.data.boards)[boardID] || (_base2[boardID] = {}))[threadID] = val;
      } else {
        this.data.boards[boardID] = val;
      }
      return this.save();
    };

    DataBoard.prototype.get = function(_arg) {
      var ID, board, boardID, defaultValue, postID, thread, threadID, val, _i, _len;
      boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, defaultValue = _arg.defaultValue;
      if (board = this.data.boards[boardID]) {
        if (!threadID) {
          if (postID) {
            for (thread = _i = 0, _len = board.length; _i < _len; thread = ++_i) {
              ID = board[thread];
              if (postID in thread) {
                val = thread[postID];
                break;
              }
            }
          } else {
            val = board;
          }
        } else if (thread = board[threadID]) {
          val = postID ? thread[postID] : thread;
        }
      }
      return val || defaultValue;
    };

    DataBoard.prototype.clean = function() {
      var boardID, now, threadID, val, _ref;
      _ref = this.data.boards;
      for (boardID in _ref) {
        val = _ref[boardID];
        this.deleteIfEmpty({
          boardID: boardID
        });
      }
      now = Date.now();
      if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) {
        this.data.lastChecked = now;
        for (boardID in this.data.boards) {
          for (threadID in this.data.boards[boardID]) {
            this.ajaxClean(boardID, threadID);
          }
        }
      }
      return this.save();
    };

    DataBoard.prototype.ajaxClean = function(boardID, threadID) {
      return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
        onloadend: (function(_this) {
          return function(e) {
            if (e.target.status === 404) {
              return _this["delete"]({
                boardID: boardID,
                threadID: threadID
              });
            }
          };
        })(this)
      }, {
        type: 'head'
      });
    };

    DataBoard.prototype.onSync = function(data) {
      this.data = data || {
        boards: {}
      };
      return typeof this.sync === "function" ? this.sync() : void 0;
    };

    return DataBoard;

  })();

  Notice = (function() {
    function Notice(type, content, timeout, onclose) {
      this.timeout = timeout;
      this.onclose = onclose;
      this.close = __bind(this.close, this);
      this.add = __bind(this.add, this);
      this.el = $.el('div', {
        innerHTML: "<a href=\"javascript:;\" class=\"close fa fa-times\" title=\"Close\"></a><div class=\"message\"></div>"
      });
      this.el.style.opacity = 0;
      this.setType(type);
      $.on(this.el.firstElementChild, 'click', this.close);
      if (typeof content === 'string') {
        content = $.tn(content);
      }
      $.add(this.el.lastElementChild, content);
      $.ready(this.add);
    }

    Notice.prototype.setType = function(type) {
      return this.el.className = "notification " + type;
    };

    Notice.prototype.add = function() {
      if (d.hidden) {
        $.on(d, 'visibilitychange', this.add);
        return;
      }
      $.off(d, 'visibilitychange', this.add);
      $.add(Header.noticesRoot, this.el);
      this.el.clientHeight;
      this.el.style.opacity = 1;
      if (this.timeout) {
        return setTimeout(this.close, this.timeout * $.SECOND);
      }
    };

    Notice.prototype.close = function() {
      $.off(d, 'visibilitychange', this.add);
      $.rm(this.el);
      return typeof this.onclose === "function" ? this.onclose() : void 0;
    };

    return Notice;

  })();

  RandomAccessList = (function() {
    function RandomAccessList(items) {
      var item, _i, _len;
      this.length = 0;
      if (items) {
        for (_i = 0, _len = items.length; _i < _len; _i++) {
          item = items[_i];
          this.push(item);
        }
      }
    }

    RandomAccessList.prototype.push = function(data) {
      var ID, item, last;
      ID = data.ID;
      ID || (ID = data.id);
      if (this[ID]) {
        return;
      }
      last = this.last;
      this[ID] = item = {
        prev: last,
        next: null,
        data: data,
        ID: ID
      };
      item.prev = last;
      this.last = last ? last.next = item : this.first = item;
      return this.length++;
    };

    RandomAccessList.prototype.before = function(root, item) {
      var prev;
      if (item.next === root) {
        return;
      }
      this.rmi(item);
      prev = root.prev;
      root.prev = item;
      item.next = root;
      item.prev = prev;
      if (prev) {
        return prev.next = item;
      } else {
        return this.first = item;
      }
    };

    RandomAccessList.prototype.after = function(root, item) {
      var next;
      if (item.prev === root) {
        return;
      }
      this.rmi(item);
      next = root.next;
      root.next = item;
      item.prev = root;
      item.next = next;
      if (next) {
        return next.prev = item;
      } else {
        return this.last = item;
      }
    };

    RandomAccessList.prototype.prepend = function(item) {
      var first;
      first = this.first;
      if (item === first || !this[item.ID]) {
        return;
      }
      this.rmi(item);
      item.next = first;
      if (first) {
        first.prev = item;
      } else {
        this.last = item;
      }
      this.first = item;
      return delete item.prev;
    };

    RandomAccessList.prototype.shift = function() {
      return this.rm(this.first.ID);
    };

    RandomAccessList.prototype.order = function() {
      var item, order;
      order = [item = this.first];
      while (item = item.next) {
        order.push(item);
      }
      return order;
    };

    RandomAccessList.prototype.rm = function(ID) {
      var item;
      item = this[ID];
      if (!item) {
        return;
      }
      delete this[ID];
      this.length--;
      this.rmi(item);
      delete item.next;
      return delete item.prev;
    };

    RandomAccessList.prototype.rmi = function(item) {
      var next, prev;
      prev = item.prev, next = item.next;
      if (prev) {
        prev.next = next;
      } else {
        this.first = next;
      }
      if (next) {
        return next.prev = prev;
      } else {
        return this.last = prev;
      }
    };

    return RandomAccessList;

  })();

  SimpleDict = (function() {
    function SimpleDict() {
      this.keys = [];
    }

    SimpleDict.prototype.push = function(key, data) {
      key = "" + key;
      if (!this[key]) {
        this.keys.push(key);
      }
      return this[key] = data;
    };

    SimpleDict.prototype.rm = function(key) {
      var i;
      key = "" + key;
      if ((i = this.keys.indexOf(key)) !== -1) {
        this.keys.splice(i, 1);
        return delete this[key];
      }
    };

    SimpleDict.prototype.forEach = function(fn) {
      var key, _i, _len, _ref;
      _ref = __slice.call(this.keys);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        key = _ref[_i];
        fn(this[key]);
      }
    };

    return SimpleDict;

  })();

  Polyfill = {
    init: function() {
      this.notificationPermission();
      this.toBlob();
      return this.visibility();
    },
    notificationPermission: function() {
      if (!window.Notification || 'permission' in Notification || !window.webkitNotifications) {
        return;
      }
      return Object.defineProperty(Notification, 'permission', {
        get: function() {
          switch (webkitNotifications.checkPermission()) {
            case 0:
              return 'granted';
            case 1:
              return 'default';
            case 2:
              return 'denied';
          }
        }
      });
    },
    toBlob: function() {
      var _base;
      return (_base = HTMLCanvasElement.prototype).toBlob || (_base.toBlob = function(cb) {
        var data, i, l, ui8a, _i;
        data = atob(this.toDataURL().slice(22));
        l = data.length;
        ui8a = new Uint8Array(l);
        for (i = _i = 0; _i < l; i = _i += 1) {
          ui8a[i] = data.charCodeAt(i);
        }
        return cb(new Blob([ui8a], {
          type: 'image/png'
        }));
      });
    },
    visibility: function() {
      if ('visibilityState' in d) {
        return;
      }
      Object.defineProperties(HTMLDocument.prototype, {
        visibilityState: {
          get: function() {
            return this.webkitVisibilityState;
          }
        },
        hidden: {
          get: function() {
            return this.webkitHidden;
          }
        }
      });
      return $.on(d, 'webkitvisibilitychange', function() {
        return $.event('visibilitychange');
      });
    }
  };

  Header = {
    init: function() {
      var barFixedToggler, barPositionToggler, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler;
      this.menu = new UI.Menu('header');
      menuButton = $.el('span', {
        className: 'menu-button'
      });
      $.extend(menuButton, {
        innerHTML: "<i></i>"
      });
      barFixedToggler = UI.checkbox('Fixed Header', ' Fixed Header');
      headerToggler = UI.checkbox('Header auto-hide', ' Auto-hide header');
      scrollHeaderToggler = UI.checkbox('Header auto-hide on scroll', ' Auto-hide header on scroll');
      barPositionToggler = UI.checkbox('Bottom Header', ' Bottom header');
      linkJustifyToggler = UI.checkbox('Centered links', ' Centered links');
      customNavToggler = UI.checkbox('Custom Board Navigation', ' Custom board navigation');
      footerToggler = UI.checkbox('Bottom Board List', ' Hide bottom board list');
      shortcutToggler = UI.checkbox('Shortcut Icons', ' Shortcut Icons');
      editCustomNav = $.el('a', {
        textContent: 'Edit custom board navigation',
        href: 'javascript:;'
      });
      this.barFixedToggler = barFixedToggler.firstElementChild;
      this.scrollHeaderToggler = scrollHeaderToggler.firstElementChild;
      this.barPositionToggler = barPositionToggler.firstElementChild;
      this.linkJustifyToggler = linkJustifyToggler.firstElementChild;
      this.headerToggler = headerToggler.firstElementChild;
      this.footerToggler = footerToggler.firstElementChild;
      this.shortcutToggler = shortcutToggler.firstElementChild;
      this.customNavToggler = customNavToggler.firstElementChild;
      $.on(menuButton, 'click', this.menuToggle);
      $.on(this.headerToggler, 'change', this.toggleBarVisibility);
      $.on(this.barFixedToggler, 'change', this.toggleBarFixed);
      $.on(this.barPositionToggler, 'change', this.toggleBarPosition);
      $.on(this.scrollHeaderToggler, 'change', this.toggleHideBarOnScroll);
      $.on(this.linkJustifyToggler, 'change', this.toggleLinkJustify);
      $.on(this.headerToggler, 'change', this.toggleBarVisibility);
      $.on(this.footerToggler, 'change', this.toggleFooterVisibility);
      $.on(this.shortcutToggler, 'change', this.toggleShortcutIcons);
      $.on(this.customNavToggler, 'change', this.toggleCustomNav);
      $.on(editCustomNav, 'click', this.editCustomNav);
      this.setBarFixed(Conf['Fixed Header']);
      this.setHideBarOnScroll(Conf['Header auto-hide on scroll']);
      this.setBarVisibility(Conf['Header auto-hide']);
      this.setLinkJustify(Conf['Centered links']);
      this.setShortcutIcons(Conf['Shortcut Icons']);
      $.sync('Fixed Header', this.setBarFixed);
      $.sync('Header auto-hide on scroll', this.setHideBarOnScroll);
      $.sync('Bottom Header', this.setBarPosition);
      $.sync('Shortcut Icons', this.setShortcutIcons);
      $.sync('Header auto-hide', this.setBarVisibility);
      $.sync('Centered links', this.setLinkJustify);
      this.addShortcut(menuButton);
      this.menu.addEntry({
        el: $.el('span', {
          textContent: 'Header'
        }),
        order: 107,
        subEntries: [
          {
            el: barFixedToggler
          }, {
            el: headerToggler
          }, {
            el: scrollHeaderToggler
          }, {
            el: barPositionToggler
          }, {
            el: linkJustifyToggler
          }, {
            el: footerToggler
          }, {
            el: shortcutToggler
          }, {
            el: customNavToggler
          }, {
            el: editCustomNav
          }
        ]
      });
      $.on(window, 'load hashchange', Header.hashScroll);
      $.on(d, 'CreateNotification', this.createNotification);
      $.asap((function() {
        return d.body;
      }), (function(_this) {
        return function() {
          if (!Main.isThisPageLegit()) {
            return;
          }
          $.asap((function() {
            return $.id('boardNavMobile') || d.readyState !== 'loading';
          }), Header.setBoardList);
          $.prepend(d.body, [_this.bar, _this.noticesRoot]);
          $.add(d.body, Header.hover);
          _this.setBarPosition(Conf['Bottom Header']);
          return _this;
        };
      })(this));
      $.ready((function(_this) {
        return function() {
          var a, cs, footer;
          _this.footer = footer = $.id('boardNavDesktopFoot');
          if (a = $("a[href*='/" + g.BOARD + "/']", footer)) {
            a.className = 'current';
          }
          cs = $.el('a', {
            href: 'javascript:;',
            textContent: 'Catalog Settings'
          });
          $.on(cs, 'click', function() {
            return $.id('settingsWindowLink').click();
          });
          if (g.VIEW === 'catalog') {
            _this.addShortcut(cs);
          }
          Header.setFooterVisibility(Conf['Bottom Board List']);
          return $.sync('Bottom Board List', Header.setFooterVisibility);
        };
      })(this));
      return this.enableDesktopNotifications();
    },
    bar: $.el('div', {
      id: 'header-bar'
    }),
    noticesRoot: $.el('div', {
      id: 'notifications'
    }),
    shortcuts: $.el('span', {
      id: 'shortcuts'
    }),
    hover: $.el('div', {
      id: 'hoverUI'
    }),
    toggle: $.el('div', {
      id: 'scroll-marker'
    }),
    setBoardList: function() {
      var a, boardList, btn, fourchannav, fullBoardList, _i, _len, _ref;
      fourchannav = $.id('boardNavDesktop');
      Header.boardList = boardList = $.el('span', {
        id: 'board-list'
      });
      $.extend(boardList, {
        innerHTML: "<span id=\"custom-board-list\"></span><span id=\"full-board-list\" hidden><span class=\"hide-board-list-container brackets-wrap\"><a href=\"javascript:;\" class=\"hide-board-list-button\">&nbsp;-&nbsp;</a></span> " + fourchannav.innerHTML + "</span>"
      });
      _ref = $$('a', boardList);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        a = _ref[_i];
        if (a.pathname.split('/')[1] === g.BOARD.ID) {
          a.className = 'current';
        }
      }
      fullBoardList = $('#full-board-list', boardList);
      btn = $('.hide-board-list-button', fullBoardList);
      $.on(btn, 'click', Header.toggleBoardList);
      $.rm($('#navtopright', fullBoardList));
      $.add(boardList, fullBoardList);
      $.add(Header.bar, [Header.boardList, Header.shortcuts, Header.toggle]);
      Header.setCustomNav(Conf['Custom Board Navigation']);
      Header.generateBoardList(Conf['boardnav'].replace(/(\r\n|\n|\r)/g, ' '));
      $.sync('Custom Board Navigation', Header.setCustomNav);
      return $.sync('boardnav', Header.generateBoardList);
    },
    generateBoardList: function(text) {
      var as, list, nodes;
      list = $('#custom-board-list', Header.boardList);
      $.rmAll(list);
      if (!text) {
        return;
      }
      as = $$('#full-board-list a[title]', Header.boardList);
      nodes = text.match(/[\w@]+((-(all|title|replace|full|index|catalog|url:"[^"]+[^"]"|text:"[^"]+")|\,"[^"]+[^"]"))*|[^\w@]+/g).map(function(t) {
        var a, board, m, _i, _len;
        if (/^[^\w@]/.test(t)) {
          return $.tn(t);
        }
        if (/^toggle-all/.test(t)) {
          a = $.el('a', {
            className: 'show-board-list-button',
            textContent: (t.match(/-text:"(.+)"/) || [null, '+'])[1],
            href: 'javascript:;'
          });
          $.on(a, 'click', Header.toggleBoardList);
          return a;
        }
        if (/^external/.test(t)) {
          a = $.el('a', {
            href: (t.match(/\,"(.+)"/) || [null, '+'])[1],
            textContent: (t.match(/-text:"(.+)"\,/) || [null, '+'])[1],
            className: 'external'
          });
          return a;
        }
        board = /^current/.test(t) ? g.BOARD.ID : t.match(/^[^-]+/)[0];
        for (_i = 0, _len = as.length; _i < _len; _i++) {
          a = as[_i];
          if (a.textContent === board) {
            a = a.cloneNode(true);
            a.textContent = /-title/.test(t) || /-replace/.test(t) && $.hasClass(a, 'current') ? a.title : /-full/.test(t) ? "/" + board + "/ - " + a.title : (m = t.match(/-text:"(.+)"/)) ? m[1] : a.textContent;
            if (m = t.match(/-(index|catalog)/)) {
              a.dataset.only = m[1];
              a.href = "//boards.4chan.org/" + board + "/";
              if (m[1] === 'catalog') {
                if (Conf['External Catalog']) {
                  a.href = CatalogLinks.external(board);
                } else {
                  a.href += 'catalog';
                }
                $.addClass(a, 'catalog');
              }
            }
            if (board === '@') {
              $.addClass(a, 'navSmall');
            }
            return a;
          }
        }
        return $.tn(t);
      });
      $.add(list, nodes);
      return $.ready(CatalogLinks.initBoardList);
    },
    toggleBoardList: function() {
      var bar, custom, full, showBoardList;
      bar = Header.bar;
      custom = $('#custom-board-list', bar);
      full = $('#full-board-list', bar);
      showBoardList = !full.hidden;
      custom.hidden = !showBoardList;
      return full.hidden = showBoardList;
    },
    setLinkJustify: function(centered) {
      Header.linkJustifyToggler.checked = centered;
      if (centered) {
        return $.addClass(doc, 'centered-links');
      } else {
        return $.rmClass(doc, 'centered-links');
      }
    },
    toggleLinkJustify: function() {
      var centered;
      $.event('CloseMenu');
      centered = this.nodeName === 'INPUT' ? this.checked : void 0;
      Header.setLinkJustify(centered);
      return $.set('Centered links', centered);
    },
    setBarFixed: function(fixed) {
      Header.barFixedToggler.checked = fixed;
      if (fixed) {
        $.addClass(doc, 'fixed');
        return $.addClass(Header.bar, 'dialog');
      } else {
        $.rmClass(doc, 'fixed');
        return $.rmClass(Header.bar, 'dialog');
      }
    },
    toggleBarFixed: function() {
      $.event('CloseMenu');
      Header.setBarFixed(this.checked);
      Conf['Fixed Header'] = this.checked;
      return $.set('Fixed Header', this.checked);
    },
    setShortcutIcons: function(show) {
      Header.shortcutToggler.checked = show;
      if (show) {
        return $.addClass(doc, 'shortcut-icons');
      } else {
        return $.rmClass(doc, 'shortcut-icons');
      }
    },
    toggleShortcutIcons: function() {
      $.event('CloseMenu');
      Header.setShortcutIcons(this.checked);
      Conf['Shortcut Icons'] = this.checked;
      return $.set('Shortcut Icons', this.checked);
    },
    setBarVisibility: function(hide) {
      Header.headerToggler.checked = hide;
      $.event('CloseMenu');
      (hide ? $.addClass : $.rmClass)(Header.bar, 'autohide');
      return (hide ? $.addClass : $.rmClass)(doc, 'autohide');
    },
    toggleBarVisibility: function() {
      var hide, message;
      hide = this.nodeName === 'INPUT' ? this.checked : !$.hasClass(Header.bar, 'autohide');
      this.checked = hide;
      $.set('Header auto-hide', Conf['Header auto-hide'] = hide);
      Header.setBarVisibility(hide);
      message = "The header bar will " + (hide ? 'automatically hide itself.' : 'remain visible.');
      return new Notice('info', message, 2);
    },
    setHideBarOnScroll: function(hide) {
      Header.scrollHeaderToggler.checked = hide;
      if (hide) {
        $.on(window, 'scroll', Header.hideBarOnScroll);
        return;
      }
      $.off(window, 'scroll', Header.hideBarOnScroll);
      $.rmClass(Header.bar, 'scroll');
      if (!Conf['Header auto-hide']) {
        return $.rmClass(Header.bar, 'autohide');
      }
    },
    toggleHideBarOnScroll: function(e) {
      var hide;
      hide = this.checked;
      $.cb.checked.call(this);
      return Header.setHideBarOnScroll(hide);
    },
    hideBarOnScroll: function() {
      var offsetY;
      offsetY = window.pageYOffset;
      if (offsetY > (Header.previousOffset || 0)) {
        $.addClass(Header.bar, 'autohide', 'scroll');
      } else {
        $.rmClass(Header.bar, 'autohide', 'scroll');
      }
      return Header.previousOffset = offsetY;
    },
    setBarPosition: function(bottom) {
      var args;
      Header.barPositionToggler.checked = bottom;
      $.event('CloseMenu');
      args = bottom ? ['bottom-header', 'top-header', 'bottom'] : ['top-header', 'bottom-header', 'top'];
      $.addClass(doc, args[0]);
      $.rmClass(doc, args[1]);
      return Header.bar.parentNode.className = args[2];
    },
    toggleBarPosition: function() {
      $.cb.checked.call(this);
      return Header.setBarPosition(this.checked);
    },
    setFooterVisibility: function(hide) {
      Header.footerToggler.checked = hide;
      return Header.footer.hidden = hide;
    },
    toggleFooterVisibility: function() {
      var hide, message;
      $.event('CloseMenu');
      hide = this.nodeName === 'INPUT' ? this.checked : !!Header.footer.hidden;
      Header.setFooterVisibility(hide);
      $.set('Bottom Board List', hide);
      message = hide ? 'The bottom navigation will now be hidden.' : 'The bottom navigation will remain visible.';
      return new Notice('info', message, 2);
    },
    setCustomNav: function(show) {
      var btn, cust, full, _ref;
      Header.customNavToggler.checked = show;
      cust = $('#custom-board-list', Header.bar);
      full = $('#full-board-list', Header.bar);
      btn = $('.hide-board-list-button', full);
      return _ref = show ? [false, true] : [true, false], cust.hidden = _ref[0], full.hidden = _ref[1], _ref;
    },
    toggleCustomNav: function() {
      $.cb.checked.call(this);
      return Header.setCustomNav(this.checked);
    },
    editCustomNav: function() {
      var settings;
      Settings.open('Advanced');
      settings = $.id('fourchanx-settings');
      return $('textarea[name=boardnav]', settings).focus();
    },
    hashScroll: function() {
      var hash, post;
      hash = this.location.hash.slice(1);
      if (!(/^p\d+$/.test(hash) && (post = $.id(hash)))) {
        return;
      }
      if ((Get.postFromRoot(post)).isHidden) {
        return;
      }
      return Header.scrollTo(post);
    },
    scrollTo: function(root, down, needed) {
      var height, x;
      if (down) {
        x = Header.getBottomOf(root);
        if (Conf['Header auto-hide on scroll'] && Conf['Bottom header']) {
          height = Header.bar.getBoundingClientRect().height;
          if (x <= 0) {
            if (!Header.isHidden()) {
              x += height;
            }
          } else {
            if (Header.isHidden()) {
              x -= height;
            }
          }
        }
        if (!(needed && x >= 0)) {
          return window.scrollBy(0, -x);
        }
      } else {
        x = Header.getTopOf(root);
        if (Conf['Header auto-hide on scroll'] && !Conf['Bottom header']) {
          height = Header.bar.getBoundingClientRect().height;
          if (x >= 0) {
            if (!Header.isHidden()) {
              x += height;
            }
          } else {
            if (Header.isHidden()) {
              x -= height;
            }
          }
        }
        if (!(needed && x >= 0)) {
          return window.scrollBy(0, x);
        }
      }
    },
    scrollToIfNeeded: function(root, down) {
      return Header.scrollTo(root, down, true);
    },
    getTopOf: function(root) {
      var headRect, top;
      top = root.getBoundingClientRect().top;
      if (Conf['Fixed Header'] && !Conf['Bottom Header']) {
        headRect = Header.toggle.getBoundingClientRect();
        top -= headRect.top + headRect.height;
      }
      return top;
    },
    getBottomOf: function(root) {
      var bottom, clientHeight, headRect;
      clientHeight = doc.clientHeight;
      bottom = clientHeight - root.getBoundingClientRect().bottom;
      if (Conf['Bottom Header']) {
        headRect = Header.toggle.getBoundingClientRect();
        bottom -= clientHeight - headRect.bottom + headRect.height;
      }
      return bottom;
    },
    isNodeVisible: function(node) {
      var height;
      if (d.hidden || !doc.contains(node)) {
        return false;
      }
      height = node.getBoundingClientRect().height;
      return Header.getTopOf(node) + height >= 0 && Header.getBottomOf(node) + height >= 0;
    },
    isHidden: function() {
      var top;
      top = Header.bar.getBoundingClientRect().top;
      if (Conf['Bottom header']) {
        return top === doc.clientHeight;
      } else {
        return top < 0;
      }
    },
    addShortcut: function(el) {
      var shortcut;
      shortcut = $.el('span', {
        className: 'shortcut brackets-wrap'
      });
      $.add(shortcut, el);
      return $.prepend(Header.shortcuts, shortcut);
    },
    rmShortcut: function(el) {
      return $.rm(el.parentElement);
    },
    menuToggle: function(e) {
      return Header.menu.toggle(e, this, g);
    },
    createNotification: function(e) {
      var content, lifetime, notice, type, _ref;
      _ref = e.detail, type = _ref.type, content = _ref.content, lifetime = _ref.lifetime;
      return notice = new Notice(type, content, lifetime);
    },
    areNotificationsEnabled: false,
    enableDesktopNotifications: function() {
      var authorize, disable, el, notice, _ref;
      if (!(window.Notification && Conf['Desktop Notifications'])) {
        return;
      }
      switch (Notification.permission) {
        case 'granted':
          Header.areNotificationsEnabled = true;
          return;
        case 'denied':
          return;
      }
      el = $.el('span', {
        innerHTML: E(g.NAME) + " needs your permission to show desktop notifications. [<a href=\"" + E(g.FAQ) + "#why-is-4chan-x-asking-for-permission-to-show-desktop-notifications\" target=\"_blank\">FAQ</a>]<br><button>Authorize</button> or <button>Disable</button>"
      });
      _ref = $$('button', el), authorize = _ref[0], disable = _ref[1];
      $.on(authorize, 'click', function() {
        return Notification.requestPermission(function(status) {
          Header.areNotificationsEnabled = status === 'granted';
          if (status === 'default') {
            return;
          }
          return notice.close();
        });
      });
      $.on(disable, 'click', function() {
        $.set('Desktop Notifications', false);
        return notice.close();
      });
      return notice = new Notice('info', el);
    }
  };

  Index = {
    init: function() {
      var anchorEntry, input, label, modeEntry, name, refNavEntry, repliesEntry, sortEntry, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2;
      if (g.BOARD.ID === 'f' || g.VIEW !== 'index' || !Conf['JSON Navigation']) {
        return;
      }
      this.board = "" + g.BOARD;
      this.button = $.el('a', {
        className: 'index-refresh-shortcut fa fa-refresh',
        title: 'Refresh',
        href: 'javascript:;',
        textContent: 'Refresh Index'
      });
      $.on(this.button, 'click', this.update);
      Header.addShortcut(this.button, 1);
      modeEntry = {
        el: $.el('span', {
          textContent: 'Index mode'
        }),
        subEntries: [
          {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Mode\" value=\"paged\"> Paged"
            })
          }, {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Mode\" value=\"infinite\"> Infinite scrolling"
            })
          }, {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Mode\" value=\"all pages\"> All threads"
            })
          }
        ]
      };
      _ref = modeEntry.subEntries;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        label = _ref[_i];
        input = label.el.firstChild;
        input.checked = Conf['Index Mode'] === input.value;
        $.on(input, 'change', $.cb.value);
        $.on(input, 'change', this.cb.mode);
      }
      sortEntry = {
        el: $.el('span', {
          textContent: 'Sort by'
        }),
        subEntries: [
          {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Sort\" value=\"bump\"> Bump order"
            })
          }, {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Sort\" value=\"lastreply\"> Last reply"
            })
          }, {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Sort\" value=\"birth\"> Creation date"
            })
          }, {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Sort\" value=\"replycount\"> Reply count"
            })
          }, {
            el: $.el('label', {
              innerHTML: "<input type=\"radio\" name=\"Index Sort\" value=\"filecount\"> File count"
            })
          }
        ]
      };
      _ref1 = sortEntry.subEntries;
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        label = _ref1[_j];
        input = label.el.firstChild;
        input.checked = Conf['Index Sort'] === input.value;
        $.on(input, 'change', $.cb.value);
        $.on(input, 'change', this.cb.sort);
      }
      repliesEntry = {
        el: UI.checkbox('Show Replies', ' Show replies')
      };
      anchorEntry = {
        el: UI.checkbox('Anchor Hidden Threads', ' Anchor hidden threads')
      };
      refNavEntry = {
        el: UI.checkbox('Refreshed Navigation', ' Refreshed navigation')
      };
      anchorEntry.el.title = 'Move hidden threads at the end of the index.';
      refNavEntry.el.title = 'Refresh index when navigating through pages.';
      _ref2 = [repliesEntry, anchorEntry, refNavEntry];
      for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
        label = _ref2[_k];
        input = label.el.firstChild;
        name = input.name;
        $.on(input, 'change', $.cb.checked);
        switch (name) {
          case 'Show Replies':
            $.on(input, 'change', this.cb.replies);
            break;
          case 'Anchor Hidden Threads':
            $.on(input, 'change', this.cb.sort);
        }
      }
      Header.menu.addEntry({
        el: $.el('span', {
          textContent: 'Index Navigation'
        }),
        order: 98,
        subEntries: [repliesEntry, anchorEntry, refNavEntry, modeEntry, sortEntry]
      });
      $.addClass(doc, 'index-loading');
      this.root = $.el('div', {
        className: 'board'
      });
      this.pagelist = $.el('div', {
        className: 'pagelist',
        hidden: true
      });
      $.extend(this.pagelist, {
        innerHTML: "<div class=\"prev\"><a><button disabled>Previous</button></a></div><div class=\"pages\"></div><div class=\"next\"><a><button disabled>Next</button></a></div><div class=\"pages cataloglink\"><a href=\"./catalog\">Catalog</a></div>"
      });
      this.navLinks = $.el('div', {
        className: 'navLinks'
      });
      $.extend(this.navLinks, {
        innerHTML: "<span class=\"brackets-wrap returnlink\"><a href=\"javascript:;\">Return</a></span> <span class=\"brackets-wrap cataloglink\"><a href=\"javascript:;\">Catalog</a></span> <span class=\"brackets-wrap bottomlink\"><a href=\"#bottom\">Bottom</a></span> <span class=\"brackets-wrap\" id=\"index-last-refresh\"><time title=\"Last index refresh\">...</time></span> <input type=\"search\" id=\"index-search\" class=\"field\" placeholder=\"Search\"><a id=\"index-search-clear\" href=\"javascript:;\" title=\"Clear search\">×</a>"
      });
      $('.returnlink a', this.navLinks).href = "//boards.4chan.org/" + g.BOARD + "/";
      $('.cataloglink a', this.navLinks).href = "//boards.4chan.org/" + g.BOARD + "/catalog";
      this.searchInput = $('#index-search', this.navLinks);
      this.currentPage = this.getCurrentPage();
      $.on(window, 'popstate', this.cb.popstate);
      $.on(d, 'scroll', Index.scroll);
      $.on(this.pagelist, 'click', this.cb.pageNav);
      $.on(this.searchInput, 'input', this.onSearchInput);
      $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch);
      this.update();
      $.asap((function() {
        return $('.board', doc) || d.readyState !== 'loading';
      }), function() {
        var board, el, topNavPos, _l, _len3, _ref3, _ref4;
        board = $('.board');
        $.replace(board, Index.root);
        $.event('PostsInserted');
        d.implementation.createDocument(null, null, null).appendChild(board);
        _ref3 = $$('.navLinks');
        for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
          el = _ref3[_l];
          $.rm(el);
        }
        if ((_ref4 = $.id('search-box')) != null) {
          _ref4.parentNode.remove();
        }
        topNavPos = $.id('delform').previousElementSibling;
        $.before(topNavPos, $.el('hr'));
        return $.before(topNavPos, Index.navLinks);
      });
      return $.asap((function() {
        return $('.pagelist', doc) || d.readyState !== 'loading';
      }), function() {
        var pagelist;
        if (pagelist = $('.pagelist')) {
          $.replace(pagelist, Index.pagelist);
        } else {
          $.after($.id('delform'), Index.pagelist);
        }
        return $.rmClass(doc, 'index-loading');
      });
    },
    scroll: function() {
      var nodes, pageNum;
      if (Index.req || Conf['Index Mode'] !== 'infinite' || (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight))) {
        return;
      }
      if (Index.pageNum == null) {
        Index.pageNum = Index.getCurrentPage();
      }
      pageNum = ++Index.pageNum;
      if (pageNum > Index.pagesNum) {
        return Index.endNotice();
      }
      nodes = Index.buildSinglePage(pageNum);
      if (Conf['Show Replies']) {
        Index.buildReplies(nodes);
      }
      Index.buildStructure(nodes);
      return Index.setPage(pageNum);
    },
    endNotice: (function() {
      var notify, reset;
      notify = false;
      reset = function() {
        return notify = false;
      };
      return function() {
        if (notify) {
          return;
        }
        notify = true;
        new Notice('info', "Last page reached.", 2);
        return setTimeout(reset, 3 * $.SECOND);
      };
    })(),
    cb: {
      mode: function() {
        Index.togglePagelist();
        return Index.buildIndex();
      },
      sort: function() {
        Index.sort();
        return Index.buildIndex();
      },
      replies: function() {
        Index.buildThreads();
        Index.sort();
        return Index.buildIndex();
      },
      popstate: function(e) {
        var pageNum;
        pageNum = Index.getCurrentPage();
        if (Index.currentPage !== pageNum) {
          return Index.pageLoad(pageNum);
        }
      },
      pageNav: function(e) {
        var a;
        if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
          return;
        }
        switch (e.target.nodeName) {
          case 'BUTTON':
            e.target.blur();
            a = e.target.parentNode;
            break;
          case 'A':
            a = e.target;
            break;
          default:
            return;
        }
        if (a.textContent === 'Catalog') {
          return;
        }
        e.preventDefault();
        return Index.userPageNav(+a.pathname.split('/')[2] || 1);
      }
    },
    scrollToIndex: function() {
      return Header.scrollToIfNeeded(Index.root);
    },
    getCurrentPage: function() {
      return +window.location.pathname.split('/')[2] || 1;
    },
    userPageNav: function(pageNum) {
      history.pushState(null, '', pageNum === 1 ? './' : pageNum);
      if (Conf['Refreshed Navigation'] && Conf['Index Mode'] !== 'all pages') {
        return Index.update(pageNum);
      } else {
        if (Index.currentPage === pageNum) {
          return;
        }
        return Index.pageLoad(pageNum);
      }
    },
    pageLoad: function(pageNum) {
      Index.currentPage = pageNum;
      if (Conf['Index Mode'] === 'all pages') {
        return;
      }
      Index.buildIndex();
      Index.setPage();
      return Index.scrollToIndex();
    },
    getPagesNum: function() {
      if (Index.isSearching) {
        return Math.ceil(Index.sortedNodes.length / Index.threadsNumPerPage);
      } else {
        return Index.pagesNum;
      }
    },
    getMaxPageNum: function() {
      return Math.max(1, Index.getPagesNum());
    },
    togglePagelist: function() {
      return Index.pagelist.hidden = Conf['Index Mode'] !== 'paged';
    },
    buildPagelist: function() {
      var a, i, maxPageNum, nodes, pagesRoot, _i;
      pagesRoot = $('.pages', Index.pagelist);
      maxPageNum = Index.getMaxPageNum();
      if (pagesRoot.childElementCount !== maxPageNum) {
        nodes = [];
        for (i = _i = 1; _i <= maxPageNum; i = _i += 1) {
          a = $.el('a', {
            textContent: i,
            href: i === 1 ? './' : i
          });
          nodes.push($.tn('['), a, $.tn('] '));
        }
        $.rmAll(pagesRoot);
        $.add(pagesRoot, nodes);
      }
      return Index.togglePagelist();
    },
    setPage: function(pageNum) {
      var a, href, maxPageNum, next, pagesRoot, prev, strong;
      pageNum || (pageNum = Index.getCurrentPage());
      maxPageNum = Index.getMaxPageNum();
      pagesRoot = $('.pages', Index.pagelist);
      prev = pagesRoot.previousSibling.firstChild;
      next = pagesRoot.nextSibling.firstChild;
      href = Math.max(pageNum - 1, 1);
      prev.href = href === 1 ? './' : href;
      prev.firstChild.disabled = href === pageNum;
      href = Math.min(pageNum + 1, maxPageNum);
      next.href = href === 1 ? './' : href;
      next.firstChild.disabled = href === pageNum;
      if (strong = $('strong', pagesRoot)) {
        if (+strong.textContent === pageNum) {
          return;
        }
        $.replace(strong, strong.firstChild);
      } else {
        strong = $.el('strong');
      }
      a = pagesRoot.children[pageNum - 1];
      $.before(a, strong);
      return $.add(strong, a);
    },
    update: function(pageNum, forceReparse) {
      var now, onload, _ref, _ref1;
      if (!navigator.onLine) {
        return;
      }
      delete Index.pageNum;
      if ((_ref = Index.req) != null) {
        _ref.abort();
      }
      if ((_ref1 = Index.notice) != null) {
        _ref1.close();
      }
      now = Date.now();
      $.ready(function() {
        return Index.nTimeout = setTimeout((function() {
          if (Index.req && !Index.notice) {
            return Index.notice = new Notice('info', 'Refreshing index...', 2);
          }
        }), 3 * $.SECOND - (Date.now() - now));
      });
      if (typeof pageNum !== 'number') {
        pageNum = null;
      }
      onload = function(e) {
        return Index.load(e, pageNum);
      };
      Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", {
        onabort: onload,
        onloadend: onload
      }, {
        whenModified: !forceReparse
      });
      return $.addClass(Index.button, 'fa-spin');
    },
    load: function(e, pageNum) {
      var err, nTimeout, notice, req, timeEl, _ref;
      $.rmClass(Index.button, 'fa-spin');
      req = Index.req, notice = Index.notice, nTimeout = Index.nTimeout;
      if (nTimeout) {
        clearTimeout(nTimeout);
      }
      delete Index.nTimeout;
      delete Index.req;
      delete Index.notice;
      if (e.type === 'abort') {
        req.onloadend = null;
        notice.close();
        return;
      }
      if ((_ref = req.status) !== 200 && _ref !== 304) {
        err = "Index refresh failed. Error " + req.statusText + " (" + req.status + ")";
        if (notice) {
          notice.setType('warning');
          notice.el.lastElementChild.textContent = err;
          setTimeout(notice.close, $.SECOND);
        } else {
          new Notice('warning', err, 1);
        }
        return;
      }
      try {
        if (req.status === 200) {
          Index.parse(req.response, pageNum);
        } else if (req.status === 304 && (pageNum != null)) {
          Index.pageLoad(pageNum);
        }
      } catch (_error) {
        err = _error;
        c.error("Index failure: " + err.message, err.stack);
        if (notice) {
          notice.setType('error');
          notice.el.lastElementChild.textContent = 'Index refresh failed.';
          setTimeout(notice.close, $.SECOND);
        } else {
          new Notice('error', 'Index refresh failed.', 1);
        }
        return;
      }
      timeEl = $('#index-last-refresh time', Index.navLinks);
      timeEl.dataset.utc = Date.parse(req.getResponseHeader('Last-Modified'));
      RelativeDates.update(timeEl);
      return Index.scrollToIndex();
    },
    parse: function(pages, pageNum) {
      $.cleanCache(function(url) {
        return /^\/\/a\.4cdn\.org\//.test(url);
      });
      Index.parseThreadList(pages);
      Index.buildThreads();
      Index.sort();
      Index.buildPagelist();
      if (pageNum != null) {
        Index.pageLoad(pageNum);
        return;
      }
      Index.buildIndex();
      return Index.setPage();
    },
    parseThreadList: function(pages) {
      Index.pagesNum = pages.length;
      Index.threadsNumPerPage = pages[0].threads.length;
      Index.liveThreadData = pages.reduce((function(arr, next) {
        return arr.concat(next.threads);
      }), []);
      Index.liveThreadIDs = Index.liveThreadData.map(function(data) {
        return data.no;
      });
      g.BOARD.threads.forEach(function(thread) {
        var _ref;
        if (_ref = thread.ID, __indexOf.call(Index.liveThreadIDs, _ref) < 0) {
          return thread.collect();
        }
      });
    },
    buildThreads: function() {
      var err, errors, i, posts, thread, threadData, threadRoot, threads, _i, _len, _ref;
      Index.nodes = [];
      threads = [];
      posts = [];
      _ref = Index.liveThreadData;
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        threadData = _ref[i];
        try {
          threadRoot = Build.thread(g.BOARD, threadData);
          if (thread = g.BOARD.threads[threadData.no]) {
            thread.setStatus('Sticky', !!threadData.sticky);
            thread.setStatus('Closed', !!threadData.closed);
          } else {
            thread = new Thread(threadData.no, g.BOARD);
            threads.push(thread);
          }
          Index.nodes.push(threadRoot);
          if (!(thread.ID in thread.posts)) {
            posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD));
          }
          thread.setPage(Math.floor(i / Index.threadsNumPerPage) + 1);
        } catch (_error) {
          err = _error;
          if (!errors) {
            errors = [];
          }
          errors.push({
            message: "Parsing of Thread No." + thread + " failed. Thread will be skipped.",
            error: err
          });
        }
      }
      if (errors) {
        Main.handleErrors(errors);
      }
      $.nodes(Index.nodes);
      Main.callbackNodes(Thread, threads);
      Main.callbackNodes(Post, posts);
      return $.event('IndexRefresh');
    },
    buildReplies: function(threadRoots) {
      var data, err, errors, i, lastReplies, node, nodes, post, posts, thread, threadRoot, _i, _j, _len, _len1;
      posts = [];
      for (_i = 0, _len = threadRoots.length; _i < _len; _i++) {
        threadRoot = threadRoots[_i];
        thread = Get.threadFromRoot(threadRoot);
        i = Index.liveThreadIDs.indexOf(thread.ID);
        if (!(lastReplies = Index.liveThreadData[i].last_replies)) {
          continue;
        }
        nodes = [];
        for (_j = 0, _len1 = lastReplies.length; _j < _len1; _j++) {
          data = lastReplies[_j];
          if (post = thread.posts[data.no]) {
            nodes.push(post.nodes.root);
            continue;
          }
          nodes.push(node = Build.postFromObject(data, thread.board.ID));
          try {
            posts.push(new Post(node, thread, thread.board));
          } catch (_error) {
            err = _error;
            if (!errors) {
              errors = [];
            }
            errors.push({
              message: "Parsing of Post No." + data.no + " failed. Post will be skipped.",
              error: err
            });
          }
        }
        $.add(threadRoot, nodes);
      }
      if (errors) {
        Main.handleErrors(errors);
      }
      return Main.callbackNodes(Post, posts);
    },
    sort: function() {
      var liveThreadData, liveThreadIDs, nodes, sortedNodes, sortedThreadIDs, threadID, _i, _len;
      liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData;
      sortedThreadIDs = {
        lastreply: __slice.call(liveThreadData).sort(function(a, b) {
          var num;
          if ((num = a.last_replies)) {
            a = num[num.length - 1];
          }
          if ((num = b.last_replies)) {
            b = num[num.length - 1];
          }
          return b.no - a.no;
        }).map(function(post) {
          return post.no;
        }),
        bump: liveThreadIDs,
        birth: __slice.call(liveThreadIDs).sort(function(a, b) {
          return b - a;
        }),
        replycount: __slice.call(liveThreadData).sort(function(a, b) {
          return b.replies - a.replies;
        }).map(function(post) {
          return post.no;
        }),
        filecount: __slice.call(liveThreadData).sort(function(a, b) {
          return b.images - a.images;
        }).map(function(post) {
          return post.no;
        })
      }[Conf['Index Sort']];
      Index.sortedNodes = sortedNodes = [];
      nodes = Index.nodes;
      for (_i = 0, _len = sortedThreadIDs.length; _i < _len; _i++) {
        threadID = sortedThreadIDs[_i];
        sortedNodes.push(nodes[Index.liveThreadIDs.indexOf(threadID)]);
      }
      if (Index.isSearching && (nodes = Index.querySearch(Index.searchInput.value))) {
        Index.sortedNodes = nodes;
      }
      Index.sortOnTop(function(thread) {
        return thread.isSticky;
      });
      if (Conf['Filter']) {
        Index.sortOnTop(function(thread) {
          return thread.isOnTop;
        });
      }
      if (Conf['Anchor Hidden Threads']) {
        return Index.sortOnTop(function(thread) {
          return !thread.isHidden;
        });
      }
    },
    sortOnTop: function(match) {
      var bottomNodes, threadRoot, topNodes, _i, _len, _ref;
      topNodes = [];
      bottomNodes = [];
      _ref = Index.sortedNodes;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        threadRoot = _ref[_i];
        (match(Get.threadFromRoot(threadRoot)) ? topNodes : bottomNodes).push(threadRoot);
      }
      return Index.sortedNodes = topNodes.concat(bottomNodes);
    },
    buildIndex: function() {
      var nodes;
      if (Conf['Index Mode'] !== 'all pages') {
        nodes = Index.buildSinglePage(Index.getCurrentPage());
      } else {
        nodes = Index.sortedNodes;
      }
      $.rmAll(Index.root);
      $.rmAll(Header.hover);
      if (Conf['Show Replies']) {
        Index.buildReplies(nodes);
      }
      return Index.buildStructure(nodes);
    },
    buildSinglePage: function(pageNum) {
      var nodesPerPage, offset;
      nodesPerPage = Index.threadsNumPerPage;
      offset = nodesPerPage * (pageNum - 1);
      return Index.sortedNodes.slice(offset, offset + nodesPerPage);
    },
    buildStructure: function(nodes) {
      var node, _i, _len;
      for (_i = 0, _len = nodes.length; _i < _len; _i++) {
        node = nodes[_i];
        $.add(Index.root, [node, $.el('hr')]);
      }
      if (doc.contains(Index.root)) {
        $.event('PostsInserted');
      }
      return ThreadHiding.onIndexBuild(nodes);
    },
    isSearching: false,
    clearSearch: function() {
      Index.searchInput.value = null;
      Index.onSearchInput();
      return Index.searchInput.focus();
    },
    onSearchInput: function() {
      var pageNum;
      if (Index.isSearching = !!Index.searchInput.value.trim()) {
        if (!Index.searchInput.dataset.searching) {
          Index.searchInput.dataset.searching = 1;
          Index.pageBeforeSearch = Index.getCurrentPage();
          pageNum = 1;
        } else {
          pageNum = Index.getCurrentPage();
        }
      } else {
        if (!Index.searchInput.dataset.searching) {
          return;
        }
        pageNum = Index.pageBeforeSearch;
        delete Index.pageBeforeSearch;
        Index.searchInput.removeAttribute('data-searching');
      }
      Index.sort();
      if (Conf['Index Mode'] !== 'all pages') {
        pageNum = Math.min(pageNum, Index.getMaxPageNum());
      }
      Index.buildPagelist();
      if (Index.currentPage === pageNum) {
        Index.buildIndex();
        return Index.setPage();
      } else {
        history.pushState(null, '', pageNum === 1 ? './' : pageNum);
        return Index.pageLoad(pageNum);
      }
    },
    querySearch: function(query) {
      var keywords;
      if (!(keywords = query.toLowerCase().match(/\S+/g))) {
        return;
      }
      return Index.search(keywords);
    },
    search: function(keywords) {
      return Index.sortedNodes.filter(function(threadRoot) {
        return Index.searchMatch(Get.threadFromRoot(threadRoot), keywords);
      });
    },
    searchMatch: function(thread, keywords) {
      var file, info, key, keyword, text, _i, _j, _len, _len1, _ref, _ref1;
      _ref = thread.OP, info = _ref.info, file = _ref.file;
      text = [];
      _ref1 = ['comment', 'subject', 'name', 'tripcode', 'email'];
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        key = _ref1[_i];
        if (key in info) {
          text.push(info[key]);
        }
      }
      if (file) {
        text.push(file.name);
      }
      text = text.join(' ').toLowerCase();
      for (_j = 0, _len1 = keywords.length; _j < _len1; _j++) {
        keyword = keywords[_j];
        if (-1 === text.indexOf(keyword)) {
          return false;
        }
      }
      return true;
    }
  };

  Build = {
    initPixelRatio: window.devicePixelRatio,
    spoilerRange: {},
    unescape: function(text) {
      if (text == null) {
        return text;
      }
      return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt);/g, function(c) {
        return {
          '&amp;': '&',
          '&#039;': "'",
          '&quot;': '"',
          '&lt;': '<',
          '&gt;': '>'
        }[c];
      });
    },
    shortFilename: function(filename, isReply) {
      var ext, threshold;
      threshold = 30;
      ext = filename.match(/\.?[^\.]*$/)[0];
      if (filename.length - ext.length > threshold) {
        return "" + filename.slice(0, threshold - 5) + "(...)" + ext;
      } else {
        return filename;
      }
    },
    thumbRotate: (function() {
      var n;
      n = 0;
      return function() {
        return n = (n + 1) % 2;
      };
    })(),
    sameThread: function(boardID, threadID) {
      return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID;
    },
    postURL: function(boardID, threadID, postID) {
      if (Build.sameThread(boardID, threadID)) {
        return "\#p" + postID;
      } else {
        return "/" + boardID + "/thread/" + threadID + "\#p" + postID;
      }
    },
    postFromObject: function(data, boardID) {
      var o;
      o = {
        postID: data.no,
        threadID: data.resto || data.no,
        boardID: boardID,
        name: Build.unescape(data.name),
        capcode: data.capcode,
        tripcode: data.trip,
        uniqueID: data.id,
        email: Build.unescape(data.email),
        subject: Build.unescape(data.sub),
        flagCode: data.country,
        flagName: Build.unescape(data.country_name),
        date: data.now,
        dateUTC: data.time,
        comment: {
          innerHTML: data.com || ''
        },
        isSticky: !!data.sticky,
        isClosed: !!data.closed
      };
      if (data.filedeleted) {
        o.file = {
          isDeleted: true
        };
      } else if (data.ext) {
        o.file = {
          name: (Build.unescape(data.filename)) + data.ext,
          timestamp: "" + data.tim + data.ext,
          url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext,
          height: data.h,
          width: data.w,
          MD5: data.md5,
          size: data.fsize,
          turl: "//" + (Build.thumbRotate()) + ".t.4cdn.org/" + boardID + "/" + data.tim + "s.jpg",
          theight: data.tn_h,
          twidth: data.tn_w,
          isSpoiler: !!data.spoiler,
          isDeleted: false,
          tag: data.tag
        };
      }
      return Build.post(o);
    },
    post: function(o) {

      /*
      This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS).
      @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
       */
      var boardID, capcode, capcodeClass, capcodeIcon, capcodeStart, closed, comment, container, date, dateUTC, desktop2, email, emailField, emailProcessed, file, fileBlock, fileCont, fileDims, fileLink, fileSize, fileText, fileThumb, flag, flagCode, flagName, highlightPost, href, isClosed, isOP, isSticky, match, message, name, nameBlock, nameClass, postID, postInfo, postLink, quote, quoteLink, replyLink, retina, shortFilename, spoilerRange, sticky, subject, subjectField, threadID, tripcode, tripcodeField, uniqueID, userID, wholePost, _i, _len, _ref;
      postID = o.postID, threadID = o.threadID, boardID = o.boardID, 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;
      name || (name = '');
      subject || (subject = '');
      isOP = postID === threadID;
      retina = Build.initPixelRatio >= 2 ? '@2x' : '';

      /* Name Block */
      switch (capcode) {
        case 'admin':
        case 'admin_highlight':
          capcodeClass = ' capcodeAdmin';
          capcodeStart = {
            innerHTML: " <strong class=\"capcode hand id_admin\" title=\"Highlight posts by the Administrator\">## Admin</strong>"
          };
          capcodeIcon = {
            innerHTML: "<img src=\"//s.4cdn.org/image/adminicon" + E(retina) + ".gif\" alt=\"Admin Icon\" title=\"This user is the 4chan Administrator.\" class=\"identityIcon retina\">"
          };
          break;
        case 'mod':
          capcodeClass = ' capcodeMod';
          capcodeStart = {
            innerHTML: " <strong class=\"capcode hand id_mod\" title=\"Highlight posts by Moderators\">## Mod</strong>"
          };
          capcodeIcon = {
            innerHTML: "<img src=\"//s.4cdn.org/image/modicon" + E(retina) + ".gif\" alt=\"Mod Icon\" title=\"This user is a 4chan Moderator.\" class=\"identityIcon retina\">"
          };
          break;
        case 'developer':
          capcodeClass = ' capcodeDeveloper';
          capcodeStart = {
            innerHTML: " <strong class=\"capcode hand id_developer\" title=\"Highlight posts by Developers\">## Developer</strong>"
          };
          capcodeIcon = {
            innerHTML: "<img src=\"//s.4cdn.org/image/developericon" + E(retina) + ".gif\" alt=\"Developer Icon\" title=\"This user is a 4chan Developer.\" class=\"identityIcon retina\">"
          };
          break;
        default:
          capcodeClass = '';
          capcodeStart = {
            innerHTML: ""
          };
          capcodeIcon = {
            innerHTML: ""
          };
      }
      nameClass = capcode ? ' capcode' : '';
      tripcodeField = tripcode ? {
        innerHTML: " <span class=\"postertrip\">" + E(tripcode) + "</span>"
      } : {
        innerHTML: ""
      };
      emailField = {
        innerHTML: "<span class=\"name" + E(nameClass) + "\">" + E(name) + "</span>" + tripcodeField.innerHTML + capcodeStart.innerHTML
      };
      if (email) {
        emailProcessed = encodeURIComponent(email).replace(/%40/g, '@');
        emailField = {
          innerHTML: "<a href=\"mailto:" + E(emailProcessed) + "\" class=\"useremail\">" + emailField.innerHTML + "</a>"
        };
      }
      if (!(isOP && boardID === 'f')) {
        emailField = {
          innerHTML: emailField.innerHTML + " "
        };
      }
      userID = !capcode && uniqueID ? {
        innerHTML: " <span class=\"posteruid id_" + E(uniqueID) + "\">(ID: <span class=\"hand\" title=\"Highlight posts by this ID\">" + E(uniqueID) + "</span>)</span>"
      } : {
        innerHTML: ""
      };
      flag = !flagCode ? {
        innerHTML: ""
      } : boardID === 'pol' ? {
        innerHTML: "<img src=\"//s.4cdn.org/image/country/troll/" + E(flagCode.toLowerCase()) + ".gif\" alt=\"" + E(flagCode) + "\" title=\"" + E(flagName) + "\" class=\"countryFlag\">"
      } : {
        innerHTML: "<span title=\"" + E(flagName) + "\" class=\"flag flag-" + E(flagCode.toLowerCase()) + "\"></span>"
      };
      nameBlock = {
        innerHTML: "<span class=\"nameBlock" + E(capcodeClass) + "\">" + emailField.innerHTML + capcodeIcon.innerHTML + userID.innerHTML + flag.innerHTML + "</span> "
      };

      /* Post Info */
      subjectField = isOP || boardID === 'f' ? {
        innerHTML: "<span class=\"subject\">" + E(subject) + "</span> "
      } : {
        innerHTML: ""
      };
      desktop2 = isOP && boardID === 'f' ? '' : ' desktop';
      postLink = Build.postURL(boardID, threadID, postID);
      quoteLink = Build.sameThread(boardID, threadID) ? "javascript:quote('" + (+postID) + "');" : "/" + boardID + "/thread/" + threadID + "\#q" + postID;
      sticky = isSticky ? {
        innerHTML: " <img src=\"//s.4cdn.org/image/sticky" + E(retina) + ".gif\" alt=\"Sticky\" title=\"Sticky\" class=\"stickyIcon retina\">"
      } : {
        innerHTML: ""
      };
      closed = isClosed ? {
        innerHTML: " <img src=\"//s.4cdn.org/image/closed" + E(retina) + ".gif\" alt=\"Closed\" title=\"Closed\" class=\"closedIcon retina\">"
      } : {
        innerHTML: ""
      };
      replyLink = isOP && g.VIEW === 'index' ? {
        innerHTML: " &nbsp; <span>[<a href=\"/" + E(boardID) + "/thread/" + E(threadID) + "\" class=\"replylink\">Reply</a>]</span>"
      } : {
        innerHTML: ""
      };
      postInfo = {
        innerHTML: "<div class=\"postInfo desktop\" id=\"pi" + E(postID) + "\"><input type=\"checkbox\" name=\"" + E(postID) + "\" value=\"delete\"> " + subjectField.innerHTML + nameBlock.innerHTML + "<span class=\"dateTime\" data-utc=\"" + E(dateUTC) + "\">" + E(date) + "</span> <span class=\"postNum" + E(desktop2) + "\"><a href=\"" + E(postLink) + "\" title=\"Link to this post\">No.</a><a href=\"" + E(quoteLink) + "\" title=\"Reply to this post\">" + E(postID) + "</a>" + sticky.innerHTML + closed.innerHTML + replyLink.innerHTML + "</span></div>"
      };

      /* File Info */
      fileCont = (file != null ? file.isDeleted : void 0) ? {
        innerHTML: "<span class=\"fileThumb\"><img src=\"//s.4cdn.org/image/filedeleted-res" + E(retina) + ".gif\" alt=\"File deleted.\" class=\"fileDeletedRes retina\"></span>"
      } : file && boardID === 'f' ? {
        innerHTML: "<div class=\"fileInfo\"><span class=\"fileText\" id=\"fT" + E(postID) + "\">File: <a data-width=\"" + E(file.width) + "\" data-height=\"" + E(file.height) + "\" href=\"" + E(file.url) + "\" target=\"_blank\">" + E(file.name) + "</a>-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ")</span></div>"
      } : file ? (file.isSpoiler ? (shortFilename = 'Spoiler Image', (spoilerRange = Build.spoilerRange[boardID]) ? fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png" : fileThumb = '//s.4cdn.org/image/spoiler.png', file.twidth = file.theight = 100) : (shortFilename = Build.shortFilename(file.name, !isOP), fileThumb = file.turl), fileSize = $.bytesToString(file.size), fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height, fileLink = file.isSpoiler || file.name === shortFilename ? {
        innerHTML: "<a href=\"" + E(file.url) + "\" target=\"_blank\">" + E(shortFilename) + "</a>"
      } : {
        innerHTML: "<a title=\"" + E(file.name) + "\" href=\"" + E(file.url) + "\" target=\"_blank\">" + E(shortFilename) + "</a>"
      }, fileText = file.isSpoiler ? {
        innerHTML: "<div class=\"fileText\" id=\"fT" + E(postID) + "\" title=\"" + E(file.name) + "\">File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")</div>"
      } : {
        innerHTML: "<div class=\"fileText\" id=\"fT" + E(postID) + "\">File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")</div>"
      }, {
        innerHTML: fileText.innerHTML + "<a class=\"fileThumb" + E(file.isSpoiler ? " imgspoiler" : "") + "\" href=\"" + E(file.url) + "\" target=\"_blank\"><img src=\"" + E(fileThumb) + "\" alt=\"" + E(fileSize) + "\" data-md5=\"" + E(file.MD5) + "\" style=\"height: " + E(file.theight) + "px; width: " + E(file.twidth) + "px;\"></a>"
      }) : void 0;
      fileBlock = file ? {
        innerHTML: "<div class=\"file\" id=\"f" + E(postID) + "\">" + fileCont.innerHTML + "</div>"
      } : {
        innerHTML: ""
      };

      /* Whole Post */
      highlightPost = capcode === 'admin_highlight' ? ' highlightPost' : '';
      message = {
        innerHTML: "<blockquote class=\"postMessage\" id=\"m" + E(postID) + "\">" + comment.innerHTML + "</blockquote>"
      };
      wholePost = isOP ? {
        innerHTML: "<div id=\"p" + E(postID) + "\" class=\"post op" + E(highlightPost) + "\">" + fileBlock.innerHTML + postInfo.innerHTML + message.innerHTML + "</div>"
      } : {
        innerHTML: "<div class=\"sideArrows\" id=\"sa" + E(postID) + "\">&gt;&gt;</div><div id=\"p" + E(postID) + "\" class=\"post reply" + E(highlightPost) + "\">" + postInfo.innerHTML + fileBlock.innerHTML + message.innerHTML + "</div>"
      };
      container = $.el('div', {
        className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
        id: "pc" + postID
      });
      $.extend(container, wholePost);
      _ref = $$('.quotelink', container);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quote = _ref[_i];
        href = quote.getAttribute('href');
        if ((href[0] === '#') && !(Build.sameThread(boardID, threadID))) {
          quote.href = ("/" + boardID + "/thread/" + threadID) + href;
        } else if ((match = href.match(/^\/([^\/]+)\/thread\/(\d+)/)) && (Build.sameThread(match[1], match[2]))) {
          quote.href = href.match(/(#[^#]*)?$/)[0] || '#';
        }
      }
      return container;
    },
    summary: function(boardID, threadID, posts, files) {
      var text;
      text = [];
      text.push("" + posts + " post" + (posts > 1 ? 's' : ''));
      if (files) {
        text.push("and " + files + " image repl" + (files > 1 ? 'ies' : 'y'));
      }
      text.push('omitted.');
      return $.el('a', {
        className: 'summary',
        textContent: text.join(' '),
        href: "/" + boardID + "/thread/" + threadID
      });
    },
    thread: function(board, data, full) {
      var OP, root;
      Build.spoilerRange[board] = data.custom_spoiler;
      if ((OP = board.posts[data.no]) && (root = OP.nodes.root.parentNode)) {
        $.rmAll(root);
      } else {
        root = $.el('div', {
          className: 'thread',
          id: "t" + data.no
        });
      }
      $.add(root, Build[full ? 'fullThread' : 'excerptThread'](board, data, OP));
      return root;
    },
    excerptThread: function(board, data, OP) {
      var files, nodes, posts, _ref;
      nodes = [OP ? OP.nodes.root : Build.postFromObject(data, board.ID)];
      if (data.omitted_posts || !Conf['Show Replies'] && data.replies) {
        _ref = Conf['Show Replies'] ? [data.omitted_posts, data.omitted_images] : [
          data.replies, data.omitted_images + data.last_replies.filter(function(data) {
            return !!data.ext;
          }).length
        ], posts = _ref[0], files = _ref[1];
        nodes.push(Build.summary(board.ID, data.no, posts, files));
      }
      return nodes;
    },
    fullThread: function(board, data) {
      return Build.postFromObject(data, board.ID);
    }
  };

  Get = {
    threadExcerpt: function(thread) {
      var OP, excerpt, _ref;
      OP = thread.OP;
      excerpt = ("/" + thread.board + "/ - ") + (((_ref = OP.info.subject) != null ? _ref.trim() : void 0) || OP.info.comment.replace(/\n+/g, ' // ') || Conf['Anonymize'] && 'Anonymous' || $('.nameBlock', OP.nodes.info).textContent.trim());
      if (excerpt.length > 73) {
        return "" + excerpt.slice(0, 70) + "...";
      }
      return excerpt;
    },
    threadFromRoot: function(root) {
      return g.threads["" + g.BOARD + "." + root.id.slice(1)];
    },
    threadFromNode: function(node) {
      return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node));
    },
    postFromRoot: function(root) {
      var boardID, index, link, post, postID;
      link = $('a[title="Link to this post"]', root);
      boardID = link.pathname.split('/')[1];
      postID = link.hash.slice(2);
      index = root.dataset.clone;
      post = g.posts["" + boardID + "." + postID];
      if (index) {
        return post.clones[index];
      } else {
        return post;
      }
    },
    postFromNode: function(root) {
      return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root));
    },
    contextFromNode: function(node) {
      return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', node));
    },
    postDataFromLink: function(link) {
      var boardID, path, postID, threadID, _ref;
      if (link.hostname === 'boards.4chan.org') {
        path = link.pathname.split('/');
        boardID = path[1];
        threadID = path[3];
        postID = link.hash.slice(2);
      } else {
        _ref = link.dataset, boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID;
        threadID || (threadID = 0);
      }
      return {
        boardID: boardID,
        threadID: +threadID,
        postID: +postID
      };
    },
    allQuotelinksLinkingTo: function(post) {
      var fullID, handleQuotes, posts, qPost, quote, quotelinks, _i, _len, _ref;
      quotelinks = [];
      posts = g.posts;
      fullID = {
        post: post
      };
      handleQuotes = function(qPost, type) {
        var clone, _i, _len, _ref;
        quotelinks.push.apply(quotelinks, qPost.nodes[type]);
        _ref = qPost.clones;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          clone = _ref[_i];
          quotelinks.push.apply(quotelinks, clone.nodes[type]);
        }
      };
      posts.forEach(function(qPost) {
        if (__indexOf.call(qPost.quotes, fullID) >= 0) {
          return handleQuotes(qPost, 'quotelinks');
        }
      });
      if (Conf['Quote Backlinks']) {
        _ref = post.quotes;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          quote = _ref[_i];
          if (qPost = posts[quote]) {
            handleQuotes(qPost, 'backlinks');
          }
        }
      }
      return quotelinks.filter(function(quotelink) {
        var boardID, postID, _ref1;
        _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID;
        return boardID === post.board.ID && postID === post.ID;
      });
    },
    postClone: function(boardID, threadID, postID, root, context) {
      var post;
      if (post = g.posts["" + boardID + "." + postID]) {
        Get.insert(post, root, context);
        return;
      }
      root.textContent = "Loading post No." + postID + "...";
      if (threadID) {
        return $.cache("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", function() {
          return Get.fetchedPost(this, boardID, threadID, postID, root, context);
        });
      } else {
        return Get.archivedPost(boardID, postID, root, context);
      }
    },
    insert: function(post, root, context) {
      var clone, nodes;
      if (!root.parentNode) {
        return;
      }
      clone = post.addClone(context, $.hasClass(root, 'dialog'));
      Main.callbackNodes(Clone, [clone]);
      nodes = clone.nodes;
      $.rmAll(nodes.root);
      $.add(nodes.root, nodes.post);
      $.rmAll(root);
      $.add(root, nodes.root);
      return $.event('PostsInserted');
    },
    fetchedPost: function(req, boardID, threadID, postID, root, context) {
      var api, board, post, posts, status, thread, _i, _len;
      if (post = g.posts["" + boardID + "." + postID]) {
        Get.insert(post, root, context);
        return;
      }
      status = req.status;
      if (status !== 200 && status !== 304) {
        if (!Get.archivedPost(boardID, postID, root, context)) {
          $.addClass(root, 'warning');
          root.textContent = status === 404 ? "Thread No." + threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ").";
        }
        return;
      }
      posts = req.response.posts;
      Build.spoilerRange[boardID] = posts[0].custom_spoiler;
      for (_i = 0, _len = posts.length; _i < _len; _i++) {
        post = posts[_i];
        if (post.no === postID) {
          break;
        }
      }
      if (post.no !== postID) {
        if (req.cached) {
          api = "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json";
          $.cleanCache(function(url) {
            return url === api;
          });
          $.cache(api, function() {
            return Get.fetchedPost(this, boardID, threadID, postID, root, context);
          });
          return;
        }
        if (!Get.archivedPost(boardID, postID, root, context)) {
          $.addClass(root, 'warning');
          root.textContent = "Post No." + postID + " was not found.";
        }
        return;
      }
      board = g.boards[boardID] || new Board(boardID);
      thread = g.threads["" + boardID + "." + threadID] || new Thread(threadID, board);
      post = new Post(Build.postFromObject(post, boardID), thread, board);
      post.isFetchedQuote = true;
      Main.callbackNodes(Post, [post]);
      return Get.insert(post, root, context);
    },
    archivedPost: function(boardID, postID, root, context) {
      var url;
      if (!Conf['Resurrect Quotes']) {
        return false;
      }
      if (!(url = Redirect.to('post', {
        boardID: boardID,
        postID: postID
      }))) {
        return false;
      }
      if (/^https:\/\//.test(url) || location.protocol === 'http:') {
        $.cache(url, function() {
          return Get.parseArchivedPost(this.response, boardID, postID, root, context);
        }, {
          responseType: 'json',
          withCredentials: url.archive.withCredentials
        });
        return true;
      } else if (Conf['Except Archives from Encryption']) {
        CrossOrigin.json(url, function(response) {
          var key, media, _ref;
          media = response.media;
          if (media) {
            for (key in media) {
              if (/_link$/.test(key)) {
                if (_ref = media[key].match(/^(http:\/\/[^\/]+\/)?/)[0], __indexOf.call(url.archive.imagehosts, _ref) < 0) {
                  delete media[key];
                }
              }
            }
          }
          return Get.parseArchivedPost(response, boardID, postID, root, context);
        });
        return true;
      }
      return false;
    },
    parseArchivedPost: function(data, boardID, postID, root, context) {
      var board, comment, greentext, i, j, o, post, text, text2, thread, threadID, _ref;
      if (post = g.posts["" + boardID + "." + postID]) {
        Get.insert(post, root, context);
        return;
      }
      if (data.error) {
        $.addClass(root, 'warning');
        root.textContent = data.error;
        return;
      }
      comment = (data.comment || '').split(/(\n|\[\/?(?:b|spoiler|code|moot|banned)\])/);
      comment = (function() {
        var _i, _len, _results;
        _results = [];
        for (i = _i = 0, _len = comment.length; _i < _len; i = ++_i) {
          text = comment[i];
          if (i % 2 === 1) {
            _results.push(Get.archiveTags[text]);
          } else {
            greentext = text[0] === '>';
            text = text.replace(/(\[\/?[a-z]+):lit(\])/, '$1$2');
            text = (function() {
              var _j, _len1, _ref, _results1;
              _ref = text.split(/(>>(?:>\/[a-z\d]+\/)?\d+)/g);
              _results1 = [];
              for (j = _j = 0, _len1 = _ref.length; _j < _len1; j = ++_j) {
                text2 = _ref[j];
                if (j % 2 === 1) {
                  _results1.push({
                    innerHTML: "<span class=\"deadlink\">" + E(text2) + "</span>"
                  });
                } else {
                  _results1.push({
                    innerHTML: E(text2)
                  });
                }
              }
              return _results1;
            })();
            text = {
              innerHTML: text.map(function(x) {
                return x.innerHTML;
              }).join('')
            };
            if (greentext) {
              text = {
                innerHTML: "<span class=\"quote\">" + text.innerHTML + "</span>"
              };
            }
            _results.push(text);
          }
        }
        return _results;
      })();
      comment = {
        innerHTML: comment.map(function(x) {
          return x.innerHTML;
        }).join('')
      };
      threadID = +data.thread_num;
      o = {
        postID: postID,
        threadID: threadID,
        boardID: boardID,
        name: data.name,
        capcode: (function() {
          switch (data.capcode) {
            case 'M':
              return 'mod';
            case 'A':
              return 'admin';
            case 'D':
              return 'developer';
          }
        })(),
        tripcode: data.trip,
        uniqueID: data.poster_hash,
        email: data.email || '',
        subject: data.title,
        flagCode: data.poster_country,
        flagName: data.poster_country_name,
        date: data.fourchan_date,
        dateUTC: data.timestamp,
        comment: comment
      };
      if ((_ref = data.media) != null ? _ref.media_filename : void 0) {
        o.file = {
          name: data.media.media_filename,
          timestamp: data.media.media_orig,
          url: data.media.media_link || data.media.remote_media_link || ("//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.media[boardID === 'f' ? 'media_filename' : 'media_orig']))),
          height: data.media.media_h,
          width: data.media.media_w,
          MD5: data.media.media_hash,
          size: data.media.media_size,
          turl: data.media.thumb_link || ("//t.4cdn.org/" + boardID + "/" + data.media.preview_orig),
          theight: data.media.preview_h,
          twidth: data.media.preview_w,
          isSpoiler: data.media.spoiler === '1'
        };
        if (boardID === 'f') {
          o.file.tag = JSON.parse(data.media.exif).Tag;
        }
      }
      board = g.boards[boardID] || new Board(boardID);
      thread = g.threads["" + boardID + "." + threadID] || new Thread(threadID, board);
      post = new Post(Build.post(o), thread, board, {
        isArchived: true
      });
      if (post.file) {
        post.file.thumbURL = o.file.turl;
      }
      post.isFetchedQuote = true;
      Main.callbackNodes(Post, [post]);
      return Get.insert(post, root, context);
    },
    archiveTags: {
      '\n': {
        innerHTML: "<br>"
      },
      '[b]': {
        innerHTML: "<b>"
      },
      '[/b]': {
        innerHTML: "</b>"
      },
      '[spoiler]': {
        innerHTML: "<s>"
      },
      '[/spoiler]': {
        innerHTML: "</s>"
      },
      '[code]': {
        innerHTML: "<pre class=\"prettyprint\">"
      },
      '[/code]': {
        innerHTML: "</pre>"
      },
      '[moot]': {
        innerHTML: "<div style=\"padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px\">"
      },
      '[/moot]': {
        innerHTML: "</div>"
      },
      '[banned]': {
        innerHTML: "<strong style=\"color: red;\">"
      },
      '[/banned]': {
        innerHTML: "</strong>"
      }
    }
  };

  UI = (function() {
    var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
    dialog = function(id, position, properties) {
      var child, el, move, _i, _len, _ref;
      el = $.el('div', {
        className: 'dialog',
        id: id
      });
      $.extend(el, properties);
      el.style.cssText = position;
      $.get("" + id + ".position", position, function(item) {
        return el.style.cssText = item["" + id + ".position"];
      });
      move = $('.move', el);
      $.on(move, 'touchstart mousedown', dragstart);
      _ref = move.children;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        child = _ref[_i];
        if (!child.tagName) {
          continue;
        }
        $.on(child, 'touchstart mousedown', function(e) {
          return e.stopPropagation();
        });
      }
      return el;
    };
    Menu = (function() {
      var currentMenu, lastToggledButton;

      currentMenu = null;

      lastToggledButton = null;

      function Menu(type) {
        this.type = type;
        this.addEntry = __bind(this.addEntry, this);
        this.onFocus = __bind(this.onFocus, this);
        this.keybinds = __bind(this.keybinds, this);
        this.close = __bind(this.close, this);
        $.on(d, 'AddMenuEntry', (function(_this) {
          return function(_arg) {
            var detail;
            detail = _arg.detail;
            if (detail.type !== _this.type) {
              return;
            }
            delete detail.open;
            return _this.addEntry(detail);
          };
        })(this));
        this.entries = [];
      }

      Menu.prototype.makeMenu = function() {
        var menu;
        menu = $.el('div', {
          className: 'dialog',
          id: 'menu',
          tabIndex: 0
        });
        $.on(menu, 'click', function(e) {
          return e.stopPropagation();
        });
        $.on(menu, 'keydown', this.keybinds);
        return menu;
      };

      Menu.prototype.toggle = function(e, button, data) {
        var previousButton;
        e.preventDefault();
        e.stopPropagation();
        if (currentMenu) {
          previousButton = lastToggledButton;
          this.close();
          if (previousButton === button) {
            return;
          }
        }
        if (!this.entries.length) {
          return;
        }
        return this.open(button, data);
      };

      Menu.prototype.open = function(button, data) {
        var bLeft, bRect, bTop, bottom, cHeight, cWidth, entry, left, mRect, menu, right, style, top, _i, _len, _ref, _ref1, _ref2;
        menu = this.makeMenu();
        currentMenu = menu;
        lastToggledButton = button;
        this.entries.sort(function(first, second) {
          return first.order - second.order;
        });
        _ref = this.entries;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          entry = _ref[_i];
          this.insertEntry(entry, menu, data);
        }
        $.addClass(lastToggledButton, 'active');
        $.on(d, 'click', this.close);
        $.on(d, 'CloseMenu', this.close);
        $.add(button, menu);
        mRect = menu.getBoundingClientRect();
        bRect = button.getBoundingClientRect();
        bTop = window.scrollY + bRect.top;
        bLeft = window.scrollX + bRect.left;
        cHeight = doc.clientHeight;
        cWidth = doc.clientWidth;
        _ref1 = bRect.top + bRect.height + mRect.height < cHeight ? [bRect.bottom, null] : [null, cHeight - bRect.top], top = _ref1[0], bottom = _ref1[1];
        _ref2 = bRect.left + mRect.width < cWidth ? [bRect.left, null] : [null, cWidth - bRect.right], left = _ref2[0], right = _ref2[1];
        style = menu.style;
        style.top = "" + top + "px";
        style.right = "" + right + "px";
        style.bottom = "" + bottom + "px";
        style.left = "" + left + "px";
        if (right) {
          $.addClass(menu, 'left');
        }
        entry = $('.entry', menu);
        this.focus(entry);
        return menu.focus();
      };

      Menu.prototype.insertEntry = function(entry, parent, data) {
        var subEntry, submenu, _i, _len, _ref;
        if (typeof entry.open === 'function') {
          if (!entry.open(data)) {
            return;
          }
        }
        $.add(parent, entry.el);
        if (!entry.subEntries) {
          return;
        }
        if (submenu = $('.submenu', entry.el)) {
          $.rm(submenu);
        }
        submenu = $.el('div', {
          className: 'dialog submenu'
        });
        _ref = entry.subEntries;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          subEntry = _ref[_i];
          this.insertEntry(subEntry, submenu, data);
        }
        $.add(entry.el, submenu);
      };

      Menu.prototype.close = function() {
        $.rm(currentMenu);
        $.rmClass(lastToggledButton, 'active');
        currentMenu = null;
        lastToggledButton = null;
        return $.off(d, 'click CloseMenu', this.close);
      };

      Menu.prototype.findNextEntry = function(entry, direction) {
        var entries;
        entries = __slice.call(entry.parentNode.children);
        entries.sort(function(first, second) {
          return first.style.order - second.style.order;
        });
        return entries[entries.indexOf(entry) + direction];
      };

      Menu.prototype.keybinds = function(e) {
        var entry, next, nextPrev, subEntry, submenu;
        entry = $('.focused', currentMenu);
        while (subEntry = $('.focused', entry)) {
          entry = subEntry;
        }
        switch (e.keyCode) {
          case 27:
            lastToggledButton.focus();
            this.close();
            break;
          case 13:
          case 32:
            entry.click();
            break;
          case 38:
            if (next = this.findNextEntry(entry, -1)) {
              this.focus(next);
            }
            break;
          case 40:
            if (next = this.findNextEntry(entry, +1)) {
              this.focus(next);
            }
            break;
          case 39:
            if ((submenu = $('.submenu', entry)) && (next = submenu.firstElementChild)) {
              while (nextPrev = this.findNextEntry(next, -1)) {
                next = nextPrev;
              }
              this.focus(next);
            }
            break;
          case 37:
            if (next = $.x('parent::*[contains(@class,"submenu")]/parent::*', entry)) {
              this.focus(next);
            }
            break;
          default:
            return;
        }
        e.preventDefault();
        return e.stopPropagation();
      };

      Menu.prototype.onFocus = function(e) {
        e.stopPropagation();
        return this.focus(e.target);
      };

      Menu.prototype.focus = function(entry) {
        var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2;
        while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) {
          $.rmClass(focused, 'focused');
        }
        _ref = $$('.focused', entry);
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          focused = _ref[_i];
          $.rmClass(focused, 'focused');
        }
        $.addClass(entry, 'focused');
        if (!(submenu = $('.submenu', entry))) {
          return;
        }
        sRect = submenu.getBoundingClientRect();
        eRect = entry.getBoundingClientRect();
        cHeight = doc.clientHeight;
        cWidth = doc.clientWidth;
        _ref1 = eRect.top + sRect.height < cHeight ? ['0px', 'auto'] : ['auto', '0px'], top = _ref1[0], bottom = _ref1[1];
        _ref2 = eRect.right + sRect.width < cWidth - 150 ? ['100%', 'auto'] : ['auto', '100%'], left = _ref2[0], right = _ref2[1];
        style = submenu.style;
        style.top = top;
        style.bottom = bottom;
        style.left = left;
        return style.right = right;
      };

      Menu.prototype.addEntry = function(entry) {
        this.parseEntry(entry);
        return this.entries.push(entry);
      };

      Menu.prototype.parseEntry = function(entry) {
        var el, subEntries, subEntry, _i, _len;
        el = entry.el, subEntries = entry.subEntries;
        $.addClass(el, 'entry');
        $.on(el, 'focus mouseover', this.onFocus);
        el.style.order = entry.order || 100;
        if (!subEntries) {
          return;
        }
        $.addClass(el, 'has-submenu');
        for (_i = 0, _len = subEntries.length; _i < _len; _i++) {
          subEntry = subEntries[_i];
          this.parseEntry(subEntry);
        }
      };

      return Menu;

    })();
    dragstart = function(e) {
      var el, isTouching, o, rect, screenHeight, screenWidth, _ref;
      if (e.type === 'mousedown' && e.button !== 0) {
        return;
      }
      e.preventDefault();
      if (isTouching = e.type === 'touchstart') {
        e = e.changedTouches[e.changedTouches.length - 1];
      }
      el = $.x('ancestor::div[contains(@class,"dialog")][1]', this);
      rect = el.getBoundingClientRect();
      screenHeight = doc.clientHeight;
      screenWidth = doc.clientWidth;
      o = {
        id: el.id,
        style: el.style,
        dx: e.clientX - rect.left,
        dy: e.clientY - rect.top,
        height: screenHeight - rect.height,
        width: screenWidth - rect.width,
        screenHeight: screenHeight,
        screenWidth: screenWidth,
        isTouching: isTouching
      };
      _ref = Conf['Header auto-hide'] || !Conf['Fixed Header'] ? [0, 0] : Conf['Bottom Header'] ? [0, Header.bar.getBoundingClientRect().height] : [Header.bar.getBoundingClientRect().height, 0], o.topBorder = _ref[0], o.bottomBorder = _ref[1];
      if (isTouching) {
        o.identifier = e.identifier;
        o.move = touchmove.bind(o);
        o.up = touchend.bind(o);
        $.on(d, 'touchmove', o.move);
        return $.on(d, 'touchend touchcancel', o.up);
      } else {
        o.move = drag.bind(o);
        o.up = dragend.bind(o);
        $.on(d, 'mousemove', o.move);
        return $.on(d, 'mouseup', o.up);
      }
    };
    touchmove = function(e) {
      var touch, _i, _len, _ref;
      _ref = e.changedTouches;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        touch = _ref[_i];
        if (touch.identifier === this.identifier) {
          drag.call(this, touch);
          return;
        }
      }
    };
    drag = function(e) {
      var bottom, clientX, clientY, left, right, style, top;
      clientX = e.clientX, clientY = e.clientY;
      left = clientX - this.dx;
      left = left < 10 ? 0 : this.width - left < 10 ? null : left / this.screenWidth * 100 + '%';
      top = clientY - this.dy;
      top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? null : top / this.screenHeight * 100 + '%';
      right = left === null ? 0 : null;
      bottom = top === null ? this.bottomBorder + 'px' : null;
      style = this.style;
      style.left = left;
      style.right = right;
      style.top = top;
      return style.bottom = bottom;
    };
    touchend = function(e) {
      var touch, _i, _len, _ref;
      _ref = e.changedTouches;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        touch = _ref[_i];
        if (touch.identifier === this.identifier) {
          dragend.call(this);
          return;
        }
      }
    };
    dragend = function() {
      if (this.isTouching) {
        $.off(d, 'touchmove', this.move);
        $.off(d, 'touchend touchcancel', this.up);
      } else {
        $.off(d, 'mousemove', this.move);
        $.off(d, 'mouseup', this.up);
      }
      return $.set("" + this.id + ".position", this.style.cssText);
    };
    hoverstart = function(_arg) {
      var asapTest, cb, el, endEvents, latestEvent, noRemove, o, root, _ref;
      root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, cb = _arg.cb, noRemove = _arg.noRemove;
      o = {
        root: root,
        el: el,
        style: el.style,
        isImage: (_ref = el.nodeName) === 'IMG' || _ref === 'VIDEO',
        cb: cb,
        endEvents: endEvents,
        latestEvent: latestEvent,
        clientHeight: doc.clientHeight,
        clientWidth: doc.clientWidth,
        noRemove: noRemove
      };
      o.hover = hover.bind(o);
      o.hoverend = hoverend.bind(o);
      $.asap(function() {
        return !el.parentNode || asapTest();
      }, function() {
        if (el.parentNode) {
          return o.hover(o.latestEvent);
        }
      });
      $.on(root, endEvents, o.hoverend);
      if ($.x('ancestor::div[contains(@class,"inline")][1]', root)) {
        $.on(d, 'keydown', o.hoverend);
      }
      $.on(root, 'mousemove', o.hover);
      o.workaround = function(e) {
        if (!root.contains(e.target)) {
          return o.hoverend(e);
        }
      };
      return $.on(doc, 'mousemove', o.workaround);
    };
    hover = function(e) {
      var clientX, clientY, height, left, right, style, threshold, top, _ref;
      this.latestEvent = e;
      height = this.el.offsetHeight;
      clientX = e.clientX, clientY = e.clientY;
      top = this.isImage ? Math.max(0, clientY * (this.clientHeight - height) / this.clientHeight) : Math.max(0, Math.min(this.clientHeight - height, clientY - 120));
      threshold = this.clientWidth / 2;
      if (!this.isImage) {
        threshold = Math.max(threshold, this.clientWidth - 400);
      }
      _ref = clientX <= threshold ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = _ref[0], right = _ref[1];
      style = this.style;
      style.top = top + 'px';
      style.left = left;
      return style.right = right;
    };
    hoverend = function(e) {
      if (e.type === 'keydown' && e.keyCode !== 13 || e.target.nodeName === "TEXTAREA") {
        return;
      }
      if (!this.noRemove) {
        $.rm(this.el);
      }
      $.off(this.root, this.endEvents, this.hoverend);
      $.off(d, 'keydown', this.hoverend);
      $.off(this.root, 'mousemove', this.hover);
      $.off(doc, 'mousemove', this.workaround);
      if (this.cb) {
        return this.cb.call(this);
      }
    };
    checkbox = function(name, text, checked) {
      var input, label;
      if (checked == null) {
        checked = Conf[name];
      }
      label = $.el('label');
      input = $.el('input', {
        type: 'checkbox',
        name: name,
        checked: checked
      });
      $.add(label, [input, $.tn(text)]);
      return label;
    };
    return {
      dialog: dialog,
      Menu: Menu,
      hover: hoverstart,
      checkbox: checkbox
    };
  })();

  CrossOrigin = (function() {
    return {
      file: (function() {
        var makeBlob;
        makeBlob = function(urlBlob, contentType, contentDisposition, url) {
          var blob, match, mime, name, _ref, _ref1, _ref2;
          name = (_ref = url.match(/([^\/]+)\/*$/)) != null ? _ref[1] : void 0;
          mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream';
          match = (contentDisposition != null ? (_ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref1[1] : void 0 : void 0) || (contentType != null ? (_ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref2[1] : void 0 : void 0);
          if (match) {
            name = match.replace(/\\"/g, '"');
          }
          blob = new Blob([urlBlob], {
            type: mime
          });
          blob.name = name;
          return blob;
        };
        return function(url, cb) {
          return GM_xmlhttpRequest({
            method: "GET",
            url: url,
            overrideMimeType: "text/plain; charset=x-user-defined",
            onload: function(xhr) {
              var contentDisposition, contentType, data, i, r, _ref, _ref1;
              r = xhr.responseText;
              data = new Uint8Array(r.length);
              i = 0;
              while (i < r.length) {
                data[i] = r.charCodeAt(i);
                i++;
              }
              contentType = (_ref = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? _ref[1] : void 0;
              contentDisposition = (_ref1 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? _ref1[1] : void 0;
              return cb(makeBlob(data, contentType, contentDisposition, url));
            },
            onerror: function() {
              return cb(null);
            }
          });
        };
      })(),
      json: (function() {
        var callbacks, responses;
        callbacks = {};
        responses = {};
        return function(url, cb) {
          if (responses[url]) {
            cb(responses[url]);
            return;
          }
          if (callbacks[url]) {
            callbacks[url].push(cb);
            return;
          }
          callbacks[url] = [cb];
          return GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function(xhr) {
              var response, _i, _len, _ref;
              response = JSON.parse(xhr.responseText);
              _ref = callbacks[url];
              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                cb = _ref[_i];
                cb(response);
              }
              delete callbacks[url];
              return responses[url] = response;
            },
            onerror: function() {
              return delete callbacks[url];
            },
            onabort: function() {
              return delete callbacks[url];
            }
          });
        };
      })()
    };
  })();

  Anonymize = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Anonymize']) {
        return;
      }
      return Post.callbacks.push({
        name: 'Anonymize',
        cb: this.node
      });
    },
    node: function() {
      var email, name, tripcode, _ref;
      if (this.info.capcode || this.isClone) {
        return;
      }
      _ref = this.nodes, name = _ref.name, tripcode = _ref.tripcode, email = _ref.email;
      if (this.info.name !== 'Anonymous') {
        name.textContent = 'Anonymous';
      }
      if (tripcode) {
        $.rm(tripcode);
        delete this.nodes.tripcode;
      }
      if (this.info.email) {
        $.replace(email, name);
        return delete this.nodes.email;
      }
    }
  };

  Filter = {
    filters: {},
    init: function() {
      var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
      if (g.VIEW === 'catalog' || !Conf['Filter']) {
        return;
      }
      if (!Conf['Filtered Backlinks']) {
        $.addClass(doc, 'hide-backlinks');
      }
      for (key in Config.filter) {
        this.filters[key] = [];
        _ref = Conf[key].split('\n');
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          filter = _ref[_i];
          if (filter[0] === '#') {
            continue;
          }
          if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) {
            continue;
          }
          filter = filter.replace(regexp[0], '');
          boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global';
          if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
            continue;
          }
          if (key === 'uniqueID' || key === 'MD5') {
            regexp = regexp[1];
          } else {
            try {
              regexp = RegExp(regexp[1], regexp[2]);
            } catch (_error) {
              err = _error;
              new Notice('warning', err.message, 60);
              continue;
            }
          }
          op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1] : void 0) || 'yes';
          stub = (function() {
            var _ref4;
            switch ((_ref4 = filter.match(/stub:(yes|no)/)) != null ? _ref4[1] : void 0) {
              case 'yes':
                return true;
              case 'no':
                return false;
              default:
                return Conf['Stubs'];
            }
          })();
          if (hl = /highlight/.test(filter)) {
            hl = ((_ref4 = filter.match(/highlight:(\w+)/)) != null ? _ref4[1] : void 0) || 'filter-highlight';
            top = ((_ref5 = filter.match(/top:(yes|no)/)) != null ? _ref5[1] : void 0) || 'yes';
            top = top === 'yes';
          }
          this.filters[key].push(this.createFilter(regexp, op, stub, hl, top));
        }
        if (!this.filters[key].length) {
          delete this.filters[key];
        }
      }
      if (!Object.keys(this.filters).length) {
        return;
      }
      return Post.callbacks.push({
        name: 'Filter',
        cb: this.node
      });
    },
    createFilter: function(regexp, op, stub, hl, top) {
      var settings, test;
      test = typeof regexp === 'string' ? function(value) {
        return regexp === value;
      } : function(value) {
        return regexp.test(value);
      };
      settings = {
        hide: !hl,
        stub: stub,
        "class": hl,
        top: top
      };
      return function(value, isReply) {
        if (isReply && op === 'only' || !isReply && op === 'no') {
          return false;
        }
        if (!test(value)) {
          return false;
        }
        return settings;
      };
    },
    node: function() {
      var filter, key, result, value, _i, _len, _ref;
      if (this.isClone || this.isFetchedQuote) {
        return;
      }
      for (key in Filter.filters) {
        value = Filter[key](this);
        if (value === false) {
          continue;
        }
        _ref = Filter.filters[key];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          filter = _ref[_i];
          if (!(result = filter(value, this.isReply))) {
            continue;
          }
          if (result.hide) {
            if (this.isReply) {
              PostHiding.hide(this, result.stub);
            } else if (g.VIEW === 'index') {
              ThreadHiding.hide(this.thread, result.stub);
            } else {
              continue;
            }
            return;
          }
          $.addClass(this.nodes.root, result["class"]);
          if (!this.isReply && result.top) {
            this.thread.isOnTop = true;
          }
        }
      }
    },
    name: function(post) {
      if ('name' in post.info) {
        return post.info.name;
      }
      return false;
    },
    uniqueID: function(post) {
      if ('uniqueID' in post.info) {
        return post.info.uniqueID;
      }
      return false;
    },
    tripcode: function(post) {
      if ('tripcode' in post.info) {
        return post.info.tripcode;
      }
      return false;
    },
    capcode: function(post) {
      if ('capcode' in post.info) {
        return post.info.capcode;
      }
      return false;
    },
    subject: function(post) {
      if ('subject' in post.info) {
        return post.info.subject || false;
      }
      return false;
    },
    comment: function(post) {
      if ('comment' in post.info) {
        return post.info.comment;
      }
      return false;
    },
    flag: function(post) {
      if ('flag' in post.info) {
        return post.info.flag;
      }
      return false;
    },
    filename: function(post) {
      if (post.file) {
        return post.file.name;
      }
      return false;
    },
    dimensions: function(post) {
      var file;
      file = post.file;
      if (file && (file.isImage || file.isVideo)) {
        return file.dimensions;
      }
      return false;
    },
    filesize: function(post) {
      if (post.file) {
        return post.file.size;
      }
      return false;
    },
    MD5: function(post) {
      if (post.file) {
        return post.file.MD5;
      }
      return false;
    },
    menu: {
      init: function() {
        var div, entry, type, _i, _len, _ref;
        if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Filter']) {
          return;
        }
        div = $.el('div', {
          textContent: 'Filter'
        });
        entry = {
          el: div,
          order: 50,
          open: function(post) {
            Filter.menu.post = post;
            return true;
          },
          subEntries: []
        };
        _ref = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          type = _ref[_i];
          entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1]));
        }
        return Menu.menu.addEntry(entry);
      },
      createSubEntry: function(text, type) {
        var el;
        el = $.el('a', {
          href: 'javascript:;',
          textContent: text
        });
        el.dataset.type = type;
        $.on(el, 'click', Filter.menu.makeFilter);
        return {
          el: el,
          open: function(post) {
            var value;
            value = Filter[type](post);
            return value !== false;
          }
        };
      },
      makeFilter: function() {
        var re, type, value;
        type = this.dataset.type;
        value = Filter[type](Filter.menu.post);
        re = type === 'uniqueID' || type === 'MD5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) {
          if (c === '\n') {
            return '\\n';
          } else if (c === '\\') {
            return '\\\\';
          } else {
            return "\\" + c;
          }
        });
        re = type === 'uniqueID' || type === 'MD5' ? "/" + re + "/" : "/^" + re + "$/";
        return $.get(type, Conf[type], function(item) {
          var save, section, select, ta, tl;
          save = item[type];
          save = save ? "" + save + "\n" + re : re;
          $.set(type, save);
          Settings.open('Filter');
          section = $('.section-container');
          select = $('select[name=filter]', section);
          select.value = type;
          Settings.selectFilter.call(select);
          ta = $('textarea', section);
          tl = ta.textLength;
          ta.setSelectionRange(tl, tl);
          return ta.focus();
        });
      }
    }
  };

  PostHiding = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Reply Hiding Buttons'] && !Conf['Reply Hiding Link']) {
        return;
      }
      if (Conf['Reply Hiding Buttons']) {
        $.addClass(doc, "reply-hide");
      }
      this.db = new DataBoard('hiddenPosts');
      return Post.callbacks.push({
        name: 'Reply Hiding',
        cb: this.node
      });
    },
    node: function() {
      var data;
      if (!this.isReply || this.isClone || this.isFetchedQuote) {
        return;
      }
      if (data = PostHiding.db.get({
        boardID: this.board.ID,
        threadID: this.thread.ID,
        postID: this.ID
      })) {
        if (data.thisPost) {
          PostHiding.hide(this, data.makeStub, data.hideRecursively);
        } else {
          Recursive.apply(PostHiding.hide, this, data.makeStub, true);
          Recursive.add(PostHiding.hide, this, data.makeStub, true);
        }
      }
      if (!Conf['Reply Hiding Buttons']) {
        return;
      }
      return $.replace($('.sideArrows', this.nodes.root), PostHiding.makeButton(this, 'hide'));
    },
    menu: {
      init: function() {
        var apply, div, hideStubLink, makeStub, replies, thisPost;
        if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding Link']) {
          return;
        }
        div = $.el('div', {
          className: 'hide-reply-link',
          textContent: 'Hide reply'
        });
        apply = $.el('a', {
          textContent: 'Apply',
          href: 'javascript:;'
        });
        $.on(apply, 'click', PostHiding.menu.hide);
        thisPost = UI.checkbox('thisPost', ' This post', true);
        replies = UI.checkbox('replies', ' Hide replies', Conf['Recursive Hiding']);
        makeStub = UI.checkbox('makeStub', ' Make stub', Conf['Stubs']);
        Menu.menu.addEntry({
          el: div,
          order: 20,
          open: function(post) {
            if (!post.isReply || post.isClone || post.isHidden) {
              return false;
            }
            PostHiding.menu.post = post;
            return true;
          },
          subEntries: [
            {
              el: apply
            }, {
              el: thisPost
            }, {
              el: replies
            }, {
              el: makeStub
            }
          ]
        });
        div = $.el('div', {
          className: 'show-reply-link',
          textContent: 'Show reply'
        });
        apply = $.el('a', {
          textContent: 'Apply',
          href: 'javascript:;'
        });
        $.on(apply, 'click', PostHiding.menu.show);
        thisPost = UI.checkbox('thisPost', ' This post', false);
        replies = UI.checkbox('replies', ' Show replies', false);
        hideStubLink = $.el('a', {
          textContent: 'Hide stub',
          href: 'javascript:;'
        });
        $.on(hideStubLink, 'click', PostHiding.menu.hideStub);
        Menu.menu.addEntry({
          el: div,
          order: 20,
          open: function(post) {
            var data;
            if (!post.isReply || post.isClone || !post.isHidden) {
              return false;
            }
            if (!(data = PostHiding.db.get({
              boardID: post.board.ID,
              threadID: post.thread.ID,
              postID: post.ID
            }))) {
              return false;
            }
            PostHiding.menu.post = post;
            thisPost.firstChild.checked = post.isHidden;
            replies.firstChild.checked = (data != null ? data.hideRecursively : void 0) != null ? data.hideRecursively : Conf['Recursive Hiding'];
            return true;
          },
          subEntries: [
            {
              el: apply
            }, {
              el: thisPost
            }, {
              el: replies
            }
          ]
        });
        return Menu.menu.addEntry({
          el: hideStubLink,
          order: 15,
          open: function(post) {
            var data;
            if (!post.isReply || post.isClone || !post.isHidden) {
              return false;
            }
            if (!(data = PostHiding.db.get({
              boardID: post.board.ID,
              threadID: post.thread.ID,
              postID: post.ID
            }))) {
              return false;
            }
            return PostHiding.menu.post = post;
          }
        });
      },
      hide: function() {
        var makeStub, parent, post, replies, thisPost;
        parent = this.parentNode;
        thisPost = $('input[name=thisPost]', parent).checked;
        replies = $('input[name=replies]', parent).checked;
        makeStub = $('input[name=makeStub]', parent).checked;
        post = PostHiding.menu.post;
        if (thisPost) {
          PostHiding.hide(post, makeStub, replies);
        } else if (replies) {
          Recursive.apply(PostHiding.hide, post, makeStub, true);
          Recursive.add(PostHiding.hide, post, makeStub, true);
        } else {
          return;
        }
        PostHiding.saveHiddenState(post, true, thisPost, makeStub, replies);
        return $.event('CloseMenu');
      },
      show: function() {
        var data, parent, post, replies, thisPost;
        parent = this.parentNode;
        thisPost = $('input[name=thisPost]', parent).checked;
        replies = $('input[name=replies]', parent).checked;
        post = PostHiding.menu.post;
        if (thisPost) {
          PostHiding.show(post, replies);
        } else if (replies) {
          Recursive.apply(PostHiding.show, post, true);
          Recursive.rm(PostHiding.hide, post, true);
        } else {
          return;
        }
        if (data = PostHiding.db.get({
          boardID: post.board.ID,
          threadID: post.thread.ID,
          postID: post.ID
        })) {
          PostHiding.saveHiddenState(post, !(thisPost && replies), !thisPost, data.makeStub, !replies);
        }
        return $.event('CloseMenu');
      },
      hideStub: function() {
        var post;
        post = PostHiding.menu.post;
        post.nodes.root.hidden = true;
        $.event('CloseMenu');
      }
    },
    makeButton: function(post, type) {
      var a, span;
      span = $.el('span', {
        className: "fa fa-" + (type === 'hide' ? 'minus' : 'plus') + "-square-o",
        textContent: ""
      });
      a = $.el('a', {
        className: "" + type + "-reply-button",
        href: 'javascript:;'
      });
      $.add(a, span);
      $.on(a, 'click', PostHiding.toggle);
      return a;
    },
    saveHiddenState: function(post, isHiding, thisPost, makeStub, hideRecursively) {
      var data;
      data = {
        boardID: post.board.ID,
        threadID: post.thread.ID,
        postID: post.ID
      };
      if (isHiding) {
        data.val = {
          thisPost: thisPost !== false,
          makeStub: makeStub,
          hideRecursively: hideRecursively
        };
        return PostHiding.db.set(data);
      } else {
        return PostHiding.db["delete"](data);
      }
    },
    toggle: function() {
      var post;
      post = Get.postFromNode(this);
      PostHiding[(post.isHidden ? 'show' : 'hide')](post);
      return PostHiding.saveHiddenState(post, post.isHidden);
    },
    hide: function(post, makeStub, hideRecursively) {
      var a, postInfo, quotelink, _i, _len, _ref;
      if (makeStub == null) {
        makeStub = Conf['Stubs'];
      }
      if (hideRecursively == null) {
        hideRecursively = Conf['Recursive Hiding'];
      }
      if (post.isHidden) {
        return;
      }
      post.isHidden = true;
      if (hideRecursively) {
        Recursive.apply(PostHiding.hide, post, makeStub, true);
        Recursive.add(PostHiding.hide, post, makeStub, true);
      }
      _ref = Get.allQuotelinksLinkingTo(post);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quotelink = _ref[_i];
        $.addClass(quotelink, 'filtered');
      }
      if (!makeStub) {
        post.nodes.root.hidden = true;
        return;
      }
      a = PostHiding.makeButton(post, 'show');
      postInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent;
      $.add(a, $.tn(" " + postInfo));
      post.nodes.stub = $.el('div', {
        className: 'stub'
      });
      $.add(post.nodes.stub, a);
      if (Conf['Menu']) {
        $.add(post.nodes.stub, Menu.makeButton());
      }
      return $.prepend(post.nodes.root, post.nodes.stub);
    },
    show: function(post, showRecursively) {
      var quotelink, _i, _len, _ref;
      if (showRecursively == null) {
        showRecursively = Conf['Recursive Hiding'];
      }
      if (post.nodes.stub) {
        $.rm(post.nodes.stub);
        delete post.nodes.stub;
      } else {
        post.nodes.root.hidden = false;
      }
      post.isHidden = false;
      if (showRecursively) {
        Recursive.apply(PostHiding.show, post, true);
        Recursive.rm(PostHiding.hide, post);
      }
      _ref = Get.allQuotelinksLinkingTo(post);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quotelink = _ref[_i];
        $.rmClass(quotelink, 'filtered');
      }
    }
  };

  Recursive = {
    recursives: {},
    init: function() {
      if (g.VIEW === 'catalog') {
        return;
      }
      return Post.callbacks.push({
        name: 'Recursive',
        cb: this.node
      });
    },
    node: function() {
      var i, obj, quote, recursive, _i, _j, _len, _len1, _ref, _ref1;
      if (this.isClone || this.isFetchedQuote) {
        return;
      }
      _ref = this.quotes;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quote = _ref[_i];
        if (obj = Recursive.recursives[quote]) {
          _ref1 = obj.recursives;
          for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
            recursive = _ref1[i];
            recursive.apply(null, [this].concat(__slice.call(obj.args[i])));
          }
        }
      }
    },
    add: function() {
      var args, obj, post, recursive, _base, _name;
      recursive = arguments[0], post = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
      obj = (_base = Recursive.recursives)[_name = post.fullID] || (_base[_name] = {
        recursives: [],
        args: []
      });
      obj.recursives.push(recursive);
      return obj.args.push(args);
    },
    rm: function(recursive, post) {
      var i, obj, rec, _i, _len, _ref;
      if (!(obj = Recursive.recursives[post.fullID])) {
        return;
      }
      _ref = obj.recursives;
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        rec = _ref[i];
        if (rec === recursive) {
          obj.recursives.splice(i, 1);
          obj.args.splice(i, 1);
        }
      }
    },
    apply: function() {
      var args, fullID, post, recursive;
      recursive = arguments[0], post = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
      fullID = post.fullID;
      return g.posts.forEach(function(post) {
        if (__indexOf.call(post.quotes, fullID) >= 0) {
          return recursive.apply(null, [post].concat(__slice.call(args)));
        }
      });
    }
  };

  ThreadHiding = {
    init: function() {
      if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link']) {
        return;
      }
      this.db = new DataBoard('hiddenThreads');
      this.syncCatalog();
      return Thread.callbacks.push({
        name: 'Thread Hiding',
        cb: this.node
      });
    },
    node: function() {
      var data;
      if (data = ThreadHiding.db.get({
        boardID: this.board.ID,
        threadID: this.ID
      })) {
        ThreadHiding.hide(this, data.makeStub);
      }
      if (!Conf['Thread Hiding Buttons']) {
        return;
      }
      return $.prepend(this.OP.nodes.root, ThreadHiding.makeButton(this, 'hide'));
    },
    onIndexBuild: function(nodes) {
      var root, thread, _i, _len;
      for (_i = 0, _len = nodes.length; _i < _len; _i++) {
        root = nodes[_i];
        thread = Get.threadFromRoot(root);
        if (thread.isHidden && thread.stub && !root.contains(thread.stub)) {
          ThreadHiding.makeStub(thread, root);
        }
      }
    },
    syncCatalog: function() {
      var hiddenThreads, hiddenThreadsOnCatalog, threadID;
      hiddenThreads = ThreadHiding.db.get({
        boardID: g.BOARD.ID,
        defaultValue: {}
      });
      hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
      for (threadID in hiddenThreadsOnCatalog) {
        if (!(threadID in hiddenThreads)) {
          hiddenThreads[threadID] = {};
        }
      }
      for (threadID in hiddenThreads) {
        if (!(threadID in hiddenThreadsOnCatalog)) {
          delete hiddenThreads[threadID];
        }
      }
      if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) {
        ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog);
      }
      return ThreadHiding.db.set({
        boardID: g.BOARD.ID,
        val: hiddenThreads
      });
    },
    cleanCatalog: function(hiddenThreadsOnCatalog) {
      return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() {
        var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
        if (this.status !== 200) {
          return;
        }
        threads = {};
        _ref = this.response;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          page = _ref[_i];
          _ref1 = page.threads;
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            thread = _ref1[_j];
            if (thread.no in hiddenThreadsOnCatalog) {
              threads[thread.no] = hiddenThreadsOnCatalog[thread.no];
            }
          }
        }
        if (Object.keys(threads).length) {
          return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads));
        } else {
          return localStorage.removeItem("4chan-hide-t-" + g.BOARD);
        }
      });
    },
    menu: {
      init: function() {
        var apply, div, hideStubLink, makeStub;
        if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding Link']) {
          return;
        }
        div = $.el('div', {
          className: 'hide-thread-link',
          textContent: 'Hide thread'
        });
        apply = $.el('a', {
          textContent: 'Apply',
          href: 'javascript:;'
        });
        $.on(apply, 'click', ThreadHiding.menu.hide);
        makeStub = UI.checkbox('Stubs', ' Make stub');
        Menu.menu.addEntry({
          el: div,
          order: 20,
          open: function(_arg) {
            var isReply, thread;
            thread = _arg.thread, isReply = _arg.isReply;
            if (isReply || thread.isHidden) {
              return false;
            }
            ThreadHiding.menu.thread = thread;
            return true;
          },
          subEntries: [
            {
              el: apply
            }, {
              el: makeStub
            }
          ]
        });
        div = $.el('a', {
          className: 'show-thread-link',
          textContent: 'Show thread',
          href: 'javascript:;'
        });
        $.on(div, 'click', ThreadHiding.menu.show);
        Menu.menu.addEntry({
          el: div,
          order: 20,
          open: function(_arg) {
            var isReply, thread;
            thread = _arg.thread, isReply = _arg.isReply;
            if (isReply || !thread.isHidden) {
              return false;
            }
            ThreadHiding.menu.thread = thread;
            return true;
          }
        });
        hideStubLink = $.el('a', {
          textContent: 'Hide stub',
          href: 'javascript:;'
        });
        $.on(hideStubLink, 'click', ThreadHiding.menu.hideStub);
        return Menu.menu.addEntry({
          el: hideStubLink,
          order: 15,
          open: function(_arg) {
            var isReply, thread;
            thread = _arg.thread, isReply = _arg.isReply;
            if (isReply || !thread.isHidden) {
              return false;
            }
            return ThreadHiding.menu.thread = thread;
          }
        });
      },
      hide: function() {
        var makeStub, thread;
        makeStub = $('input', this.parentNode).checked;
        thread = ThreadHiding.menu.thread;
        ThreadHiding.hide(thread, makeStub);
        ThreadHiding.saveHiddenState(thread, makeStub);
        return $.event('CloseMenu');
      },
      show: function() {
        var thread;
        thread = ThreadHiding.menu.thread;
        ThreadHiding.show(thread);
        ThreadHiding.saveHiddenState(thread);
        return $.event('CloseMenu');
      },
      hideStub: function() {
        var thread;
        thread = ThreadHiding.menu.thread;
        ThreadHiding.hide(thread, false);
        $.event('CloseMenu');
      }
    },
    makeButton: function(thread, type) {
      var a;
      a = $.el('a', {
        className: "" + type + "-thread-button",
        href: 'javascript:;'
      });
      $.extend(a, {
        innerHTML: "<span class=\"fa fa-" + E((type === "hide") ? "minus" : "plus") + "-square\"></span>"
      });
      a.dataset.fullID = thread.fullID;
      $.on(a, 'click', ThreadHiding.toggle);
      return a;
    },
    makeStub: function(thread, root) {
      var a, numReplies, opInfo, summary;
      numReplies = $$('.thread > .replyContainer', root).length;
      if (summary = $('.summary', root)) {
        numReplies += +summary.textContent.match(/\d+/);
      }
      opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', thread.OP.nodes.info).textContent;
      a = ThreadHiding.makeButton(thread, 'show');
      $.add(a, $.tn(" " + opInfo + " (" + (numReplies === 1 ? '1 reply' : "" + numReplies + " replies") + ")"));
      thread.stub = $.el('div', {
        className: 'stub'
      });
      if (Conf['Menu']) {
        $.add(thread.stub, [a, Menu.makeButton()]);
      } else {
        $.add(thread.stub, a);
      }
      return $.prepend(root, thread.stub);
    },
    saveHiddenState: function(thread, makeStub) {
      var hiddenThreadsOnCatalog;
      hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
      if (thread.isHidden) {
        ThreadHiding.db.set({
          boardID: thread.board.ID,
          threadID: thread.ID,
          val: {
            makeStub: makeStub
          }
        });
        hiddenThreadsOnCatalog[thread] = true;
      } else {
        ThreadHiding.db["delete"]({
          boardID: thread.board.ID,
          threadID: thread.ID
        });
        delete hiddenThreadsOnCatalog[thread];
      }
      return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog));
    },
    toggle: function(thread) {
      if (!(thread instanceof Thread)) {
        thread = g.threads[this.dataset.fullID];
      }
      if (thread.isHidden) {
        ThreadHiding.show(thread);
      } else {
        ThreadHiding.hide(thread);
      }
      return ThreadHiding.saveHiddenState(thread);
    },
    hide: function(thread, makeStub) {
      var threadRoot;
      if (makeStub == null) {
        makeStub = Conf['Stubs'];
      }
      if (thread.isHidden) {
        return;
      }
      threadRoot = thread.OP.nodes.root.parentNode;
      thread.isHidden = true;
      if (!makeStub) {
        return threadRoot.hidden = true;
      }
      return ThreadHiding.makeStub(thread, threadRoot);
    },
    show: function(thread) {
      var threadRoot;
      if (thread.stub) {
        $.rm(thread.stub);
        delete thread.stub;
      }
      threadRoot = thread.OP.nodes.root.parentNode;
      return threadRoot.hidden = thread.isHidden = false;
    }
  };

  QuoteBacklink = {
    containers: {},
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Quote Backlinks']) {
        return;
      }
      Post.callbacks.push({
        name: 'Quote Backlinking Part 1',
        cb: this.firstNode
      });
      return Post.callbacks.push({
        name: 'Quote Backlinking Part 2',
        cb: this.secondNode
      });
    },
    firstNode: function() {
      var a, clone, container, containers, hash, link, markYours, nodes, post, quote, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
      if (this.isClone || !this.quotes.length) {
        return;
      }
      markYours = Conf['Quick Reply'] && Conf['Mark Quotes of You'] && QR.db.get({
        boardID: this.board.ID,
        threadID: this.thread.ID,
        postID: this.ID
      });
      a = $.el('a', {
        href: Build.postURL(this.board.ID, this.thread.ID, this.ID),
        className: this.isHidden ? 'filtered backlink' : 'backlink',
        textContent: Conf['backlink'].replace(/%(?:id|%)/g, (function(_this) {
          return function(x) {
            return {
              '%id': _this.ID,
              '%%': '%'
            }[x];
          };
        })(this)) + (markYours ? '\u00A0(You)' : '')
      });
      _ref = this.quotes;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quote = _ref[_i];
        containers = [QuoteBacklink.getContainer(quote)];
        if ((post = g.posts[quote]) && post.nodes.backlinkContainer) {
          _ref1 = post.clones;
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            clone = _ref1[_j];
            containers.push(clone.nodes.backlinkContainer);
          }
        }
        for (_k = 0, _len2 = containers.length; _k < _len2; _k++) {
          container = containers[_k];
          nodes = [$.tn(' '), link = a.cloneNode(true)];
          if (Conf['Quote Previewing']) {
            $.on(link, 'mouseover', QuotePreview.mouseover);
          }
          if (Conf['Quote Inlining']) {
            $.on(link, 'click', QuoteInline.toggle);
            if (Conf['Quote Hash Navigation']) {
              hash = QuoteInline.qiQuote(link, $.hasClass(link, 'filtered'));
              nodes.push(hash);
            }
          }
          $.add(container, nodes);
        }
      }
    },
    secondNode: function() {
      var container;
      if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) {
        this.nodes.backlinkContainer = $('.container', this.nodes.info);
        return;
      }
      if (!(this.isReply || Conf['OP Backlinks'])) {
        return;
      }
      container = QuoteBacklink.getContainer(this.fullID);
      this.nodes.backlinkContainer = container;
      return $.add(this.nodes.info, container);
    },
    getContainer: function(id) {
      var _base;
      return (_base = this.containers)[id] || (_base[id] = $.el('span', {
        className: 'container'
      }));
    }
  };

  QuoteCT = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Mark Cross-thread Quotes']) {
        return;
      }
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      this.text = '\u00A0(Cross-thread)';
      return Post.callbacks.push({
        name: 'Mark Cross-thread Quotes',
        cb: this.node
      });
    },
    node: function() {
      var board, boardID, quotelink, thread, threadID, _i, _len, _ref, _ref1, _ref2;
      if (this.isClone && this.thread === this.context.thread) {
        return;
      }
      _ref = this.isClone ? this.context : this, board = _ref.board, thread = _ref.thread;
      _ref1 = this.nodes.quotelinks;
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        quotelink = _ref1[_i];
        _ref2 = Get.postDataFromLink(quotelink), boardID = _ref2.boardID, threadID = _ref2.threadID;
        if (!threadID) {
          continue;
        }
        if (this.isClone) {
          quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, '');
        }
        if (boardID === board.ID && threadID !== thread.ID) {
          $.add(quotelink, $.tn(QuoteCT.text));
        }
      }
    }
  };

  QuoteInline = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Quote Inlining']) {
        return;
      }
      this.process = Conf['Quote Hash Navigation'] ? function(link, clone) {
        if (!clone) {
          $.after(link, QuoteInline.qiQuote(link, $.hasClass(link, 'filtered')));
        }
        return $.on(link, 'click', QuoteInline.toggle);
      } : function(link) {
        return $.on(link, 'click', QuoteInline.toggle);
      };
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      return Post.callbacks.push({
        name: 'Quote Inlining',
        cb: this.node
      });
    },
    node: function() {
      var isClone, link, process, _i, _j, _len, _len1, _ref, _ref1;
      process = QuoteInline.process;
      isClone = this.isClone;
      _ref = this.nodes.quotelinks;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        link = _ref[_i];
        process(link, isClone);
      }
      _ref1 = this.nodes.backlinks;
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        link = _ref1[_j];
        process(link, isClone);
      }
    },
    qiQuote: function(link, hidden) {
      return $.el('a', {
        className: "hashlink" + (hidden ? ' filtered' : ''),
        textContent: '#',
        href: link.href
      });
    },
    toggle: function(e) {
      var boardID, context, postID, threadID, _ref;
      if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
        return;
      }
      e.preventDefault();
      _ref = Get.postDataFromLink(this), boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID;
      context = Get.contextFromNode(this);
      if ($.hasClass(this, 'inlined')) {
        QuoteInline.rm(this, boardID, threadID, postID, context);
      } else {
        if ($.x("ancestor::div[@id='p" + postID + "']", this)) {
          return;
        }
        QuoteInline.add(this, boardID, threadID, postID, context);
      }
      return this.classList.toggle('inlined');
    },
    findRoot: function(quotelink, isBacklink) {
      if (isBacklink) {
        return quotelink.parentNode.parentNode;
      } else {
        return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink);
      }
    },
    add: function(quotelink, boardID, threadID, postID, context) {
      var inline, isBacklink, post, qroot, root;
      isBacklink = $.hasClass(quotelink, 'backlink');
      inline = $.el('div', {
        id: "i" + postID,
        className: 'inline'
      });
      root = QuoteInline.findRoot(quotelink, isBacklink);
      $.after(root, inline);
      qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
      $.addClass(qroot, 'hasInline');
      Get.postClone(boardID, threadID, postID, inline, context);
      if (!((post = g.posts["" + boardID + "." + postID]) && context.thread === post.thread)) {
        return;
      }
      if (isBacklink && Conf['Forward Hiding']) {
        $.addClass(post.nodes.root, 'forwarded');
        post.forwarded++ || (post.forwarded = 1);
      }
      if (!Unread.posts) {
        return;
      }
      return Unread.readSinglePost(post);
    },
    rm: function(quotelink, boardID, threadID, postID, context) {
      var el, inlined, isBacklink, post, qroot, root, _ref;
      isBacklink = $.hasClass(quotelink, 'backlink');
      root = QuoteInline.findRoot(quotelink, isBacklink);
      root = $.x("following-sibling::div[@id='i" + postID + "'][1]", root);
      qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
      $.rm(root);
      if (!$('.inline', qroot)) {
        $.rmClass(qroot, 'hasInline');
      }
      if (!(el = root.firstElementChild)) {
        return;
      }
      post = g.posts["" + boardID + "." + postID];
      post.rmClone(el.dataset.clone);
      if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads["" + boardID + "." + threadID] && !--post.forwarded) {
        delete post.forwarded;
        $.rmClass(post.nodes.root, 'forwarded');
      }
      while (inlined = $('.inlined', el)) {
        _ref = Get.postDataFromLink(inlined), boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID;
        QuoteInline.rm(inlined, boardID, threadID, postID, context);
        $.rmClass(inlined, 'inlined');
      }
    }
  };

  QuoteOP = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Mark OP Quotes']) {
        return;
      }
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      this.text = '\u00A0(OP)';
      return Post.callbacks.push({
        name: 'Mark OP Quotes',
        cb: this.node
      });
    },
    node: function() {
      var boardID, fullID, i, postID, quotelink, quotelinks, quotes, _ref, _ref1;
      if (this.isClone && this.thread === this.context.thread) {
        return;
      }
      if (!(quotes = this.quotes).length) {
        return;
      }
      quotelinks = this.nodes.quotelinks;
      if (this.isClone && (_ref = this.thread.fullID, __indexOf.call(quotes, _ref) >= 0)) {
        i = 0;
        while (quotelink = quotelinks[i++]) {
          quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, '');
        }
      }
      fullID = (this.isClone ? this.context : this).thread.fullID;
      if (__indexOf.call(quotes, fullID) < 0) {
        return;
      }
      i = 0;
      while (quotelink = quotelinks[i++]) {
        _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID;
        if (("" + boardID + "." + postID) === fullID) {
          $.add(quotelink, $.tn(QuoteOP.text));
        }
      }
    }
  };

  QuotePreview = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Quote Previewing']) {
        return;
      }
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      return Post.callbacks.push({
        name: 'Quote Previewing',
        cb: this.node
      });
    },
    node: function() {
      var link, _i, _len, _ref;
      _ref = this.nodes.quotelinks.concat(__slice.call(this.nodes.backlinks));
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        link = _ref[_i];
        $.on(link, 'mouseover', QuotePreview.mouseover);
      }
    },
    mouseover: function(e) {
      var boardID, clone, origin, post, postID, posts, qp, quote, quoterID, threadID, _i, _j, _len, _len1, _ref, _ref1;
      if ($.hasClass(this, 'inlined')) {
        return;
      }
      _ref = Get.postDataFromLink(this), boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID;
      qp = $.el('div', {
        id: 'qp',
        className: 'dialog'
      });
      $.add(Header.hover, qp);
      Get.postClone(boardID, threadID, postID, qp, Get.contextFromNode(this));
      UI.hover({
        root: this,
        el: qp,
        latestEvent: e,
        endEvents: 'mouseout click',
        cb: QuotePreview.mouseout,
        asapTest: function() {
          return qp.firstElementChild;
        }
      });
      if (!(origin = g.posts["" + boardID + "." + postID])) {
        return;
      }
      if (Conf['Quote Highlighting']) {
        posts = [origin].concat(origin.clones);
        posts.pop();
        for (_i = 0, _len = posts.length; _i < _len; _i++) {
          post = posts[_i];
          $.addClass(post.nodes.post, 'qphl');
        }
      }
      quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0];
      clone = Get.postFromRoot(qp.firstChild);
      _ref1 = clone.nodes.quotelinks.concat(__slice.call(clone.nodes.backlinks));
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        quote = _ref1[_j];
        if (quote.hash.slice(2) === quoterID) {
          $.addClass(quote, 'forwardlink');
        }
      }
    },
    mouseout: function() {
      var clone, post, root, _i, _len, _ref;
      if (!(root = this.el.firstElementChild)) {
        return;
      }
      clone = Get.postFromRoot(root);
      post = clone.origin;
      post.rmClone(root.dataset.clone);
      if (!Conf['Quote Highlighting']) {
        return;
      }
      _ref = [post].concat(post.clones);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        post = _ref[_i];
        $.rmClass(post.nodes.post, 'qphl');
      }
    }
  };

  QuoteStrikeThrough = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Reply Hiding Buttons'] && !Conf['Reply Hiding Link'] && !Conf['Filter']) {
        return;
      }
      return Post.callbacks.push({
        name: 'Strike-through Quotes',
        cb: this.node
      });
    },
    node: function() {
      var boardID, postID, quotelink, _i, _len, _ref, _ref1, _ref2;
      if (this.isClone) {
        return;
      }
      _ref = this.nodes.quotelinks;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quotelink = _ref[_i];
        _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID;
        if ((_ref2 = g.posts["" + boardID + "." + postID]) != null ? _ref2.isHidden : void 0) {
          $.addClass(quotelink, 'filtered');
        }
      }
    }
  };


  /*
    <3 aeosynth
   */

  QuoteThreading = {
    init: function() {
      var input;
      if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) {
        return;
      }
      this.enabled = true;
      this.controls = $.el('span', {
        innerHTML: "<label><input id=\"threadingControl\" type=\"checkbox\" checked> Threading</label>"
      });
      input = $('input', this.controls);
      $.on(input, 'change', this.toggle);
      Header.menu.addEntry(this.entry = {
        el: this.controls,
        order: 98
      });
      if (!Conf['Unread Count']) {
        $.on(d, '4chanXInitFinished', this.ready);
      }
      return Post.callbacks.push({
        name: 'Quote Threading',
        cb: this.node
      });
    },
    ready: function() {
      $.off(d, '4chanXInitFinished', QuoteThreading.ready);
      return QuoteThreading.force();
    },
    force: function() {
      g.posts.forEach(function(post) {
        if (post.cb) {
          return post.cb(true);
        }
      });
      if (Conf['Unread Count'] && Unread.thread.OP.nodes.root.parentElement.parentElement) {
        Unread.read();
        return Unread.update();
      }
    },
    node: function() {
      var keys, len, posts, quote, _i, _len, _ref;
      posts = g.posts;
      if (this.isClone || !QuoteThreading.enabled) {
        return;
      }
      if (Conf['Unread Count']) {
        Unread.addPost(this);
      }
      if (this.thread.OP === this || this.isHidden) {
        return;
      }
      keys = [];
      len = g.BOARD.ID.length + 1;
      _ref = this.quotes;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quote = _ref[_i];
        if ((quote.slice(len) < this.ID) && quote in posts) {
          keys.push(quote);
        }
      }
      if (keys.length !== 1) {
        return;
      }
      this.threaded = keys[0];
      return this.cb = QuoteThreading.nodeinsert;
    },
    nodeinsert: function(force) {
      var bottom, height, post, posts, root, threadContainer, top, _ref;
      post = g.posts[this.threaded];
      if (this.thread.OP === post) {
        return false;
      }
      posts = Unread.posts;
      root = post.nodes.root;
      if (!force) {
        height = doc.clientHeight;
        _ref = root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
        if (!((Conf['Unread Count'] && posts[post.ID]) || ((bottom < height) && (top > 0)))) {
          return false;
        }
      }
      if ($.hasClass(root, 'threadOP')) {
        threadContainer = root.nextElementSibling;
        post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
        $.add(threadContainer, this.nodes.root);
      } else {
        threadContainer = $.el('div', {
          className: 'threadContainer'
        });
        $.add(threadContainer, this.nodes.root);
        $.after(root, threadContainer);
        $.addClass(root, 'threadOP');
      }
      if (!Conf['Unread Count']) {
        return true;
      }
      if (post = posts[post.ID]) {
        posts.after(post, posts[this.ID]);
      } else if (posts[this.ID]) {
        posts.prepend(posts[this.ID]);
      }
      return true;
    },
    toggle: function() {
      var container, containers, nodes, post, posts, thread, _i, _j, _k, _len, _len1, _len2, _ref;
      if (QuoteThreading.enabled = this.checked) {
        QuoteThreading.force();
      } else {
        thread = $('.thread');
        posts = [];
        nodes = [];
        g.posts.forEach(function(post) {
          if (!(post === post.thread.OP || post.isClone)) {
            return posts.push(post);
          }
        });
        posts.sort(function(a, b) {
          return a.ID - b.ID;
        });
        for (_i = 0, _len = posts.length; _i < _len; _i++) {
          post = posts[_i];
          nodes.push(post.nodes.root);
        }
        $.add(thread, nodes);
        containers = $$('.threadContainer', thread);
        for (_j = 0, _len1 = containers.length; _j < _len1; _j++) {
          container = containers[_j];
          $.rm(container);
        }
        _ref = $$('.threadOP');
        for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) {
          post = _ref[_k];
          $.rmClass(post, 'threadOP');
        }
      }
    },
    kb: function() {
      var control;
      control = $.id('threadingControl');
      control.checked = !control.checked;
      return QuoteThreading.toggle.call(control);
    }
  };

  QuoteYou = {
    init: function() {
      if (!(g.VIEW !== 'catalog' && Conf['Mark Quotes of You'] && Conf['Quick Reply'])) {
        return;
      }
      if (Conf['Highlight Own Posts']) {
        $.addClass(doc, 'highlight-own');
      }
      if (Conf['Highlight Posts Quoting You']) {
        $.addClass(doc, 'highlight-you');
      }
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      this.text = '\u00A0(You)';
      return Post.callbacks.push({
        name: 'Mark Quotes of You',
        cb: this.node
      });
    },
    node: function() {
      var quotelink, _i, _len, _ref;
      if (this.isClone) {
        return;
      }
      if (QR.db.get({
        boardID: this.board.ID,
        threadID: this.thread.ID,
        postID: this.ID
      })) {
        $.addClass(this.nodes.root, 'yourPost');
      }
      if (!this.quotes.length) {
        return;
      }
      _ref = this.nodes.quotelinks;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quotelink = _ref[_i];
        if (!(QR.db.get(Get.postDataFromLink(quotelink)))) {
          continue;
        }
        $.add(quotelink, $.tn(QuoteYou.text));
        $.addClass(quotelink, 'you');
        $.addClass(this.nodes.root, 'quotesYou');
      }
    },
    cb: {
      seek: function(type) {
        var highlight, post, posts, result, str;
        if (!(Conf['Mark Quotes of You'] && Conf['Quick Reply'])) {
          return;
        }
        if (highlight = $('.highlight')) {
          $.rmClass(highlight, 'highlight');
        }
        if (!QuoteYou.lastRead) {
          if (!(post = QuoteYou.lastRead = $('.quotesYou'))) {
            new Notice('warning', 'No posts are currently quoting you, loser.', 20);
            return;
          }
          if (QuoteYou.cb.scroll(post)) {
            return;
          }
        } else {
          post = QuoteYou.lastRead;
        }
        str = "" + type + "::div[contains(@class,'quotesYou')]";
        while (post = (result = $.X(str, post)).snapshotItem(type === 'preceding' ? result.snapshotLength - 1 : 0)) {
          if (QuoteYou.cb.scroll(post)) {
            return;
          }
        }
        posts = $$('.quotesYou');
        return QuoteYou.cb.scroll(posts[type === 'following' ? 0 : posts.length - 1]);
      },
      scroll: function(post) {
        if (Get.postFromRoot(post).isHidden) {
          return false;
        } else {
          QuoteYou.lastRead = post;
          window.location = "#" + post.id;
          Header.scrollTo(post);
          $.addClass($('.post', post), 'highlight');
          return true;
        }
      }
    }
  };

  Quotify = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Resurrect Quotes']) {
        return;
      }
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      return Post.callbacks.push({
        name: 'Resurrect Quotes',
        cb: this.node
      });
    },
    node: function() {
      var deadlink, _i, _len, _ref;
      _ref = $$('.deadlink', this.nodes.comment);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        deadlink = _ref[_i];
        if (this.isClone) {
          if ($.hasClass(deadlink, 'quotelink')) {
            this.nodes.quotelinks.push(deadlink);
          }
        } else {
          Quotify.parseDeadlink.call(this, deadlink);
        }
      }
    },
    parseDeadlink: function(deadlink) {
      var a, boardID, fetchable, m, post, postID, quote, quoteID, redirect, _ref;
      if ($.hasClass(deadlink.parentNode, 'prettyprint')) {
        Quotify.fixDeadlink(deadlink);
        return;
      }
      quote = deadlink.textContent;
      if (!(postID = (_ref = quote.match(/\d+$/)) != null ? _ref[0] : void 0)) {
        return;
      }
      if (postID[0] === '0') {
        Quotify.fixDeadlink(deadlink);
        return;
      }
      boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
      quoteID = "" + boardID + "." + postID;
      if (post = g.posts[quoteID]) {
        if (!post.isDead) {
          a = $.el('a', {
            href: Build.postURL(boardID, post.thread.ID, postID),
            className: 'quotelink',
            textContent: quote
          });
        } else {
          a = $.el('a', {
            href: Build.postURL(boardID, post.thread.ID, postID),
            className: 'quotelink deadlink',
            target: '_blank',
            textContent: "" + quote + "\u00A0(Dead)"
          });
          $.extend(a.dataset, {
            boardID: boardID,
            threadID: post.thread.ID,
            postID: postID
          });
        }
      } else {
        redirect = Redirect.to('thread', {
          boardID: boardID,
          threadID: 0,
          postID: postID
        });
        fetchable = Redirect.to('post', {
          boardID: boardID,
          postID: postID
        });
        if (redirect || fetchable) {
          a = $.el('a', {
            href: redirect || 'javascript:;',
            className: 'deadlink',
            target: '_blank',
            textContent: "" + quote + "\u00A0(Dead)"
          });
          if (fetchable) {
            $.addClass(a, 'quotelink');
            $.extend(a.dataset, {
              boardID: boardID,
              postID: postID
            });
          }
        }
      }
      if (__indexOf.call(this.quotes, quoteID) < 0) {
        this.quotes.push(quoteID);
      }
      if (!a) {
        return deadlink.textContent = "" + quote + "\u00A0(Dead)";
      }
      $.replace(deadlink, a);
      if ($.hasClass(a, 'quotelink')) {
        return this.nodes.quotelinks.push(a);
      }
    },
    fixDeadlink: function(deadlink) {
      var el, green;
      if (!(el = deadlink.previousSibling) || el.nodeName === 'BR') {
        green = $.el('span', {
          className: 'quote'
        });
        $.before(deadlink, green);
        $.add(green, deadlink);
      }
      return $.replace(deadlink, __slice.call(deadlink.childNodes));
    }
  };

  QR = {
    mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm'],
    init: function() {
      var sc;
      if (!Conf['Quick Reply']) {
        return;
      }
      this.db = new DataBoard('yourPosts');
      this.posts = [];
      if (Conf['QR Shortcut']) {
        sc = $.el('a', {
          className: "qr-shortcut fa fa-comment-o " + (!Conf['Persistent QR'] ? 'disabled' : ''),
          textContent: 'QR',
          title: 'Quick Reply',
          href: 'javascript:;'
        });
        $.on(sc, 'click', function() {
          if (Conf['Persistent QR'] || !QR.nodes || QR.nodes.el.hidden) {
            QR.open();
            QR.nodes.com.focus();
            return $.rmClass(this, 'disabled');
          } else {
            QR.close();
            return $.addClass(this, 'disabled');
          }
        });
        Header.addShortcut(sc);
      }
      if (Conf['Hide Original Post Form']) {
        $.asap((function() {
          return doc;
        }), function() {
          return $.addClass(doc, 'hide-original-post-form');
        });
      }
      $.on(d, '4chanXInitFinished', this.initReady);
      return Post.callbacks.push({
        name: 'Quick Reply',
        cb: this.node
      });
    },
    initReady: function() {
      var link, linkBot;
      $.off(d, '4chanXInitFinished', this.initReady);
      QR.postingIsEnabled = !!$.id('postForm');
      if (!QR.postingIsEnabled) {
        return;
      }
      link = $.el('h1', {
        className: "qr-link-container"
      });
      $.extend(link, {
        innerHTML: "<a href=\"javascript:;\" class=\"qr-link\">" + E((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + "</a>"
      });
      QR.link = link.firstElementChild;
      $.on(link.firstChild, 'click', function() {
        $.event('CloseMenu');
        QR.open();
        QR.nodes.com.focus();
        if (Conf['QR Shortcut']) {
          return $.rmClass($('.qr-shortcut'), 'disabled');
        }
      });
      if (Conf['Bottom QR Link'] && g.VIEW === 'thread') {
        linkBot = $.el('div', {
          className: "brackets-wrap qr-link-container-bottom"
        });
        $.extend(linkBot, {
          innerHTML: "<a href=\"javascript:;\" class=\"qr-link-bottom\">Reply to Thread</a>"
        });
        $.on(linkBot.firstElementChild, 'click', function() {
          $.event('CloseMenu');
          QR.open();
          QR.nodes.com.focus();
          if (Conf['QR Shortcut']) {
            return $.rmClass($('.qr-shortcut'), 'disabled');
          }
        });
        $.prepend($('.navLinksBot'), linkBot);
      }
      $.before($.id('togglePostFormLink'), link);
      $.on(d, 'paste', QR.paste);
      $.on(d, 'dragover', QR.dragOver);
      $.on(d, 'drop', QR.dropFile);
      $.on(d, 'dragstart dragend', QR.drag);
      $.on(d, 'IndexRefresh', QR.generatePostableThreadsList);
      $.on(d, 'ThreadUpdate', QR.statusCheck);
      if (!Conf['Persistent QR']) {
        return;
      }
      QR.open();
      if (Conf['Auto Hide QR']) {
        return QR.hide();
      }
    },
    statusCheck: function() {
      var thread;
      if (!QR.nodes) {
        return;
      }
      thread = QR.posts[0].thread;
      if (thread !== 'new' && g.threads["" + g.BOARD + "." + thread].isDead) {
        return QR.abort();
      } else {
        return QR.status();
      }
    },
    node: function() {
      return $.on($('a[title="Reply to this post"]', this.nodes.info), 'click', QR.quote);
    },
    open: function() {
      var err;
      if (QR.nodes) {
        QR.nodes.el.hidden = false;
        QR.unhide();
        return;
      }
      try {
        return QR.dialog();
      } catch (_error) {
        err = _error;
        delete QR.nodes;
        return Main.handleErrors({
          message: 'Quick Reply dialog creation crashed.',
          error: err
        });
      }
    },
    close: function() {
      var post, _i, _len, _ref;
      if (QR.req) {
        QR.abort();
        return;
      }
      QR.nodes.el.hidden = true;
      QR.cleanNotifications();
      d.activeElement.blur();
      $.rmClass(QR.nodes.el, 'dump');
      if (!Conf['Captcha Warning Notifications']) {
        if (QR.captcha.isEnabled) {
          $.rmClass(QR.captcha.nodes.input, 'error');
        }
      }
      if (Conf['QR Shortcut']) {
        $.toggleClass($('.qr-shortcut'), 'disabled');
      }
      new QR.post(true);
      _ref = QR.posts.splice(0, QR.posts.length - 1);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        post = _ref[_i];
        post["delete"]();
      }
      QR.cooldown.auto = false;
      QR.status();
      if (QR.captcha.isEnabled && !Conf['Auto-load captcha']) {
        return QR.captcha.destroy();
      }
    },
    focusin: function() {
      return $.addClass(QR.nodes.el, 'focus');
    },
    focusout: function() {
      return $.rmClass(QR.nodes.el, 'focus');
    },
    hide: function() {
      d.activeElement.blur();
      $.addClass(QR.nodes.el, 'autohide');
      return QR.nodes.autohide.checked = true;
    },
    unhide: function() {
      $.rmClass(QR.nodes.el, 'autohide');
      return QR.nodes.autohide.checked = false;
    },
    toggleHide: function() {
      if (this.checked) {
        return QR.hide();
      } else {
        return QR.unhide();
      }
    },
    error: function(err) {
      var el;
      QR.open();
      if (typeof err === 'string') {
        el = $.tn(err);
      } else {
        el = err;
        el.removeAttribute('style');
      }
      if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) {
        if (QR.captcha.captchas.length === 0) {
          QR.captcha.nodes.input.focus();
          QR.captcha.setup();
        }
        if (Conf['Captcha Warning Notifications'] && !d.hidden) {
          QR.notify(el);
        } else {
          $.addClass(QR.captcha.nodes.input, 'error');
          $.on(QR.captcha.nodes.input, 'keydown', function() {
            return $.rmClass(QR.captcha.nodes.input, 'error');
          });
        }
      } else {
        QR.notify(el);
      }
      if (d.hidden) {
        return alert(el.textContent);
      }
    },
    notify: function(el) {
      var notice, notif;
      notice = new Notice('warning', el);
      if (!(Header.areNotificationsEnabled && d.hidden)) {
        return QR.notifications.push(notice);
      } else {
        notif = new Notification(el.textContent, {
          body: el.textContent,
          icon: Favicon.logo
        });
        return notif.onclick = function() {
          return window.focus();
        };
      }
    },
    notifications: [],
    cleanNotifications: function() {
      var notification, _i, _len, _ref;
      _ref = QR.notifications;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        notification = _ref[_i];
        notification.close();
      }
      return QR.notifications = [];
    },
    status: function() {
      var disabled, status, thread, value;
      if (!QR.nodes) {
        return;
      }
      thread = QR.posts[0].thread;
      if (thread !== 'new' && g.threads["" + g.BOARD + "." + thread].isDead) {
        value = 404;
        disabled = true;
        QR.cooldown.auto = false;
      }
      value = QR.req ? QR.req.progress : QR.cooldown.seconds || value;
      status = QR.nodes.status;
      status.value = !value ? 'Submit' : QR.cooldown.auto ? "Auto " + value : value;
      return status.disabled = disabled || false;
    },
    quote: function(e) {
      var caretPos, com, index, post, range, s, sel, text, thread, _ref;
      if (e != null) {
        e.preventDefault();
      }
      if (!QR.postingIsEnabled) {
        return;
      }
      sel = d.getSelection();
      post = Get.postFromNode(this);
      text = ">>" + post + "\n";
      if ((s = sel.toString().trim()) && post === Get.postFromNode(sel.anchorNode)) {
        s = s.replace(/\n/g, '\n>');
        text += ">" + s + "\n";
      }
      QR.open();
      if (QR.selected.isLocked) {
        index = QR.posts.indexOf(QR.selected);
        (QR.posts[index + 1] || new QR.post()).select();
        $.addClass(QR.nodes.el, 'dump');
        QR.cooldown.auto = true;
      }
      _ref = QR.nodes, com = _ref.com, thread = _ref.thread;
      if (!com.value) {
        thread.value = Get.threadFromNode(this);
      }
      caretPos = com.selectionStart;
      com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd);
      range = caretPos + text.length;
      com.setSelectionRange(range, range);
      com.focus();
      QR.selected.save(com);
      QR.selected.save(thread);
      if (Conf['QR Shortcut']) {
        return $.rmClass($('.qr-shortcut'), 'disabled');
      }
    },
    characterCount: function() {
      var count, counter;
      counter = QR.nodes.charCount;
      count = QR.nodes.com.textLength;
      counter.textContent = count;
      counter.hidden = count < 1000;
      return (count > 1500 ? $.addClass : $.rmClass)(counter, 'warning');
    },
    drag: function(e) {
      var toggle;
      toggle = e.type === 'dragstart' ? $.off : $.on;
      toggle(d, 'dragover', QR.dragOver);
      return toggle(d, 'drop', QR.dropFile);
    },
    dragOver: function(e) {
      e.preventDefault();
      return e.dataTransfer.dropEffect = 'copy';
    },
    dropFile: function(e) {
      if (!e.dataTransfer.files.length) {
        return;
      }
      e.preventDefault();
      QR.open();
      return QR.handleFiles(e.dataTransfer.files);
    },
    paste: function(e) {
      var blob, files, item, _i, _len, _ref;
      files = [];
      _ref = e.clipboardData.items;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        item = _ref[_i];
        if (!(item.kind === 'file')) {
          continue;
        }
        blob = item.getAsFile();
        blob.name = 'file';
        if (blob.type) {
          blob.name += '.' + blob.type.split('/')[1];
        }
        files.push(blob);
      }
      if (!files.length) {
        return;
      }
      QR.open();
      QR.handleFiles(files);
      return $.addClass(QR.nodes.el, 'dump');
    },
    handleUrl: function() {
      var url;
      url = prompt('Enter a URL:');
      if (url === null) {
        return;
      }
      return CrossOrigin.file(url, function(blob) {
        if (blob) {
          return QR.handleFiles([blob]);
        } else {
          return QR.error("Can't load image.");
        }
      });
    },
    handleFiles: function(files) {
      var file, i, _i, _len;
      if (this !== QR) {
        files = __slice.call(this.files);
        this.value = null;
      }
      if (!files.length) {
        return;
      }
      QR.cleanNotifications();
      for (i = _i = 0, _len = files.length; _i < _len; i = ++_i) {
        file = files[i];
        QR.handleFile(file, i, files.length);
      }
      if (files.length !== 1) {
        return $.addClass(QR.nodes.el, 'dump');
      }
    },
    handleFile: function(file, index, nfiles) {
      var isNewPost, isSingle, max, post, _ref;
      isSingle = nfiles === 1;
      if (/^text\//.test(file.type)) {
        if (isSingle) {
          post = QR.selected;
        } else if (index !== 0 || (post = QR.posts[QR.posts.length - 1]).com) {
          post = new QR.post();
        }
        post.pasteText(file);
        return;
      }
      if (_ref = file.type, __indexOf.call(QR.mimeTypes, _ref) < 0) {
        QR.error("" + file.name + ": Unsupported file type.");
        if (!isSingle) {
          return;
        }
      }
      max = QR.nodes.fileInput.max;
      if (/^video\//.test(file.type)) {
        max = Math.min(max, QR.max_size_video);
      }
      if (file.size > max) {
        QR.error("" + file.name + ": File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
        if (!isSingle) {
          return;
        }
      }
      isNewPost = false;
      if (isSingle) {
        post = QR.selected;
      } else if (index !== 0 || (post = QR.posts[QR.posts.length - 1]).file) {
        isNewPost = true;
        post = new QR.post();
      }
      return QR.checkDimensions(file, function(pass, el) {
        if (pass || isSingle) {
          return post.setFile(file, el);
        } else if (isNewPost) {
          post.rm();
          if (el) {
            return URL.revokeObjectURL(el.src);
          }
        }
      });
    },
    checkDimensions: function(file, cb) {
      var img, video;
      if (/^image\//.test(file.type)) {
        img = new Image();
        img.onload = function() {
          var height, pass, width;
          height = img.height, width = img.width;
          pass = true;
          if (height > QR.max_height || width > QR.max_width) {
            QR.error("" + file.name + ": Image too large (image: " + height + "x" + width + "px, max: " + QR.max_height + "x" + QR.max_width + "px)");
            pass = false;
          }
          if (height < QR.min_height || width < QR.min_width) {
            QR.error("" + file.name + ": Image too small (image: " + height + "x" + width + "px, min: " + QR.min_height + "x" + QR.min_width + "px)");
            pass = false;
          }
          return cb(pass, img);
        };
        return img.src = URL.createObjectURL(file);
      } else if (/^video\//.test(file.type)) {
        video = $.el('video');
        $.on(video, 'loadeddata', function() {
          var duration, max_height, max_width, pass, videoHeight, videoWidth;
          if (!cb) {
            return;
          }
          videoHeight = video.videoHeight, videoWidth = video.videoWidth, duration = video.duration;
          max_height = Math.min(QR.max_height, QR.max_height_video);
          max_width = Math.min(QR.max_width, QR.max_width_video);
          pass = true;
          if (videoHeight > max_height || videoWidth > max_width) {
            QR.error("" + file.name + ": Video too large (video: " + videoHeight + "x" + videoWidth + "px, max: " + max_height + "x" + max_width + "px)");
            pass = false;
          }
          if (videoHeight < QR.min_height || videoWidth < QR.min_width) {
            QR.error("" + file.name + ": Video too small (video: " + videoHeight + "x" + videoWidth + "px, min: " + QR.min_height + "x" + QR.min_width + "px)");
            pass = false;
          }
          if (!isFinite(duration)) {
            QR.error("" + file.name + ": Video lacks duration metadata (try remuxing)");
            pass = false;
          } else if (duration > QR.max_duration_video) {
            QR.error("" + file.name + ": Video too long (video: " + duration + "s, max: " + QR.max_duration_video + "s)");
            pass = false;
          }
          if (video.mozHasAudio || video.webkitAudioDecodedByteCount) {
            QR.error("" + file.name + ": Audio not allowed");
            pass = false;
          }
          cb(pass, video);
          return cb = null;
        });
        $.on(video, 'error', function() {
          var _ref;
          if (!cb) {
            return;
          }
          if (_ref = file.type, __indexOf.call(QR.mimeTypes, _ref) >= 0) {
            QR.error("" + file.name + ": Video appears corrupt");
          }
          URL.revokeObjectURL(file);
          cb(false, null);
          return cb = null;
        });
        return video.src = URL.createObjectURL(file);
      } else {
        return cb(true, null);
      }
    },
    openFileInput: function(e) {
      var _ref;
      e.stopPropagation();
      if (e.shiftKey && e.type === 'click') {
        return QR.selected.rmFile();
      }
      if (e.ctrlKey && e.type === 'click') {
        $.addClass(QR.nodes.filename, 'edit');
        QR.nodes.filename.focus();
        return $.on(QR.nodes.filename, 'blur', function() {
          return $.rmClass(QR.nodes.filename, 'edit');
        });
      }
      if (e.target.nodeName === 'INPUT' || (e.keyCode && ((_ref = e.keyCode) !== 32 && _ref !== 13)) || e.ctrlKey) {
        return;
      }
      e.preventDefault();
      return QR.nodes.fileInput.click();
    },
    generatePostableThreadsList: function() {
      var list, options, thread, val, _i, _len, _ref;
      if (!QR.nodes) {
        return;
      }
      list = QR.nodes.thread;
      options = [list.firstChild];
      _ref = g.BOARD.threads.keys;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        thread = _ref[_i];
        options.push($.el('option', {
          value: thread,
          textContent: "Thread No." + thread
        }));
      }
      val = list.value;
      $.rmAll(list);
      $.add(list, options);
      list.value = val;
      if (list.value) {
        return;
      }
      list.value = g.VIEW === 'thread' ? g.THREADID : 'new';
      return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
    },
    dialog: function() {
      var dialog, elm, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode;
      QR.nodes = nodes = {
        el: dialog = UI.dialog('qr', 'top:0;right:0;', {
          innerHTML: "<div class=move><label><input type=checkbox id=autohide title=Auto-hide>Quick Reply</label><a href=javascript:; class=close title=Close>×</a><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><form><div class=persona><input name=name  data-name=name  list=\"list-name\" placeholder=Name    class=field size=1 tabindex=10><input name=email data-name=email list=\"list-email\" placeholder=Options class=field size=1 tabindex=20><input name=sub   data-name=sub   list=\"list-sub\" placeholder=Subject class=field size=1 tabindex=30> </div><div class=textarea><textarea data-name=com placeholder=Comment class=field tabindex=40></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\" tabindex=50>+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=60><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><a id=qr-filerm href=javascript:; title='Remove file'><i class=\"fa fa-times-circle\"></i></a><a id=url-button title='Post from url'><i class=\"fa fa-link\"></i></a><a id=dump-button title='Dump list'><i class=\"fa fa-plus-square\"></i></a></span></span><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70></label><input type=submit tabindex=80></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist> "
        })
      };
      setNode = function(name, query) {
        return nodes[name] = $(query, dialog);
      };
      setNode('move', '.move');
      setNode('autohide', '#autohide');
      setNode('thread', 'select');
      setNode('threadPar', '#qr-thread-select');
      setNode('close', '.close');
      setNode('form', 'form');
      setNode('dumpButton', '#dump-button');
      setNode('urlButton', '#url-button');
      setNode('name', '[data-name=name]');
      setNode('email', '[data-name=email]');
      setNode('sub', '[data-name=sub]');
      setNode('com', '[data-name=com]');
      setNode('dumpList', '#dump-list');
      setNode('addPost', '#add-post');
      setNode('charCount', '#char-count');
      setNode('fileSubmit', '#file-n-submit');
      setNode('filename', '#qr-filename');
      setNode('fileContainer', '#qr-filename-container');
      setNode('fileRM', '#qr-filerm');
      setNode('fileExtras', '#qr-extras-container');
      setNode('spoiler', '#qr-file-spoiler');
      setNode('spoilerPar', '#qr-spoiler-label');
      setNode('status', '[type=submit]');
      setNode('fileInput', '[type=file]');
      rules = $('ul.rules').textContent.trim();
      match_min = rules.match(/.+smaller than (\d+)x(\d+).+/);
      match_max = rules.match(/.+greater than (\d+)x(\d+).+/);
      QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1;
      QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1;
      QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000;
      QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000;
      nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
      QR.max_size_video = 3145728;
      QR.max_width_video = QR.max_height_video = 2048;
      QR.max_duration_video = 120;
      if (Conf['Show New Thread Option in Threads']) {
        $.addClass(QR.nodes.el, 'show-new-thread-option');
      }
      if (Conf['Show Name and Subject']) {
        $.addClass(QR.nodes.name, 'force-show');
        $.addClass(QR.nodes.sub, 'force-show');
        QR.nodes.email.placeholder = 'E-mail';
      }
      QR.forcedAnon = !!$('.postForm input[name=name][type=hidden]');
      if (QR.forcedAnon) {
        $.addClass(QR.nodes.el, 'forced-anon');
      }
      QR.spoiler = !!$('.postForm input[name=spoiler]');
      if (QR.spoiler) {
        $.addClass(QR.nodes.el, 'has-spoiler');
      } else {
        nodes.spoiler.parentElement.hidden = true;
      }
      if (g.BOARD.ID === 'f' && g.VIEW !== 'thread') {
        nodes.flashTag = $.el('select', {
          name: 'filetag'
        });
        $.extend(nodes.flashTag, {
          innerHTML: "<option value=\"0\">Hentai</option><option value=\"6\">Porn</option><option value=\"1\">Japanese</option><option value=\"2\">Anime</option><option value=\"3\">Game</option><option value=\"5\">Loop</option><option value=\"4\" selected>Other</option>"
        });
        nodes.flashTag.dataset["default"] = '4';
        $.add(nodes.form, nodes.flashTag);
      }
      QR.flagsInput();
      $.on(nodes.filename.parentNode, 'click keydown', QR.openFileInput);
      items = $$('*', QR.nodes.el);
      i = 0;
      while (elm = items[i++]) {
        $.on(elm, 'blur', QR.focusout);
        $.on(elm, 'focus', QR.focusin);
      }
      $.on(nodes.autohide, 'change', QR.toggleHide);
      $.on(nodes.close, 'click', QR.close);
      $.on(nodes.dumpButton, 'click', function() {
        return nodes.el.classList.toggle('dump');
      });
      $.on(nodes.urlButton, 'click', QR.handleUrl);
      $.on(nodes.addPost, 'click', function() {
        return new QR.post(true);
      });
      $.on(nodes.form, 'submit', QR.submit);
      $.on(nodes.fileRM, 'click', function() {
        return QR.selected.rmFile();
      });
      $.on(nodes.fileExtras, 'click', function(e) {
        return e.stopPropagation();
      });
      $.on(nodes.spoiler, 'change', function() {
        return QR.selected.nodes.spoiler.click();
      });
      $.on(nodes.fileInput, 'change', QR.handleFiles);
      items = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'];
      i = 0;
      save = function() {
        return QR.selected.save(this);
      };
      while (name = items[i++]) {
        if (!(node = nodes[name])) {
          continue;
        }
        event = node.nodeName === 'SELECT' ? 'change' : 'input';
        $.on(nodes[name], event, save);
      }
      if (Conf['Remember QR Size']) {
        $.get('QR Size', '', function(item) {
          return nodes.com.style.cssText = item['QR Size'];
        });
        $.on(nodes.com, 'mouseup', function(e) {
          if (e.button !== 0) {
            return;
          }
          return $.set('QR Size', this.style.cssText);
        });
      }
      QR.generatePostableThreadsList();
      QR.persona.init();
      new QR.post(true);
      QR.status();
      QR.cooldown.init();
      QR.captcha.init();
      $.add(d.body, dialog);
      return $.event('QRDialogCreation', null, dialog);
    },
    flags: function() {
      var flag, fn, select, _i, _len, _ref;
      select = $.el('select', {
        name: 'flag',
        className: 'flagSelector'
      });
      fn = function(val) {
        return $.add(select, $.el('option', {
          value: val[0],
          textContent: val[1]
        }));
      };
      _ref = [['0', 'None'], ['US', 'American'], ['KP', 'Best Korean'], ['BL', 'Black Nationalist'], ['CM', 'Communist'], ['CF', 'Confederate'], ['RE', 'Conservative'], ['EU', 'European'], ['GY', 'Gay'], ['PC', 'Hippie'], ['IL', 'Israeli'], ['DM', 'Liberal'], ['RP', 'Libertarian'], ['MF', 'Muslim'], ['NZ', 'Nazi'], ['OB', 'Obama'], ['PR', 'Pirate'], ['RB', 'Rebel'], ['TP', 'Tea Partier'], ['TX', 'Texan'], ['TR', 'Tree Hugger'], ['WP', 'White Supremacist']];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        flag = _ref[_i];
        fn(flag);
      }
      return select;
    },
    flagsInput: function() {
      var flag, nodes;
      nodes = QR.nodes;
      if (!nodes) {
        return;
      }
      if (nodes.flag) {
        $.rm(nodes.flag);
        delete nodes.flag;
      }
      if (g.BOARD.ID === 'pol') {
        flag = QR.flags();
        flag.dataset.name = 'flag';
        flag.dataset["default"] = '0';
        nodes.flag = flag;
        return $.add(nodes.form, flag);
      }
    },
    submit: function(e) {
      var challenge, err, extra, filetag, formData, options, post, response, textOnly, thread, threadID, _ref;
      if (e != null) {
        e.preventDefault();
      }
      if (QR.req) {
        QR.abort();
        return;
      }
      if (QR.cooldown.seconds) {
        QR.cooldown.auto = !QR.cooldown.auto;
        QR.status();
        return;
      }
      post = QR.posts[0];
      post.forceSave();
      if (g.BOARD.ID === 'f' && g.VIEW !== 'thread') {
        filetag = QR.nodes.flashTag.value;
      }
      threadID = post.thread;
      thread = g.BOARD.threads[threadID];
      if (threadID === 'new') {
        threadID = null;
        if (g.BOARD.ID === 'vg' && !post.sub) {
          err = 'New threads require a subject.';
        } else if (!(post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) {
          err = 'No file selected.';
        }
      } else if (g.BOARD.threads[threadID].isClosed) {
        err = 'You can\'t reply to this thread anymore.';
      } else if (!(post.com || post.file)) {
        err = 'No file selected.';
      } else if (post.file && thread.fileLimit) {
        err = 'Max limit of image replies has been reached.';
      }
      if (QR.captcha.isEnabled && !err) {
        _ref = QR.captcha.getOne(), challenge = _ref.challenge, response = _ref.response;
        if (!response) {
          err = 'No valid captcha.';
        }
      }
      QR.cleanNotifications();
      if (err) {
        QR.cooldown.auto = false;
        QR.status();
        QR.error(err);
        return;
      }
      QR.cooldown.auto = QR.posts.length > 1;
      if (Conf['Auto Hide QR'] && !QR.cooldown.auto) {
        QR.hide();
      }
      if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) {
        d.activeElement.blur();
      }
      post.lock();
      formData = {
        resto: threadID,
        name: !QR.forcedAnon ? post.name : void 0,
        email: post.email,
        sub: !(QR.forcedAnon || threadID) ? post.sub : void 0,
        com: post.com,
        upfile: post.file,
        filetag: filetag,
        spoiler: post.spoiler,
        flag: post.flag,
        textonly: textOnly,
        mode: 'regist',
        pwd: QR.persona.pwd,
        recaptcha_challenge_field: challenge,
        recaptcha_response_field: response
      };
      options = {
        responseType: 'document',
        withCredentials: true,
        onload: QR.response,
        onerror: function() {
          delete QR.req;
          post.unlock();
          QR.cooldown.auto = false;
          QR.status();
          return QR.error($.el('span', {
            innerHTML: "4chan X encountered an error while posting. [<a href=\"//4chan.org/banned\" target=\"_blank\">Banned?</a>] [<a href=\"" + E(g.FAQ) + "#what-does-4chan-x-encountered-an-error-while-posting-please-try-again-mean\" target=\"_blank\">More info</a>]"
          }));
        }
      };
      extra = {
        form: $.formData(formData),
        upCallbacks: {
          onload: function() {
            QR.req.isUploadFinished = true;
            QR.req.uploadEndTime = Date.now();
            QR.req.progress = '...';
            return QR.status();
          },
          onprogress: function(e) {
            QR.req.progress = "" + (Math.round(e.loaded / e.total * 100)) + "%";
            return QR.status();
          }
        }
      };
      QR.req = $.ajax("https://sys.4chan.org/" + g.BOARD + "/post", options, extra);
      QR.req.uploadStartTime = Date.now();
      QR.req.progress = '...';
      return QR.status();
    },
    response: function() {
      var URL, ban, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1;
      req = QR.req;
      delete QR.req;
      post = QR.posts[0];
      post.unlock();
      resDoc = req.response;
      if (ban = $('.banType', resDoc)) {
        err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? {
          innerHTML: "You are banned on " + $(".board", resDoc).innerHTML + "! ;_;<br>Click <a href=\"//www.4chan.org/banned\" target=\"_blank\">here</a> to see the reason."
        } : {
          innerHTML: "You were issued a warning on " + $(".board", resDoc).innerHTML + " as " + $(".nameBlock", resDoc).innerHTML + ".<br>Reason: " + $(".reason", resDoc).innerHTML
        });
      } else if (err = resDoc.getElementById('errmsg')) {
        if ((_ref = $('a', err)) != null) {
          _ref.target = '_blank';
        }
      } else if (resDoc.title !== 'Post successful!') {
        err = 'Connection error with sys.4chan.org.';
      } else if (req.status !== 200) {
        err = "Error " + req.statusText + " (" + req.status + ")";
      }
      if (err) {
        if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') {
          if (/mistyped/i.test(err.textContent)) {
            err = 'You seem to have mistyped the CAPTCHA.';
          } else if (/expired/i.test(err.textContent)) {
            err = 'This CAPTCHA is no longer valid because it has expired.';
          }
          QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
          QR.cooldown.set({
            delay: 2
          });
        } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
          QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
          QR.cooldown.set({
            delay: m[1]
          });
        } else {
          QR.cooldown.auto = false;
        }
        QR.status();
        QR.error(err);
        return;
      }
      h1 = $('h1', resDoc);
      QR.cleanNotifications();
      if (Conf['Posting Success Notifications']) {
        QR.notifications.push(new Notice('success', h1.textContent, 5));
      }
      QR.persona.set(post);
      _ref1 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref1[0], threadID = _ref1[1], postID = _ref1[2];
      postID = +postID;
      threadID = +threadID || postID;
      isReply = threadID !== postID;
      QR.db.set({
        boardID: g.BOARD.ID,
        threadID: threadID,
        postID: postID,
        val: true
      });
      ThreadUpdater.postID = postID;
      $.event('QRPostSuccessful', {
        boardID: g.BOARD.ID,
        threadID: threadID,
        postID: postID
      });
      $.event('QRPostSuccessful_', {
        boardID: g.BOARD.ID,
        threadID: threadID,
        postID: postID
      });
      postsCount = QR.posts.length - 1;
      QR.cooldown.auto = postsCount && isReply;
      if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) {
        notif = new Notification('Quick reply warning', {
          body: "You are running low on cached captchas. Cache count: " + captchasCount + ".",
          icon: Favicon.logo
        });
        notif.onclick = function() {
          QR.open();
          QR.captcha.nodes.input.focus();
          return window.focus();
        };
        notif.onshow = function() {
          return setTimeout(function() {
            return notif.close();
          }, 7 * $.SECOND);
        };
      }
      if (!(Conf['Persistent QR'] || QR.cooldown.auto)) {
        QR.close();
      } else {
        if (QR.posts.length > 1 && QR.captcha.isEnabled && QR.captcha.captchas.length === 0) {
          QR.captcha.setup();
        }
        post.rm();
      }
      QR.cooldown.set({
        req: req,
        post: post,
        isReply: isReply,
        threadID: threadID
      });
      URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
      if (URL) {
        if (Conf['Open Post in New Tab']) {
          $.open(URL);
        } else {
          window.location = URL;
        }
      }
      return QR.status();
    },
    abort: function() {
      if (QR.req && !QR.req.isUploadFinished) {
        QR.req.abort();
        delete QR.req;
        QR.posts[0].unlock();
        QR.cooldown.auto = false;
        QR.notifications.push(new Notice('info', 'QR upload aborted.', 5));
      }
      return QR.status();
    }
  };

  QR.captcha = {
    init: function() {
      var imgContainer, input;
      if (d.cookie.indexOf('pass_enabled=1') >= 0) {
        return;
      }
      if (!(this.isEnabled = !!$.id('captchaContainer'))) {
        return;
      }
      if (Conf['Auto-load captcha']) {
        $.globalEval('loadRecaptcha()');
      }
      imgContainer = $.el('div', {
        className: 'captcha-img',
        title: 'Reload reCAPTCHA'
      });
      $.extend(imgContainer, {
        innerHTML: "<img>"
      });
      input = $.el('input', {
        className: 'captcha-input field',
        title: 'Verification',
        autocomplete: 'off',
        spellcheck: false,
        tabIndex: 45
      });
      this.nodes = {
        img: imgContainer.firstChild,
        input: input
      };
      $.on(input, 'blur', QR.focusout);
      $.on(input, 'focus', QR.focusin);
      $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha));
      $.on(this.nodes.img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha));
      $.addClass(QR.nodes.el, 'has-captcha');
      $.after(QR.nodes.com.parentNode, [imgContainer, input]);
      this.captchas = [];
      $.get('captchas', [], function(_arg) {
        var captchas;
        captchas = _arg.captchas;
        QR.captcha.sync(captchas);
        return QR.captcha.clear();
      });
      $.sync('captchas', this.sync);
      new MutationObserver(this.afterSetup).observe($.id('captchaContainer'), {
        childList: true
      });
      this.beforeSetup();
      return this.afterSetup();
    },
    beforeSetup: function() {
      var img, input, _ref;
      _ref = this.nodes, img = _ref.img, input = _ref.input;
      img.parentNode.hidden = true;
      input.value = '';
      input.placeholder = 'Focus to load reCAPTCHA';
      this.count();
      return $.on(input, 'focus', this.setup);
    },
    setup: function() {
      return $.globalEval('loadRecaptcha()');
    },
    afterSetup: function() {
      var challenge, img, input, setLifetime, _ref;
      if (!(challenge = $.id('recaptcha_challenge_field_holder'))) {
        return;
      }
      if (challenge === QR.captcha.nodes.challenge) {
        return;
      }
      setLifetime = function(e) {
        return QR.captcha.lifetime = e.detail;
      };
      $.on(window, 'captcha:timeout', setLifetime);
      $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))');
      $.off(window, 'captcha:timeout', setLifetime);
      _ref = QR.captcha.nodes, img = _ref.img, input = _ref.input;
      img.parentNode.hidden = false;
      input.placeholder = 'Verification';
      QR.captcha.count();
      $.off(input, 'focus', QR.captcha.setup);
      QR.captcha.nodes.challenge = challenge;
      new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, {
        childList: true,
        subtree: true,
        attributes: true
      });
      QR.captcha.load();
      if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) {
        QR.nodes.el.style.top = null;
        return QR.nodes.el.style.bottom = '0px';
      }
    },
    destroy: function() {
      $.globalEval('Recaptcha.destroy()');
      return this.beforeSetup();
    },
    sync: function(captchas) {
      QR.captcha.captchas = captchas;
      return QR.captcha.count();
    },
    getOne: function() {
      var captcha, challenge, response;
      this.clear();
      if (captcha = this.captchas.shift()) {
        challenge = captcha.challenge, response = captcha.response;
        this.count();
        $.set('captchas', this.captchas);
      } else {
        challenge = this.nodes.img.alt;
        if (response = this.nodes.input.value) {
          if (Conf['Auto-load captcha']) {
            this.reload();
          } else {
            this.destroy();
          }
        }
      }
      return {
        challenge: challenge,
        response: response
      };
    },
    save: function() {
      var response;
      if (!/\S/.test(response = this.nodes.input.value)) {
        return;
      }
      this.nodes.input.value = '';
      this.captchas.push({
        challenge: this.nodes.img.alt,
        response: response,
        timeout: this.timeout
      });
      this.count();
      this.reload();
      return $.set('captchas', this.captchas);
    },
    clear: function() {
      var captcha, i, now, _i, _len, _ref;
      if (!this.captchas.length) {
        return;
      }
      now = Date.now();
      _ref = this.captchas;
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        captcha = _ref[i];
        if (captcha.timeout > now) {
          break;
        }
      }
      if (!i) {
        return;
      }
      this.captchas = this.captchas.slice(i);
      this.count();
      return $.set('captchas', this.captchas);
    },
    load: function() {
      var challenge, challenge_image;
      if (!this.nodes.challenge.firstChild) {
        return;
      }
      if (!(challenge_image = $.id('recaptcha_challenge_image'))) {
        return;
      }
      this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE;
      challenge = this.nodes.challenge.firstChild.value;
      this.nodes.img.alt = challenge;
      this.nodes.img.src = challenge_image.src;
      this.nodes.input.value = null;
      return this.clear();
    },
    count: function() {
      var count, placeholder;
      count = this.captchas ? this.captchas.length : 0;
      placeholder = this.nodes.input.placeholder.replace(/\ \(.*\)$/, '');
      placeholder += (function() {
        switch (count) {
          case 0:
            if (placeholder === 'Verification') {
              return ' (Shift + Enter to cache)';
            } else {
              return '';
            }
            break;
          case 1:
            return ' (1 cached captcha)';
          default:
            return " (" + count + " cached captchas)";
        }
      })();
      this.nodes.input.placeholder = placeholder;
      return this.nodes.input.alt = count;
    },
    reload: function(focus) {
      $.globalEval('Recaptcha.reload(); Recaptcha.should_focus = false;');
      if (focus) {
        return this.nodes.input.focus();
      }
    },
    keydown: function(e) {
      if (e.keyCode === 8 && !this.nodes.input.value) {
        this.reload();
      } else if (e.keyCode === 13 && e.shiftKey) {
        this.save();
      } else {
        return;
      }
      return e.preventDefault();
    }
  };

  QR.cooldown = {
    init: function() {
      var key, setTimers, type;
      if (!Conf['Cooldown']) {
        return;
      }
      setTimers = (function(_this) {
        return function(e) {
          return QR.cooldown.types = e.detail;
        };
      })(this);
      $.on(window, 'cooldown:timers', setTimers);
      $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
      $.off(window, 'cooldown:timers', setTimers);
      for (type in QR.cooldown.types) {
        QR.cooldown.types[type] = +QR.cooldown.types[type];
      }
      key = "cooldown." + g.BOARD;
      $.get(key, {}, function(item) {
        QR.cooldown.cooldowns = item[key];
        return QR.cooldown.start();
      });
      return $.sync(key, QR.cooldown.sync);
    },
    start: function() {
      if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) {
        return;
      }
      QR.cooldown.isCounting = true;
      return QR.cooldown.count();
    },
    sync: function(cooldowns) {
      var id;
      for (id in cooldowns) {
        QR.cooldown.cooldowns[id] = cooldowns[id];
      }
      return QR.cooldown.start();
    },
    set: function(data) {
      var cooldown, delay, isReply, post, req, start, threadID;
      if (!Conf['Cooldown']) {
        return;
      }
      req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay;
      start = req ? req.uploadEndTime : Date.now();
      if (delay) {
        cooldown = {
          delay: delay
        };
      } else {
        cooldown = {
          isReply: isReply,
          threadID: threadID
        };
      }
      QR.cooldown.cooldowns[start] = cooldown;
      $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
      return QR.cooldown.start();
    },
    unset: function(id) {
      delete QR.cooldown.cooldowns[id];
      if (Object.keys(QR.cooldown.cooldowns).length) {
        return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
      } else {
        return $["delete"]("cooldown." + g.BOARD);
      }
    },
    count: function() {
      var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref;
      if (!Object.keys(QR.cooldown.cooldowns).length) {
        $["delete"]("cooldown." + g.BOARD);
        delete QR.cooldown.isCounting;
        delete QR.cooldown.seconds;
        QR.status();
        return;
      }
      clearTimeout(QR.cooldown.timeout);
      QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
      now = Date.now();
      post = QR.posts[0];
      isReply = post.thread !== 'new';
      hasFile = !!post.file;
      seconds = null;
      _ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns;
      for (start in cooldowns) {
        cooldown = cooldowns[start];
        start = +start;
        if ('delay' in cooldown) {
          if (cooldown.delay) {
            seconds = Math.max(seconds, cooldown.delay--);
          } else {
            seconds = Math.max(seconds, 0);
            QR.cooldown.unset(start);
          }
          continue;
        }
        if (isReply === cooldown.isReply) {
          elapsed = Math.floor((now - start) / $.SECOND);
          if (elapsed < 0) {
            QR.cooldown.unset(start);
            continue;
          }
          type = !isReply ? 'thread' : hasFile ? 'image' : 'reply';
          maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0);
          if (!((start <= now && now <= start + maxTimer * $.SECOND))) {
            QR.cooldown.unset(start);
          }
          if (isReply && +post.thread === cooldown.threadID) {
            type += '_intra';
          }
          seconds = Math.max(seconds, types[type] - elapsed);
        }
      }
      update = seconds !== null || !!QR.cooldown.seconds;
      QR.cooldown.seconds = seconds;
      if (update) {
        QR.status();
      }
      if (seconds === 0 && QR.cooldown.auto && !QR.req) {
        return QR.submit();
      }
    }
  };

  QR.persona = {
    pwd: '',
    always: {},
    init: function() {
      QR.persona.getPassword();
      return $.get('QR.personas', Conf['QR.personas'], function(_arg) {
        var arr, item, personas, type, types, _i, _len, _ref;
        personas = _arg['QR.personas'];
        types = {
          name: [],
          email: [],
          sub: []
        };
        _ref = personas.split('\n');
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          item = _ref[_i];
          QR.persona.parseItem(item.trim(), types);
        }
        for (type in types) {
          arr = types[type];
          QR.persona.loadPersonas(type, arr);
        }
      });
    },
    parseItem: function(item, types) {
      var boards, match, type, val, _ref, _ref1, _ref2;
      if (item[0] === '#') {
        return;
      }
      if (!(match = item.match(/(name|options|email|subject|password):"(.*)"/i))) {
        return;
      }
      _ref = match, match = _ref[0], type = _ref[1], val = _ref[2];
      item = item.replace(match, '');
      boards = ((_ref1 = item.match(/boards:([^;]+)/i)) != null ? _ref1[1].toLowerCase() : void 0) || 'global';
      if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
        return;
      }
      if (type === 'password') {
        QR.persona.pwd = val;
        return;
      }
      if (type === 'options') {
        type = 'email';
      }
      if (type === 'subject') {
        type = 'sub';
      }
      if (/always/i.test(item)) {
        QR.persona.always[type] = val;
      }
      if (__indexOf.call(types[type], val) < 0) {
        return types[type].push(val);
      }
    },
    loadPersonas: function(type, arr) {
      var list, val, _i, _len;
      list = $("#list-" + type, QR.nodes.el);
      for (_i = 0, _len = arr.length; _i < _len; _i++) {
        val = arr[_i];
        if (val) {
          $.add(list, $.el('option', {
            textContent: val
          }));
        }
      }
    },
    getPassword: function() {
      var input, m, _ref;
      if (!QR.persona.pwd) {
        QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : ((_ref = $.id('delPassword')) != null ? _ref.value : void 0) || '';
      }
      return QR.persona.pwd;
    },
    get: function(cb) {
      return $.get('QR.persona', {}, function(_arg) {
        var persona;
        persona = _arg['QR.persona'];
        return cb(persona);
      });
    },
    set: function(post) {
      return $.get('QR.persona', {}, function(_arg) {
        var persona;
        persona = _arg['QR.persona'];
        persona = {
          name: post.name,
          email: /^sage$/.test(post.email) ? persona.email : post.email,
          flag: post.flag
        };
        return $.set('QR.persona', persona);
      });
    }
  };

  QR.post = (function() {
    function _Class(select) {
      this.select = __bind(this.select, this);
      var el, elm, event, prev, _i, _j, _len, _len1, _ref, _ref1;
      el = $.el('a', {
        className: 'qr-preview',
        draggable: true,
        href: 'javascript:;'
      });
      $.extend(el, {
        innerHTML: "<a class=\"remove fa fa-times-circle\" title=\"Remove\"></a><label hidden><input type=\"checkbox\"> Spoiler</label><span></span>"
      });
      this.nodes = {
        el: el,
        rm: el.firstChild,
        label: $('label', el),
        spoiler: $('input', el),
        span: el.lastChild
      };
      _ref = $$('*', el);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        elm = _ref[_i];
        $.on(elm, 'blur', QR.focusout);
        $.on(elm, 'focus', QR.focusin);
      }
      $.on(el, 'click', this.select);
      $.on(this.nodes.rm, 'click', (function(_this) {
        return function(e) {
          e.stopPropagation();
          return _this.rm();
        };
      })(this));
      $.on(this.nodes.label, 'click', (function(_this) {
        return function(e) {
          return e.stopPropagation();
        };
      })(this));
      $.on(this.nodes.spoiler, 'change', (function(_this) {
        return function(e) {
          _this.spoiler = e.target.checked;
          if (_this === QR.selected) {
            return QR.nodes.spoiler.checked = _this.spoiler;
          }
        };
      })(this));
      $.add(QR.nodes.dumpList, el);
      _ref1 = ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop'];
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        event = _ref1[_j];
        $.on(el, event.toLowerCase(), this[event]);
      }
      this.thread = g.VIEW === 'thread' ? g.THREADID : 'new';
      prev = QR.posts[QR.posts.length - 1];
      QR.posts.push(this);
      this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
      QR.persona.get((function(_this) {
        return function(persona) {
          _this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name;
          _this.email = 'email' in QR.persona.always ? QR.persona.always.email : prev && !/^sage$/.test(prev.email) ? prev.email : persona.email;
          _this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : '';
          if (QR.nodes.flag) {
            _this.flag = prev ? prev.flag : persona.flag;
          }
          if (QR.selected === _this) {
            return _this.load();
          }
        };
      })(this));
      if (select) {
        this.select();
      }
      this.unlock();
    }

    _Class.prototype.rm = function() {
      var index;
      this["delete"]();
      index = QR.posts.indexOf(this);
      if (QR.posts.length === 1) {
        new QR.post(true);
        $.rmClass(QR.nodes.el, 'dump');
      } else if (this === QR.selected) {
        (QR.posts[index - 1] || QR.posts[index + 1]).select();
      }
      QR.posts.splice(index, 1);
      return QR.status();
    };

    _Class.prototype["delete"] = function() {
      $.rm(this.nodes.el);
      return URL.revokeObjectURL(this.URL);
    };

    _Class.prototype.lock = function(lock) {
      var name, node, _i, _len, _ref;
      if (lock == null) {
        lock = true;
      }
      this.isLocked = lock;
      if (this !== QR.selected) {
        return;
      }
      _ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        name = _ref[_i];
        if (node = QR.nodes[name]) {
          node.disabled = lock;
        }
      }
      this.nodes.rm.style.visibility = lock ? 'hidden' : '';
      (lock ? $.off : $.on)(QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput);
      this.nodes.spoiler.disabled = lock;
      return this.nodes.el.draggable = !lock;
    };

    _Class.prototype.unlock = function() {
      return this.lock(false);
    };

    _Class.prototype.select = function() {
      var rectEl, rectList;
      if (QR.selected) {
        QR.selected.nodes.el.id = null;
        QR.selected.forceSave();
      }
      QR.selected = this;
      this.lock(this.isLocked);
      this.nodes.el.id = 'selected';
      rectEl = this.nodes.el.getBoundingClientRect();
      rectList = this.nodes.el.parentNode.getBoundingClientRect();
      this.nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
      return this.load();
    };

    _Class.prototype.load = function() {
      var name, node, _i, _len, _ref;
      _ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        name = _ref[_i];
        if (!(node = QR.nodes[name])) {
          continue;
        }
        node.value = this[name] || node.dataset["default"] || null;
      }
      (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
      this.showFileData();
      return QR.characterCount();
    };

    _Class.prototype.save = function(input) {
      var name, _ref;
      if (input.type === 'checkbox') {
        this.spoiler = input.checked;
        return;
      }
      name = input.dataset.name;
      this[name] = input.value || input.dataset["default"] || null;
      switch (name) {
        case 'thread':
          (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
          return QR.status();
        case 'com':
          this.nodes.span.textContent = this.com;
          QR.characterCount();
          if (QR.cooldown.auto && this === QR.posts[0] && (0 < (_ref = QR.cooldown.seconds) && _ref <= 5)) {
            return QR.cooldown.auto = false;
          }
          break;
        case 'filename':
          if (!this.file) {
            return;
          }
          this.file.newName = this.filename.replace(/[/\\]/g, '-');
          if (!/\.(jpe?g|png|gif|pdf|swf|webm)$/i.test(this.filename)) {
            this.file.newName += '.jpg';
          }
          return this.updateFilename();
      }
    };

    _Class.prototype.forceSave = function() {
      var name, node, _i, _len, _ref;
      if (this !== QR.selected) {
        return;
      }
      _ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag'];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        name = _ref[_i];
        if (!(node = QR.nodes[name])) {
          continue;
        }
        this.save(node);
      }
    };

    _Class.prototype.setFile = function(file, el) {
      this.file = file;
      this.filename = file.name;
      this.filesize = $.bytesToString(file.size);
      if (QR.spoiler) {
        this.nodes.label.hidden = false;
      }
      URL.revokeObjectURL(this.URL);
      if (this === QR.selected) {
        this.showFileData();
      } else {
        this.updateFilename();
      }
      if (el) {
        return this.setThumbnail(el);
      } else {
        return this.nodes.el.style.backgroundImage = null;
      }
    };

    _Class.prototype.setThumbnail = function(el) {
      var cv, height, isVideo, s, width;
      isVideo = el.tagName === 'VIDEO';
      s = 90 * 2 * window.devicePixelRatio;
      if (this.file.type === 'image/gif') {
        s *= 3;
      }
      if (isVideo) {
        height = el.videoHeight;
        width = el.videoWidth;
      } else {
        height = el.height, width = el.width;
        if (height < s || width < s) {
          this.URL = el.src;
          this.nodes.el.style.backgroundImage = "url(" + this.URL + ")";
          return;
        }
      }
      if (height <= width) {
        width = s / height * width;
        height = s;
      } else {
        height = s / width * height;
        width = s;
      }
      cv = $.el('canvas');
      cv.height = height;
      cv.width = width;
      cv.getContext('2d').drawImage(el, 0, 0, width, height);
      URL.revokeObjectURL(el.src);
      return cv.toBlob((function(_this) {
        return function(blob) {
          _this.URL = URL.createObjectURL(blob);
          return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")";
        };
      })(this));
    };

    _Class.prototype.rmFile = function() {
      if (this.isLocked) {
        return;
      }
      delete this.file;
      delete this.filename;
      delete this.filesize;
      this.nodes.el.title = null;
      QR.nodes.fileContainer.title = '';
      this.nodes.el.style.backgroundImage = null;
      if (QR.spoiler) {
        this.nodes.label.hidden = true;
      }
      this.showFileData();
      return URL.revokeObjectURL(this.URL);
    };

    _Class.prototype.updateFilename = function() {
      var long;
      long = "" + this.filename + " (" + this.filesize + ")\nCtrl+click to edit filename. Shift+click to clear.";
      this.nodes.el.title = long;
      if (this !== QR.selected) {
        return;
      }
      return QR.nodes.fileContainer.title = long;
    };

    _Class.prototype.showFileData = function() {
      if (this.file) {
        this.updateFilename();
        QR.nodes.filename.value = this.filename;
        QR.nodes.spoiler.checked = this.spoiler;
        return $.addClass(QR.nodes.fileSubmit, 'has-file');
      } else {
        return $.rmClass(QR.nodes.fileSubmit, 'has-file');
      }
    };

    _Class.prototype.pasteText = function(file) {
      var reader;
      reader = new FileReader();
      reader.onload = (function(_this) {
        return function(e) {
          var text;
          text = e.target.result;
          if (_this.com) {
            _this.com += "\n" + text;
          } else {
            _this.com = text;
          }
          if (QR.selected === _this) {
            QR.nodes.com.value = _this.com;
          }
          return _this.nodes.span.textContent = _this.com;
        };
      })(this);
      return reader.readAsText(file);
    };

    _Class.prototype.dragStart = function(e) {
      e.dataTransfer.setDragImage(this, e.layerX, e.layerY);
      return $.addClass(this, 'drag');
    };

    _Class.prototype.dragEnd = function() {
      return $.rmClass(this, 'drag');
    };

    _Class.prototype.dragEnter = function() {
      return $.addClass(this, 'over');
    };

    _Class.prototype.dragLeave = function() {
      return $.rmClass(this, 'over');
    };

    _Class.prototype.dragOver = function(e) {
      e.preventDefault();
      return e.dataTransfer.dropEffect = 'move';
    };

    _Class.prototype.drop = function() {
      var el, index, newIndex, oldIndex, post;
      $.rmClass(this, 'over');
      if (!this.draggable) {
        return;
      }
      el = $('.drag', this.parentNode);
      index = function(el) {
        return __slice.call(el.parentNode.children).indexOf(el);
      };
      oldIndex = index(el);
      newIndex = index(this);
      (oldIndex < newIndex ? $.after : $.before)(this, el);
      post = QR.posts.splice(oldIndex, 1)[0];
      QR.posts.splice(newIndex, 0, post);
      return QR.status();
    };

    return _Class;

  })();

  FappeTyme = {
    init: function() {
      var el, input, lc, type, _i, _len, _ref;
      if (!(Conf['Fappe Tyme'] || Conf['Werk Tyme']) || g.VIEW === 'catalog' || g.BOARD === 'f') {
        return;
      }
      _ref = ["Fappe", "Werk"];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        type = _ref[_i];
        if (!Conf["" + type + " Tyme"]) {
          continue;
        }
        lc = type.toLowerCase();
        el = UI.checkbox(lc, " " + type + " Tyme", false);
        el.title = "" + type + " Tyme";
        FappeTyme[lc] = input = el.firstElementChild;
        $.on(input, 'change', FappeTyme.cb.toggle.bind(input));
        Header.menu.addEntry({
          el: el,
          order: 97
        });
        if (Conf[lc]) {
          FappeTyme.cb.set(lc);
        }
      }
      return Post.callbacks.push({
        name: 'Fappe Tyme',
        cb: this.node
      });
    },
    node: function() {
      if (this.file) {
        return;
      }
      return $.addClass(this.nodes.root, "noFile");
    },
    cb: {
      set: function(type) {
        FappeTyme[type].checked = Conf[type];
        return $["" + (Conf[type] ? 'add' : 'rm') + "Class"](doc, "" + type + "Tyme");
      },
      toggle: function() {
        Conf[this.name] = !Conf[this.name];
        FappeTyme.cb.set(this.name);
        return $.cb.checked.call(FappeTyme[this.name]);
      }
    }
  };

  Gallery = {
    init: function() {
      var el;
      if (g.VIEW === 'catalog' || g.BOARD === 'f' || !Conf['Gallery']) {
        return;
      }
      this.delay = Conf['Slide Delay'];
      el = $.el('a', {
        href: 'javascript:;',
        id: 'appchan-gal',
        title: 'Gallery',
        className: 'fa fa-picture-o',
        textContent: 'Gallery'
      });
      $.on(el, 'click', this.cb.toggle);
      Header.addShortcut(el);
      return Post.callbacks.push({
        name: 'Gallery',
        cb: this.node
      });
    },
    node: function() {
      if (!this.file) {
        return;
      }
      if (Gallery.nodes) {
        Gallery.generateThumb(this);
        Gallery.nodes.total.textContent = Gallery.images.length;
      }
      if (!Conf['Image Expansion']) {
        return $.on(this.file.thumb.parentNode, 'click', Gallery.cb.image);
      }
    },
    build: function(image) {
      var candidate, cb, dialog, entry, file, key, menuButton, nodes, post, thumb, value, _i, _j, _len, _len1, _ref, _ref1, _ref2;
      Gallery.images = [];
      nodes = Gallery.nodes = {};
      Gallery.fullIDs = {};
      Gallery.slideshow = false;
      nodes.el = dialog = $.el('div', {
        id: 'a-gallery'
      });
      $.extend(dialog, {
        innerHTML: "<div class=\"gal-viewport\"><span class=\"gal-buttons\"><a href=\"javascript:;\" class=\"gal-start\" title=\"Start slideshow (S to toggle)\"><i></i></a><a href=\"javascript:;\" class=\"gal-stop\" title=\"Stop slideshow (S to toggle)\"><i></i></a><a href=\"javascript:;\" class=\"menu-button\"><i></i></a><a href=\"javascript:;\" class=\"gal-close\">×</a></span><a class=\"gal-name\" target=\"_blank\"></a><span class=\"gal-count\"><span class=\"count\"></span> / <span class=\"total\"></span></a></span><div class=\"gal-prev\"></div><div class=\"gal-image\"><a href=\"javascript:;\"><img></a></div><div class=\"gal-next\"></div></div><div class=\"gal-thumbnails\"></div>"
      });
      _ref = {
        buttons: '.gal-buttons',
        name: '.gal-name',
        count: '.count',
        total: '.total',
        frame: '.gal-image',
        next: '.gal-image a',
        current: '.gal-image img',
        thumbs: '.gal-thumbnails'
      };
      for (key in _ref) {
        value = _ref[key];
        nodes[key] = $(value, dialog);
      }
      menuButton = $('.menu-button', dialog);
      nodes.menu = new UI.Menu('gallery');
      cb = Gallery.cb;
      $.on(nodes.frame, 'click', cb.blank);
      $.on(nodes.next, 'click', cb.click);
      $.on($('.gal-prev', dialog), 'click', cb.prev);
      $.on($('.gal-next', dialog), 'click', cb.next);
      $.on($('.gal-start', dialog), 'click', cb.start);
      $.on($('.gal-stop', dialog), 'click', cb.stop);
      $.on($('.gal-close', dialog), 'click', cb.close);
      $.on(menuButton, 'click', function(e) {
        return nodes.menu.toggle(e, this, g);
      });
      _ref1 = Gallery.menu.createSubEntries();
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        entry = _ref1[_i];
        entry.order = 0;
        nodes.menu.addEntry(entry);
      }
      $.on(d, 'keydown', cb.keybinds);
      $.off(d, 'keydown', Keybinds.keydown);
      _ref2 = $$('.post .file');
      for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
        file = _ref2[_j];
        if (!(!$('.fileDeletedRes, .fileDeleted', file))) {
          continue;
        }
        post = Get.postFromNode(file);
        Gallery.generateThumb(post);
        if (!image && Gallery.fullIDs[post.fullID]) {
          candidate = post.file.thumb.parentNode;
          if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) {
            image = candidate;
          }
        }
      }
      $.add(d.body, dialog);
      nodes.thumbs.scrollTop = 0;
      nodes.current.parentElement.scrollTop = 0;
      if (image) {
        thumb = $("[href='" + image.href + "']", nodes.thumbs);
      }
      thumb || (thumb = Gallery.images[Gallery.images.length - 1]);
      if (thumb) {
        Gallery.open(thumb);
      }
      doc.style.overflow = 'hidden';
      return nodes.total.textContent = Gallery.images.length;
    },
    generateThumb: function(post) {
      var thumb, thumbImg;
      if (post.isClone || post.isHidden) {
        return;
      }
      if (!(post.file && (post.file.isImage || post.file.isVideo || Conf['PDF in Gallery']))) {
        return;
      }
      if (Gallery.fullIDs[post.fullID]) {
        return;
      }
      Gallery.fullIDs[post.fullID] = true;
      thumb = $.el('a', {
        className: 'gal-thumb',
        href: post.file.URL,
        target: '_blank',
        title: post.file.name
      });
      thumb.dataset.id = Gallery.images.length;
      thumb.dataset.post = post.fullID;
      thumbImg = post.file.thumb.cloneNode(false);
      thumbImg.style.cssText = '';
      $.add(thumb, thumbImg);
      $.on(thumb, 'click', Gallery.cb.open);
      Gallery.images.push(thumb);
      return $.add(Gallery.nodes.thumbs, thumb);
    },
    open: function(thumb) {
      var el, elType, file, name, newID, nodes, oldID, post, slideshow, _base, _ref;
      nodes = Gallery.nodes;
      name = nodes.name;
      oldID = +nodes.current.dataset.id;
      newID = +thumb.dataset.id;
      slideshow = Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0));
      if (el = $('.gal-highlight', nodes.thumbs)) {
        $.rmClass(el, 'gal-highlight');
      }
      $.addClass(thumb, 'gal-highlight');
      elType = 'img';
      if (/\.webm$/.test(thumb.href)) {
        elType = 'video';
      }
      if (/\.pdf$/.test(thumb.href)) {
        elType = 'iframe';
      }
      $[elType === 'iframe' ? 'addClass' : 'rmClass'](doc, 'gal-pdf');
      file = $.el(elType, {
        title: name.download = name.textContent = thumb.title
      });
      $.on(file, 'error', (function(_this) {
        return function() {
          return Gallery.error(file, thumb);
        };
      })(this));
      file.src = name.href = thumb.href;
      $.extend(file.dataset, thumb.dataset);
      if (!nodes.current.error) {
        if (typeof (_base = nodes.current).pause === "function") {
          _base.pause();
        }
      }
      $.replace(nodes.current, file);
      if (elType === 'video') {
        file.loop = true;
        if (Conf['Autoplay']) {
          file.play();
        }
        if (Conf['Show Controls']) {
          ImageCommon.addControls(file);
        }
      }
      nodes.count.textContent = +thumb.dataset.id + 1;
      nodes.current = file;
      nodes.frame.scrollTop = 0;
      nodes.next.focus();
      if (slideshow) {
        Gallery.setupTimer();
      } else {
        Gallery.cb.stop();
      }
      if (Conf['Scroll to Post'] && (post = (_ref = (post = g.posts[file.dataset.post])) != null ? _ref.nodes.root : void 0)) {
        Header.scrollTo(post);
      }
      return nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight / 2 - nodes.thumbs.clientHeight / 2;
    },
    error: function(file, thumb) {
      var _ref;
      if (((_ref = file.error) != null ? _ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) {
        return new Notice('error', 'Corrupt or unplayable video', 30);
      }
      if (file.src.split('/')[2] !== 'i.4cdn.org') {
        return;
      }
      return ImageCommon.error(file, g.posts[file.dataset.post], null, function(URL) {
        if (!URL) {
          return;
        }
        thumb.href = URL;
        if (Gallery.nodes.current === file) {
          return file.src = URL;
        }
      });
    },
    cleanupTimer: function() {
      var current;
      clearTimeout(Gallery.timeoutID);
      current = Gallery.nodes.current;
      $.off(current, 'canplaythrough load', Gallery.startTimer);
      return $.off(current, 'ended', Gallery.cb.next);
    },
    startTimer: function() {
      return Gallery.timeoutID = setTimeout(Gallery.checkTimer, Gallery.delay * $.SECOND);
    },
    setupTimer: function() {
      var current, isVideo;
      Gallery.cleanupTimer();
      current = Gallery.nodes.current;
      isVideo = current.nodeName === 'VIDEO';
      if (isVideo) {
        current.play();
      }
      if ((isVideo ? current.readyState > 4 : current.complete) || current.nodeName === 'IFRAME') {
        return Gallery.startTimer();
      } else {
        return $.on(current, (isVideo ? 'canplaythrough' : 'load'), Gallery.startTimer);
      }
    },
    checkTimer: function() {
      var current;
      current = Gallery.nodes.current;
      if (current.nodeName === 'VIDEO' && !current.paused) {
        $.on(current, 'ended', Gallery.cb.next);
        return current.loop = false;
      } else {
        return Gallery.cb.next();
      }
    },
    cb: {
      keybinds: function(e) {
        var cb, key;
        if (!(key = Keybinds.keyCode(e))) {
          return;
        }
        cb = (function() {
          switch (key) {
            case 'Esc':
            case Conf['Open Gallery']:
              return Gallery.cb.close;
            case 'Right':
              return Gallery.cb.next;
            case 'Enter':
              return Gallery.cb.enterKey;
            case 'Left':
            case '':
              return Gallery.cb.prev;
            case 'p':
              return Gallery.cb.pause;
            case 's':
              return Gallery.cb.toggleSlideshow;
          }
        })();
        if (!cb) {
          return;
        }
        e.stopPropagation();
        e.preventDefault();
        return cb();
      },
      open: function(e) {
        if (e) {
          e.preventDefault();
        }
        if (this) {
          return Gallery.open(this);
        }
      },
      image: function(e) {
        e.preventDefault();
        e.stopPropagation();
        return Gallery.build(this);
      },
      prev: function() {
        return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id - 1] || Gallery.images[Gallery.images.length - 1]);
      },
      next: function() {
        return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id + 1] || Gallery.images[0]);
      },
      enterKey: function() {
        if (Gallery.nodes.current.paused) {
          return Gallery.nodes.current.play();
        } else {
          return Gallery.cb.next();
        }
      },
      click: function() {
        return Gallery.cb[Gallery.nodes.current.controls ? 'stop' : 'enterKey']();
      },
      toggle: function() {
        return (Gallery.nodes ? Gallery.cb.close : Gallery.build)();
      },
      blank: function(e) {
        if (e.target === this) {
          return Gallery.cb.close();
        }
      },
      toggleSlideshow: function() {
        return Gallery.cb[Gallery.slideshow ? 'stop' : 'start']();
      },
      pause: function() {
        var current;
        Gallery.cb.stop();
        current = Gallery.nodes.current;
        if (current.nodeName === 'VIDEO') {
          return current[current.paused ? 'play' : 'pause']();
        }
      },
      start: function() {
        $.addClass(Gallery.nodes.buttons, 'gal-playing');
        Gallery.slideshow = true;
        return Gallery.setupTimer();
      },
      stop: function() {
        var current;
        if (!Gallery.slideshow) {
          return;
        }
        Gallery.cleanupTimer();
        current = Gallery.nodes.current;
        if (current.nodeName === 'VIDEO') {
          current.loop = true;
        }
        $.rmClass(Gallery.nodes.buttons, 'gal-playing');
        return Gallery.slideshow = false;
      },
      close: function() {
        var _base;
        if (typeof (_base = Gallery.nodes.current).pause === "function") {
          _base.pause();
        }
        $.rm(Gallery.nodes.el);
        delete Gallery.nodes;
        delete Gallery.fullIDs;
        doc.style.overflow = '';
        $.off(d, 'keydown', Gallery.cb.keybinds);
        $.on(d, 'keydown', Keybinds.keydown);
        return clearTimeout(Gallery.timeoutID);
      },
      setFitness: function() {
        return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-')));
      },
      setDelay: function() {
        return Gallery.delay = +this.value;
      }
    },
    menu: {
      init: function() {
        var el;
        if (g.VIEW === 'catalog' || !Conf['Gallery']) {
          return;
        }
        el = $.el('span', {
          textContent: 'Gallery',
          className: 'gallery-link'
        });
        return Header.menu.addEntry({
          el: el,
          order: 105,
          subEntries: Gallery.menu.createSubEntries()
        });
      },
      createSubEntry: function(name) {
        var input, label;
        label = UI.checkbox(name, " " + name);
        input = label.firstElementChild;
        $.on(input, 'change', Gallery.cb.setFitness);
        $.event('change', null, input);
        $.on(input, 'change', $.cb.checked);
        return {
          el: label
        };
      },
      createSubEntries: function() {
        var delayInput, delayLabel, subEntries;
        subEntries = ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Scroll to Post'].map(Gallery.menu.createSubEntry);
        delayLabel = $.el('label', {
          innerHTML: "Slide Delay: <input type=\"number\" name=\"Slide Delay\" min=\"0\" step=\"any\" class=\"field\">"
        });
        delayInput = delayLabel.firstElementChild;
        delayInput.value = Gallery.delay;
        $.on(delayInput, 'change', Gallery.cb.setDelay);
        $.on(delayInput, 'change', $.cb.value);
        subEntries.push({
          el: delayLabel
        });
        return subEntries;
      }
    }
  };

  ImageCommon = {
    decodeError: function(file, post) {
      var message, _ref;
      if (((_ref = file.error) != null ? _ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) {
        return false;
      }
      if (!(message = $('.warning', post.file.thumb.parentNode))) {
        message = $.el('div', {
          className: 'warning'
        });
        $.after(post.file.thumb, message);
      }
      message.textContent = 'Error: Corrupt or unplayable video';
      return true;
    },
    error: function(file, post, delay, cb) {
      var URL, redirect, src, timeoutID;
      src = post.file.URL.split('/');
      URL = Redirect.to('file', {
        boardID: post.board.ID,
        filename: src[src.length - 1]
      });
      if (!(Conf['404 Redirect'] && URL && Redirect.securityCheck(URL))) {
        URL = null;
      }
      if ((post.isDead || post.file.isDead) && file.src.split('/')[2] === 'i.4cdn.org') {
        return cb(URL);
      }
      if (delay != null) {
        timeoutID = setTimeout((function() {
          return cb(URL);
        }), delay);
      }
      if (post.isDead || post.file.isDead) {
        return;
      }
      redirect = function() {
        if (file.src.split('/')[2] === 'i.4cdn.org') {
          if (delay != null) {
            clearTimeout(timeoutID);
          }
          return cb(URL);
        }
      };
      return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
        onload: function() {
          var postObj, _i, _len, _ref;
          if (this.status === 404) {
            post.kill();
          }
          if (this.status !== 200) {
            return redirect();
          }
          _ref = this.response.posts;
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            postObj = _ref[_i];
            if (postObj.no === post.ID) {
              break;
            }
          }
          if (postObj.no !== post.ID) {
            post.kill();
            return redirect();
          } else if (postObj.filedeleted) {
            post.kill(true);
            return redirect();
          } else {
            return URL = post.file.URL;
          }
        }
      });
    },
    addControls: function(video) {
      var handler;
      handler = function() {
        var t;
        $.off(video, 'mouseover', handler);
        t = new Date().getTime();
        return $.asap((function() {
          return (typeof chrome !== "undefined" && chrome !== null) || (video.readyState >= 3 && video.currentTime <= Math.max(0.1, video.duration - 0.5)) || new Date().getTime() >= t + 1000;
        }), function() {
          return video.controls = true;
        });
      };
      return $.on(video, 'mouseover', handler);
    }
  };

  ImageExpand = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
        return;
      }
      this.EAI = $.el('a', {
        className: 'expand-all-shortcut fa fa-expand',
        textContent: 'EAI',
        title: 'Expand All Images',
        href: 'javascript:;'
      });
      $.on(this.EAI, 'click', this.cb.toggleAll);
      Header.addShortcut(this.EAI, 3);
      $.on(d, 'scroll visibilitychange', this.cb.playVideos);
      this.videoControls = $.el('span', {
        className: 'video-controls'
      });
      $.extend(this.videoControls, {
        innerHTML: " <a href=\"javascript:;\" title=\"You can also contract the video by dragging it to the left.\">contract</a>"
      });
      return Post.callbacks.push({
        name: 'Image Expansion',
        cb: this.node
      });
    },
    node: function() {
      var _ref;
      if (!(this.file && (this.file.isImage || this.file.isVideo))) {
        return;
      }
      $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle);
      if (this.isClone) {
        if (this.file.isExpanding) {
          ImageExpand.contract(this);
          return ImageExpand.expand(this);
        } else if (this.file.isExpanded && this.file.isVideo) {
          ImageExpand.setupVideoCB(this);
          return ImageExpand.setupVideo(this, !((_ref = this.origin.file.fullImage) != null ? _ref.paused : void 0) || this.origin.file.wasPlaying, this.file.fullImage.controls);
        }
      } else if (ImageExpand.on && !this.isHidden && !this.isFetchedQuote && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
        return ImageExpand.expand(this);
      }
    },
    cb: {
      toggle: function(e) {
        var file, post;
        if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
          return;
        }
        post = Get.postFromNode(this);
        file = post.file;
        if (file.isExpanded && file.isVideo && file.fullImage.controls) {
          return;
        }
        e.preventDefault();
        return ImageExpand.toggle(post);
      },
      toggleAll: function() {
        var func, toggle;
        $.event('CloseMenu');
        toggle = function(post) {
          var file;
          file = post.file;
          if (!(file && (file.isImage || file.isVideo) && doc.contains(post.nodes.root))) {
            return;
          }
          if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) {
            return;
          }
          return $.queueTask(func, post);
        };
        if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
          ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress';
          ImageExpand.EAI.title = 'Contract All Images';
          func = function(post) {
            return ImageExpand.expand(post);
          };
        } else {
          ImageExpand.EAI.className = 'expand-all-shortcut fa fa-expand';
          ImageExpand.EAI.title = 'Expand All Images';
          func = ImageExpand.contract;
        }
        return g.posts.forEach(function(post) {
          var _i, _len, _ref;
          _ref = [post].concat(__slice.call(post.clones));
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            post = _ref[_i];
            toggle(post);
          }
        });
      },
      playVideos: function(e) {
        return g.posts.forEach(function(post) {
          var video, visible, _i, _len, _ref;
          _ref = [post].concat(__slice.call(post.clones));
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            post = _ref[_i];
            if (!(post.file && post.file.isVideo && post.file.isExpanded)) {
              continue;
            }
            video = post.file.fullImage;
            visible = Header.isNodeVisible(video);
            if (visible && post.file.wasPlaying) {
              delete post.file.wasPlaying;
              video.play();
            } else if (!visible && !video.paused) {
              post.file.wasPlaying = true;
              video.pause();
            }
          }
        });
      },
      setFitness: function() {
        return (this.checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-'));
      }
    },
    toggle: function(post) {
      var headRect, left, root, top, x, y, _ref;
      if (!(post.file.isExpanding || post.file.isExpanded)) {
        post.file.scrollIntoView = Conf['Scroll into view'];
        ImageExpand.expand(post);
        return;
      }
      ImageExpand.contract(post);
      root = post.nodes.root;
      _ref = (Conf['Advance on contract'] ? (function() {
        var next;
        next = root;
        while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) {
          if ($('.stub', next) || next.offsetHeight === 0) {
            continue;
          }
          return next;
        }
        return root;
      })() : root).getBoundingClientRect(), top = _ref.top, left = _ref.left;
      if (top < 0) {
        y = top;
        if (Conf['Fixed Header'] && !Conf['Bottom Header']) {
          headRect = Header.bar.getBoundingClientRect();
          y -= headRect.top + headRect.height;
        }
      }
      if (left < 0) {
        x = -window.scrollX;
      }
      if (x || y) {
        return window.scrollBy(x, y);
      }
    },
    contract: function(post) {
      var bottom, cb, el, eventName, file, oldHeight, x, _i, _len, _ref, _ref1;
      file = post.file;
      bottom = post.nodes.root.getBoundingClientRect().bottom;
      oldHeight = d.body.clientHeight;
      $.rmClass(post.nodes.root, 'expanded-image');
      $.rmClass(file.thumb, 'expanding');
      if (file.videoControls) {
        $.rm(file.videoControls);
      }
      file.thumb.parentNode.href = file.URL;
      file.thumb.parentNode.target = '_blank';
      _ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView'];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        x = _ref[_i];
        delete file[x];
      }
      if (doc.contains(post.nodes.root) && bottom <= 0) {
        window.scrollBy(0, d.body.clientHeight - oldHeight);
      }
      if (el = file.fullImage) {
        $.off(el, 'error', ImageExpand.error);
        if (file.isVideo) {
          el.pause();
          _ref1 = ImageExpand.videoCB;
          for (eventName in _ref1) {
            cb = _ref1[eventName];
            $.off(el, eventName, cb);
          }
        }
        return TrashQueue.add(el, post);
      }
    },
    expand: function(post, src) {
      var el, file, isVideo, thumb;
      file = post.file;
      thumb = file.thumb, isVideo = file.isVideo;
      if (post.isHidden || file.isExpanding || file.isExpanded) {
        return;
      }
      $.addClass(thumb, 'expanding');
      file.isExpanding = true;
      el = file.fullImage || $.el((isVideo ? 'video' : 'img'), {
        className: 'full-image'
      });
      $.on(el, 'error', ImageExpand.error);
      if (file.fullImage) {
        TrashQueue.remove(el);
        if (!file.isHovered) {
          if (/\.gif$/.test(el.src)) {
            $.queueTask(function() {
              return el.src = el.src;
            });
          }
          if (isVideo && el.readyState >= el.HAVE_METADATA) {
            el.currentTime = 0;
          }
        }
      } else {
        el.src = src || file.URL;
        $.after(thumb, el);
        file.fullImage = el;
      }
      if (isVideo) {
        if (Conf['Show Controls'] && !file.videoControls) {
          file.videoControls = ImageExpand.videoControls.cloneNode(true);
          $.add(file.text, file.videoControls);
        }
        thumb.parentNode.removeAttribute('href');
        thumb.parentNode.removeAttribute('target');
        el.loop = true;
        ImageExpand.setupVideoCB(post);
      }
      if (!isVideo) {
        return $.asap((function() {
          return el.naturalHeight;
        }), function() {
          return ImageExpand.completeExpand(post);
        });
      } else if (el.readyState >= el.HAVE_METADATA) {
        return ImageExpand.completeExpand(post);
      } else {
        return $.on(el, 'loadedmetadata', function() {
          return ImageExpand.completeExpand(post);
        });
      }
    },
    completeExpand: function(post) {
      var bottom, file, imageBottom, oldHeight;
      file = post.file;
      if (!file.isExpanding) {
        return;
      }
      bottom = post.nodes.root.getBoundingClientRect().bottom;
      oldHeight = d.body.clientHeight;
      $.addClass(post.nodes.root, 'expanded-image');
      $.rmClass(file.thumb, 'expanding');
      file.isExpanded = true;
      delete file.isExpanding;
      if (doc.contains(post.nodes.root) && bottom <= 0) {
        window.scrollBy(0, d.body.clientHeight - oldHeight);
      }
      if (file.scrollIntoView) {
        delete file.scrollIntoView;
        imageBottom = Header.getBottomOf(file.fullImage) - 25;
        if (imageBottom < 0) {
          window.scrollBy(0, Math.min(-imageBottom, Header.getTopOf(file.fullImage)));
        }
      }
      if (file.isVideo) {
        return ImageExpand.setupVideo(post, Conf['Autoplay'], Conf['Show Controls']);
      }
    },
    setupVideo: function(post, playing, controls) {
      var fullImage;
      fullImage = post.file.fullImage;
      if (!playing) {
        fullImage.controls = controls;
        return;
      }
      fullImage.controls = false;
      $.asap(((function(_this) {
        return function() {
          return doc.contains(fullImage);
        };
      })(this)), (function(_this) {
        return function() {
          if (!d.hidden && Header.isNodeVisible(fullImage)) {
            return fullImage.play();
          } else {
            return post.file.wasPlaying = true;
          }
        };
      })(this));
      if (controls) {
        return ImageCommon.addControls(fullImage);
      }
    },
    videoCB: (function() {
      var mousedown;
      mousedown = false;
      return {
        mouseover: function() {
          return mousedown = false;
        },
        mousedown: function(e) {
          if (e.button === 0) {
            return mousedown = true;
          }
        },
        mouseup: function(e) {
          if (e.button === 0) {
            return mousedown = false;
          }
        },
        mouseout: function(e) {
          if (mousedown && e.clientX <= this.getBoundingClientRect().left) {
            return ImageExpand.toggle(Get.postFromNode(this));
          }
        },
        click: function(e) {
          if (this.paused && !this.controls) {
            this.play();
            return e.stopPropagation();
          }
        }
      };
    })(),
    setupVideoCB: function(post) {
      var cb, eventName, _ref;
      _ref = ImageExpand.videoCB;
      for (eventName in _ref) {
        cb = _ref[eventName];
        $.on(post.file.fullImage, eventName, cb);
      }
      if (post.file.videoControls) {
        return $.on(post.file.videoControls.firstElementChild, 'click', function() {
          return ImageExpand.toggle(post);
        });
      }
    },
    error: function() {
      var post;
      post = Get.postFromNode(this);
      $.rm(this);
      delete post.file.fullImage;
      if (!(post.file.isExpanding || post.file.isExpanded)) {
        return;
      }
      if (ImageCommon.decodeError(this, post)) {
        return ImageExpand.contract(post);
      }
      if (this.src.split('/')[2] !== 'i.4cdn.org') {
        return ImageExpand.contract(post);
      }
      return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) {
        if (post.file.isExpanding || post.file.isExpanded) {
          ImageExpand.contract(post);
          if (URL) {
            return ImageExpand.expand(post, URL);
          }
        }
      });
    },
    menu: {
      init: function() {
        var conf, createSubEntry, el, name, subEntries, _ref;
        if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
          return;
        }
        el = $.el('span', {
          textContent: 'Image Expansion',
          className: 'image-expansion-link'
        });
        createSubEntry = ImageExpand.menu.createSubEntry;
        subEntries = [];
        _ref = Config.imageExpansion;
        for (name in _ref) {
          conf = _ref[name];
          subEntries.push(createSubEntry(name, conf[1]));
        }
        return Header.menu.addEntry({
          el: el,
          order: 105,
          subEntries: subEntries
        });
      },
      createSubEntry: function(name, desc) {
        var input, label;
        label = UI.checkbox(name, " " + name);
        label.title = desc;
        input = label.firstElementChild;
        if (name === 'Fit width' || name === 'Fit height') {
          $.on(input, 'change', ImageExpand.cb.setFitness);
        }
        $.event('change', null, input);
        $.on(input, 'change', $.cb.checked);
        return {
          el: label
        };
      }
    }
  };

  ImageHover = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Image Hover']) {
        return;
      }
      return Post.callbacks.push({
        name: 'Image Hover',
        cb: this.node
      });
    },
    node: function() {
      var _ref, _ref1;
      if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) {
        return;
      }
      return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover);
    },
    mouseover: function(e) {
      var el, file, isVideo, post;
      if (!doc.contains(this)) {
        return;
      }
      post = Get.postFromNode(this);
      file = post.file;
      isVideo = file.isVideo;
      if (file.isExpanding || file.isExpanded) {
        return;
      }
      file.isHovered = true;
      if (el = file.fullImage) {
        el.id = 'ihover';
        TrashQueue.remove(el);
        if (/\.gif$/.test(el.src)) {
          $.queueTask(function() {
            return el.src = el.src;
          });
        }
        if (isVideo && el.readyState >= el.HAVE_METADATA) {
          el.currentTime = 0;
        }
      } else {
        file.fullImage = el = $.el((isVideo ? 'video' : 'img'), {
          className: 'full-image',
          id: 'ihover'
        });
        $.on(el, 'error', ImageHover.error);
        el.src = file.URL;
        $.after(file.thumb, el);
      }
      if (isVideo) {
        el.loop = true;
        el.controls = false;
        if (Conf['Autoplay']) {
          el.play();
        }
      }
      return UI.hover({
        root: this,
        el: el,
        latestEvent: e,
        endEvents: 'mouseout click',
        asapTest: function() {
          if (isVideo) {
            return el.readyState >= el.HAVE_CURRENT_DATA;
          } else {
            return el.naturalHeight;
          }
        },
        noRemove: true,
        cb: function() {
          if (isVideo) {
            el.pause();
          }
          TrashQueue.add(el, post);
          el.removeAttribute('id');
          return $.queueTask(function() {
            return delete file.isHovered;
          });
        }
      });
    },
    error: function() {
      var post;
      post = Get.postFromNode(this);
      if (post.file.isExpanding || post.file.isExpanded) {
        return;
      }
      if (this.id === 'ihover') {
        if (ImageCommon.decodeError(this, post)) {
          return;
        }
        return ImageCommon.error(this, post, 3 * $.SECOND, (function(_this) {
          return function(URL) {
            if (URL) {
              return _this.src = URL + (_this.src === URL ? '?' + Date.now() : '');
            } else {
              $.rm(_this);
              return delete post.file.fullImage;
            }
          };
        })(this));
      } else {
        $.rm(this);
        return delete post.file.fullImage;
      }
    }
  };

  ImageLoader = {
    init: function() {
      var prefetch;
      if (g.VIEW === 'catalog') {
        return;
      }
      if (!(Conf['Image Prefetching'] || Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM'])) {
        return;
      }
      Post.callbacks.push({
        name: 'Image Replace',
        cb: this.node
      });
      $.on(d, 'PostsInserted', function() {
        return g.posts.forEach(ImageLoader.prefetch);
      });
      if (Conf['Replace WEBM']) {
        $.on(d, 'scroll visibilitychange 4chanXInitFinished PostsInserted', function() {
          var qpClone, _ref;
          qpClone = (_ref = $.id('qp')) != null ? _ref.firstElementChild : void 0;
          return g.posts.forEach(function(post) {
            var thumb, _i, _len, _ref1, _ref2;
            _ref1 = [post].concat(__slice.call(post.clones));
            for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
              post = _ref1[_i];
              if (!((_ref2 = post.file) != null ? _ref2.videoThumb : void 0)) {
                continue;
              }
              thumb = post.file.thumb;
              if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) {
                thumb.play();
              } else {
                thumb.pause();
              }
            }
          });
        });
      }
      if (!Conf['Image Prefetching']) {
        return;
      }
      prefetch = $.el('label', {
        innerHTML: "<input type=\"checkbox\" name=\"prefetch\"> Prefetch Images"
      });
      this.el = prefetch.firstElementChild;
      $.on(this.el, 'change', function() {
        if (Conf['prefetch'] = this.checked) {
          return g.posts.forEach(ImageLoader.prefetch);
        }
      });
      return Header.menu.addEntry({
        el: prefetch,
        order: 104
      });
    },
    node: function() {
      if (this.isClone || !this.file) {
        return;
      }
      if (Conf['Replace WEBM'] && this.file.isVideo) {
        ImageLoader.replaceVideo(this);
      }
      return ImageLoader.prefetch(this);
    },
    replaceVideo: function(post) {
      var attr, file, thumb, video, _i, _len, _ref;
      file = post.file;
      thumb = file.thumb;
      video = $.el('video', {
        preload: 'none',
        loop: true,
        poster: thumb.src,
        textContent: thumb.alt,
        className: thumb.className
      });
      video.dataset.md5 = thumb.dataset.md5;
      _ref = ['height', 'width', 'maxHeight', 'maxWidth'];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        attr = _ref[_i];
        video.style[attr] = thumb.style[attr];
      }
      video.src = file.URL;
      if (Conf['Image Hover']) {
        $.on(video, 'mouseover', ImageHover.mouseover);
      }
      $.replace(thumb, video);
      file.thumb = video;
      return file.videoThumb = true;
    },
    prefetch: function(post) {
      var URL, clone, el, file, isImage, isVideo, match, replace, thumb, type, _i, _len, _ref;
      file = post.file;
      if (!file) {
        return;
      }
      isImage = file.isImage, isVideo = file.isVideo, thumb = file.thumb, URL = file.URL;
      if (file.isPrefetched || !(isImage || isVideo) || post.isHidden || post.thread.isHidden) {
        return;
      }
      type = (match = URL.match(/\.([^.]+)$/)[1].toUpperCase()) === 'JPEG' ? 'JPG' : match;
      replace = Conf["Replace " + type] && !/spoiler/.test(thumb.src);
      if (!(replace || Conf['prefetch'])) {
        return;
      }
      if (![post].concat(__slice.call(post.clones)).some(function(clone) {
        return doc.contains(clone.nodes.root);
      })) {
        return;
      }
      file.isPrefetched = true;
      if (file.videoThumb) {
        _ref = post.clones;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          clone = _ref[_i];
          clone.file.thumb.preload = 'auto';
        }
        thumb.preload = 'auto';
        if (typeof chrome === "undefined" || chrome === null) {
          $.on(thumb, 'loadeddata', function() {
            return this.removeAttribute('poster');
          });
        }
        return;
      }
      el = $.el(isImage ? 'img' : 'video');
      if (replace && isImage) {
        $.on(el, 'load', function() {
          var _j, _len1, _ref1;
          _ref1 = post.clones;
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            clone = _ref1[_j];
            clone.file.thumb.src = URL;
          }
          return thumb.src = URL;
        });
      }
      return el.src = URL;
    }
  };

  RevealSpoilers = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Reveal Spoiler Thumbnails']) {
        return;
      }
      return Post.callbacks.push({
        cb: this.node
      });
    },
    node: function() {
      var thumb, _ref;
      if (this.isClone || !((_ref = this.file) != null ? _ref.isSpoiler : void 0)) {
        return;
      }
      thumb = this.file.thumb;
      thumb.removeAttribute('style');
      thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px';
      return thumb.src = this.file.thumbURL;
    }
  };

  Sauce = {
    init: function() {
      var err, link, links, _i, _len, _ref;
      if (g.VIEW === 'catalog' || !Conf['Sauce']) {
        return;
      }
      links = [];
      _ref = Conf['sauces'].split('\n');
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        link = _ref[_i];
        try {
          if (link[0] !== '#') {
            links.push(link.trim());
          }
        } catch (_error) {
          err = _error;
        }
      }
      if (!links.length) {
        return;
      }
      this.links = links;
      this.link = $.el('a', {
        target: '_blank'
      });
      return Post.callbacks.push({
        name: 'Sauce',
        cb: this.node
      });
    },
    createSauceLink: function(link, post) {
      var a, ext, i, key, m, part, parts, _i, _len, _ref, _ref1, _ref2, _ref3;
      parts = {};
      _ref = link.split(/;(?=(?:text|boards|types):)/);
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        part = _ref[i];
        if (i === 0) {
          parts['url'] = part;
        } else {
          m = part.match(/^(\w*):(.*)$/);
          parts[m[1]] = m[2];
        }
      }
      parts['text'] || (parts['text'] = ((_ref1 = parts['url'].match(/(\w+)\.\w+\//)) != null ? _ref1[1] : void 0) || '?');
      for (key in parts) {
        parts[key] = parts[key].replace(/%(T?URL|MD5|board|name|%|semi)/g, function(parameter) {
          var type;
          type = {
            '%TURL': post.file.thumbURL,
            '%URL': post.file.URL,
            '%MD5': post.file.MD5,
            '%board': post.board,
            '%name': post.file.name,
            '%%': '%',
            '%semi': ';'
          }[parameter];
          if (key === 'url' && parameter !== '%%' && parameter !== '%semi') {
            return encodeURIComponent(type);
          } else {
            return type;
          }
        });
      }
      ext = ((_ref2 = post.file.URL.match(/\.([^\.]*)$/)) != null ? _ref2[1] : void 0) || '';
      if (!(!parts['boards'] || (_ref3 = post.board.ID, __indexOf.call(parts['boards'].split(','), _ref3) >= 0))) {
        return null;
      }
      if (!(!parts['types'] || __indexOf.call(parts['types'].split(','), ext) >= 0)) {
        return null;
      }
      a = Sauce.link.cloneNode(true);
      a.href = parts['url'];
      a.textContent = parts['text'];
      return a;
    },
    node: function() {
      var link, node, nodes, _i, _len, _ref;
      if (this.isClone || !this.file) {
        return;
      }
      nodes = [];
      _ref = Sauce.links;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        link = _ref[_i];
        if (node = Sauce.createSauceLink(link, this)) {
          nodes.push($.tn('\u00A0'), node);
        }
      }
      return $.add(this.file.text, nodes);
    }
  };

  TrashQueue = {
    init: function() {},
    add: function(video, post) {
      var _ref, _ref1;
      if (this.killNext && video !== this.killNext) {
        if ((_ref = this.killNextPost) != null) {
          if ((_ref1 = _ref.file) != null) {
            delete _ref1.fullImage;
          }
        }
        $.rm(this.killNext);
      }
      this.killNext = video;
      return this.killNextPost = post;
    },
    remove: function(video) {
      if (video === this.killNext) {
        return delete this.killNext;
      }
    }
  };

  Linkify = {
    init: function() {
      var type, _i, _len, _ref;
      if (g.VIEW === 'catalog' || !Conf['Linkify']) {
        return;
      }
      this.types = {};
      _ref = this.ordered_types;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        type = _ref[_i];
        this.types[type.key] = type;
      }
      if (Conf['Comment Expansion']) {
        ExpandComment.callbacks.push(this.node);
      }
      return Post.callbacks.push({
        name: 'Linkify',
        cb: this.node
      });
    },
    events: function(post) {
      var el, i, items;
      i = 0;
      items = $$('.embedder', post.nodes.comment);
      while (el = items[i++]) {
        $.on(el, 'click', Linkify.cb.toggle);
        if ($.hasClass(el, 'embedded')) {
          Linkify.cb.toggle.call(el);
        }
      }
    },
    node: function() {
      var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word;
      if (this.isClone) {
        return (Conf['Embedding'] ? Linkify.events(this) : null);
      }
      if (!Linkify.regString.test(this.info.comment)) {
        return;
      }
      test = /[^\s'"]+/g;
      space = /[\s'"]/;
      snapshot = $.X('.//br|.//text()', this.nodes.comment);
      i = 0;
      links = [];
      while (node = snapshot.snapshotItem(i++)) {
        data = node.data;
        if (!data || node.parentElement.nodeName === "A") {
          continue;
        }
        while (result = test.exec(data)) {
          index = result.index;
          endNode = node;
          word = result[0];
          if ((length = index + word.length) === data.length) {
            test.lastIndex = 0;
            while ((saved = snapshot.snapshotItem(i++))) {
              if (saved.nodeName === 'BR') {
                break;
              }
              endNode = saved;
              data = saved.data;
              word += data;
              length = data.length;
              if (end = space.exec(data)) {
                test.lastIndex = length = end.index;
                i--;
                break;
              }
            }
          }
          if (Linkify.regString.exec(word)) {
            links.push(Linkify.makeRange(node, endNode, index, length));
          }
          if (!(test.lastIndex && node === endNode)) {
            break;
          }
        }
      }
      i = links.length;
      while (i--) {
        link = Linkify.makeLink(links[i]);
        if (!$.x('ancestor::pre', link)) {
          Linkify.embedProcess(link, this);
        }
      }
    },
    embedProcess: function(link, post) {
      var data;
      if (data = Linkify.services(link)) {
        data.push(post);
        if (Conf['Embedding']) {
          Linkify.embed(data);
        }
        if (Conf['Link Title']) {
          return Linkify.title(data);
        }
      }
    },
    regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i,
    makeRange: function(startNode, endNode, startOffset, endOffset) {
      var range;
      range = document.createRange();
      range.setStart(startNode, startOffset);
      range.setEnd(endNode, endOffset);
      return range;
    },
    makeLink: function(range) {
      var a, i, t, text;
      text = range.toString();
      i = 0;
      while (/[(\[{<>]/.test(text.charAt(i))) {
        i++;
      }
      if (i) {
        text = text.slice(i);
        while (range.startOffset + i >= range.startContainer.data.length) {
          i--;
        }
        if (i) {
          range.setStart(range.startContainer, range.startOffset + i);
        }
      }
      i = 0;
      while (/[)\]}>.,]/.test(t = text.charAt(text.length - (1 + i)))) {
        if (!(/[.,]/.test(t) || (text.match(/[()\[\]{}<>]/g)).length % 2)) {
          break;
        }
        i++;
      }
      if (i) {
        text = text.slice(0, -i);
        while (range.endOffset - i < 0) {
          i--;
        }
        if (i) {
          range.setEnd(range.endContainer, range.endOffset - i);
        }
      }
      if (!/(mailto:|.+:\/\/)/.test(text)) {
        text = (/@/.test(text) ? 'mailto:' : 'http://') + text;
      }
      a = $.el('a', {
        className: 'linkify',
        rel: 'nofollow noreferrer',
        target: '_blank',
        href: text
      });
      $.add(a, range.extractContents());
      range.insertNode(a);
      range.detach();
      return a;
    },
    services: function(link) {
      var href, match, type, _i, _len, _ref;
      href = link.href;
      _ref = Linkify.ordered_types;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        type = _ref[_i];
        if (!(match = type.regExp.exec(href))) {
          continue;
        }
        if (type.dummy || type.httpOnly && location.protocol !== 'http:') {
          return;
        }
        return [type.key, match[1], match[2], link];
      }
    },
    embed: function(data) {
      var embed, key, link, name, options, post, uid, value, _ref;
      key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
      embed = $.el('a', {
        className: 'embedder',
        rel: 'nofollow noreferrer',
        href: link.href,
        textContent: '(embed)'
      });
      _ref = {
        key: key,
        uid: uid,
        options: options
      };
      for (name in _ref) {
        value = _ref[name];
        embed.dataset[name] = value;
      }
      $.addClass(link, "" + embed.dataset.key);
      $.on(embed, 'click', Linkify.cb.toggle);
      $.after(link, [$.tn(' '), embed]);
      if (Conf['Auto-embed'] && !post.isFetchedQuote) {
        return $.asap((function() {
          return doc.contains(embed);
        }), function() {
          return Linkify.cb.toggle.call(embed);
        });
      }
    },
    title: function(data) {
      var err, key, link, options, post, service, uid;
      key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
      if (!(service = Linkify.types[key].title)) {
        return;
      }
      try {
        return $.cache(service.api(uid), (function() {
          return Linkify.cb.title(this, data);
        }), {
          responseType: 'json'
        });
      } catch (_error) {
        err = _error;
        $.extend(link, {
          innerHTML: "[" + E(key) + "] <span class=\"warning\">Title Link Blocked</span> (are you using NoScript?)</a>"
        });
      }
    },
    cb: {
      toggle: function(e) {
        if (e != null) {
          e.preventDefault();
        }
        if ($.hasClass(this, "embedded")) {
          if (!$.hasClass(this.previousElementSibling, 'linkify')) {
            $.rm(this.previousElementSibling);
          }
          this.previousElementSibling.hidden = false;
          this.textContent = '(embed)';
        } else {
          this.previousElementSibling.hidden = true;
          $.before(this, Linkify.cb.embed(this));
          this.textContent = '(unembed)';
        }
        return $.toggleClass(this, 'embedded');
      },
      embed: function(a) {
        var el, type;
        el = (type = Linkify.types[a.dataset.key]).el(a);
        el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px";
        return el;
      },
      title: function(req, data) {
        var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1;
        key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
        status = req.status;
        service = Linkify.types[key].title;
        text = "[" + key + "] " + ((function() {
          switch (status) {
            case 200:
            case 304:
              return service.text(req.response);
            case 404:
              return "Not Found";
            case 403:
              return "Forbidden or Private";
            default:
              return "" + status + "'d";
          }
        })());
        link.textContent = text;
        _ref = post.clones;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          post2 = _ref[_i];
          _ref1 = $$('a.linkify', post2.nodes.comment);
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            link2 = _ref1[_j];
            if (link2.href === link.href) {
              link2.textContent = text;
            }
          }
        }
      }
    },
    ordered_types: [
      {
        key: 'audio',
        regExp: /\.(?:mp3|ogg|wav)$/i,
        style: '',
        el: function(a) {
          return $.el('audio', {
            controls: true,
            preload: 'auto',
            src: a.href
          });
        }
      }, {
        key: 'gist',
        regExp: /^\w+:\/\/gist\.github\.com\/(?:[\w\-]+\/)?(\w+)/,
        el: function(a) {
          var content, el;
          el = $.el('iframe');
          el.setAttribute('sandbox', 'allow-scripts');
          content = {
            innerHTML: "<html><head><title>" + E(a.dataset.uid) + "</title></head><body><script src=\"https://gist.github.com/" + E(a.dataset.uid) + ".js\"></script></body></html>"
          };
          el.src = "data:text/html;charset=utf-8,<!doctype html>" + (encodeURIComponent(content.innerHTML));
          return el;
        },
        title: {
          api: function(uid) {
            return "https://api.github.com/gists/" + uid;
          },
          text: function(_arg) {
            var file, files;
            files = _arg.files;
            for (file in files) {
              if (files.hasOwnProperty(file)) {
                return file;
              }
            }
          }
        }
      }, {
        key: 'image',
        regExp: /\.(?:gif|png|jpg|jpeg|bmp)$/i,
        style: 'border: 0; width: auto; height: auto;',
        el: function(a) {
          return $.el('div', {
            innerHTML: "<a target=\"_blank\" href=\"" + E(a.href) + "\"><img src=\"" + E(a.href) + "\"></a>"
          });
        }
      }, {
        key: 'InstallGentoo',
        regExp: /^\w+:\/\/paste\.installgentoo\.com\/view\/(?:raw\/|download\/|embed\/)?(\w+)/,
        el: function(a) {
          return $.el('iframe', {
            src: "https://paste.installgentoo.com/view/embed/" + a.dataset.uid
          });
        }
      }, {
        key: 'Twitter',
        regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/,
        el: function(a) {
          return $.el('iframe', {
            src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid
          });
        }
      }, {
        key: 'LiveLeak',
        regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/,
        httpOnly: true,
        el: function(a) {
          var el;
          el = $.el('iframe', {
            width: "640",
            height: "360",
            src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid,
            frameborder: "0"
          });
          el.setAttribute("allowfullscreen", "true");
          return el;
        }
      }, {
        key: 'MediaCrush',
        regExp: /^\w+:\/\/(?:www\.)?mediacru\.sh\/([\w\-]+)/,
        style: 'border: 0;',
        el: function(a) {
          var el;
          el = $.el('div');
          $.queueTask(function() {
            return $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() {
              var embed, ext, file, files, i, status, type, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _results;
              if (!doc.contains(el)) {
                return;
              }
              status = this.status;
              if (status !== 200 && status !== 304) {
                return el.textContent = "ERROR " + status;
              }
              files = this.response.files;
              _ref = ['video/mp4', 'video/webm', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg'];
              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                type = _ref[_i];
                for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
                  file = files[_j];
                  if (file.type === type) {
                    embed = file;
                    break;
                  }
                }
                if (embed) {
                  break;
                }
              }
              if (!embed) {
                return div.textContent = "ERROR: Not a valid filetype";
              }
              switch (embed.type) {
                case 'video/mp4':
                case 'video/webm':
                case 'video/ogv':
                  $.extend(el, {
                    innerHTML: "<video controls loop><source type=\"video/mp4\"><source type=\"video/webm\"></video>"
                  });
                  _ref1 = ['mp4', 'webm'];
                  _results = [];
                  for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) {
                    ext = _ref1[i];
                    _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext);
                  }
                  return _results;
                  break;
                case 'image/svg+xml':
                case 'image/png':
                case 'image/gif':
                case 'image/jpeg':
                  return $.extend(el, {
                    innerHTML: "<a target=\"_blank\" href=\"" + E(a.href) + "\"><img src=\"https://mediacru.sh/" + E(file.file) + "\"></a>"
                  });
                case 'audio/mpeg':
                case 'audio/ogg':
                  return $.extend(el, {
                    innerHTML: "<audio controls><source type=\"audio/ogg\" src=\"https://mediacru.sh/" + E(a.dataset.uid) + ".ogg\"></audio>"
                  });
                default:
                  return el.textContent = "ERROR: No valid filetype.";
              }
            });
          });
          return el;
        }
      }, {
        key: 'pastebin',
        regExp: /^\w+:\/\/(?:\w+\.)?pastebin\.com\/(?!u\/)(?:[\w\.]+\?i\=)?(\w+)/,
        httpOnly: true,
        el: function(a) {
          var div;
          return div = $.el('iframe', {
            src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid
          });
        }
      }, {
        key: 'gfycat',
        regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/,
        el: function(a) {
          var div;
          return div = $.el('iframe', {
            src: "//gfycat.com/iframe/" + a.dataset.uid
          });
        }
      }, {
        key: 'SoundCloud',
        regExp: /^\w+:\/\/(?:www\.)?(?:soundcloud\.com\/|snd\.sc\/)([\w\-\/]+)/,
        style: 'border: 0; width: 500px; height: 400px;',
        el: function(a) {
          return $.el('iframe', {
            src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid))
          });
        },
        title: {
          api: function(uid) {
            return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid));
          },
          text: function(_) {
            return _.title;
          }
        }
      }, {
        key: 'StrawPoll',
        regExp: /^\w+:\/\/(?:www\.)?strawpoll\.me\/(?:embed_\d+\/)?(\d+(?:\/r)?)/,
        httpOnly: true,
        style: 'border: 0; width: 600px; height: 406px;',
        el: function(a) {
          return $.el('iframe', {
            src: "http://strawpoll.me/embed_1/" + a.dataset.uid
          });
        }
      }, {
        key: 'TwitchTV',
        regExp: /^\w+:\/\/(?:www\.)?twitch\.tv\/([^#\&\?]*)/,
        httpOnly: true,
        style: "border: none; width: 640px; height: 360px;",
        el: function(a) {
          var channel, id, idparam, obj, result, type, _;
          if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) {
            _ = result[0], channel = result[1], type = result[2], id = result[3];
            idparam = {
              'b': 'archive_id',
              'c': 'chapter_id'
            };
            obj = $.el('object', {
              data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
            });
            $.extend(obj, {
              innerHTML: "<param name=\"allowFullScreen\" value=\"true\"><param name=\"flashvars\">"
            });
            obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id;
            return obj;
          } else {
            channel = (/(\w+)/.exec(a.dataset.uid))[0];
            obj = $.el('object', {
              data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel
            });
            $.extend(obj, {
              innerHTML: "<param name=\"allowFullScreen\" value=\"true\"><param name=\"flashvars\">"
            });
            obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25";
            return obj;
          }
        }
      }, {
        key: 'Vocaroo',
        regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/,
        style: '',
        el: function(a) {
          return $.el('audio', {
            controls: true,
            preload: 'auto',
            src: "http://vocaroo.com/media_command.php?media=" + a.dataset.uid + "&command=download_ogg"
          });
        }
      }, {
        key: 'Vimeo',
        regExp: /^\w+:\/\/(?:www\.)?vimeo\.com\/(\d+)/,
        el: function(a) {
          return $.el('iframe', {
            src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque"
          });
        },
        title: {
          api: function(uid) {
            return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid;
          },
          text: function(_) {
            return _.title;
          }
        }
      }, {
        key: 'Vine',
        regExp: /^\w+:\/\/(?:www\.)?vine\.co\/v\/(\w+)/,
        style: 'border: none; width: 500px; height: 500px;',
        el: function(a) {
          return $.el('iframe', {
            src: "https://vine.co/v/" + a.dataset.uid + "/card"
          });
        }
      }, {
        key: 'YouTube',
        regExp: /^\w+:\/\/(?:youtu.be\/|[\w\.]*youtube[\w\.]*\/.*(?:v=|\/embed\/|\/v\/|\/videos\/))([\w\-]{11})[^#\&\?]?(.*)/,
        el: function(a) {
          var el, start;
          start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/);
          if (start) {
            start = start[1];
          }
          if (start && !/^\d+$/.test(start)) {
            start += ' 0h0m0s';
            start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1];
          }
          el = $.el('iframe', {
            src: "//www.youtube.com/embed/" + a.dataset.uid + "?wmode=opaque" + (start ? '&start=' + start : '')
          });
          el.setAttribute("allowfullscreen", "true");
          return el;
        },
        title: {
          api: function(uid) {
            return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode";
          },
          text: function(data) {
            return data.entry.title.$t;
          }
        }
      }, {
        key: 'Loopvid',
        regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/((?:pf|kd|lv|mc|gd|gh|db|nn)\/[\w\-]+(,[\w\-]+)*|fc\/\w+\/\d+)/,
        style: 'border: 0; width: auto; height: auto;',
        el: function(a) {
          var base, el, host, name, names, type, types, url, _, _i, _j, _len, _len1, _ref, _ref1;
          el = $.el('video', {
            controls: true,
            preload: 'auto',
            loop: true
          });
          _ref = a.dataset.uid.match(/(\w+)\/(.*)/), _ = _ref[0], host = _ref[1], names = _ref[2];
          types = host === 'gd' || host === 'fc' ? [''] : ['.webm', '.mp4'];
          _ref1 = names.split(',');
          for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
            name = _ref1[_i];
            for (_j = 0, _len1 = types.length; _j < _len1; _j++) {
              type = types[_j];
              base = "" + name + type;
              url = (function() {
                switch (host) {
                  case 'pf':
                    return "http://a.pomf.se/" + base;
                  case 'kd':
                    return "http://kastden.org/loopvid/" + base;
                  case 'lv':
                    return "http://loopvid.mooo.com/videos/" + base;
                  case 'mc':
                    return "https://cdn.mediacru.sh/" + base;
                  case 'gd':
                    return "https://docs.google.com/uc?export=download&id=" + base;
                  case 'gh':
                    return "https://googledrive.com/host/" + base;
                  case 'db':
                    return "https://googledrive.com/host/" + base;
                  case 'fc':
                    return "//i.4cdn.org/" + base + ".webm";
                  case 'nn':
                    return "http://naenara.eu/loopvids/" + base;
                }
              })();
              $.add(el, $.el('source', {
                src: url
              }));
            }
          }
          return el;
        }
      }, {
        key: 'Clyp',
        regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/,
        style: '',
        el: function(a) {
          return $.el('audio', {
            controls: true,
            preload: 'auto',
            src: "http://clyp.it/" + a.dataset.uid + ".ogg"
          });
        }
      }, {
        key: 'Loopvid-dummy',
        regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\//,
        dummy: true
      }, {
        key: 'MediaFire-dummy',
        regExp: /^\w+:\/\/(?:www\.)?mediafire.com\//,
        dummy: true
      }, {
        key: 'video',
        regExp: /\.(?:ogv|webm|mp4)$/i,
        style: 'border: 0; width: auto; height: auto;',
        el: function(a) {
          return $.el('video', {
            controls: true,
            preload: 'auto',
            src: a.href
          });
        }
      }
    ]
  };

  ArchiveLink = {
    init: function() {
      var div, entry, type, _i, _len, _ref;
      if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Archive Link']) {
        return;
      }
      div = $.el('div', {
        textContent: 'Archive'
      });
      entry = {
        el: div,
        order: 90,
        open: function(_arg) {
          var ID, board, thread;
          ID = _arg.ID, thread = _arg.thread, board = _arg.board;
          return !!Redirect.to('thread', {
            postID: ID,
            threadID: thread.ID,
            boardID: board.ID
          });
        },
        subEntries: []
      };
      _ref = [['Post', 'post'], ['Name', 'name'], ['Tripcode', 'tripcode'], ['Subject', 'subject'], ['Filename', 'filename'], ['Image MD5', 'MD5']];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        type = _ref[_i];
        entry.subEntries.push(this.createSubEntry(type[0], type[1]));
      }
      return Menu.menu.addEntry(entry);
    },
    createSubEntry: function(text, type) {
      var el, open;
      el = $.el('a', {
        textContent: text,
        target: '_blank'
      });
      open = type === 'post' ? function(_arg) {
        var ID, board, thread;
        ID = _arg.ID, thread = _arg.thread, board = _arg.board;
        el.href = Redirect.to('thread', {
          postID: ID,
          threadID: thread.ID,
          boardID: board.ID
        });
        return true;
      } : function(post) {
        var value;
        value = Filter[type](post);
        if (!value) {
          return false;
        }
        el.href = Redirect.to('search', {
          boardID: post.board.ID,
          type: type,
          value: value,
          isSearch: true
        });
        return true;
      };
      return {
        el: el,
        open: open
      };
    }
  };

  DeleteLink = {
    init: function() {
      var div, fileEl, fileEntry, postEl, postEntry;
      if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Delete Link']) {
        return;
      }
      div = $.el('div', {
        className: 'delete-link',
        textContent: 'Delete'
      });
      postEl = $.el('a', {
        className: 'delete-post',
        href: 'javascript:;'
      });
      fileEl = $.el('a', {
        className: 'delete-file',
        href: 'javascript:;'
      });
      postEntry = {
        el: postEl,
        open: function() {
          postEl.textContent = 'Post';
          $.on(postEl, 'click', DeleteLink["delete"]);
          return true;
        }
      };
      fileEntry = {
        el: fileEl,
        open: function(_arg) {
          var file;
          file = _arg.file;
          if (!file || file.isDead) {
            return false;
          }
          fileEl.textContent = 'File';
          $.on(fileEl, 'click', DeleteLink["delete"]);
          return true;
        }
      };
      return Menu.menu.addEntry({
        el: div,
        order: 40,
        open: function(post) {
          var node;
          if (post.isDead) {
            return false;
          }
          DeleteLink.post = post;
          node = div.firstChild;
          node.textContent = 'Delete';
          DeleteLink.cooldown.start(post, node);
          return true;
        },
        subEntries: [postEntry, fileEntry]
      });
    },
    "delete": function() {
      var fileOnly, form, link, post;
      post = DeleteLink.post;
      if (DeleteLink.cooldown.counting === post) {
        return;
      }
      $.off(this, 'click', DeleteLink["delete"]);
      fileOnly = $.hasClass(this, 'delete-file');
      this.textContent = "Deleting " + (fileOnly ? 'file' : 'post') + "...";
      form = {
        mode: 'usrdel',
        onlyimgdel: fileOnly,
        pwd: QR.persona.getPassword()
      };
      form[post.ID] = 'delete';
      link = this;
      return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), {
        responseType: 'document',
        withCredentials: true,
        onload: function() {
          return DeleteLink.load(link, post, fileOnly, this.response);
        },
        onerror: function() {
          return DeleteLink.error(link);
        }
      }, {
        form: $.formData(form)
      });
    },
    load: function(link, post, fileOnly, resDoc) {
      var msg, s;
      if (resDoc.title === '4chan - Banned') {
        s = 'Banned!';
      } else if (msg = resDoc.getElementById('errmsg')) {
        s = msg.textContent;
        $.on(link, 'click', DeleteLink["delete"]);
      } else {
        if (resDoc.title === 'Updating index...') {
          (post.origin || post).kill(fileOnly);
        }
        s = 'Deleted';
      }
      return link.textContent = s;
    },
    error: function(link) {
      link.textContent = 'Connection error, please retry.';
      return $.on(link, 'click', DeleteLink["delete"]);
    },
    cooldown: {
      start: function(post, node) {
        var length, seconds, _ref;
        if (!((_ref = QR.db) != null ? _ref.get({
          boardID: post.board.ID,
          threadID: post.thread.ID,
          postID: post.ID
        }) : void 0)) {
          delete DeleteLink.cooldown.counting;
          return;
        }
        DeleteLink.cooldown.counting = post;
        length = 60;
        seconds = Math.ceil((length * $.SECOND - (Date.now() - post.info.date)) / $.SECOND);
        return DeleteLink.cooldown.count(post, seconds, length, node);
      },
      count: function(post, seconds, length, node) {
        if (DeleteLink.cooldown.counting !== post) {
          return;
        }
        if (!((0 <= seconds && seconds <= length))) {
          if (DeleteLink.cooldown.counting === post) {
            node.textContent = 'Delete';
            delete DeleteLink.cooldown.counting;
          }
          return;
        }
        setTimeout(DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node);
        return node.textContent = "Delete (" + seconds + ")";
      }
    }
  };

  DownloadLink = {
    init: function() {
      var a;
      if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Download Link']) {
        return;
      }
      a = $.el('a', {
        className: 'download-link',
        textContent: 'Download file'
      });
      $.on(a, 'click', function(e) {
        if (this.protocol === 'blob:') {
          return true;
        }
        e.preventDefault();
        return CrossOrigin.file(this.href, (function(_this) {
          return function(blob) {
            if (blob) {
              _this.href = URL.createObjectURL(blob);
              return _this.click();
            } else {
              return new Notice('error', "Could not download " + file.URL, 30);
            }
          };
        })(this));
      });
      return Menu.menu.addEntry({
        el: a,
        order: 100,
        open: function(_arg) {
          var file;
          file = _arg.file;
          if (!file) {
            return false;
          }
          a.href = file.URL;
          a.download = file.name;
          return true;
        }
      });
    }
  };

  Menu = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Menu']) {
        return;
      }
      this.button = $.el('a', {
        className: 'menu-button',
        href: 'javascript:;'
      });
      $.extend(this.button, {
        innerHTML: "<i class=\"fa fa-angle-down\"></i>"
      });
      this.menu = new UI.Menu('post');
      return Post.callbacks.push({
        name: 'Menu',
        cb: this.node
      });
    },
    node: function() {
      if (this.isClone) {
        $.on($('.menu-button', this.nodes.info), 'click', Menu.toggle);
        return;
      }
      return $.add(this.nodes.info, Menu.makeButton());
    },
    makeButton: function() {
      var clone;
      clone = Menu.button.cloneNode(true);
      $.on(clone, 'click', Menu.toggle);
      return clone;
    },
    toggle: function(e) {
      var post;
      post = Get.postFromNode(this);
      return Menu.menu.toggle(e, this, post);
    }
  };

  ReportLink = {
    init: function() {
      var a;
      if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Report Link']) {
        return;
      }
      a = $.el('a', {
        className: 'report-link',
        href: 'javascript:;',
        textContent: 'Report this post'
      });
      $.on(a, 'click', ReportLink.report);
      return Menu.menu.addEntry({
        el: a,
        order: 10,
        open: function(post) {
          ReportLink.post = post;
          return !post.isDead;
        }
      });
    },
    report: function() {
      var id, post, set, url;
      post = ReportLink.post;
      url = "//sys.4chan.org/" + post.board + "/imgboard.php?mode=report&no=" + post;
      id = Date.now();
      set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200";
      return window.open(url, id, set);
    }
  };

  Favicon = {
    init: function() {
      return $.asap((function() {
        return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head));
      }), Favicon.initAsap);
    },
    initAsap: function() {
      var href;
      Favicon.el.type = 'image/x-icon';
      href = Favicon.el.href;
      Favicon.SFW = /ws\.ico$/.test(href);
      Favicon["default"] = href;
      return Favicon["switch"]();
    },
    "switch": function() {
      var f, funreadDeadY, i, items, t;
      items = {
        ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='],
        'xat-': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEX9AAD8AAD/AAD+AADAExKKXl2CfHqLkZFub2yfaF3bZ2PzZGL/zs//iYr/AAASAAAGAAAAAAAAAAAAAADpOCseAAAADHRSTlP9MAcAATVYeprJ5O/MbzqoAAAAXklEQVQY03VPQQ7AIAgz8QAG4dL//3VVcVk2Vw4tDVQp9YVyMACIEkIxDEQEGjHFnBjCbPU5EXBfnBns6WRG1Wbuvbtb0z9jr6Qh2KGQenp2/+xpsFQnrePAuulz7QUTuwm5NnwmIAAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAMAAACuAq9NAAAAY1BMVEUBAAACAQELCQkPDQwgFBMzKilOSEdva2iEgoCReHOadXClamDIaWbxcG7+hIX+mpv+m5z+oqP+tLX+zc7//f3+9PT97Oz23t750NDbra3zwL87LCwAAAAGAABHAADPAAD/AABkWeLDAAAAHHRSTlO5/fTv8Na2n42lsMvi8v3+/v749OaITDsDAQABSG2w8gAAAGdJREFUCNdNjtEKgDAIRYVGCmsyqCe7q/3/V2azQfpwPehVyQCIMIt4YYTeO7LHKMiGlDIkuh2qofR6obUqhtc4F637XreU1h+m41gcJX/DHyJWXYHzkCMm+hd3a4GezLNr8PQA4bQHEXEQFRJP5NAAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEUAAAAAAAAAAAAAAABFRUdsa2yRjop4dXVpZ2tdcI9dfKdBirUzlMBHpdxSquRisfOs2/99xv8umMMAAABljCUFAAAAEHRSTlN7FwUAQVt6kZ2/zej59vTv0aAplgAAAGNJREFUGNNtj1EOwCAIQ5eYIPCD0vvfdYi6LJvy0fICNVzl864DAECVuVKYAeDuEFVJkxPDmM1+TTh6n7oy0FvrWBmF1aIPYspnUGWvSE1A2KGgcvp2AtU3iGJOmcch6pHftTekXQrRd6slMAAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAMAAACuAq9NAAAAY1BMVEUAAAAAAAAAAAAAAAAREBAWFRY1NDROTE1iYGFzdXp4eoCAgYVlc4mHjZiYoa6zvcqy1/Pg8v+e1f+b1P6X0f2DyP5jsu49msgymcctkLomc5QbPU0SIiwNFxwumMMAAAAAAADALpU1AAAAHnRSTlPNLgcBAAABBxhdc4WznarD8P7+/v3+8/z9/vz2+PUOYDHSAAAAZElEQVQI102OsQ6AMAhEMWGDpTbUQUvu/79ShDYRhuMFDiAGIKIqEgUT3B0akQVxyhgp1XWYldLnhfXTkF5WHdZb69cz9YdPazNQdA0vRK2ahftQDGNjfHHXZjgSV5cRGQHCwS8j7A9loVSnzwAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEUAAAAAAAAAAAAAAAAfJSBLUU1ydHR8fn6Ri5Frbm9dn19jvEFt30tv5VB082KR/33Z/9Gq/5tmzDMAAADw+5ntAAAAEHRSTlP++ywHAAE2Wnuayez19O/+EzXeOQAAAF9JREFUGNN1TzESwCAIc3AABxDy/78WFXu91oYhIYcRSn2hHAwAxAEKMQy4O1pgijkxhMjqc8KhujgzoGaKzKjcRK13U2n8Z+wnaRB2KKievt2bPY0o5knrOETd9Ln2AuDLCz1j8HTeAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAMAAACuAq9NAAAAY1BMVEUPGgsCBAIBAQEBAQAAAQAAAAABAQEFBQQQEw85SDdVa1GhzJm967TZ+NLP+sbM+8S6/a3k/9+s/pyr/puX/oSd15KIuoGBj39tfm1qj2RepFlu2VRkwzZlyTNatC5myzMAAAAOPREWAAAAHnRSTlP4/fz331IPBQIBAAECOly37/7+/v7XwpWktNDy+f7X56yoAAAAZElEQVQI102NwQ7AIAhDMdku3JwkIiaz//+VQ9FkcCgvpUAMoKpX9YEJYww0s7YG4iW9Lwl3QCSUZhZSHsHKslqXknPpRPpDypkmtr0cWBGntnseOeKgGd6UAr1Vj8vw9sKFmz+fERAp5vutHwAAAABJRU5ErkJggg=='],
        Mayhem: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABFklEQVR4AZ2R4WqEMBCEFy1yiJQQ14gcIhIuFBFR+qPQ93+v66QMksrlTwMfkZ2ZZbMKTgVqYIDl3YAbeCM31lJP/Zul4MAEPJjBQGNDLGsz8PQ6aqLAP5PTdd1WlmU09mSKtdTDRgrkzspJPKq6RxMahfj9yhOzQEZwZAwfzrk1ox3MXibIN8hO4MAjeV72CemJGWblnRsOYOdoGw0jebB20BPAwKzUQPlrFhrXFw1Wagu9yuzZwINzVAZCURRL+gRr7Wd8Vtqg4Th/lsUmewyk9WQ/A7NiwJz5VV/GmO+MNjMrFvh/NPDMigHTaeJN09a27ZHRJmalBg54CgfvAGYSLpoHjlmpuAwFdzDy7oGS/qIpM9UPFGg1b1kUlssAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABR0lEQVR4AYWSQWq0QBCFCw0SRIK0PQ4hiIhEZBhEySLyewUPEMgqR/JIXiDhzz7kKKYePIZajEzDRxfV9dWU3SO6IiVWUsVxT5R75Y4gTmwNnUh4kCulUiuV8sjChDjmKtaUcHgmHsnNrMPh0IVhiMIjKZGzNXDoyhMzF7C89z2KtFGD+FoNXEUKZdgpaPM8P++cDXTtBDca7EyQK8+bXTufYBccuvLAG26UnqN1LCgI4g/lm7zTgSux4vk0J8rnKw3+m1//pBPbBrVyGZVNmiAITviEtm3t+D+2QcJx7GUxlN4594K4ZY75Xzh0JVWqnad6TdP0H+LRNBjHcYNDV5xS32qwaC4my7Lwn6guu5QoomgbdFmWDYhnM8E8zxscuhLzPWtKA/dGqUizrityX9M0YX+DQ1ciXobnP6vgfmTOM7Znnk70B58pPaEvx+epAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVR4AZ3RUWqEMBSF4ftQZAhSREQJIiIXpQwi+tSldkFdWPsLhyEE0ocKH2Fyzg1mNJ4KAQ1arTUeeJMH6qwTUJmCHjMcC6KKtbSIylzdXpl18J/k4fdTpUFmPLOOa9bGe+P4+n5RYYfLXuiMsAlXofBxK2QXpvwN/jqg+AY91vR+pStk+apZe0fEhhMXDhUmWXEoO9WNmrWAzvRPq7jnB2jvUGfWTEgPcJzZFTbZk/0Tnh5QI+af6lVGvq/Do2atwVL4VJ+3QrZo1lr4Pw5wzVqDWaV7SUvHrZDNmrWAHq7g0rphkS3LXDMBVqFGhxGT1gGdDFnWaab6BRmXRvbxDmYiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABQElEQVR4AY2SQUrEQBBFS9CMNFEkhAQdYmiCIUgcZlYGc4VsBcGVF/AuWXme4F7RtXiVWF9+Y9MYtOHRTdX/NZWaEj2RYpQTJeEdK4fKPuA7DjSGXiQkU0qlUqxySmFMEsYsNSU8zEmK4OwdEbmkKCclYoGmolfWCGyenh1O0EJE2gXNWpFC2S0IGrCQ29EbdPCPAmEHmXIxByf8hDAPD71yzAnXypatbSgoAN8Pyju5h4deMUrqJk1z+0uBN+/XX+gxfoFK2QafUJO2aRq//Q+/QIx2wr+Kwq0rusrP/QKf9MTCtbQLf9U1wNvYnz3qug45S68kSvVXgbPbx3nvYPXNOI7cRPWySukK+DcGCvA+urqZ3RmGAbmSXjFK5rpwW8nhWVJP04TYa9/3uO/goVciDiPlZhW8c8ZAHuRSeqIv32FK/GYGL8YAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVR4AZ3RUWqEMBSF4ftQZAihDCKKiAQJShERQx+6o662e2p/4TCEQF468BEm95yLovFr4PBEq9PjgTd5wBcZp6559AiIWDAq6KXV3aJMUMfDOsTf7Mf/XaFBAvYiE9W16b74/vl8UeBAlKOSmWAzUiXwcavMkrrFE9QXVJ+gx5q9XvUVivmqrr1jxIYLCacCs6y6S8psGNU1hw4Bu4JHuUB3pzJBHZcviLiKV9jkyO4vxHyBx1h+qlcY5b2Wj+raE0vlU33dKrNFXWsR/7EgqmtPBIXuIw+dt8osqGsOPaIGSeeGRbZiFtVxsAYeHSbMOgd0MhSzTp3mD4RaQX4aW3NMAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABP0lEQVR4AYWS0UqFQBCGhziImNRBRImDmUgiIaF0kWSP4AMEXXXTE/QiPpL3UdR19Crb/PAvLEtyFj5mmfn/cdxd0RUokbJXEsZYCZUd4D72NBG8wkKmlEqtVMoFhTFJmKuoKelBTVIkjbNE5IainJTIeZqaXjkg8fp+Z7GCjiLQbWgOihTKsCFowUZtoNef4HgDf4JMuTbe8n/Br8NDr5zxhBul52i3FBQE+xflmzzTA69ESmpPmubunwZfztc/6IncBrXSe7/QkK5tW3f8H7dBjHH8q6Kwt033V6Hb4JeeWPgsq42rugfYZ92psWscRwMPvZIo9bEGD2+F2YUnBizLwpeoXnYpbQM34kAB9peP58aueZ4NPPRKxPusaRoYG6UizbquyH1O04T4RA+8EvAwUr6sgjFnDuReLaUn+ANygUa7+9SCWgAAAABJRU5ErkJggg=='],
        '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AAD///9nZ2f77Y6hAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8P///9nZ2cgIeMlAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDP///9lyjJnZ2cIHys9AAAAAXRSTlMAQObYZgAAAENJREFUeF6NjUEKwEAMAjNm9/9fLkEslFwqgjoEUn8EfAqSdrkwzj6ieyyTkQEVGWRvANfO1iEX620AjgBEwqR4Y+sBeGAA6d+vQ4IAAAAASUVORK5CYII='],
        Original: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX/////AAD///8AAABBZmS3AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42q1RwQnAMAjMu5M4guAKXa4j5dUROo5tipSDcrFChUONd0di2m/hEGVOHDyIPufgwAFASDkpoSzmBrkJ2UMyR9LsJ3rvrqo3Rt1YMIMhhNnOxLMnoMFBxHyJAr2IOBFzA8U+6pLBdmEJTA0aMVjpDd6Loks0s5HZNwYx8tfZCZ0kll7ORffZAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///8ul8P///8AAACaqgkzAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eILZO5/XI0UAgm7H9tOsu0yGWAQSOoFijHOxOANGqm/LczpOaXs4gISrPZ+gc2+hO5w2xdwgOjBFUIF+sEJrhUl9JFr+badFwR+BfqlmGUJAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///9mzDP///8AAACT0n1lAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eIDhbn/cTVSCCTsfmw7ybbLZIBBIKkXKKU0E4M3aKT+tjCn5xiziwuIsNr7BTb7ErrDZV/AAaIHdwgV6AcnuFaU0Eeu5dt2XiUyBjCQ2bIrAAAAAElFTkSuQmCC'],
        'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAHAAAdAAApAAAsAAA4AABsAACQAAC/AAD///9SVhtjAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAACAkAISUALzQAMTcAQEcAeokAorYA1/H///8BrzTFAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAADCgANKAASOAATOwAZTAAwkQBAwQBV/wH////+Fmy4AAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC']
      }[Conf['favicon']];
      f = Favicon;
      t = 'data:image/png;base64,';
      i = 0;
      while (items[i]) {
        items[i] = t + items[i++];
      }
      f.unreadDead = items[0], funreadDeadY = items[1], f.unreadSFW = items[2], f.unreadSFWY = items[3], f.unreadNSFW = items[4], f.unreadNSFWY = items[5];
      return f.update();
    },
    update: function() {
      if (this.SFW) {
        this.unread = this.unreadSFW;
        return this.unreadY = this.unreadSFWY;
      } else {
        this.unread = this.unreadNSFW;
        return this.unreadY = this.unreadNSFWY;
      }
    },
    dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
    logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg=='
  };

  ThreadExcerpt = {
    init: function() {
      if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
        return;
      }
      return Thread.callbacks.push({
        name: 'Thread Excerpt',
        cb: this.node
      });
    },
    node: function() {
      return d.title = Get.threadExcerpt(this);
    }
  };

  ThreadStats = {
    init: function() {
      var countHTML, sc;
      if (g.VIEW !== 'thread' || !Conf['Thread Stats']) {
        return;
      }
      countHTML = {
        innerHTML: "<span id=\"post-count\">0</span> / <span id=\"file-count\">0</span>"
      };
      if (Conf['Page Count in Stats']) {
        countHTML = {
          innerHTML: countHTML.innerHTML + " / <span id=\"page-count\">0</span>"
        };
      }
      if (Conf['Updater and Stats in Header']) {
        this.dialog = sc = $.el('span', {
          id: 'thread-stats',
          title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "")
        });
        $.extend(sc, countHTML);
        $.ready(function() {
          return Header.addShortcut(sc);
        });
      } else {
        this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', {
          innerHTML: "<div class=\"move\" title=\"Post Count / File Count" + E(Conf["Page Count in Stats"] ? " / Page Count" : "") + "\">" + countHTML.innerHTML + "</div>"
        });
        $.ready((function(_this) {
          return function() {
            return $.add(d.body, sc);
          };
        })(this));
      }
      this.postCountEl = $('#post-count', sc);
      this.fileCountEl = $('#file-count', sc);
      this.pageCountEl = $('#page-count', sc);
      return Thread.callbacks.push({
        name: 'Thread Stats',
        cb: this.node
      });
    },
    node: function() {
      var fileCount, postCount;
      postCount = 0;
      fileCount = 0;
      this.posts.forEach(function(post) {
        postCount++;
        if (post.file) {
          return fileCount++;
        }
      });
      ThreadStats.thread = this;
      ThreadStats.fetchPage();
      ThreadStats.update(postCount, fileCount);
      return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate);
    },
    onUpdate: function(e) {
      var fileCount, postCount, _ref;
      if (e.detail[404]) {
        return;
      }
      _ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount;
      return ThreadStats.update(postCount, fileCount);
    },
    update: function(postCount, fileCount) {
      var fileCountEl, postCountEl, thread;
      thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl;
      postCountEl.textContent = postCount;
      fileCountEl.textContent = fileCount;
      (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning');
      return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning');
    },
    fetchPage: function() {
      if (!Conf["Page Count in Stats"]) {
        return;
      }
      if (ThreadStats.thread.isDead) {
        ThreadStats.pageCountEl.textContent = 'Dead';
        $.addClass(ThreadStats.pageCountEl, 'warning');
        return;
      }
      ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE);
      return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", {
        onload: ThreadStats.onThreadsLoad
      }, {
        whenModified: true
      });
    },
    onThreadsLoad: function() {
      var page, thread, _i, _j, _len, _len1, _ref, _ref1;
      if (!(Conf["Page Count in Stats"] && this.status === 200)) {
        return;
      }
      _ref = this.response;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        page = _ref[_i];
        _ref1 = page.threads;
        for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
          thread = _ref1[_j];
          if (!(thread.no === ThreadStats.thread.ID)) {
            continue;
          }
          ThreadStats.pageCountEl.textContent = page.page;
          (page.page === this.response.length ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning');
          return;
        }
      }
    }
  };

  ThreadUpdater = {
    init: function() {
      var conf, el, input, name, sc, subEntries, updateLink, _ref;
      if (g.VIEW !== 'thread' || !Conf['Thread Updater']) {
        return;
      }
      if (Conf['Updater and Stats in Header']) {
        this.dialog = sc = $.el('span', {
          id: 'updater'
        });
        $.extend(sc, {
          innerHTML: "<span id=\"update-status\"></span><span id=\"update-timer\" title=\"Update now\"></span>"
        });
        $.ready(function() {
          return Header.addShortcut(sc);
        });
      } else {
        this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', {
          innerHTML: "<div class=\"move\"></div><span id=\"update-status\"></span><span id=\"update-timer\" title=\"Update now\"></span>"
        });
        $.addClass(doc, 'float');
        $.ready((function(_this) {
          return function() {
            $.addClass(doc, 'float');
            return $.add(d.body, sc);
          };
        })(this));
      }
      this.checkPostCount = 0;
      this.timer = $('#update-timer', sc);
      this.status = $('#update-status', sc);
      this.isUpdating = Conf['Auto Update'];
      $.on(this.timer, 'click', this.update);
      $.on(this.status, 'click', this.update);
      updateLink = $.el('span', {
        className: 'brackets-wrap updatelink'
      });
      $.extend(updateLink, {
        innerHTML: "<a href=\"javascript:;\">Update</a>"
      });
      $.ready(function() {
        return $.add($('.navLinksBot'), [$.tn(' '), updateLink]);
      });
      $.on(updateLink.firstElementChild, 'click', this.update);
      subEntries = [];
      _ref = Config.updater.checkbox;
      for (name in _ref) {
        conf = _ref[name];
        el = UI.checkbox(name, " " + name);
        el.title = conf[1];
        input = el.firstElementChild;
        $.on(input, 'change', $.cb.checked);
        if (input.name === 'Scroll BG') {
          $.on(input, 'change', this.cb.scrollBG);
          this.cb.scrollBG();
        } else if (input.name === 'Auto Update') {
          $.on(input, 'change', this.cb.update);
        }
        subEntries.push({
          el: el
        });
      }
      this.settings = $.el('span', {
        innerHTML: "<a href=\"javascript:;\">Interval</a>"
      });
      $.on(this.settings, 'click', this.intervalShortcut);
      subEntries.push({
        el: this.settings
      });
      Header.menu.addEntry(this.entry = {
        el: $.el('span', {
          textContent: 'Updater'
        }),
        order: 110,
        subEntries: subEntries
      });
      return Thread.callbacks.push({
        name: 'Thread Updater',
        cb: this.node
      });
    },
    node: function() {
      ThreadUpdater.thread = this;
      ThreadUpdater.root = this.OP.nodes.root.parentNode;
      ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0];
      ThreadUpdater.outdateCount = 0;
      ThreadUpdater.cb.interval.call($.el('input', {
        value: Conf['Interval']
      }));
      $.on(window, 'online offline', ThreadUpdater.cb.online);
      $.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost);
      $.on(d, 'visibilitychange', ThreadUpdater.cb.visibility);
      if (ThreadUpdater.thread.isArchived) {
        return ThreadUpdater.set('status', 'Archived', 'warning');
      } else {
        return ThreadUpdater.cb.online();
      }
    },

    /*
    http://freesound.org/people/pierrecartoons1979/sounds/90112/
    cc-by-nc-3.0
     */
    beep: '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',
    cb: {
      online: function() {
        if (ThreadUpdater.thread.isDead) {
          return;
        }
        if (ThreadUpdater.online = navigator.onLine) {
          ThreadUpdater.outdateCount = 0;
          ThreadUpdater.setInterval();
          ThreadUpdater.set('status', null, null);
          return;
        }
        ThreadUpdater.set('timer', null);
        return ThreadUpdater.set('status', 'Offline', 'warning');
      },
      post: function(e) {
        if (!(ThreadUpdater.isUpdating && e.detail.threadID === ThreadUpdater.thread.ID)) {
          return;
        }
        ThreadUpdater.outdateCount = 0;
        if (ThreadUpdater.seconds > 2) {
          return setTimeout(ThreadUpdater.update, 1000);
        }
      },
      checkpost: function(e) {
        if (!ThreadUpdater.checkPostCount) {
          if (e.detail.threadID !== ThreadUpdater.thread.ID) {
            return;
          }
          ThreadUpdater.seconds = 0;
          ThreadUpdater.outdateCount = 0;
          ThreadUpdater.set('timer', '...');
        }
        if (!(ThreadUpdater.thread.isDead || ThreadUpdater.foundPost || ThreadUpdater.checkPostCount >= 5)) {
          return setTimeout(ThreadUpdater.update, ++ThreadUpdater.checkPostCount * $.SECOND);
        }
        ThreadUpdater.setInterval();
        ThreadUpdater.checkPostCount = 0;
        delete ThreadUpdater.foundPost;
        return delete ThreadUpdater.postID;
      },
      visibility: function() {
        if (d.hidden) {
          return;
        }
        ThreadUpdater.outdateCount = 0;
        if (ThreadUpdater.seconds > ThreadUpdater.interval) {
          return ThreadUpdater.setInterval();
        }
      },
      scrollBG: function() {
        return ThreadUpdater.scrollBG = Conf['Scroll BG'] ? function() {
          return true;
        } : function() {
          return !d.hidden;
        };
      },
      interval: function() {
        var val;
        val = parseInt(this.value, 10);
        if (val < 1) {
          val = 1;
        }
        ThreadUpdater.interval = this.value = val;
        return $.cb.value.call(this);
      },
      load: function(e) {
        var req;
        req = ThreadUpdater.req;
        switch (req.status) {
          case 200:
            ThreadUpdater.parse(req.response.posts);
            if (!!+req.response.posts[0].archived) {
              ThreadUpdater.thread.isArchived = true;
              ThreadUpdater.set('status', 'Archived', 'warning');
              ThreadUpdater.kill();
            } else {
              ThreadUpdater.setInterval();
            }
            break;
          case 404:
            $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", {
              onloadend: function() {
                var confirmed, page, thread, _i, _j, _len, _len1, _ref, _ref1;
                if (this.status === 200) {
                  confirmed = true;
                  _ref = this.response;
                  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                    page = _ref[_i];
                    _ref1 = page.threads;
                    for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
                      thread = _ref1[_j];
                      if (thread.no === ThreadUpdater.thread.ID) {
                        confirmed = false;
                        break;
                      }
                    }
                  }
                } else {
                  confirmed = false;
                }
                if (confirmed) {
                  ThreadUpdater.set('status', '404', 'warning');
                  return ThreadUpdater.kill();
                } else {
                  return ThreadUpdater.error(req);
                }
              }
            });
            break;
          default:
            ThreadUpdater.error(req);
        }
        if (ThreadUpdater.postID) {
          return ThreadUpdater.cb.checkpost();
        }
      }
    },
    kill: function() {
      ThreadUpdater.set('timer', null);
      clearTimeout(ThreadUpdater.timeoutID);
      ThreadUpdater.thread.kill();
      return $.event('ThreadUpdate', {
        404: true,
        threadID: ThreadUpdater.thread.fullID
      });
    },
    error: function(req) {
      var klass, text, _ref;
      ThreadUpdater.outdateCount++;
      ThreadUpdater.setInterval();
      _ref = req.status === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1];
      return ThreadUpdater.set('status', text, klass);
    },
    setInterval: function() {
      var cur, i, j, limit;
      i = ThreadUpdater.interval + 1;
      if (Conf['Optional Increase']) {
        cur = ThreadUpdater.outdateCount || 1;
        limit = d.hidden ? 7 : 10;
        j = cur <= limit ? cur : limit;
        cur = (Math.floor(i * 0.1) || 1) * j * j;
        ThreadUpdater.seconds = cur > i ? cur <= 300 ? cur : 300 : i;
      } else {
        ThreadUpdater.seconds = i;
      }
      ThreadUpdater.set('timer', ThreadUpdater.seconds);
      return ThreadUpdater.count(true);
    },
    intervalShortcut: function() {
      var settings;
      Settings.open('Advanced');
      settings = $.id('fourchanx-settings');
      return $('input[name=Interval]', settings).focus();
    },
    set: function(name, text, klass) {
      var el, node;
      el = ThreadUpdater[name];
      if (node = el.firstChild) {
        node.data = text;
      } else {
        el.textContent = text;
      }
      if (klass !== void 0) {
        return el.className = klass;
      }
    },
    count: function(start) {
      clearTimeout(ThreadUpdater.timeoutID);
      if (start && ThreadUpdater.isUpdating && navigator.onLine) {
        return ThreadUpdater.timeout();
      }
    },
    timeout: function() {
      var n;
      ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
      if (!(n = --ThreadUpdater.seconds)) {
        return ThreadUpdater.update();
      } else if (n <= -60) {
        ThreadUpdater.set('status', 'Retrying', null);
        return ThreadUpdater.update();
      } else if (n > 0) {
        return ThreadUpdater.set('timer', n);
      }
    },
    update: function() {
      var _ref;
      if (!navigator.onLine) {
        return;
      }
      ThreadUpdater.count();
      if (Conf['Auto Update']) {
        ThreadUpdater.set('timer', '...');
      } else {
        ThreadUpdater.set('timer', 'Update');
      }
      if ((_ref = ThreadUpdater.req) != null) {
        _ref.abort();
      }
      return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", {
        onloadend: ThreadUpdater.cb.load,
        whenModified: true
      });
    },
    updateThreadStatus: function(type, status) {
      var change, hasChanged;
      if (!(hasChanged = ThreadUpdater.thread["is" + type] !== status)) {
        return;
      }
      ThreadUpdater.thread.setStatus(type, status);
      change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore';
      return new Notice('info', "The thread is " + change + ".", 30);
    },
    parse: function(postObjects) {
      var OP, count, files, index, node, num, post, postObject, posts, root, scroll, _i, _j, _len, _len1;
      OP = postObjects[0];
      Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler;
      ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky);
      ThreadUpdater.updateThreadStatus('Closed', !!OP.closed);
      ThreadUpdater.thread.postLimit = !!OP.bumplimit;
      ThreadUpdater.thread.fileLimit = !!OP.imagelimit;
      posts = [];
      index = [];
      files = [];
      count = 0;
      for (_i = 0, _len = postObjects.length; _i < _len; _i++) {
        postObject = postObjects[_i];
        num = postObject.no;
        index.push(num);
        if (postObject.fsize) {
          files.push(num);
        }
        if (num <= ThreadUpdater.lastPost) {
          continue;
        }
        count++;
        node = Build.postFromObject(postObject, ThreadUpdater.thread.board.ID);
        posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board));
      }
      ThreadUpdater.thread.posts.forEach(function(post) {
        var ID;
        ID = +post.ID;
        if (__indexOf.call(index, ID) < 0) {
          post.kill();
        } else if (post.isDead) {
          post.resurrect();
        } else if (post.file && !(post.file.isDead || __indexOf.call(files, ID) >= 0)) {
          post.kill(true);
        }
        if (ThreadUpdater.postID && ThreadUpdater.postID === ID) {
          return ThreadUpdater.foundPost = true;
        }
      });
      if (!count) {
        ThreadUpdater.set('status', null, null);
        ThreadUpdater.outdateCount++;
      } else {
        ThreadUpdater.set('status', "+" + count, 'new');
        ThreadUpdater.outdateCount = 0;
        if (Conf['Beep'] && d.hidden && Unread.posts && !Unread.posts.length) {
          if (!ThreadUpdater.audio) {
            ThreadUpdater.audio = $.el('audio', {
              src: ThreadUpdater.beep
            });
          }
          ThreadUpdater.audio.play();
        }
        ThreadUpdater.lastPost = posts[count - 1].ID;
        Main.callbackNodes(Post, posts);
        scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25;
        for (_j = 0, _len1 = posts.length; _j < _len1; _j++) {
          post = posts[_j];
          root = post.nodes.root;
          if (post.cb) {
            if (!post.cb()) {
              $.add(ThreadUpdater.root, root);
            }
          } else {
            $.add(ThreadUpdater.root, root);
          }
        }
        $.event('PostsInserted');
        if (scroll) {
          if (Conf['Bottom Scroll']) {
            window.scrollTo(0, d.body.clientHeight);
          } else {
            if (root) {
              Header.scrollTo(root);
            }
          }
        }
        $.queueTask(function() {
          var length, threadID;
          threadID = ThreadUpdater.thread.ID;
          length = $$('.thread > .postContainer', ThreadUpdater.root).length;
          return Fourchan.parseThread(threadID, length - count, length);
        });
      }
      return $.event('ThreadUpdate', {
        404: false,
        threadID: ThreadUpdater.thread.fullID,
        newPosts: posts.map(function(post) {
          return post.fullID;
        }),
        postCount: OP.replies + 1,
        fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead)
      });
    }
  };

  ThreadWatcher = {
    init: function() {
      var now, sc;
      if (!Conf['Thread Watcher']) {
        return;
      }
      this.shortcut = sc = $.el('a', {
        id: 'watcher-link',
        textContent: 'Watcher',
        title: 'Thread Watcher',
        href: 'javascript:;',
        className: 'disabled fa fa-eye'
      });
      this.db = new DataBoard('watchedThreads', this.refresh, true);
      this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', {
        innerHTML: "<div class=\"move\">Thread Watcher <a class=\"refresh fa fa-refresh\" title=\"Check threads\" href=\"javascript:;\"></a><span id=\"watcher-status\"></span><a class=\"menu-button\" href=\"javascript:;\"><i class=\"fa fa-angle-down\"></i></a><a class=\"close\" href=\"javascript:;\">×</a></div><div id=\"watched-threads\"></div>"
      });
      this.status = $('#watcher-status', this.dialog);
      this.list = this.dialog.lastElementChild;
      this.refreshButton = $('.move > .refresh', this.dialog);
      this.unreaddb = Unread.db || new DataBoard('lastReadPosts');
      $.on(d, 'QRPostSuccessful', this.cb.post);
      $.on(sc, 'click', this.toggleWatcher);
      $.on(this.refreshButton, 'click', this.fetchAllStatus);
      $.on($('.move > .close', this.dialog), 'click', this.toggleWatcher);
      $.on(d, '4chanXInitFinished', this.ready);
      switch (g.VIEW) {
        case 'index':
          $.on(d, 'IndexRefresh', this.cb.onIndexRefresh);
          break;
        case 'thread':
          $.on(d, 'ThreadUpdate', this.cb.onThreadRefresh);
      }
      if (Conf['Toggleable Thread Watcher']) {
        Header.addShortcut(sc);
        $.addClass(doc, 'fixed-watcher');
      }
      now = Date.now();
      if ((this.db.data.lastChecked || 0) < now - 2 * $.HOUR) {
        this.db.data.lastChecked = now;
        ThreadWatcher.fetchAllStatus();
        this.db.save();
      }
      return Thread.callbacks.push({
        name: 'Thread Watcher',
        cb: this.node
      });
    },
    node: function() {
      var toggler;
      toggler = $.el('img', {
        className: 'watch-thread-link'
      });
      $.on(toggler, 'click', ThreadWatcher.cb.toggle);
      return $.before($('input', this.OP.nodes.post), toggler);
    },
    ready: function() {
      $.off(d, '4chanXInitFinished', ThreadWatcher.ready);
      if (!Main.isThisPageLegit()) {
        return;
      }
      ThreadWatcher.refresh();
      $.add(d.body, ThreadWatcher.dialog);
      if (Conf['Toggleable Thread Watcher']) {
        ThreadWatcher.dialog.hidden = true;
      }
      if (!Conf['Auto Watch']) {
        return;
      }
      return $.get('AutoWatch', 0, function(_arg) {
        var AutoWatch, thread;
        AutoWatch = _arg.AutoWatch;
        if (!(thread = g.BOARD.threads[AutoWatch])) {
          return;
        }
        ThreadWatcher.add(thread);
        return $["delete"]('AutoWatch');
      });
    },
    toggleWatcher: function() {
      $.toggleClass(ThreadWatcher.shortcut, 'disabled');
      return ThreadWatcher.dialog.hidden = !ThreadWatcher.dialog.hidden;
    },
    cb: {
      openAll: function() {
        var a, _i, _len, _ref;
        if ($.hasClass(this, 'disabled')) {
          return;
        }
        _ref = $$('a[title]', ThreadWatcher.list);
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          a = _ref[_i];
          $.open(a.href);
        }
        return $.event('CloseMenu');
      },
      pruneDeads: function() {
        var boardID, data, threadID, _i, _len, _ref, _ref1;
        if ($.hasClass(this, 'disabled')) {
          return;
        }
        _ref = ThreadWatcher.getAll();
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          _ref1 = _ref[_i], boardID = _ref1.boardID, threadID = _ref1.threadID, data = _ref1.data;
          if (!data.isDead) {
            continue;
          }
          delete ThreadWatcher.db.data.boards[boardID][threadID];
          ThreadWatcher.db.deleteIfEmpty({
            boardID: boardID
          });
        }
        ThreadWatcher.db.save();
        ThreadWatcher.refresh();
        return $.event('CloseMenu');
      },
      toggle: function() {
        return ThreadWatcher.toggle(Get.postFromNode(this).thread);
      },
      rm: function() {
        var boardID, threadID, _ref;
        _ref = this.parentNode.dataset.fullID.split('.'), boardID = _ref[0], threadID = _ref[1];
        return ThreadWatcher.rm(boardID, +threadID);
      },
      post: function(e) {
        var boardID, postID, threadID, _ref;
        _ref = e.detail, boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID;
        if (postID === threadID) {
          if (Conf['Auto Watch']) {
            return $.set('AutoWatch', threadID);
          }
        } else if (Conf['Auto Watch Reply']) {
          return ThreadWatcher.add(g.threads[boardID + '.' + threadID]);
        }
      },
      onIndexRefresh: function() {
        var boardID, data, db, threadID, _ref;
        db = ThreadWatcher.db;
        boardID = g.BOARD.ID;
        _ref = db.data.boards[boardID];
        for (threadID in _ref) {
          data = _ref[threadID];
          if (!data.isDead && !(threadID in g.BOARD.threads)) {
            if (Conf['Auto Prune']) {
              ThreadWatcher.db["delete"]({
                boardID: boardID,
                threadID: threadID
              });
            } else {
              data.isDead = true;
              ThreadWatcher.db.set({
                boardID: boardID,
                threadID: threadID,
                val: data
              });
            }
          }
        }
        return ThreadWatcher.refresh();
      },
      onThreadRefresh: function(e) {
        var thread;
        thread = g.threads[e.detail.threadID];
        if (!(e.detail[404] && ThreadWatcher.db.get({
          boardID: thread.board.ID,
          threadID: thread.ID
        }))) {
          return;
        }
        return ThreadWatcher.add(thread);
      }
    },
    fetchCount: {
      fetched: 0,
      fetching: 0
    },
    fetchAllStatus: function() {
      var thread, threads, _i, _len;
      if (!(threads = ThreadWatcher.getAll()).length) {
        return;
      }
      for (_i = 0, _len = threads.length; _i < _len; _i++) {
        thread = threads[_i];
        ThreadWatcher.fetchStatus(thread);
      }
    },
    fetchStatus: function(_arg) {
      var boardID, data, fetchCount, threadID;
      boardID = _arg.boardID, threadID = _arg.threadID, data = _arg.data;
      if (data.isDead && !Conf['Show Unread Count']) {
        return;
      }
      fetchCount = ThreadWatcher.fetchCount;
      if (fetchCount.fetching === 0) {
        ThreadWatcher.status.textContent = '...';
        $.addClass(ThreadWatcher.refreshButton, 'fa-spin');
      }
      fetchCount.fetching++;
      return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
        onloadend: function() {
          var isDead, lastReadPost, postObj, status, unread, _i, _len, _ref, _ref1;
          fetchCount.fetched++;
          if (fetchCount.fetched === fetchCount.fetching) {
            fetchCount.fetched = 0;
            fetchCount.fetching = 0;
            status = '';
            $.rmClass(ThreadWatcher.refreshButton, 'fa-spin');
          } else {
            status = "" + (Math.round(fetchCount.fetched / fetchCount.fetching * 100)) + "%";
          }
          ThreadWatcher.status.textContent = status;
          if (this.status === 200 && this.response) {
            isDead = !!this.response.posts[0].archived;
            if (isDead && Conf['Auto Prune']) {
              ThreadWatcher.db["delete"]({
                boardID: boardID,
                threadID: threadID
              });
              ThreadWatcher.refresh();
              return;
            }
            lastReadPost = ThreadWatcher.unreaddb.get({
              boardID: boardID,
              threadID: threadID,
              defaultValue: 0
            });
            unread = 0;
            _ref = this.response.posts.slice(1);
            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
              postObj = _ref[_i];
              if (postObj.no > lastReadPost && !((_ref1 = QR.db) != null ? _ref1.get({
                boardID: boardID,
                threadID: threadID,
                postID: postObj.no
              }) : void 0)) {
                unread++;
              }
            }
            if (isDead !== data.isDead || unread !== data.unread) {
              data.isDead = isDead;
              data.unread = unread;
              ThreadWatcher.db.set({
                boardID: boardID,
                threadID: threadID,
                val: data
              });
              return ThreadWatcher.refresh();
            }
          } else if (this.status === 404) {
            if (Conf['Auto Prune']) {
              ThreadWatcher.db["delete"]({
                boardID: boardID,
                threadID: threadID
              });
            } else {
              data.isDead = true;
              delete data.unread;
              ThreadWatcher.db.set({
                boardID: boardID,
                threadID: threadID,
                val: data
              });
            }
            return ThreadWatcher.refresh();
          }
        }
      });
    },
    getAll: function() {
      var all, boardID, data, threadID, threads, _ref;
      all = [];
      _ref = ThreadWatcher.db.data.boards;
      for (boardID in _ref) {
        threads = _ref[boardID];
        if (Conf['Current Board'] && boardID !== g.BOARD.ID) {
          continue;
        }
        for (threadID in threads) {
          data = threads[threadID];
          all.push({
            boardID: boardID,
            threadID: threadID,
            data: data
          });
        }
      }
      return all;
    },
    makeLine: function(boardID, threadID, data) {
      var count, div, fullID, link, title, x;
      x = $.el('a', {
        className: 'fa fa-times',
        href: 'javascript:;'
      });
      $.on(x, 'click', ThreadWatcher.cb.rm);
      title = $.el('span', {
        textContent: data.excerpt,
        className: 'watcher-title'
      });
      count = $.el('span', {
        textContent: Conf['Show Unread Count'] && (data.unread != null) ? "\u00A0(" + data.unread + ")" : '',
        className: 'watcher-unread'
      });
      link = $.el('a', {
        href: "/" + boardID + "/thread/" + threadID,
        title: data.excerpt,
        className: 'watcher-link'
      });
      $.add(link, [title, count]);
      div = $.el('div');
      fullID = "" + boardID + "." + threadID;
      div.dataset.fullID = fullID;
      if (g.VIEW === 'thread' && fullID === ("" + g.BOARD + "." + g.THREADID)) {
        $.addClass(div, 'current');
      }
      if (data.isDead) {
        $.addClass(div, 'dead-thread');
      }
      $.add(div, [x, $.tn(' '), link]);
      return div;
    },
    refresh: function() {
      var boardID, data, helper, list, nodes, refresher, thread, threadID, threads, toggler, watched, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3;
      nodes = [];
      _ref = ThreadWatcher.getAll();
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        _ref1 = _ref[_i], boardID = _ref1.boardID, threadID = _ref1.threadID, data = _ref1.data;
        nodes.push(ThreadWatcher.makeLine(boardID, threadID, data));
      }
      list = ThreadWatcher.list;
      $.rmAll(list);
      $.add(list, nodes);
      threads = g.BOARD.threads;
      _ref2 = threads.keys;
      for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
        threadID = _ref2[_j];
        thread = threads[threadID];
        toggler = $('.watch-thread-link', thread.OP.nodes.post);
        watched = ThreadWatcher.db.get({
          boardID: thread.board.ID,
          threadID: threadID
        });
        helper = watched ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
        $[helper[0]](toggler, 'watched');
        toggler.title = "" + helper[1] + " Thread";
      }
      _ref3 = ThreadWatcher.menu.refreshers;
      for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
        refresher = _ref3[_k];
        refresher();
      }
    },
    toggle: function(thread) {
      var boardID, threadID;
      boardID = thread.board.ID;
      threadID = thread.ID;
      if (ThreadWatcher.db.get({
        boardID: boardID,
        threadID: threadID
      })) {
        return ThreadWatcher.rm(boardID, threadID);
      } else {
        return ThreadWatcher.add(thread);
      }
    },
    add: function(thread) {
      var boardID, data, threadID;
      data = {};
      boardID = thread.board.ID;
      threadID = thread.ID;
      if (thread.isDead) {
        if (Conf['Auto Prune'] && ThreadWatcher.db.get({
          boardID: boardID,
          threadID: threadID
        })) {
          ThreadWatcher.rm(boardID, threadID);
          return;
        }
        data.isDead = true;
      }
      data.excerpt = Get.threadExcerpt(thread);
      ThreadWatcher.db.set({
        boardID: boardID,
        threadID: threadID,
        val: data
      });
      ThreadWatcher.refresh();
      if (Conf['Show Unread Count']) {
        return ThreadWatcher.fetchStatus({
          boardID: boardID,
          threadID: threadID,
          data: data
        });
      }
    },
    rm: function(boardID, threadID) {
      ThreadWatcher.db["delete"]({
        boardID: boardID,
        threadID: threadID
      });
      return ThreadWatcher.refresh();
    },
    convert: function(oldFormat) {
      var boardID, data, newFormat, threadID, threads;
      newFormat = {};
      for (boardID in oldFormat) {
        threads = oldFormat[boardID];
        for (threadID in threads) {
          data = threads[threadID];
          (newFormat[boardID] || (newFormat[boardID] = {}))[threadID] = {
            excerpt: data.textContent
          };
        }
      }
      return newFormat;
    },
    menu: {
      refreshers: [],
      init: function() {
        var menu;
        if (!Conf['Thread Watcher']) {
          return;
        }
        menu = this.menu = new UI.Menu('thread watcher');
        $.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) {
          return menu.toggle(e, this, ThreadWatcher);
        });
        this.addHeaderMenuEntry();
        return this.addMenuEntries();
      },
      addHeaderMenuEntry: function() {
        var entryEl;
        if (g.VIEW !== 'thread') {
          return;
        }
        entryEl = $.el('a', {
          href: 'javascript:;'
        });
        Header.menu.addEntry({
          el: entryEl,
          order: 60
        });
        $.on(entryEl, 'click', function() {
          return ThreadWatcher.toggle(g.threads["" + g.BOARD + "." + g.THREADID]);
        });
        return this.refreshers.push(function() {
          var addClass, rmClass, text, _ref;
          _ref = $('.current', ThreadWatcher.list) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = _ref[0], rmClass = _ref[1], text = _ref[2];
          $.addClass(entryEl, addClass);
          $.rmClass(entryEl, rmClass);
          return entryEl.textContent = text;
        });
      },
      addMenuEntries: function() {
        var cb, conf, entries, entry, name, refresh, subEntries, _i, _len, _ref, _ref1;
        entries = [];
        entries.push({
          cb: ThreadWatcher.cb.openAll,
          entry: {
            el: $.el('a', {
              textContent: 'Open all threads'
            })
          },
          refresh: function() {
            return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled');
          }
        });
        entries.push({
          cb: ThreadWatcher.cb.pruneDeads,
          entry: {
            el: $.el('a', {
              textContent: 'Prune dead threads'
            })
          },
          refresh: function() {
            return ($('.dead-thread', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled');
          }
        });
        subEntries = [];
        _ref = Config.threadWatcher;
        for (name in _ref) {
          conf = _ref[name];
          subEntries.push(this.createSubEntry(name, conf[1]));
        }
        entries.push({
          entry: {
            el: $.el('span', {
              textContent: 'Settings'
            }),
            subEntries: subEntries
          }
        });
        for (_i = 0, _len = entries.length; _i < _len; _i++) {
          _ref1 = entries[_i], entry = _ref1.entry, cb = _ref1.cb, refresh = _ref1.refresh;
          if (entry.el.nodeName === 'A') {
            entry.el.href = 'javascript:;';
          }
          if (cb) {
            $.on(entry.el, 'click', cb);
          }
          if (refresh) {
            this.refreshers.push(refresh.bind(entry));
          }
          this.menu.addEntry(entry);
        }
      },
      createSubEntry: function(name, desc) {
        var entry, input;
        entry = {
          type: 'thread watcher',
          el: UI.checkbox(name, " " + name)
        };
        entry.el.title = desc;
        input = entry.el.firstElementChild;
        $.on(input, 'change', $.cb.checked);
        if (name === 'Current Board' || name === 'Show Unread Count') {
          $.on(input, 'change', ThreadWatcher.refresh);
        }
        return entry;
      }
    }
  };

  Unread = {
    init: function() {
      if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Unread Line'] && !Conf['Desktop Notifications'] && !(Conf['Thread Watcher'] && Conf['Show Unread Count'])) {
        return;
      }
      this.db = new DataBoard('lastReadPosts', this.sync);
      this.hr = $.el('hr', {
        id: 'unread-line'
      });
      this.posts = new RandomAccessList;
      this.postsQuotingYou = {};
      return Thread.callbacks.push({
        name: 'Unread',
        cb: this.node
      });
    },
    node: function() {
      Unread.thread = this;
      Unread.title = d.title;
      Unread.lastReadPost = Unread.db.get({
        boardID: this.board.ID,
        threadID: this.ID,
        defaultValue: 0
      });
      $.on(d, '4chanXInitFinished', Unread.ready);
      $.on(d, 'ThreadUpdate', Unread.onUpdate);
      $.on(d, 'scroll visibilitychange', Unread.read);
      if (Conf['Unread Line'] && !Conf['Quote Threading']) {
        return $.on(d, 'visibilitychange', Unread.setLine);
      }
    },
    ready: function() {
      var posts;
      $.off(d, '4chanXInitFinished', Unread.ready);
      if (!Conf['Quote Threading']) {
        posts = [];
        Unread.thread.posts.forEach(function(post) {
          if (post.isReply) {
            return posts.push(post);
          }
        });
        Unread.addPosts(posts);
      }
      if (Conf['Quote Threading']) {
        QuoteThreading.force();
      }
      if (Conf['Scroll to Last Read Post'] && !Conf['Quote Threading']) {
        return Unread.scroll();
      }
    },
    scroll: function() {
      var down, hash, keys, post, posts, root;
      if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
        return;
      }
      if (post = Unread.posts.first) {
        while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.data.nodes.root)) {
          if (!(post = Get.postFromRoot(root)).isHidden) {
            break;
          }
        }
        if (!root) {
          return;
        }
        down = true;
      } else {
        posts = Unread.thread.posts;
        keys = posts.keys;
        root = posts[keys[keys.length - 1]].nodes.root;
      }
      if (Header.getBottomOf(root) < 0) {
        return Header.scrollTo(root, down);
      }
    },
    sync: function() {
      var ID, lastReadPost, post;
      lastReadPost = Unread.db.get({
        boardID: Unread.thread.board.ID,
        threadID: Unread.thread.ID,
        defaultValue: 0
      });
      if (!(Unread.lastReadPost < lastReadPost)) {
        return;
      }
      Unread.lastReadPost = lastReadPost;
      post = Unread.posts.first;
      while (post) {
        if ((ID = post.ID, post) > Unread.lastReadPost) {
          break;
        }
        post = post.next;
        Unread.posts.rm(ID);
        delete Unread.postsQuotingYou[ID];
      }
      if (Conf['Unread Line'] && !Conf['Quote Threading']) {
        Unread.setLine();
      }
      return Unread.update();
    },
    addPost: function(post) {
      var _ref;
      if (post.ID <= Unread.lastReadPost || post.isHidden || ((_ref = QR.db) != null ? _ref.get({
        boardID: post.board.ID,
        threadID: post.thread.ID,
        postID: post.ID
      }) : void 0)) {
        return;
      }
      Unread.posts.push(post);
      return Unread.addPostQuotingYou(post);
    },
    addPosts: function(posts) {
      var post, _i, _len, _ref, _ref1;
      for (_i = 0, _len = posts.length; _i < _len; _i++) {
        post = posts[_i];
        Unread.addPost(post);
      }
      if (Conf['Unread Line'] && !Conf['Quote Threading']) {
        Unread.setLine((_ref = (_ref1 = Unread.posts.first) != null ? _ref1.data : void 0, __indexOf.call(posts, _ref) >= 0));
      }
      Unread.read();
      return Unread.update();
    },
    addPostQuotingYou: function(post) {
      var quotelink, _i, _len, _ref, _ref1;
      _ref = post.nodes.quotelinks;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        quotelink = _ref[_i];
        if (!((_ref1 = QR.db) != null ? _ref1.get(Get.postDataFromLink(quotelink)) : void 0)) {
          continue;
        }
        Unread.postsQuotingYou[post.ID] = post;
        Unread.openNotification(post);
        return;
      }
    },
    openNotification: function(post) {
      var name, notif;
      if (!Header.areNotificationsEnabled) {
        return;
      }
      name = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent.trim();
      notif = new Notification("" + name + " replied to you", {
        body: post.info[Conf['Remove Spoilers'] || Conf['Reveal Spoilers'] ? 'comment' : 'commentSpoilered'],
        icon: Favicon.logo
      });
      notif.onclick = function() {
        Header.scrollToIfNeeded(post.nodes.root, true);
        return window.focus();
      };
      return notif.onshow = function() {
        return setTimeout(function() {
          return notif.close();
        }, 7 * $.SECOND);
      };
    },
    onUpdate: function(e) {
      var fullID;
      if (e.detail[404]) {
        return Unread.update();
      } else if (!QuoteThreading.enabled) {
        return Unread.addPosts((function() {
          var _i, _len, _ref, _results;
          _ref = e.detail.newPosts;
          _results = [];
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            fullID = _ref[_i];
            _results.push(g.posts[fullID]);
          }
          return _results;
        })());
      } else {
        Unread.read();
        return Unread.update();
      }
    },
    readSinglePost: function(post) {
      var ID, posts;
      ID = post.ID;
      posts = Unread.posts;
      if (!posts[ID]) {
        return;
      }
      if (post === posts.first && !(Conf['Quote Threading'] && Unread.posts.length)) {
        Unread.lastReadPost = ID;
        Unread.saveLastReadPost();
      }
      posts.rm(ID);
      delete Unread.postsQuotingYou[ID];
      return Unread.update();
    },
    read: $.debounce(100, function(e) {
      var ID, data, height, maxID, post, posts, _ref;
      if (d.hidden || !Unread.posts.length) {
        return;
      }
      height = doc.clientHeight;
      posts = Unread.posts;
      maxID = 0;
      while (post = posts.first) {
        if (!(Header.getBottomOf(post.data.nodes.root) > -1)) {
          break;
        }
        ID = post.ID, data = post.data;
        maxID = Math.max(maxID, ID);
        posts.rm(ID);
        delete Unread.postsQuotingYou[ID];
        if (Conf['Mark Quotes of You'] && ((_ref = QR.db) != null ? _ref.get({
          boardID: data.board.ID,
          threadID: data.thread.ID,
          postID: ID
        }) : void 0)) {
          QuoteYou.lastRead = data.nodes.root;
        }
      }
      if (!maxID) {
        return;
      }
      if (!(Conf['Quote Threading'] && posts.length)) {
        if (Unread.lastReadPost < maxID || !Unread.lastReadPost) {
          Unread.lastReadPost = maxID;
        }
        Unread.saveLastReadPost();
      }
      if (e) {
        return Unread.update();
      }
    }),
    saveLastReadPost: $.debounce(2 * $.SECOND, function() {
      if (Unread.thread.isDead && !Unread.thread.isArchived) {
        return;
      }
      return Unread.db.set({
        boardID: Unread.thread.board.ID,
        threadID: Unread.thread.ID,
        val: Unread.lastReadPost
      });
    }),
    setLine: function(force) {
      var post;
      if (!(d.hidden || force === true)) {
        return;
      }
      if (!(post = Unread.posts.first)) {
        return $.rm(Unread.hr);
      }
      if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.data.nodes.root)) {
        return $.before(post.data.nodes.root, Unread.hr);
      }
    },
    update: function() {
      var count, countQuotingYou, titleCount, titleDead, titleQuotingYou;
      count = Unread.posts.length;
      countQuotingYou = Object.keys(Unread.postsQuotingYou).length;
      if (Conf['Unread Count']) {
        titleQuotingYou = Conf['Quoted Title'] && countQuotingYou ? '(!) ' : '';
        titleCount = count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '';
        titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title;
        d.title = "" + titleQuotingYou + titleCount + titleDead;
      }
      if (!Conf['Unread Favicon']) {
        return;
      }
      Favicon.el.href = Unread.thread.isDead ? countQuotingYou ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? countQuotingYou ? Favicon.unreadY : Favicon.unread : Favicon["default"];
      return $.add(d.head, Favicon.el);
    }
  };

  Redirect = {
    init: function() {
      var archive, archives, boardID, boards, data, files, id, name, o, record, software, type, withCredentials, _i, _j, _len, _len1, _ref, _ref1;
      o = {
        thread: {},
        post: {},
        file: {}
      };
      archives = {};
      _ref = Redirect.archives;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        data = _ref[_i];
        name = data.name, boards = data.boards, files = data.files, software = data.software, withCredentials = data.withCredentials;
        archives[name] = data;
        for (_j = 0, _len1 = boards.length; _j < _len1; _j++) {
          boardID = boards[_j];
          if (!(!withCredentials)) {
            continue;
          }
          if (!(boardID in o.thread)) {
            o.thread[boardID] = data;
          }
          if (!(boardID in o.post || software !== 'foolfuuka')) {
            o.post[boardID] = data;
          }
          if (!(boardID in o.file || __indexOf.call(files, boardID) < 0)) {
            o.file[boardID] = data;
          }
        }
      }
      _ref1 = Conf['selectedArchives'];
      for (boardID in _ref1) {
        record = _ref1[boardID];
        for (type in record) {
          id = record[type];
          if (id === 'disabled') {
            delete o[type][boardID];
          } else if (archive = archives[id]) {
            boards = type === 'file' ? archive.files : archive.boards;
            if (__indexOf.call(boards, boardID) >= 0) {
              o[type][boardID] = archive;
            }
          }
        }
      }
      return Redirect.data = o;
    },
    archives: [{"uid":0,"name":"Moe","domain":"archive.moe","http":true,"https":true,"software":"foolfuuka","boards":["a","biz","c","co","diy","gd","i","int","jp","m","mlp","out","po","s4s","sci","sp","tg","tv","v","vg","vp","vr","wsg"],"files":["a","biz","c","co","diy","gd","i","jp","m","po","s4s","sci","tg","v","vg","vp","vr","wsg"]},{"uid":1,"name":"NSFW Moe","domain":"nsfw.archive.moe","http":true,"https":true,"software":"foolfuuka","boards":["h","u"],"files":["h","u"]},{"uid":3,"name":"4plebs Archive","domain":"archive.4plebs.org","http":true,"https":true,"software":"foolfuuka","boards":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"],"files":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"]},{"uid":5,"name":"Love is Over","domain":"archive.loveisover.me","http":true,"https":true,"software":"foolfuuka","boards":["c","d","e","i","lgbt","t","u","w","wg"],"files":["c","d","e","i","lgbt","t","u","w","wg"]},{"uid":8,"name":"Rebecca Black Tech","domain":"rbt.asia","http":false,"https":true,"software":"fuuka","boards":["cgl","g","mu","w"],"files":["cgl","g","mu","w"]},{"uid":9,"name":"Heinessen","domain":"archive.heinessen.com","http":true,"https":false,"software":"fuuka","boards":["an","fit","k","mlp","r9k","toy"],"files":["an","fit","k","mlp","r9k","toy"]},{"uid":10,"name":"warosu","domain":"warosu.org","http":false,"https":true,"software":"fuuka","boards":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"],"files":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"]},{"uid":15,"name":"fgts","domain":"fgts.jp","http":true,"https":true,"software":"foolfuuka","boards":["asp","cm","h","hc","hm","n","p","r","s","soc","y"],"files":["asp","cm","h","hc","hm","n","p","r","s","soc","y"]}],
    to: function(dest, data) {
      var archive;
      archive = (dest === 'search' ? Redirect.data.thread : Redirect.data[dest])[data.boardID];
      if (!archive) {
        return '';
      }
      return Redirect[dest](archive, data);
    },
    protocol: function(archive) {
      var protocol;
      protocol = location.protocol;
      if (!archive[protocol.slice(0, -1)]) {
        protocol = protocol === 'https:' ? 'http:' : 'https:';
      }
      return "" + protocol + "//";
    },
    thread: function(archive, _arg) {
      var boardID, path, postID, threadID;
      boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID;
      path = threadID ? "" + boardID + "/thread/" + threadID : "" + boardID + "/post/" + postID;
      if (archive.software === 'foolfuuka') {
        path += '/';
      }
      if (threadID && postID) {
        path += archive.software === 'foolfuuka' ? "#" + postID : "#p" + postID;
      }
      return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path;
    },
    post: function(archive, _arg) {
      var URL, boardID, postID, protocol;
      boardID = _arg.boardID, postID = _arg.postID;
      protocol = Redirect.protocol(archive);
      URL = new String("" + protocol + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID);
      if (!Redirect.securityCheck(URL)) {
        return '';
      }
      URL.archive = archive;
      return URL;
    },
    file: function(archive, _arg) {
      var boardID, filename;
      boardID = _arg.boardID, filename = _arg.filename;
      return "" + (Redirect.protocol(archive)) + archive.domain + "/" + boardID + "/full_image/" + filename;
    },
    search: function(archive, _arg) {
      var boardID, path, type, value;
      boardID = _arg.boardID, type = _arg.type, value = _arg.value;
      type = type === 'name' ? 'username' : type === 'MD5' ? 'image' : type;
      value = encodeURIComponent(value);
      path = archive.software === 'foolfuuka' ? "" + boardID + "/search/" + type + "/" + value : "" + boardID + "/?task=search2&search_" + (type === 'image' ? 'media_hash' : type) + "=" + value;
      return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path;
    },
    securityCheck: function(URL) {
      return /^https:\/\//.test(URL) || location.protocol === 'http:' || Conf['Except Archives from Encryption'];
    },
    navigate: function(URL, alternative) {
      if (URL && (Redirect.securityCheck(URL) || confirm("Redirect to " + URL + "?\n\nYour connection will not be encrypted."))) {
        return location.replace(URL);
      } else if (alternative) {
        return location.replace(alternative);
      }
    }
  };

  PSAHiding = {
    init: function() {
      if (!Conf['Announcement Hiding']) {
        return;
      }
      $.addClass(doc, 'hide-announcement');
      return $.on(d, '4chanXInitFinished', this.setup);
    },
    setup: function() {
      var btn, entry, psa;
      $.off(d, '4chanXInitFinished', PSAHiding.setup);
      if (!(psa = $.id('globalMessage'))) {
        $.rmClass(doc, 'hide-announcement');
        return;
      }
      entry = {
        el: $.el('a', {
          textContent: 'Show announcement',
          className: 'show-announcement',
          href: 'javascript:;'
        }),
        order: 50,
        open: function() {
          return psa.hidden;
        }
      };
      Header.menu.addEntry(entry);
      $.on(entry.el, 'click', PSAHiding.toggle);
      PSAHiding.btn = btn = $.el('span', {
        title: 'Mark announcement as read and hide.',
        className: 'hide-announcement'
      });
      $.extend(btn, {
        innerHTML: "[<a href=\"javascript:;\">Dismiss</a>]"
      });
      $.on(btn, 'click', PSAHiding.toggle);
      $.get('hiddenPSA', 0, function(_arg) {
        var hiddenPSA;
        hiddenPSA = _arg.hiddenPSA;
        PSAHiding.sync(hiddenPSA);
        $.add(psa, btn);
        return $.rmClass(doc, 'hide-announcement');
      });
      return $.sync('hiddenPSA', PSAHiding.sync);
    },
    toggle: function(e) {
      var UTC;
      if ($.hasClass(this, 'hide-announcement')) {
        UTC = +$.id('globalMessage').dataset.utc;
        $.set('hiddenPSA', UTC);
      } else {
        $.event('CloseMenu');
        $["delete"]('hiddenPSA');
      }
      return PSAHiding.sync(UTC);
    },
    sync: function(UTC) {
      var hr, psa;
      psa = $.id('globalMessage');
      psa.hidden = PSAHiding.btn.hidden = UTC && UTC >= +psa.dataset.utc ? true : false;
      if ((hr = psa.nextElementSibling) && hr.nodeName === 'HR') {
        return hr.hidden = psa.hidden;
      }
    }
  };

  Banner = {
    init: function() {
      return $.asap((function() {
        return d.body;
      }), function() {
        return $.asap((function() {
          return $('hr');
        }), Banner.ready);
      });
    },
    ready: function() {
      var banner, child, children, i;
      banner = $(".boardBanner");
      children = banner.children;
      i = 0;
      while (child = children[i++]) {
        if (i === 1) {
          child.title = "Click to change";
          $.on(child, 'click', Banner.cb.toggle);
          continue;
        }
        if (Conf['Custom Board Titles']) {
          Banner.custom(child).title = "Ctrl+click to edit board " + (i === 3 ? 'sub' : '') + "title";
          child.spellcheck = false;
        }
      }
    },
    cb: {
      toggle: (function() {
        var types;
        types = {
          jpg: 227,
          png: 270,
          gif: 253
        };
        return function() {
          var num, type;
          type = Object.keys(types)[Math.floor(3 * Math.random())];
          num = Math.floor(types[type] * Math.random());
          return $('img', this.parentNode).src = "//s.4cdn.org/image/title/" + num + "." + type;
        };
      })(),
      click: function(e) {
        if (e.ctrlKey) {
          this.contentEditable = true;
          return this.focus();
        }
      },
      keydown: function(e) {
        e.stopPropagation();
        if (!e.shiftKey && e.keyCode === 13) {
          return this.blur();
        }
      },
      focus: function() {
        var items, string, string2;
        string = "" + g.BOARD + "." + this.className;
        string2 = "" + string + ".orig";
        items = {
          title: this.textContent
        };
        items[string] = '';
        items[string2] = false;
        $.get(items, function(items) {
          if (!(items[string2] && items.title === items[string])) {
            return $.set(string2, items.title);
          }
        });
      },
      blur: function() {
        this.contentEditable = false;
        return $.set("" + g.BOARD + "." + this.className, this.textContent);
      }
    },
    custom: function(child) {
      var cachedTest, string;
      cachedTest = child.textContent;
      string = "" + g.BOARD + "." + child.className;
      $.on(child, 'click keydown focus blur', function(e) {
        return Banner.cb[e.type].apply(this, [e]);
      });
      $.get(string, cachedTest, function(item) {
        var string2, title;
        if (!(title = item[string])) {
          return;
        }
        if (Conf['Persistent Custom Board Titles']) {
          return child.textContent = title;
        }
        string2 = "" + string + ".orig";
        return $.get(string2, cachedTest, function(itemb) {
          if (cachedTest === itemb[string2]) {
            return child.textContent = title;
          } else {
            $.set(string, cachedTest);
            return $.set(string2, cachedTest);
          }
        });
      });
      return child;
    }
  };

  CatalogLinks = {
    init: function() {
      var el, input;
      if (!Conf['Catalog Links']) {
        return;
      }
      CatalogLinks.el = el = UI.checkbox('Header catalog links', ' Catalog Links');
      el.id = 'toggleCatalog';
      input = $('input', el);
      $.on(input, 'change', this.toggle);
      $.sync('Header catalog links', CatalogLinks.set);
      return Header.menu.addEntry({
        el: el,
        order: 95
      });
    },
    initBoardList: function() {
      if (!Conf['Catalog Links']) {
        return;
      }
      return CatalogLinks.set(Conf['Header catalog links']);
    },
    toggle: function() {
      $.event('CloseMenu');
      $.set('Header catalog links', this.checked);
      return CatalogLinks.set(this.checked);
    },
    set: function(useCatalog) {
      var a, board, generateURL, path, _i, _len, _ref, _ref1;
      path = useCatalog ? 'catalog' : '';
      generateURL = useCatalog && Conf['External Catalog'] ? CatalogLinks.external : function(board) {
        return a.href = "/" + board + "/" + path;
      };
      _ref = $$("#board-list a:not(.catalog), #boardNavDesktopFoot a");
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        a = _ref[_i];
        if (((_ref1 = a.hostname) !== 'boards.4chan.org' && _ref1 !== 'catalog.neet.tv' && _ref1 !== '4index.gropes.us') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || $.hasClass(a, 'external')) {
          continue;
        }
        a.href = generateURL(board);
      }
      return CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + ".";
    },
    external: function(board) {
      if (board === 'a' || board === 'c' || board === 'g' || board === 'biz' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'vr' || board === 'w' || board === 'wg' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'asp' || board === 'cgl' || board === 'ck' || board === 'co' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'gd' || board === 'int' || board === 'jp' || board === 'lit' || board === 'mlp' || board === 'mu' || board === 'n' || board === 'out' || board === 'po' || board === 'sci' || board === 'sp' || board === 'tg' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'wsg' || board === 'x' || board === 'f' || board === 'pol' || board === 's4s' || board === 'lgbt') {
        return "http://catalog.neet.tv/" + board;
      } else {
        return "/" + board + "/catalog";
      }
    }
  };

  CustomCSS = {
    init: function() {
      if (!Conf['Custom CSS']) {
        return;
      }
      return this.addStyle();
    },
    addStyle: function() {
      return this.style = $.addStyle(Conf['usercss']);
    },
    rmStyle: function() {
      if (this.style) {
        $.rm(this.style);
        return delete this.style;
      }
    },
    update: function() {
      if (!this.style) {
        this.addStyle();
      }
      return this.style.textContent = Conf['usercss'];
    }
  };

  ExpandComment = {
    init: function() {
      if (g.VIEW !== 'index' || !Conf['Comment Expansion']) {
        return;
      }
      if (g.BOARD.ID === 'g') {
        this.callbacks.push(Fourchan.code);
      }
      if (g.BOARD.ID === 'sci') {
        this.callbacks.push(Fourchan.math);
      }
      return Post.callbacks.push({
        name: 'Comment Expansion',
        cb: this.node
      });
    },
    node: function() {
      var a;
      if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) {
        return $.on(a, 'click', ExpandComment.cb);
      }
    },
    callbacks: [],
    cb: function(e) {
      e.preventDefault();
      return ExpandComment.expand(Get.postFromNode(this));
    },
    expand: function(post) {
      var a;
      if (post.nodes.longComment && !post.nodes.longComment.parentNode) {
        $.replace(post.nodes.shortComment, post.nodes.longComment);
        post.nodes.comment = post.nodes.longComment;
        return;
      }
      if (!(a = $('.abbr > a', post.nodes.comment))) {
        return;
      }
      a.textContent = "Post No." + post + " Loading...";
      return $.cache("//a.4cdn.org" + (a.pathname.split('/').splice(0, 4).join('/')) + ".json", function() {
        return ExpandComment.parse(this, a, post);
      });
    },
    contract: function(post) {
      var a;
      if (!post.nodes.shortComment) {
        return;
      }
      a = $('.abbr > a', post.nodes.shortComment);
      a.textContent = 'here';
      $.replace(post.nodes.longComment, post.nodes.shortComment);
      return post.nodes.comment = post.nodes.shortComment;
    },
    parse: function(req, a, post) {
      var callback, clone, comment, href, postObj, posts, quote, spoilerRange, status, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
      status = req.status;
      if (status !== 200 && status !== 304) {
        a.textContent = "Error " + req.statusText + " (" + status + ")";
        return;
      }
      posts = req.response.posts;
      if (spoilerRange = posts[0].custom_spoiler) {
        Build.spoilerRange[g.BOARD] = spoilerRange;
      }
      for (_i = 0, _len = posts.length; _i < _len; _i++) {
        postObj = posts[_i];
        if (postObj.no === post.ID) {
          break;
        }
      }
      if (postObj.no !== post.ID) {
        a.textContent = "Post No." + post + " not found.";
        return;
      }
      comment = post.nodes.comment;
      clone = comment.cloneNode(false);
      clone.innerHTML = postObj.com;
      _ref = $$('.quotelink', clone);
      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
        quote = _ref[_j];
        href = quote.getAttribute('href');
        if (href[0] === '/') {
          continue;
        }
        if (href[0] === '#') {
          quote.href = "" + (a.pathname.split('/').splice(0, 4).join('/')) + href;
        } else {
          quote.href = "" + (a.pathname.split('/').splice(0, 3).join('/')) + "/" + href;
        }
      }
      post.nodes.shortComment = comment;
      $.replace(comment, clone);
      post.nodes.comment = post.nodes.longComment = clone;
      post.parseComment();
      post.parseQuotes();
      _ref1 = ExpandComment.callbacks;
      for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
        callback = _ref1[_k];
        callback.call(post);
      }
    }
  };

  ExpandThread = {
    statuses: {},
    init: function() {
      if (g.VIEW === 'thread' || !Conf['Thread Expansion']) {
        return;
      }
      if (Conf['JSON Navigation']) {
        return $.on(d, 'IndexRefresh', this.onIndexRefresh);
      } else {
        return Thread.callbacks.push({
          name: 'Expand Thread',
          cb: function() {
            return ExpandThread.setButton(this);
          }
        });
      }
    },
    setButton: function(thread) {
      var a;
      if (!(a = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) {
        return;
      }
      a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g))));
      a.style.cursor = 'pointer';
      return $.on(a, 'click', ExpandThread.cbToggle);
    },
    disconnect: function(refresh) {
      var status, threadID, _ref, _ref1;
      if (g.VIEW === 'thread' || !Conf['Thread Expansion']) {
        return;
      }
      _ref = ExpandThread.statuses;
      for (threadID in _ref) {
        status = _ref[threadID];
        if ((_ref1 = status.req) != null) {
          _ref1.abort();
        }
        delete ExpandThread.statuses[threadID];
      }
      if (!refresh) {
        return $.off(d, 'IndexRefresh', this.onIndexRefresh);
      }
    },
    onIndexRefresh: function() {
      ExpandThread.disconnect(true);
      return g.BOARD.threads.forEach(function(thread) {
        return ExpandThread.setButton(thread);
      });
    },
    text: function(status, posts, files) {
      return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + ".");
    },
    cbToggle: function(e) {
      if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
        return;
      }
      e.preventDefault();
      return ExpandThread.toggle(Get.threadFromNode(this));
    },
    toggle: function(thread) {
      var a, threadRoot;
      threadRoot = thread.OP.nodes.root.parentNode;
      if (!(a = $('.summary', threadRoot))) {
        return;
      }
      if (thread.ID in ExpandThread.statuses) {
        return ExpandThread.contract(thread, a, threadRoot);
      } else {
        return ExpandThread.expand(thread, a, threadRoot);
      }
    },
    expand: function(thread, a, threadRoot) {
      var status;
      ExpandThread.statuses[thread] = status = {};
      a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g))));
      return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() {
        delete status.req;
        return ExpandThread.parse(this, thread, a);
      });
    },
    contract: function(thread, a, threadRoot) {
      var filesCount, inlined, num, postsCount, replies, reply, status, _i, _len;
      status = ExpandThread.statuses[thread];
      delete ExpandThread.statuses[thread];
      if (status.req) {
        status.req.abort();
        if (a) {
          a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g))));
        }
        return;
      }
      replies = $$('.thread > .replyContainer', threadRoot);
      if (!Conf['JSON Navigation'] || Conf['Show Replies']) {
        num = (function() {
          if (thread.isSticky) {
            return 1;
          } else {
            switch (g.BOARD.ID) {
              case 'b':
              case 'vg':
                return 3;
              case 't':
                return 1;
              default:
                return 5;
            }
          }
        })();
        replies = replies.slice(0, -num);
      }
      postsCount = 0;
      filesCount = 0;
      for (_i = 0, _len = replies.length; _i < _len; _i++) {
        reply = replies[_i];
        if (Conf['Quote Inlining']) {
          while (inlined = $('.inlined', reply)) {
            inlined.click();
          }
        }
        postsCount++;
        if ('file' in Get.postFromRoot(reply)) {
          filesCount++;
        }
        $.rm(reply);
      }
      return a.textContent = ExpandThread.text('+', postsCount, filesCount);
    },
    parse: function(req, thread, a) {
      var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1;
      if ((_ref = req.status) !== 200 && _ref !== 304) {
        a.textContent = "Error " + req.statusText + " (" + req.status + ")";
        return;
      }
      Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler;
      posts = [];
      postsRoot = [];
      filesCount = 0;
      _ref1 = req.response.posts;
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        postData = _ref1[_i];
        if (postData.no === thread.ID) {
          continue;
        }
        if (post = thread.posts[postData.no]) {
          if ('file' in post) {
            filesCount++;
          }
          postsRoot.push(post.nodes.root);
          continue;
        }
        root = Build.postFromObject(postData, thread.board.ID);
        post = new Post(root, thread, thread.board);
        if ('file' in post) {
          filesCount++;
        }
        posts.push(post);
        postsRoot.push(root);
      }
      Main.callbackNodes(Post, posts);
      $.after(a, postsRoot);
      $.event('PostsInserted');
      postsCount = postsRoot.length;
      a.textContent = ExpandThread.text('-', postsCount, filesCount);
      return Fourchan.parseThread(thread.ID, 1, postsCount);
    }
  };

  FileInfo = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['File Info Formatting']) {
        return;
      }
      return Post.callbacks.push({
        name: 'File Info Formatting',
        cb: this.node
      });
    },
    node: function() {
      if (!this.file || this.isClone) {
        return;
      }
      $.extend(this.file.text, {
        innerHTML: "<span class=\"file-info\"></span>"
      });
      return FileInfo.format(Conf['fileInfo'], this, this.file.text.firstElementChild);
    },
    format: function(formatString, post, outputNode) {
      var output;
      output = [];
      formatString.replace(/%(.)|[^%]+/g, function(s, c) {
        output.push(c in FileInfo.formatters ? FileInfo.formatters[c].call(post) : {
          innerHTML: E(s)
        });
        return '';
      });
      return $.extend(outputNode, {
        innerHTML: output.map(function(x) {
          return x.innerHTML;
        }).join('')
      });
    },
    formatters: {
      t: function() {
        return {
          innerHTML: E(this.file.URL.match(/\d+\..+$/)[0])
        };
      },
      T: function() {
        return {
          innerHTML: "<a href=\"" + E(this.file.URL) + "\" target=\"_blank\">" + FileInfo.formatters.t.call(this).innerHTML + "</a>"
        };
      },
      l: function() {
        return {
          innerHTML: "<a href=\"" + E(this.file.URL) + "\" target=\"_blank\">" + FileInfo.formatters.n.call(this).innerHTML + "</a>"
        };
      },
      L: function() {
        return {
          innerHTML: "<a href=\"" + E(this.file.URL) + "\" target=\"_blank\">" + FileInfo.formatters.N.call(this).innerHTML + "</a>"
        };
      },
      n: function() {
        var fullname, shortname;
        fullname = this.file.name;
        shortname = Build.shortFilename(this.file.name, this.isReply);
        if (fullname === shortname) {
          return {
            innerHTML: E(fullname)
          };
        } else {
          return {
            innerHTML: "<span class=\"fnswitch\"><span class=\"fntrunc\">" + E(shortname) + "</span><span class=\"fnfull\">" + E(fullname) + "</span></span>"
          };
        }
      },
      N: function() {
        return {
          innerHTML: E(this.file.name)
        };
      },
      p: function() {
        if (this.file.isSpoiler) {
          return {
            innerHTML: "Spoiler, "
          };
        } else {
          return {
            innerHTML: ""
          };
        }
      },
      s: function() {
        return {
          innerHTML: E(this.file.size)
        };
      },
      B: function() {
        return {
          innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes"
        };
      },
      K: function() {
        return {
          innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB"
        };
      },
      M: function() {
        return {
          innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB"
        };
      },
      r: function() {
        return {
          innerHTML: E(this.file.dimensions || "PDF")
        };
      },
      '%': function() {
        return {
          innerHTML: "%"
        };
      }
    }
  };

  Fourchan = {
    init: function() {
      var board;
      if (g.VIEW === 'catalog') {
        return;
      }
      board = g.BOARD.ID;
      if (board === 'g') {
        $.globalEval('window.addEventListener(\'prettyprint\', function(e) {\n  window.dispatchEvent(new CustomEvent(\'prettyprint:cb\', {\n    detail: prettyPrintOne(e.detail)\n  }));\n}, false);');
        Post.callbacks.push({
          name: 'Parse /g/ code',
          cb: this.code
        });
      }
      if (board === 'sci') {
        $.globalEval('window.addEventListener(\'jsmath\', function(e) {\n  if (!jsMath) return;\n  if (jsMath.loaded) {\n    // process one post\n    jsMath.ProcessBeforeShowing(document.getElementById(e.detail));\n  } else if (jsMath.Autoload && jsMath.Autoload.checked) {\n    // load jsMath and process whole document\n    jsMath.Autoload.Script.Push(\'ProcessBeforeShowing\', [null]);\n    jsMath.Autoload.LoadJsMath();\n  }\n}, false);');
        return Post.callbacks.push({
          name: 'Parse /sci/ math',
          cb: this.math
        });
      }
    },
    code: function() {
      var apply, pre, _i, _len, _ref;
      if (this.isClone) {
        return;
      }
      apply = function(e) {
        pre.innerHTML = e.detail;
        return $.addClass(pre, 'prettyprinted');
      };
      $.on(window, 'prettyprint:cb', apply);
      _ref = $$('.prettyprint:not(.prettyprinted)', this.nodes.comment);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        pre = _ref[_i];
        $.event('prettyprint', pre.innerHTML, window);
      }
      $.off(window, 'prettyprint:cb', apply);
    },
    math: function() {
      if ((this.isClone && doc.contains(this.origin.nodes.root)) || !$('.math', this.nodes.comment)) {
        return;
      }
      return $.asap(((function(_this) {
        return function() {
          return doc.contains(_this.nodes.post);
        };
      })(this)), (function(_this) {
        return function() {
          return $.event('jsmath', _this.nodes.post.id, window);
        };
      })(this));
    },
    parseThread: function(threadID, offset, limit) {
      return $.event('4chanParsingDone', {
        threadId: threadID,
        offset: offset,
        limit: limit
      });
    }
  };

  IDColor = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Color User IDs']) {
        return;
      }
      this.ids = {};
      return Post.callbacks.push({
        name: 'Color User IDs',
        cb: this.node
      });
    },
    node: function() {
      var rgb, span, style, uid;
      if (this.isClone || !(uid = this.info.uniqueID)) {
        return;
      }
      span = $('.hand', this.nodes.uniqueID);
      if (!(span && span.nodeName === 'SPAN')) {
        return;
      }
      rgb = IDColor.compute(uid);
      style = span.style;
      style.color = rgb[3];
      style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
      $.addClass(span, 'painted');
      return span.title = 'Highlight posts by this ID';
    },
    compute: function(uid) {
      var hash, rgb;
      if (IDColor.ids[uid]) {
        return IDColor.ids[uid];
      }
      hash = IDColor.hash(uid);
      rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF];
      rgb[3] = (rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff';
      return this.ids[uid] = rgb;
    },
    hash: function(uid) {
      var i, msg;
      msg = 0;
      i = 0;
      while (i < 8) {
        msg = (msg << 5) - msg + uid.charCodeAt(i++);
      }
      return msg;
    }
  };

  Keybinds = {
    init: function() {
      var hotkey, init;
      if (!Conf['Keybinds']) {
        return;
      }
      for (hotkey in Conf.hotkeys) {
        $.sync(hotkey, Keybinds.sync);
      }
      init = function() {
        var node, _i, _len, _ref;
        $.off(d, '4chanXInitFinished', init);
        $.on(d, 'keydown', Keybinds.keydown);
        _ref = $$('[accesskey]');
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          node = _ref[_i];
          node.removeAttribute('accesskey');
        }
      };
      return $.on(d, '4chanXInitFinished', init);
    },
    sync: function(key, hotkey) {
      return Conf[hotkey] = key;
    },
    keydown: function(e) {
      var form, key, notification, notifications, op, searchInput, target, thread, threadRoot, _i, _len, _ref;
      if (!(key = Keybinds.keyCode(e))) {
        return;
      }
      target = e.target;
      if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') {
        if (!/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key)) {
          return;
        }
      }
      if (g.VIEW !== 'catalog') {
        threadRoot = Nav.getThread();
        if (op = $('.op', threadRoot)) {
          thread = Get.postFromNode(op).thread;
        }
      }
      switch (key) {
        case Conf['Toggle board list']:
          if (Conf['Custom Board Navigation']) {
            Header.toggleBoardList();
          }
          break;
        case Conf['Toggle header']:
          Header.toggleBarVisibility();
          break;
        case Conf['Open empty QR']:
          Keybinds.qr();
          break;
        case Conf['Open QR']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Keybinds.qr(threadRoot);
          break;
        case Conf['Open settings']:
          Settings.open();
          break;
        case Conf['Close']:
          if (Settings.dialog) {
            Settings.close();
          } else if ((notifications = $$('.notification')).length) {
            for (_i = 0, _len = notifications.length; _i < _len; _i++) {
              notification = notifications[_i];
              $('.close', notification).click();
            }
          } else if (QR.nodes) {
            if (Conf['Persistent QR']) {
              QR.hide();
            } else {
              QR.close();
            }
          }
          break;
        case Conf['Spoiler tags']:
          if (target.nodeName !== 'TEXTAREA') {
            return;
          }
          Keybinds.tags('spoiler', target);
          break;
        case Conf['Code tags']:
          if (target.nodeName !== 'TEXTAREA') {
            return;
          }
          Keybinds.tags('code', target);
          break;
        case Conf['Eqn tags']:
          if (target.nodeName !== 'TEXTAREA') {
            return;
          }
          Keybinds.tags('eqn', target);
          break;
        case Conf['Math tags']:
          if (target.nodeName !== 'TEXTAREA') {
            return;
          }
          Keybinds.tags('math', target);
          break;
        case Conf['Toggle sage']:
          if (QR.nodes) {
            Keybinds.sage();
          }
          break;
        case Conf['Submit QR']:
          if (QR.nodes && !QR.status()) {
            QR.submit();
          }
          break;
        case Conf['Update']:
          switch (g.VIEW) {
            case 'thread':
              if (Conf['Thread Updater']) {
                ThreadUpdater.update();
              }
              break;
            case 'index':
              if (Conf['JSON Navigation']) {
                Index.update();
              }
          }
          break;
        case Conf['Watch']:
          if (g.VIEW === 'catalog') {
            return;
          }
          ThreadWatcher.toggle(thread);
          break;
        case Conf['Expand image']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Keybinds.img(threadRoot);
          break;
        case Conf['Expand images']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Keybinds.img(threadRoot, true);
          break;
        case Conf['Open Gallery']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Gallery.cb.toggle();
          break;
        case Conf['fappeTyme']:
          if (g.VIEW === 'catalog') {
            return;
          }
          FappeTyme.cb.toggle.call({
            name: 'fappe'
          });
          break;
        case Conf['werkTyme']:
          if (g.VIEW === 'catalog') {
            return;
          }
          FappeTyme.cb.toggle.call({
            name: 'werk'
          });
          break;
        case Conf['Front page']:
          if (Conf['JSON Navigation'] && g.VIEW === 'index') {
            Index.userPageNav(1);
          } else {
            window.location = "/" + g.BOARD + "/";
          }
          break;
        case Conf['Open front page']:
          $.open("/" + g.BOARD + "/");
          break;
        case Conf['Next page']:
          if (g.VIEW !== 'index') {
            return;
          }
          if (Conf['JSON Navigation']) {
            if (Conf['Index Mode'] !== 'all pages') {
              $('.next button', Index.pagelist).click();
            }
          } else {
            if (form = $('.next form')) {
              window.location = form.action;
            }
          }
          break;
        case Conf['Previous page']:
          if (g.VIEW !== 'index') {
            return;
          }
          if (Conf['JSON Navigation']) {
            if (Conf['Index Mode'] !== 'all pages') {
              $('.prev button', Index.pagelist).click();
            }
          } else {
            if (form = $('.prev form')) {
              window.location = form.action;
            }
          }
          break;
        case Conf['Search form']:
          if (g.VIEW !== 'index') {
            return;
          }
          searchInput = Conf['JSON Navigation'] ? Index.searchInput : $.id('search-box');
          Header.scrollToIfNeeded(searchInput);
          searchInput.focus();
          break;
        case Conf['Open catalog']:
          if (Conf['External Catalog']) {
            window.location = CatalogLinks.external(g.BOARD.ID);
          } else {
            window.location = "/" + g.BOARD + "/catalog";
          }
          break;
        case Conf['Next thread']:
          if (g.VIEW !== 'index') {
            return;
          }
          Nav.scroll(+1);
          break;
        case Conf['Previous thread']:
          if (g.VIEW !== 'index') {
            return;
          }
          Nav.scroll(-1);
          break;
        case Conf['Expand thread']:
          if (g.VIEW !== 'index') {
            return;
          }
          ExpandThread.toggle(thread);
          break;
        case Conf['Open thread']:
          if (g.VIEW !== 'index') {
            return;
          }
          Keybinds.open(thread);
          break;
        case Conf['Open thread tab']:
          if (g.VIEW !== 'index') {
            return;
          }
          Keybinds.open(thread, true);
          break;
        case Conf['Next reply']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Keybinds.hl(+1, threadRoot);
          break;
        case Conf['Previous reply']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Keybinds.hl(-1, threadRoot);
          break;
        case Conf['Deselect reply']:
          if (g.VIEW === 'catalog') {
            return;
          }
          Keybinds.hl(0, threadRoot);
          break;
        case Conf['Hide']:
          if (g.VIEW === 'catalog') {
            return;
          }
          if (ThreadHiding.db) {
            ThreadHiding.toggle(thread);
          }
          break;
        case Conf['Previous Post Quoting You']:
          if (g.VIEW === 'catalog') {
            return;
          }
          QuoteYou.cb.seek('preceding');
          break;
        case Conf['Next Post Quoting You']:
          if (g.VIEW === 'catalog') {
            return;
          }
          QuoteYou.cb.seek('following');
          break;
        default:
          return;
      }
      e.preventDefault();
      return e.stopPropagation();
    },
    keyCode: function(e) {
      var kc, key;
      key = (function() {
        switch (kc = e.keyCode) {
          case 8:
            return '';
          case 13:
            return 'Enter';
          case 27:
            return 'Esc';
          case 37:
            return 'Left';
          case 38:
            return 'Up';
          case 39:
            return 'Right';
          case 40:
            return 'Down';
          default:
            if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) {
              return String.fromCharCode(kc).toLowerCase();
            } else {
              return null;
            }
        }
      })();
      if (key) {
        if (e.altKey) {
          key = 'Alt+' + key;
        }
        if (e.ctrlKey) {
          key = 'Ctrl+' + key;
        }
        if (e.metaKey) {
          key = 'Meta+' + key;
        }
        if (e.shiftKey) {
          key = 'Shift+' + key;
        }
      }
      return key;
    },
    qr: function(thread) {
      if (!(Conf['Quick Reply'] && QR.postingIsEnabled)) {
        return;
      }
      QR.open();
      if (thread != null) {
        QR.quote.call($('input', $('.post.highlight', thread) || thread));
      }
      QR.nodes.com.focus();
      if (Conf['QR Shortcut']) {
        return $.rmClass($('.qr-shortcut'), 'disabled');
      }
    },
    tags: function(tag, ta) {
      var range, selEnd, selStart, value;
      value = ta.value;
      selStart = ta.selectionStart;
      selEnd = ta.selectionEnd;
      ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd);
      range = ("[" + tag + "]").length + selEnd;
      ta.setSelectionRange(range, range);
      return $.event('input', null, ta);
    },
    sage: function() {
      var isSage;
      isSage = /sage/i.test(QR.nodes.email.value);
      return QR.nodes.email.value = isSage ? "" : "sage";
    },
    img: function(thread, all) {
      var post;
      if (all) {
        return ImageExpand.cb.toggleAll();
      } else {
        post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread));
        return ImageExpand.toggle(post);
      }
    },
    open: function(thread, tab) {
      var url;
      if (g.VIEW !== 'index') {
        return;
      }
      url = "/" + thread.board + "/thread/" + thread;
      if (tab) {
        return $.open(url);
      } else {
        return location.href = url;
      }
    },
    hl: function(delta, thread) {
      var axis, height, next, postEl, replies, reply, root, _i, _len;
      postEl = $('.reply.highlight', thread);
      if (!delta) {
        if (postEl) {
          $.rmClass(postEl, 'highlight');
        }
        return;
      }
      if (postEl) {
        height = postEl.getBoundingClientRect().height;
        if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) {
          root = postEl.parentNode;
          axis = delta === +1 ? 'following' : 'preceding';
          if (!(next = $.x("" + axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) {
            return;
          }
          Header.scrollToIfNeeded(next, delta === +1);
          this.focus(next);
          $.rmClass(postEl, 'highlight');
          return;
        }
        $.rmClass(postEl, 'highlight');
      }
      replies = $$('.reply', thread);
      if (delta === -1) {
        replies.reverse();
      }
      for (_i = 0, _len = replies.length; _i < _len; _i++) {
        reply = replies[_i];
        if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) {
          this.focus(reply);
          return;
        }
      }
    },
    focus: function(post) {
      return $.addClass(post, 'highlight');
    }
  };

  Nav = {
    init: function() {
      var append, next, prev, span;
      switch (g.VIEW) {
        case 'index':
          if (!Conf['Index Navigation']) {
            return;
          }
          break;
        case 'thread':
          if (!Conf['Reply Navigation']) {
            return;
          }
          break;
        default:
          return;
      }
      span = $.el('span', {
        id: 'navlinks'
      });
      prev = $.el('a', {
        textContent: '▲',
        href: 'javascript:;'
      });
      next = $.el('a', {
        textContent: '▼',
        href: 'javascript:;'
      });
      $.on(prev, 'click', this.prev);
      $.on(next, 'click', this.next);
      $.add(span, [prev, $.tn(' '), next]);
      append = function() {
        $.off(d, '4chanXInitFinished', append);
        return $.add(d.body, span);
      };
      return $.on(d, '4chanXInitFinished', append);
    },
    prev: function() {
      if (g.VIEW === 'thread') {
        return window.scrollTo(0, 0);
      } else {
        return Nav.scroll(-1);
      }
    },
    next: function() {
      if (g.VIEW === 'thread') {
        return window.scrollTo(0, d.body.scrollHeight);
      } else {
        return Nav.scroll(+1);
      }
    },
    getThread: function() {
      var thread, threadRoot, _i, _len, _ref;
      _ref = $$('.thread');
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        threadRoot = _ref[_i];
        thread = Get.threadFromRoot(threadRoot);
        if (thread.isHidden && !thread.stub) {
          continue;
        }
        if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) {
          return threadRoot;
        }
      }
      return $('.board');
    },
    scroll: function(delta) {
      var axis, next, thread, top;
      thread = Nav.getThread();
      axis = delta === +1 ? 'following' : 'preceding';
      if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) {
        top = Header.getTopOf(thread);
        if (delta === +1 && top < 5 || delta === -1 && top > -5) {
          thread = next;
        }
      }
      return Header.scrollTo(thread);
    }
  };

  RelativeDates = {
    INTERVAL: $.MINUTE / 2,
    init: function() {
      switch (g.VIEW) {
        case 'index':
          this.flush();
          $.on(d, 'visibilitychange', this.flush);
          if (!Conf['Relative Post Dates']) {
            return;
          }
          break;
        case 'thread':
          if (!Conf['Relative Post Dates']) {
            return;
          }
          this.flush();
          if (g.VIEW === 'thread') {
            $.on(d, 'visibilitychange ThreadUpdate', this.flush);
          }
          break;
        default:
          return;
      }
      return Post.callbacks.push({
        name: 'Relative Post Dates',
        cb: this.node
      });
    },
    node: function() {
      var dateEl;
      if (this.isClone) {
        return;
      }
      dateEl = this.nodes.date;
      dateEl.title = dateEl.textContent;
      return RelativeDates.update(this);
    },
    relative: function(diff, now, date) {
      var days, months, number, rounded, unit, years;
      unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second');
      rounded = Math.round(number);
      if (rounded !== 1) {
        unit += 's';
      }
      return "" + rounded + " " + unit + " ago";
    },
    stale: [],
    flush: function() {
      var data, now, _i, _len, _ref;
      if (d.hidden) {
        return;
      }
      now = new Date();
      _ref = RelativeDates.stale;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        data = _ref[_i];
        RelativeDates.update(data, now);
      }
      RelativeDates.stale = [];
      clearTimeout(RelativeDates.timeout);
      return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL);
    },
    update: function(data, now) {
      var date, diff, isPost, relative, singlePost, _i, _len, _ref;
      isPost = data instanceof Post;
      date = isPost ? data.info.date : new Date(+data.dataset.utc);
      now || (now = new Date());
      diff = now - date;
      relative = RelativeDates.relative(diff, now, date);
      if (isPost) {
        _ref = [data].concat(data.clones);
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          singlePost = _ref[_i];
          singlePost.nodes.date.firstChild.textContent = relative;
        }
      } else {
        data.firstChild.textContent = relative;
      }
      return RelativeDates.setOwnTimeout(diff, data);
    },
    setOwnTimeout: function(diff, data) {
      var delay;
      delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY;
      return setTimeout(RelativeDates.markStale, delay, data);
    },
    markStale: function(data) {
      if (__indexOf.call(RelativeDates.stale, data) >= 0) {
        return;
      }
      if (data instanceof Post && !g.posts[data.fullID]) {
        return;
      }
      return RelativeDates.stale.push(data);
    }
  };

  RemoveSpoilers = {
    init: function() {
      if (Conf['Reveal Spoilers'] && !Conf['Remove Spoilers']) {
        $.addClass(doc, 'reveal-spoilers');
      }
      if (!Conf['Remove Spoilers']) {
        return;
      }
      if (Conf['Reveal Spoilers']) {
        this.wrapper = function(text) {
          return "[spoiler]" + text + "[/spoiler]";
        };
      }
      return Post.callbacks.push({
        name: 'Reveal Spoilers',
        cb: this.node
      });
    },
    wrapper: function(text) {
      return text;
    },
    node: function(post) {
      var spoiler, spoilers, _i, _len;
      spoilers = $$('s', this.nodes.comment);
      for (_i = 0, _len = spoilers.length; _i < _len; _i++) {
        spoiler = spoilers[_i];
        $.replace(spoiler, $.tn(RemoveSpoilers.wrapper(spoiler.textContent)));
      }
    }
  };

  Report = {
    init: function() {
      if (!/report/.test(location.search)) {
        return;
      }
      return $.asap((function() {
        return $.id('recaptcha_response_field');
      }), Report.ready);
    },
    ready: function() {
      var field;
      field = $.id('recaptcha_response_field');
      return $.on(field, 'keydown', function(e) {
        if (e.keyCode === 8 && !field.value) {
          return $.globalEval('Recaptcha.reload()');
        }
      });
    }
  };

  Time = {
    init: function() {
      if (g.VIEW === 'catalog' || !Conf['Time Formatting']) {
        return;
      }
      return Post.callbacks.push({
        name: 'Time Formatting',
        cb: this.node
      });
    },
    node: function() {
      if (this.isClone) {
        return;
      }
      return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date);
    },
    format: function(formatString, date) {
      return formatString.replace(/%(.)/g, function(s, c) {
        if (c in Time.formatters) {
          return Time.formatters[c].call(date);
        } else {
          return s;
        }
      });
    },
    day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    zeroPad: function(n) {
      if (n < 10) {
        return "0" + n;
      } else {
        return n;
      }
    },
    formatters: {
      a: function() {
        return Time.day[this.getDay()].slice(0, 3);
      },
      A: function() {
        return Time.day[this.getDay()];
      },
      b: function() {
        return Time.month[this.getMonth()].slice(0, 3);
      },
      B: function() {
        return Time.month[this.getMonth()];
      },
      d: function() {
        return Time.zeroPad(this.getDate());
      },
      e: function() {
        return this.getDate();
      },
      H: function() {
        return Time.zeroPad(this.getHours());
      },
      I: function() {
        return Time.zeroPad(this.getHours() % 12 || 12);
      },
      k: function() {
        return this.getHours();
      },
      l: function() {
        return this.getHours() % 12 || 12;
      },
      m: function() {
        return Time.zeroPad(this.getMonth() + 1);
      },
      M: function() {
        return Time.zeroPad(this.getMinutes());
      },
      p: function() {
        if (this.getHours() < 12) {
          return 'AM';
        } else {
          return 'PM';
        }
      },
      P: function() {
        if (this.getHours() < 12) {
          return 'am';
        } else {
          return 'pm';
        }
      },
      S: function() {
        return Time.zeroPad(this.getSeconds());
      },
      y: function() {
        return this.getFullYear().toString().slice(2);
      },
      Y: function() {
        return this.getFullYear();
      },
      '%': function() {
        return '%';
      }
    }
  };

  Settings = {
    init: function() {
      var link, settings;
      link = $.el('a', {
        className: 'settings-link fa fa-wrench',
        textContent: 'Settings',
        title: '4chan X Settings',
        href: 'javascript:;'
      });
      $.on(link, 'click', Settings.open);
      Header.addShortcut(link);
      Settings.addSection('Main', Settings.main);
      Settings.addSection('Filter', Settings.filter);
      Settings.addSection('Sauce', Settings.sauce);
      Settings.addSection('Advanced', Settings.advanced);
      Settings.addSection('Keybinds', Settings.keybinds);
      $.on(d, 'AddSettingsSection', Settings.addSection);
      $.on(d, 'OpenSettings', function(e) {
        return Settings.open(e.detail);
      });
      settings = JSON.parse(localStorage.getItem('4chan-settings')) || {};
      if (settings.disableAll) {
        return;
      }
      settings.disableAll = true;
      return localStorage.setItem('4chan-settings', JSON.stringify(settings));
    },
    open: function(openSection) {
      var dialog, link, links, overlay, section, sectionToOpen, _i, _len, _ref;
      if (Settings.dialog) {
        return;
      }
      $.event('CloseMenu');
      Settings.overlay = overlay = $.el('div', {
        id: 'overlay'
      });
      Settings.dialog = dialog = $.el('div', {
        id: 'fourchanx-settings',
        className: 'dialog'
      });
      $.extend(dialog, {
        innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.3.8</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
      });
      $.on($('.export', Settings.dialog), 'click', Settings["export"]);
      $.on($('.import', Settings.dialog), 'click', Settings["import"]);
      $.on($('.reset', Settings.dialog), 'click', Settings.reset);
      $.on($('input', Settings.dialog), 'change', Settings.onImport);
      links = [];
      _ref = Settings.sections;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        section = _ref[_i];
        link = $.el('a', {
          className: "tab-" + section.hyphenatedTitle,
          textContent: section.title,
          href: 'javascript:;'
        });
        $.on(link, 'click', Settings.openSection.bind(section));
        links.push(link, $.tn(' | '));
        if (section.title === openSection) {
          sectionToOpen = link;
        }
      }
      links.pop();
      $.add($('.sections-list', dialog), links);
      (sectionToOpen ? sectionToOpen : links[0]).click();
      $.on($('.close', dialog), 'click', Settings.close);
      $.on(overlay, 'click', Settings.close);
      $.add(d.body, [overlay, dialog]);
      return $.event('OpenSettings', null, dialog);
    },
    close: function() {
      if (!Settings.dialog) {
        return;
      }
      $.rm(Settings.overlay);
      $.rm(Settings.dialog);
      delete Settings.overlay;
      return delete Settings.dialog;
    },
    sections: [],
    addSection: function(title, open) {
      var hyphenatedTitle, _ref;
      if (typeof title !== 'string') {
        _ref = title.detail, title = _ref.title, open = _ref.open;
      }
      hyphenatedTitle = title.toLowerCase().replace(/\s+/g, '-');
      return Settings.sections.push({
        title: title,
        hyphenatedTitle: hyphenatedTitle,
        open: open
      });
    },
    openSection: function() {
      var section, selected;
      if (selected = $('.tab-selected', Settings.dialog)) {
        $.rmClass(selected, 'tab-selected');
      }
      $.addClass($(".tab-" + this.hyphenatedTitle, Settings.dialog), 'tab-selected');
      section = $('section', Settings.dialog);
      $.rmAll(section);
      section.className = "section-" + this.hyphenatedTitle;
      this.open(section, g);
      section.scrollTop = 0;
      return $.event('OpenSettings', null, section);
    },
    main: function(section) {
      var arr, button, description, div, fs, input, inputs, items, key, obj, _ref;
      items = {};
      inputs = {};
      _ref = Config.main;
      for (key in _ref) {
        obj = _ref[key];
        fs = $.el('fieldset', {
          innerHTML: "<legend>" + E(key) + "</legend>"
        });
        for (key in obj) {
          arr = obj[key];
          description = arr[1];
          div = $.el('div');
          $.add(div, [
            UI.checkbox(key, key, false), $.el('span', {
              "class": 'description',
              textContent: ": " + description
            })
          ]);
          input = $('input', div);
          $.on(input, 'change', $.cb.checked);
          items[key] = Conf[key];
          inputs[key] = input;
          $.add(fs, div);
        }
        $.add(section, fs);
      }
      $.get(items, function(items) {
        var val;
        for (key in items) {
          val = items[key];
          inputs[key].checked = val;
        }
      });
      div = $.el('div', {
        innerHTML: "<button></button><span class=\"description\">: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
      });
      button = $('button', div);
      $.get({
        hiddenThreads: {},
        hiddenPosts: {}
      }, function(_arg) {
        var ID, board, hiddenNum, hiddenPosts, hiddenThreads, thread, _ref1, _ref2;
        hiddenThreads = _arg.hiddenThreads, hiddenPosts = _arg.hiddenPosts;
        hiddenNum = 0;
        _ref1 = hiddenThreads.boards;
        for (ID in _ref1) {
          board = _ref1[ID];
          hiddenNum += Object.keys(board).length;
        }
        _ref2 = hiddenPosts.boards;
        for (ID in _ref2) {
          board = _ref2[ID];
          for (ID in board) {
            thread = board[ID];
            hiddenNum += Object.keys(thread).length;
          }
        }
        return button.textContent = "Hidden: " + hiddenNum;
      });
      $.on(button, 'click', function() {
        this.textContent = 'Hidden: 0';
        return $.get('hiddenThreads', {}, function(_arg) {
          var boardID, hiddenThreads;
          hiddenThreads = _arg.hiddenThreads;
          for (boardID in hiddenThreads.boards) {
            localStorage.removeItem("4chan-hide-t-" + boardID);
          }
          return $["delete"](['hiddenThreads', 'hiddenPosts']);
        });
      });
      return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div);
    },
    "export": function() {
      return $.get(Conf, function(Conf) {
        delete Conf['archives'];
        return Settings.downloadExport({
          version: g.VERSION,
          date: Date.now(),
          Conf: Conf
        });
      });
    },
    downloadExport: function(data) {
      var a, p;
      a = $.el('a', {
        download: "4chan X v" + g.VERSION + "-" + data.date + ".json",
        href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2)))))
      });
      p = $('.imp-exp-result', Settings.dialog);
      $.rmAll(p);
      $.add(p, a);
      return a.click();
    },
    "import": function() {
      return $('input', this.parentNode).click();
    },
    onImport: function() {
      var file, output, reader;
      if (!(file = this.files[0])) {
        return;
      }
      output = $('.imp-exp-result');
      if (!confirm('Your current settings will be entirely overwritten, are you sure?')) {
        output.textContent = 'Import aborted.';
        return;
      }
      reader = new FileReader();
      reader.onload = function(e) {
        var err;
        try {
          Settings.loadSettings(JSON.parse(e.target.result));
          if (confirm('Import successful. Reload now?')) {
            return window.location.reload();
          }
        } catch (_error) {
          err = _error;
          output.textContent = 'Import failed due to an error.';
          return c.error(err.stack);
        }
      };
      return reader.readAsText(file);
    },
    loadSettings: function(data) {
      var convertSettings, key, val, version, _ref;
      version = data.version.split('.');
      if (version[0] === '2') {
        convertSettings = function(data, map) {
          var newKey, prevKey;
          for (prevKey in map) {
            newKey = map[prevKey];
            if (newKey) {
              data.Conf[newKey] = data.Conf[prevKey];
            }
            delete data.Conf[prevKey];
          }
          return data;
        };
        data = convertSettings(data, {
          'Disable 4chan\'s extension': '',
          'Remove Slug': '',
          'Check for Updates': '',
          'Recursive Filtering': 'Recursive Hiding',
          'Reply Hiding': 'Reply Hiding Buttons',
          'Thread Hiding': 'Thread Hiding Buttons',
          'Show Stubs': 'Stubs',
          'Image Auto-Gif': 'Replace GIF',
          'Reveal Spoilers': 'Reveal Spoiler Thumbnails',
          'Expand From Current': 'Expand from here',
          'Post in Title': 'Thread Excerpt',
          'Open Reply in New Tab': 'Open Post in New Tab',
          'Remember QR size': 'Remember QR Size',
          'Remember Subject': '',
          'Quote Inline': 'Quote Inlining',
          'Quote Preview': 'Quote Previewing',
          'Indicate OP quote': 'Mark OP Quotes',
          'Indicate You quote': 'Mark Quotes of You',
          'Indicate Cross-thread Quotes': 'Mark Cross-thread Quotes',
          'uniqueid': 'uniqueID',
          'mod': 'capcode',
          'email': '',
          'country': 'flag',
          'md5': 'MD5',
          'openEmptyQR': 'Open empty QR',
          'openQR': 'Open QR',
          'openOptions': 'Open settings',
          'close': 'Close',
          'spoiler': 'Spoiler tags',
          'sageru': 'Toggle sage',
          'code': 'Code tags',
          'submit': 'Submit QR',
          'watch': 'Watch',
          'update': 'Update',
          'unreadCountTo0': '',
          'expandAllImages': 'Expand images',
          'expandImage': 'Expand image',
          'zero': 'Front page',
          'nextPage': 'Next page',
          'previousPage': 'Previous page',
          'nextThread': 'Next thread',
          'previousThread': 'Previous thread',
          'expandThread': 'Expand thread',
          'openThreadTab': 'Open thread',
          'openThread': 'Open thread tab',
          'nextReply': 'Next reply',
          'previousReply': 'Previous reply',
          'hide': 'Hide',
          'Scrolling': 'Auto Scroll',
          'Verbose': ''
        });
        data.Conf.sauces = data.Conf.sauces.replace(/\$\d/g, function(c) {
          switch (c) {
            case '$1':
              return '%TURL';
            case '$2':
              return '%URL';
            case '$3':
              return '%MD5';
            case '$4':
              return '%board';
            default:
              return c;
          }
        });
        _ref = Config.hotkeys;
        for (key in _ref) {
          val = _ref[key];
          if (key in data.Conf) {
            data.Conf[key] = data.Conf[key].replace(/ctrl|alt|meta/g, function(s) {
              return "" + (s[0].toUpperCase()) + s.slice(1);
            }).replace(/(^|.+\+)[A-Z]$/g, function(s) {
              return "Shift+" + s.slice(0, -1) + (s.slice(-1).toLowerCase());
            });
          }
        }
        data.Conf['WatchedThreads'] = data.WatchedThreads;
      }
      if (data.Conf['WatchedThreads']) {
        data.Conf['watchedThreads'] = {
          boards: ThreadWatcher.convert(data.Conf['WatchedThreads'])
        };
        delete data.Conf['WatchedThreads'];
      }
      return $.set(data.Conf);
    },
    reset: function() {
      if (confirm('Your current settings will be entirely wiped, are you sure?')) {
        return $.clear(function() {
          if (confirm('Reset successful. Reload now?')) {
            return window.location.reload();
          }
        });
      }
    },
    filter: function(section) {
      var select;
      $.extend(section, {
        innerHTML: "<select name=filter><option value=guide>Guide</option><option value=name>Name</option><option value=uniqueID>Unique ID</option><option value=tripcode>Tripcode</option><option value=capcode>Capcode</option><option value=subject>Subject</option><option value=comment>Comment</option><option value=flag>Flag</option><option value=filename>Filename</option><option value=dimensions>Image dimensions</option><option value=filesize>Filesize</option><option value=MD5>Image MD5</option></select><div></div>"
      });
      select = $('select', section);
      $.on(select, 'change', Settings.selectFilter);
      return Settings.selectFilter.call(select);
    },
    selectFilter: function() {
      var div, name, ta;
      div = this.nextElementSibling;
      if ((name = this.value) !== 'guide') {
        $.rmAll(div);
        ta = $.el('textarea', {
          name: name,
          className: 'field',
          spellcheck: false
        });
        $.get(name, Conf[name], function(item) {
          return ta.value = item[name];
        });
        $.on(ta, 'change', $.cb.value);
        $.add(div, ta);
        return;
      }
      $.extend(div, {
        innerHTML: "<div class=warning><code>Filter</code> is disabled.</div><p>Use <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\">regular expressions</a>, one per line.<br>Lines starting with a <code>#</code> will be ignored.<br>For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>MD5 filtering uses exact string matching, not regular expressions.</p><ul>You can use these settings with each regular expression, separate them with semicolons:<li>Per boards, separate them with commas. It is global if not specified.<br>For example: <code>boards:a,jp;</code>.</li><li>Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.</li><li>Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>For example: <code>stub:yes;</code> or <code>stub:no;</code>.</li><li>Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.</li><li>Highlighted OPs will have their threads put on top of the board index by default.<br>For example: <code>top:yes;</code> or <code>top:no;</code>.</li></ul>"
      });
      return $('.warning', div).hidden = Conf['Filter'];
    },
    sauce: function(section) {
      var ta;
      $.extend(section, {
        innerHTML: "<div class=\"warning\"><code>Sauce</code> is disabled.</div><div>Lines starting with a <code>#</code> will be ignored.</div><div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div><div>You can specify the applicable boards by appending <code>;boards:[board1],[board2]</code>.</div><div>You can specify the applicable file types by appending <code>;types:[extension1],[extension2]</code>.</div><ul>These parameters will be replaced by their corresponding values:<li><code>%TURL</code>: Thumbnail URL.</li><li><code>%URL</code>: Full image URL.</li><li><code>%MD5</code>: MD5 hash.</li><li><code>%name</code>: Original file name.</li><li><code>%board</code>: Current board.</li><li><code>%%</code>, <code>%semi</code>: Literal <code>%</code> and <code>;</code>.</li></ul><textarea name=\"sauces\" class=\"field\" spellcheck=\"false\"></textarea>"
      });
      $('.warning', section).hidden = Conf['Sauce'];
      ta = $('textarea', section);
      $.get('sauces', Conf['sauces'], function(item) {
        return ta.value = item['sauces'];
      });
      return $.on(ta, 'change', $.cb.value);
    },
    advanced: function(section) {
      var archBoards, boardID, boardOptions, boardSelect, boards, customCSS, event, files, i, input, inputs, interval, item, items, name, o, row, rows, software, ta, table, warning, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
      $.extend(section, {
        innerHTML: "<fieldset><legend>Archiver</legend><div class=\"warning\" data-feature='404 Redirect'><code>404 Redirect</code> is disabled.</div><select id='archive-board-select'></select><table id='archive-table'><thead><th>Thread redirection</th><th>Post fetching</th><th>File redirection</th></thead><tbody></tbody></table></fieldset><fieldset><legend>Custom Board Navigation</span></legend><div><textarea name=boardnav class=field spellcheck=false></textarea></div><span class=note>New lines will be converted into spaces.</span><br><br><div class=note>In the following examples for /g/, <code>g</code> can be changed to a different board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Twitter link (<code>@</code>).</div><div>Board link: <code>g</code></div><div>Title link: <code>g-title</code></div><div>Board link (Replace with title when on that board): <code>g-replace</code></div><div>Full text link: <code>g-full</code></div><div>Custom text link: <code>g-text:\"Install Gentoo\"</code></div><div>Index-only link: <code>g-index</code></div><div>Catalog-only link: <code>g-catalog</code></div><div>External link: <code>external-text:\"Google\",\"http://www.google.com\"</code></div><div>Combinations are possible: <code>g-index-text:\"Technology Index\"</code></div><div>Full board list toggle: <code>toggle-all</code></div><br><div class=note><code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]</code><br>will give you<br><code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>if you are on /g/.</div></fieldset><fieldset><legend>Time Formatting <span class=warning data-feature='Time Formatting'>is disabled.</span></legend><div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div><div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div><div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div><div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div><div>Year: <code>%y</code>, <code>%Y</code></div><div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div><div>Minute: <code>%M</code></div><div>Second: <code>%S</code></div><div>Literal <code>%</code>: <code>%%</code></div></fieldset><fieldset><legend>Quote Backlinks formatting <span class=warning data-feature='Quote Backlinks'>is disabled.</span></legend><div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div></fieldset><fieldset><legend>File Info Formatting <span class=warning data-feature='File Info Formatting'>is disabled.</span></legend><div><input name=fileInfo class=field spellcheck=false>: <span class='file-info file-info-preview'></span></div><div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div><div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div><div>Spoiler indicator: <code>%p</code></div><div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div><div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div><div>Literal <code>%</code>: <code>%%</code></div></fieldset><fieldset><legend>Quick Reply Personas</legend><textarea class=personafield name=\"QR.personas\" class=\"field\" spellcheck=\"false\"></textarea><p>One item per line.<br>Items will be added in the relevant input's auto-completion list.<br>Password items will always be used, since there is no password input.<br>Lines starting with a <code>#</code> will be ignored.</p><ul>You can use these settings with each item, separate them with semicolons:<li>Possible items are: <code>name</code>, <code>options</code> (or equivalently <code>email</code>), <code>subject</code> and <code>password</code>.</li><li>Wrap values of items with quotes, like this: <code>options:\"sage\"</code>.</li><li>Force values as defaults with the <code>always</code> keyword, for example: <code>options:\"sage\";always</code>.</li><li>Select specific boards for an item, separated with commas, for example: <code>options:\"sage\";boards:jp;always</code>.</li></ul></fieldset><fieldset><legend>Unread Favicon <span class=warning data-feature='Unread Favicon'>is disabled.</span></legend><select name=favicon><option value=ferongr>ferongr</option><option value=xat->xat-</option><option value=4chanJS>4chanJS</option><option value=Mayhem>Mayhem</option><option value=Original>Original</option><option value=Metro>Metro</option></select><span class=favicon-preview><img src=\"data:image/gif;base64,R0lGODlhEAAQAPAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAIOhI%2Bpy%2B0Po5y02ouzPgUAOw%3D%3D\"><img src=\"data:image/gif;base64,R0lGODlhEAAQAPAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAIOhI%2Bpy%2B0Po5y02ouzPgUAOw%3D%3D\"><img src=\"data:image/gif;base64,R0lGODlhEAAQAPAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAIOhI%2Bpy%2B0Po5y02ouzPgUAOw%3D%3D\"><img src=\"data:image/gif;base64,R0lGODlhEAAQAPAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAIOhI%2Bpy%2B0Po5y02ouzPgUAOw%3D%3D\"></span></fieldset><fieldset><legend>Thread Updater <span class=warning data-feature='Thread Updater'>is disabled.</span></legend><div>Interval: <input type=\"number\" name=\"Interval\" class=\"field\" min=\"1\"> seconds</div></fieldset><fieldset><legend><label><input type=checkbox name='Custom CSS'> Custom CSS</label></legend><button id=apply-css>Apply CSS</button><textarea name=usercss class=field spellcheck=false></textarea></fieldset>"
      });
      _ref = $$('.warning', section);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        warning = _ref[_i];
        warning.hidden = Conf[warning.dataset.feature];
      }
      items = {};
      inputs = {};
      _ref1 = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'];
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        name = _ref1[_j];
        input = $("[name=" + name + "]", section);
        items[name] = Conf[name];
        inputs[name] = input;
        event = name === 'favicon' || name === 'usercss' ? 'change' : 'input';
        $.on(input, event, $.cb.value);
      }
      ta = $('.personafield', section);
      $.get('QR.personas', Conf['QR.personas'], function(item) {
        return ta.value = item['QR.personas'];
      });
      $.on(ta, 'change', $.cb.value);
      $.get(items, function(items) {
        var key, val;
        for (key in items) {
          val = items[key];
          input = inputs[key];
          input.value = val;
          if (key === 'usercss') {
            continue;
          }
          $.on(input, event, Settings[key]);
          Settings[key].call(input);
        }
      });
      interval = $('input[name="Interval"]', section);
      customCSS = $('input[name="Custom CSS"]', section);
      interval.value = Conf['Interval'];
      customCSS.checked = Conf['Custom CSS'];
      inputs['usercss'].disabled = !Conf['Custom CSS'];
      $.on(interval, 'change', ThreadUpdater.cb.interval);
      $.on(customCSS, 'change', Settings.togglecss);
      $.on($('#apply-css', section), 'click', Settings.usercss);
      archBoards = {};
      _ref2 = Redirect.archives;
      for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
        _ref3 = _ref2[_k], name = _ref3.name, boards = _ref3.boards, files = _ref3.files, software = _ref3.software, withCredentials = _ref3.withCredentials;
        for (_l = 0, _len3 = boards.length; _l < _len3; _l++) {
          boardID = boards[_l];
          o = archBoards[boardID] || (archBoards[boardID] = {
            thread: [[], []],
            post: [[], []],
            file: [[], []]
          });
          i = +(!!withCredentials);
          o.thread[i].push(name);
          if (software === 'foolfuuka') {
            o.post[i].push(name);
          }
          if (__indexOf.call(files, boardID) >= 0) {
            o.file[i].push(name);
          }
        }
      }
      for (boardID in archBoards) {
        o = archBoards[boardID];
        _ref4 = ['thread', 'post', 'file'];
        for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) {
          item = _ref4[_m];
          i = o[item][0].length ? 1 : 0;
          o[item][i].push('disabled');
          o[item] = o[item][0].concat(o[item][1]);
        }
      }
      rows = [];
      boardOptions = [];
      _ref5 = Object.keys(archBoards).sort();
      for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) {
        boardID = _ref5[_n];
        row = $.el('tr', {
          className: "board-" + boardID
        });
        row.hidden = boardID !== g.BOARD.ID;
        boardOptions.push($.el('option', {
          textContent: "/" + boardID + "/",
          value: "board-" + boardID,
          selected: boardID === g.BOARD.ID
        }));
        o = archBoards[boardID];
        _ref6 = ['thread', 'post', 'file'];
        for (_o = 0, _len6 = _ref6.length; _o < _len6; _o++) {
          item = _ref6[_o];
          $.add(row, Settings.addArchiveCell(boardID, o, item));
        }
        rows.push(row);
      }
      if (!(g.BOARD.ID in archBoards)) {
        rows[0].hidden = false;
      }
      $.add($('tbody', section), rows);
      boardSelect = $('#archive-board-select', section);
      $.add(boardSelect, boardOptions);
      table = $('#archive-table', section);
      $.on(boardSelect, 'change', function() {
        $('tbody > :not([hidden])', table).hidden = true;
        return $("tbody > ." + this.value, table).hidden = false;
      });
      $.get('selectedArchives', Conf['selectedArchives'], function(_arg) {
        var data, option, selectedArchives, type;
        selectedArchives = _arg.selectedArchives;
        for (boardID in selectedArchives) {
          data = selectedArchives[boardID];
          for (type in data) {
            name = data[type];
            if (option = $("select[data-boardid='" + boardID + "'][data-type='" + type + "'] > option[value='" + name + "']", section)) {
              option.selected = true;
            }
          }
        }
      });
    },
    addArchiveCell: function(boardID, data, type) {
      var archive, i, length, options, select, td;
      length = data[type].length;
      td = $.el('td', {
        className: 'archive-cell'
      });
      if (!length) {
        td.textContent = '--';
        return td;
      }
      options = [];
      i = 0;
      while (i < length) {
        archive = data[type][i++];
        options.push($.el('option', {
          textContent: archive,
          value: archive
        }));
      }
      $.extend(td, {
        innerHTML: "<select></select>"
      });
      select = td.firstElementChild;
      if (!(select.disabled = length === 1)) {
        select.setAttribute('data-boardid', boardID);
        select.setAttribute('data-type', type);
        $.on(select, 'change', Settings.saveSelectedArchive);
      }
      $.add(select, options);
      return td;
    },
    saveSelectedArchive: function() {
      return $.get('selectedArchives', Conf['selectedArchives'], (function(_this) {
        return function(_arg) {
          var selectedArchives, _name;
          selectedArchives = _arg.selectedArchives;
          (selectedArchives[_name = _this.dataset.boardid] || (selectedArchives[_name] = {}))[_this.dataset.type] = _this.value;
          return $.set('selectedArchives', selectedArchives);
        };
      })(this));
    },
    boardnav: function() {
      return Header.generateBoardList(this.value);
    },
    time: function() {
      return this.nextElementSibling.textContent = Time.format(this.value, new Date());
    },
    backlink: function() {
      return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) {
        return {
          '%id': '123456789',
          '%%': '%'
        }[x];
      });
    },
    fileInfo: function() {
      var data;
      data = {
        isReply: true,
        file: {
          URL: '//i.4cdn.org/g/1334437723720.jpg',
          name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
          size: '276 KB',
          sizeInBytes: 276 * 1024,
          dimensions: '1280x720',
          isImage: true,
          isSpoiler: true
        }
      };
      return FileInfo.format(this.value, data, this.nextElementSibling);
    },
    favicon: function() {
      var img;
      Favicon["switch"]();
      if (g.VIEW === 'thread' && Conf['Unread Favicon']) {
        Unread.update();
      }
      img = this.nextElementSibling.children;
      img[0].src = Favicon["default"];
      img[1].src = Favicon.unreadSFW;
      img[2].src = Favicon.unreadNSFW;
      return img[3].src = Favicon.unreadDead;
    },
    togglecss: function() {
      if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = !this.checked) {
        CustomCSS.rmStyle();
      } else {
        CustomCSS.addStyle();
      }
      return $.cb.checked.call(this);
    },
    usercss: function() {
      return CustomCSS.update();
    },
    keybinds: function(section) {
      var arr, input, inputs, items, key, tbody, tr, _ref;
      $.extend(section, {
        innerHTML: "<div class=warning><code>Keybinds</code> are disabled.</div><div>Allowed keys: <kbd>a-z</kbd>, <kbd>0-9</kbd>, <kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>, <kbd>Enter</kbd>, <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>, <kbd>Right</kbd>, <kbd>Left</kbd>.</div><div>Press <kbd>Backspace</kbd> to disable a keybind.</div><table><tbody><tr><th>Actions</th><th>Keybinds</th></tr></tbody></table>"
      });
      $('.warning', section).hidden = Conf['Keybinds'];
      tbody = $('tbody', section);
      items = {};
      inputs = {};
      _ref = Config.hotkeys;
      for (key in _ref) {
        arr = _ref[key];
        tr = $.el('tr', {
          innerHTML: "<td>" + E(arr[1]) + "</td><td><input class=\"field\"></td>"
        });
        input = $('input', tr);
        input.name = key;
        input.spellcheck = false;
        items[key] = Conf[key];
        inputs[key] = input;
        $.on(input, 'keydown', Settings.keybind);
        $.add(tbody, tr);
      }
      return $.get(items, function(items) {
        var val;
        for (key in items) {
          val = items[key];
          inputs[key].value = val;
        }
      });
    },
    keybind: function(e) {
      var key;
      if (e.keyCode === 9) {
        return;
      }
      e.preventDefault();
      e.stopPropagation();
      if ((key = Keybinds.keyCode(e)) == null) {
        return;
      }
      this.value = key;
      return $.cb.value.call(this);
    }
  };

  Main = {
    init: function() {
      var db, flatten, pathname, _i, _len, _ref, _ref1;
      g.threads = new SimpleDict;
      g.posts = new SimpleDict;
      pathname = location.pathname.split('/');
      g.BOARD = new Board(pathname[1]);
      if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') {
        return;
      }
      g.VIEW = (function() {
        switch (pathname[2]) {
          case 'res':
          case 'thread':
            return 'thread';
          case 'catalog':
            return 'catalog';
          default:
            return 'index';
        }
      })();
      if (g.VIEW === 'thread') {
        g.THREADID = +pathname[3];
      }
      flatten = function(parent, obj) {
        var key, val;
        if (obj instanceof Array) {
          Conf[parent] = obj[0];
        } else if (typeof obj === 'object') {
          for (key in obj) {
            val = obj[key];
            flatten(key, val);
          }
        } else {
          Conf[parent] = obj;
        }
      };
      flatten(null, Config);
      _ref1 = DataBoard.keys;
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        db = _ref1[_i];
        Conf[db] = {
          boards: {}
        };
      }
      Conf['selectedArchives'] = {};
      $.get(Conf, function(items) {
        $.extend(Conf, items);
        return $.asap((function() {
          return doc = d.documentElement;
        }), Main.initFeatures);
      });
      return $.on(d, '4chanMainInit', Main.initStyle);
    },
    initFeatures: function() {
      var err, feature, name, pathname, _i, _len, _ref, _ref1;
      switch (location.hostname) {
        case 'a.4cdn.org':
          return;
        case 'sys.4chan.org':
          Report.init();
          return;
        case 'i.4cdn.org':
          $.asap((function() {
            return d.readyState !== 'loading';
          }), function() {
            var URL, pathname, video, _ref;
            if (Conf['404 Redirect'] && ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found')) {
              Redirect.init();
              pathname = location.pathname.split('/');
              URL = Redirect.to('file', {
                boardID: g.BOARD.ID,
                filename: pathname[pathname.length - 1]
              });
              return Redirect.navigate(URL);
            } else if (Conf['Loop in New Tab'] && (video = $('video'))) {
              video.loop = true;
              video.controls = false;
              video.play();
              return ImageCommon.addControls(video);
            }
          });
          return;
      }
      if (Conf['Normalize URL'] && g.VIEW === 'thread') {
        pathname = location.pathname.split('/');
        if (pathname[2] !== 'thread' || pathname.length > 4) {
          pathname[2] = 'thread';
          history.replaceState(null, '', pathname.slice(0, 4).join('/') + location.hash);
        }
      }
      _ref = Main.features;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        _ref1 = _ref[_i], name = _ref1[0], feature = _ref1[1];
        try {
          feature.init();
        } catch (_error) {
          err = _error;
          Main.handleErrors({
            message: "\"" + name + "\" initialization crashed.",
            error: err
          });
        }
      }
      return $.ready(Main.initReady);
    },
    initStyle: function() {
      var _ref;
      $.off(d, '4chanMainInit', Main.initStyle);
      if (!Main.isThisPageLegit() || $.hasClass(doc, 'fourchan-x')) {
        return;
      }
      if ((_ref = $('link[href*=mobile]', d.head)) != null) {
        _ref.disabled = true;
      }
      $.addClass(doc, 'fourchan-x', 'seaweedchan');
      $.addClass(doc, g.VIEW === 'thread' ? 'thread-view' : g.VIEW);
      $.addClass(doc, typeof chrome !== "undefined" && chrome !== null ? 'blink' : 'gecko');
      $.addStyle(Main.css);
      return Main.setClass();
    },
    setClass: function() {
      var mainStyleSheet, setStyle, style, styleSheets;
      if (g.VIEW === 'catalog') {
        $.addClass(doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace(/_+/g, '-'));
        return;
      }
      style = 'yotsuba-b';
      mainStyleSheet = $('link[title=switch]', d.head);
      styleSheets = $$('link[rel="alternate stylesheet"]', d.head);
      setStyle = function() {
        var styleSheet, _i, _len;
        $.rmClass(doc, style);
        for (_i = 0, _len = styleSheets.length; _i < _len; _i++) {
          styleSheet = styleSheets[_i];
          if (styleSheet.href === mainStyleSheet.href) {
            style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-');
            break;
          }
        }
        return $.addClass(doc, style);
      };
      setStyle();
      if (!mainStyleSheet) {
        return;
      }
      return new MutationObserver(setStyle).observe(mainStyleSheet, {
        attributes: true,
        attributeFilter: ['href']
      });
    },
    initReady: function() {
      var GMver, err, href, i, passLink, styleSelector, v, _i, _len, _ref, _ref1;
      if ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found') {
        if (Conf['404 Redirect'] && g.VIEW === 'thread') {
          href = Redirect.to('thread', {
            boardID: g.BOARD.ID,
            threadID: g.THREADID,
            postID: +location.hash.match(/\d+/)
          });
          Redirect.navigate(href, "/" + g.BOARD + "/");
        }
        return;
      }
      Main.initStyle();
      if (styleSelector = $.id('styleSelector')) {
        passLink = $.el('a', {
          textContent: '4chan Pass',
          href: 'javascript:;'
        });
        $.on(passLink, 'click', function() {
          return window.open('//sys.4chan.org/auth', 'This will steal your data.', 'left=0,top=0,width=500,height=255,toolbar=0,resizable=0');
        });
        $.before(styleSelector.previousSibling, [$.tn('['), passLink, $.tn(']\u00A0\u00A0')]);
      }
      if (!(Conf['JSON Navigation'] && g.VIEW === 'index')) {
        Main.initThread();
      } else {
        $.event('4chanXInitFinished');
      }
      if (!Conf['Show Support Message']) {
        return;
      }
      GMver = GM_info.version.split('.');
      _ref1 = "1.14".split('.');
      for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) {
        v = _ref1[i];
        if (v === GMver[i]) {
          continue;
        }
        (v < GMver[i]) || new Notice('warning', "Your version of Greasemonkey is outdated (v" + GM_info.version + " instead of v1.14 minimum) and 4chan X may not operate correctly.", 30);
        break;
      }
      try {
        return localStorage.getItem('4chan-settings');
      } catch (_error) {
        err = _error;
        return new Notice('warning', 'Cookies need to be enabled on 4chan for 4chan X to operate properly.', 30);
      }
    },
    initThread: function() {
      var board, err, errors, postRoot, posts, thread, threadRoot, threads, _i, _j, _len, _len1, _ref, _ref1, _ref2;
      if (board = $('.board')) {
        threads = [];
        posts = [];
        _ref = $$('.board > .thread', board);
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          threadRoot = _ref[_i];
          thread = new Thread(+threadRoot.id.slice(1), g.BOARD);
          if (g.VIEW === 'thread') {
            thread.isDead = thread.isArchived = !!((_ref1 = $('.closed')) != null ? _ref1.textContent.match(/Thread archived/) : void 0);
          }
          threads.push(thread);
          _ref2 = $$('.thread > .postContainer', threadRoot);
          for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
            postRoot = _ref2[_j];
            try {
              posts.push(new Post(postRoot, thread, g.BOARD));
            } catch (_error) {
              err = _error;
              if (!errors) {
                errors = [];
              }
              errors.push({
                message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
                error: err
              });
            }
          }
        }
        if (errors) {
          Main.handleErrors(errors);
        }
        Main.callbackNodes(Thread, threads);
        Main.callbackNodesDB(Post, posts, function() {
          return $.event('4chanXInitFinished');
        });
      } else {
        $.event('4chanXInitFinished');
      }
      return $.get('previousversion', null, function(_arg) {
        var el, previousversion;
        previousversion = _arg.previousversion;
        if (previousversion === g.VERSION) {
          return;
        }
        if (previousversion) {
          el = $.el('span', {
            innerHTML: E(g.NAME) + " has been updated to <a href=\"" + E(g.CHANGELOG) + "\" target=\"_blank\">version " + E(g.VERSION) + "</a>."
          });
          new Notice('info', el, 15);
        } else {
          Settings.open();
        }
        return $.set('previousversion', g.VERSION);
      });
    },
    callbackNodes: function(klass, nodes) {
      var cb, i, node;
      i = 0;
      cb = klass.callbacks;
      while (node = nodes[i++]) {
        cb.execute(node);
      }
    },
    callbackNodesDB: function(klass, nodes, cb) {
      var cbs, fn, i, softTask;
      i = 0;
      cbs = klass.callbacks;
      fn = function() {
        var node;
        if (!(node = nodes[i])) {
          return false;
        }
        cbs.execute(node);
        return ++i % 25;
      };
      softTask = function() {
        while (fn()) {
          continue;
        }
        if (!nodes[i]) {
          if (cb) {
            cb();
          }
          return;
        }
        return setTimeout(softTask, 0);
      };
      return softTask();
    },
    handleErrors: function(errors) {
      var div, error, logs, _i, _len;
      if (!(errors instanceof Array)) {
        error = errors;
      } else if (errors.length === 1) {
        error = errors[0];
      }
      if (error) {
        new Notice('error', Main.parseError(error), 15);
        return;
      }
      div = $.el('div', {
        innerHTML: E(errors.length) + " errors occurred. [<a href=\"javascript:;\">show</a>]"
      });
      $.on(div.lastElementChild, 'click', function() {
        var _ref;
        return _ref = this.textContent === 'show' ? ['hide', false] : ['show', true], this.textContent = _ref[0], logs.hidden = _ref[1], _ref;
      });
      logs = $.el('div', {
        hidden: true
      });
      for (_i = 0, _len = errors.length; _i < _len; _i++) {
        error = errors[_i];
        $.add(logs, Main.parseError(error));
      }
      return new Notice('error', [div, logs], 30);
    },
    parseError: function(data) {
      var error, message;
      c.error(data.message, data.error.stack);
      message = $.el('div', {
        textContent: data.message
      });
      error = $.el('div', {
        textContent: "" + (data.error.name || 'Error') + ": " + (data.error.message || 'see console for details')
      });
      return [message, error];
    },
    isThisPageLegit: function() {
      var _ref;
      if (!('thisPageIsLegit' in Main)) {
        Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((_ref = d.title) !== '4chan - Temporarily Offline' && _ref !== '4chan - Error' && _ref !== '504 Gateway Time-out');
      }
      return Main.thisPageIsLegit;
    },
    css: "/*! * Font Awesome 4.0.3 * the iconic font designed for Bootstrap * ------------------------------------------------------------------------------ * The full suite of pictographic icons, examples, and documentation can be * found at http://fontawesome.io. Stay up to date on Twitter at * http://twitter.com/fontawesome. * * License * ------------------------------------------------------------------------------ * - The Font Awesome font is licensed under SIL OFL 1.1 - * http://scripts.sil.org/OFL * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - * http://opensource.org/licenses/mit-license.html * - Font Awesome documentation licensed under CC BY 3.0 - * http://creativecommons.org/licenses/by/3.0/ * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: * \"Font Awesome by Dave Gandy - http://fontawesome.io\" * * Author - Dave Gandy * ------------------------------------------------------------------------------ * Email: dave@fontawesome.io * Twitter: http://twitter.com/davegandy * Work: Lead Product Designer @ Kyruus - http://kyruus.com */ @font-face{font-family:FontAwesome;src:url('data:application/font-woff;base64,[encoded data, 87272 characters]') format('woff');font-weight:400;font-style:normal}.fa::before{font-family:FontAwesome;font-weight:400;font-style:normal;-webkit-font-smoothing:antialiased;*margin-right:.3em;text-decoration:inherit;display:none;speak:none} .fa::before {display:inline-block;font-size:13px;visibility:visible} :root:not(.shortcut-icons) #shortcuts .fa::before {display:none} :root.shortcut-icons #shortcuts .fa::before{font-size:15px!important;margin-top:-3px!important;position:relative;top:1px} :root.shortcut-icons #shortcuts .fa, .menu-button .fa{font-size:0;visibility:hidden} :root.shortcut-icons .shortcut.brackets-wrap::after,:root.shortcut-icons .shortcut.brackets-wrap::before{display:none} :root.shortcut-icons #shortcuts a .fa, .menu-button .fa, .hide-reply-button .fa, .hide-thread-button .fa {display:inline} /* Update this line only */ .fa-glass:before{content:\"\\f000\"}.fa-music:before{content:\"\\f001\"}.fa-search:before{content:\"\\f002\"}.fa-envelope-o:before{content:\"\\f003\"}.fa-heart:before{content:\"\\f004\"}.fa-star:before{content:\"\\f005\"}.fa-star-o:before{content:\"\\f006\"}.fa-user:before{content:\"\\f007\"}.fa-film:before{content:\"\\f008\"}.fa-th-large:before{content:\"\\f009\"}.fa-th:before{content:\"\\f00a\"}.fa-th-list:before{content:\"\\f00b\"}.fa-check:before{content:\"\\f00c\"}.fa-times:before{content:\"\\f00d\"}.fa-search-plus:before{content:\"\\f00e\"}.fa-search-minus:before{content:\"\\f010\"}.fa-power-off:before{content:\"\\f011\"}.fa-signal:before{content:\"\\f012\"}.fa-gear:before,.fa-cog:before{content:\"\\f013\"}.fa-trash-o:before{content:\"\\f014\"}.fa-home:before{content:\"\\f015\"}.fa-file-o:before{content:\"\\f016\"}.fa-clock-o:before{content:\"\\f017\"}.fa-road:before{content:\"\\f018\"}.fa-download:before{content:\"\\f019\"}.fa-arrow-circle-o-down:before{content:\"\\f01a\"}.fa-arrow-circle-o-up:before{content:\"\\f01b\"}.fa-inbox:before{content:\"\\f01c\"}.fa-play-circle-o:before{content:\"\\f01d\"}.fa-rotate-right:before,.fa-repeat:before{content:\"\\f01e\"}.fa-refresh:before{content:\"\\f021\"}.fa-list-alt:before{content:\"\\f022\"}.fa-lock:before{content:\"\\f023\"}.fa-flag:before{content:\"\\f024\"}.fa-headphones:before{content:\"\\f025\"}.fa-volume-off:before{content:\"\\f026\"}.fa-volume-down:before{content:\"\\f027\"}.fa-volume-up:before{content:\"\\f028\"}.fa-qrcode:before{content:\"\\f029\"}.fa-barcode:before{content:\"\\f02a\"}.fa-tag:before{content:\"\\f02b\"}.fa-tags:before{content:\"\\f02c\"}.fa-book:before{content:\"\\f02d\"}.fa-bookmark:before{content:\"\\f02e\"}.fa-print:before{content:\"\\f02f\"}.fa-camera:before{content:\"\\f030\"}.fa-font:before{content:\"\\f031\"}.fa-bold:before{content:\"\\f032\"}.fa-italic:before{content:\"\\f033\"}.fa-text-height:before{content:\"\\f034\"}.fa-text-width:before{content:\"\\f035\"}.fa-align-left:before{content:\"\\f036\"}.fa-align-center:before{content:\"\\f037\"}.fa-align-right:before{content:\"\\f038\"}.fa-align-justify:before{content:\"\\f039\"}.fa-list:before{content:\"\\f03a\"}.fa-dedent:before,.fa-outdent:before{content:\"\\f03b\"}.fa-indent:before{content:\"\\f03c\"}.fa-video-camera:before{content:\"\\f03d\"}.fa-picture-o:before{content:\"\\f03e\"}.fa-pencil:before{content:\"\\f040\"}.fa-map-marker:before{content:\"\\f041\"}.fa-adjust:before{content:\"\\f042\"}.fa-tint:before{content:\"\\f043\"}.fa-edit:before,.fa-pencil-square-o:before{content:\"\\f044\"}.fa-share-square-o:before{content:\"\\f045\"}.fa-check-square-o:before{content:\"\\f046\"}.fa-arrows:before{content:\"\\f047\"}.fa-step-backward:before{content:\"\\f048\"}.fa-fast-backward:before{content:\"\\f049\"}.fa-backward:before{content:\"\\f04a\"}.fa-play:before{content:\"\\f04b\"}.fa-pause:before{content:\"\\f04c\"}.fa-stop:before{content:\"\\f04d\"}.fa-forward:before{content:\"\\f04e\"}.fa-fast-forward:before{content:\"\\f050\"}.fa-step-forward:before{content:\"\\f051\"}.fa-eject:before{content:\"\\f052\"}.fa-chevron-left:before{content:\"\\f053\"}.fa-chevron-right:before{content:\"\\f054\"}.fa-plus-circle:before{content:\"\\f055\"}.fa-minus-circle:before{content:\"\\f056\"}.fa-times-circle:before{content:\"\\f057\"}.fa-check-circle:before{content:\"\\f058\"}.fa-question-circle:before{content:\"\\f059\"}.fa-info-circle:before{content:\"\\f05a\"}.fa-crosshairs:before{content:\"\\f05b\"}.fa-times-circle-o:before{content:\"\\f05c\"}.fa-check-circle-o:before{content:\"\\f05d\"}.fa-ban:before{content:\"\\f05e\"}.fa-arrow-left:before{content:\"\\f060\"}.fa-arrow-right:before{content:\"\\f061\"}.fa-arrow-up:before{content:\"\\f062\"}.fa-arrow-down:before{content:\"\\f063\"}.fa-mail-forward:before,.fa-share:before{content:\"\\f064\"}.fa-expand:before{content:\"\\f065\"}.fa-compress:before{content:\"\\f066\"}.fa-plus:before{content:\"\\f067\"}.fa-minus:before{content:\"\\f068\"}.fa-asterisk:before{content:\"\\f069\"}.fa-exclamation-circle:before{content:\"\\f06a\"}.fa-gift:before{content:\"\\f06b\"}.fa-leaf:before{content:\"\\f06c\"}.fa-fire:before{content:\"\\f06d\"}.fa-eye:before{content:\"\\f06e\"}.fa-eye-slash:before{content:\"\\f070\"}.fa-warning:before,.fa-exclamation-triangle:before{content:\"\\f071\"}.fa-plane:before{content:\"\\f072\"}.fa-calendar:before{content:\"\\f073\"}.fa-random:before{content:\"\\f074\"}.fa-comment:before{content:\"\\f075\"}.fa-magnet:before{content:\"\\f076\"}.fa-chevron-up:before{content:\"\\f077\"}.fa-chevron-down:before{content:\"\\f078\"}.fa-retweet:before{content:\"\\f079\"}.fa-shopping-cart:before{content:\"\\f07a\"}.fa-folder:before{content:\"\\f07b\"}.fa-folder-open:before{content:\"\\f07c\"}.fa-arrows-v:before{content:\"\\f07d\"}.fa-arrows-h:before{content:\"\\f07e\"}.fa-bar-chart-o:before{content:\"\\f080\"}.fa-twitter-square:before{content:\"\\f081\"}.fa-facebook-square:before{content:\"\\f082\"}.fa-camera-retro:before{content:\"\\f083\"}.fa-key:before{content:\"\\f084\"}.fa-gears:before,.fa-cogs:before{content:\"\\f085\"}.fa-comments:before{content:\"\\f086\"}.fa-thumbs-o-up:before{content:\"\\f087\"}.fa-thumbs-o-down:before{content:\"\\f088\"}.fa-star-half:before{content:\"\\f089\"}.fa-heart-o:before{content:\"\\f08a\"}.fa-sign-out:before{content:\"\\f08b\"}.fa-linkedin-square:before{content:\"\\f08c\"}.fa-thumb-tack:before{content:\"\\f08d\"}.fa-external-link:before{content:\"\\f08e\"}.fa-sign-in:before{content:\"\\f090\"}.fa-trophy:before{content:\"\\f091\"}.fa-github-square:before{content:\"\\f092\"}.fa-upload:before{content:\"\\f093\"}.fa-lemon-o:before{content:\"\\f094\"}.fa-phone:before{content:\"\\f095\"}.fa-square-o:before{content:\"\\f096\"}.fa-bookmark-o:before{content:\"\\f097\"}.fa-phone-square:before{content:\"\\f098\"}.fa-twitter:before{content:\"\\f099\"}.fa-facebook:before{content:\"\\f09a\"}.fa-github:before{content:\"\\f09b\"}.fa-unlock:before{content:\"\\f09c\"}.fa-credit-card:before{content:\"\\f09d\"}.fa-rss:before{content:\"\\f09e\"}.fa-hdd-o:before{content:\"\\f0a0\"}.fa-bullhorn:before{content:\"\\f0a1\"}.fa-bell:before{content:\"\\f0f3\"}.fa-certificate:before{content:\"\\f0a3\"}.fa-hand-o-right:before{content:\"\\f0a4\"}.fa-hand-o-left:before{content:\"\\f0a5\"}.fa-hand-o-up:before{content:\"\\f0a6\"}.fa-hand-o-down:before{content:\"\\f0a7\"}.fa-arrow-circle-left:before{content:\"\\f0a8\"}.fa-arrow-circle-right:before{content:\"\\f0a9\"}.fa-arrow-circle-up:before{content:\"\\f0aa\"}.fa-arrow-circle-down:before{content:\"\\f0ab\"}.fa-globe:before{content:\"\\f0ac\"}.fa-wrench:before{content:\"\\f0ad\"}.fa-tasks:before{content:\"\\f0ae\"}.fa-filter:before{content:\"\\f0b0\"}.fa-briefcase:before{content:\"\\f0b1\"}.fa-arrows-alt:before{content:\"\\f0b2\"}.fa-group:before,.fa-users:before{content:\"\\f0c0\"}.fa-chain:before,.fa-link:before{content:\"\\f0c1\"}.fa-cloud:before{content:\"\\f0c2\"}.fa-flask:before{content:\"\\f0c3\"}.fa-cut:before,.fa-scissors:before{content:\"\\f0c4\"}.fa-copy:before,.fa-files-o:before{content:\"\\f0c5\"}.fa-paperclip:before{content:\"\\f0c6\"}.fa-save:before,.fa-floppy-o:before{content:\"\\f0c7\"}.fa-square:before{content:\"\\f0c8\"}.fa-bars:before{content:\"\\f0c9\"}.fa-list-ul:before{content:\"\\f0ca\"}.fa-list-ol:before{content:\"\\f0cb\"}.fa-strikethrough:before{content:\"\\f0cc\"}.fa-underline:before{content:\"\\f0cd\"}.fa-table:before{content:\"\\f0ce\"}.fa-magic:before{content:\"\\f0d0\"}.fa-truck:before{content:\"\\f0d1\"}.fa-pinterest:before{content:\"\\f0d2\"}.fa-pinterest-square:before{content:\"\\f0d3\"}.fa-google-plus-square:before{content:\"\\f0d4\"}.fa-google-plus:before{content:\"\\f0d5\"}.fa-money:before{content:\"\\f0d6\"}.fa-caret-down:before{content:\"\\f0d7\"}.fa-caret-up:before{content:\"\\f0d8\"}.fa-caret-left:before{content:\"\\f0d9\"}.fa-caret-right:before{content:\"\\f0da\"}.fa-columns:before{content:\"\\f0db\"}.fa-unsorted:before,.fa-sort:before{content:\"\\f0dc\"}.fa-sort-down:before,.fa-sort-asc:before{content:\"\\f0dd\"}.fa-sort-up:before,.fa-sort-desc:before{content:\"\\f0de\"}.fa-envelope:before{content:\"\\f0e0\"}.fa-linkedin:before{content:\"\\f0e1\"}.fa-rotate-left:before,.fa-undo:before{content:\"\\f0e2\"}.fa-legal:before,.fa-gavel:before{content:\"\\f0e3\"}.fa-dashboard:before,.fa-tachometer:before{content:\"\\f0e4\"}.fa-comment-o:before{content:\"\\f0e5\"}.fa-comments-o:before{content:\"\\f0e6\"}.fa-flash:before,.fa-bolt:before{content:\"\\f0e7\"}.fa-sitemap:before{content:\"\\f0e8\"}.fa-umbrella:before{content:\"\\f0e9\"}.fa-paste:before,.fa-clipboard:before{content:\"\\f0ea\"}.fa-lightbulb-o:before{content:\"\\f0eb\"}.fa-exchange:before{content:\"\\f0ec\"}.fa-cloud-download:before{content:\"\\f0ed\"}.fa-cloud-upload:before{content:\"\\f0ee\"}.fa-user-md:before{content:\"\\f0f0\"}.fa-stethoscope:before{content:\"\\f0f1\"}.fa-suitcase:before{content:\"\\f0f2\"}.fa-bell-o:before{content:\"\\f0a2\"}.fa-coffee:before{content:\"\\f0f4\"}.fa-cutlery:before{content:\"\\f0f5\"}.fa-file-text-o:before{content:\"\\f0f6\"}.fa-building-o:before{content:\"\\f0f7\"}.fa-hospital-o:before{content:\"\\f0f8\"}.fa-ambulance:before{content:\"\\f0f9\"}.fa-medkit:before{content:\"\\f0fa\"}.fa-fighter-jet:before{content:\"\\f0fb\"}.fa-beer:before{content:\"\\f0fc\"}.fa-h-square:before{content:\"\\f0fd\"}.fa-plus-square:before{content:\"\\f0fe\"}.fa-angle-double-left:before{content:\"\\f100\"}.fa-angle-double-right:before{content:\"\\f101\"}.fa-angle-double-up:before{content:\"\\f102\"}.fa-angle-double-down:before{content:\"\\f103\"}.fa-angle-left:before{content:\"\\f104\"}.fa-angle-right:before{content:\"\\f105\"}.fa-angle-up:before{content:\"\\f106\"}.fa-angle-down:before{content:\"\\f107\"}.fa-desktop:before{content:\"\\f108\"}.fa-laptop:before{content:\"\\f109\"}.fa-tablet:before{content:\"\\f10a\"}.fa-mobile-phone:before,.fa-mobile:before{content:\"\\f10b\"}.fa-circle-o:before{content:\"\\f10c\"}.fa-quote-left:before{content:\"\\f10d\"}.fa-quote-right:before{content:\"\\f10e\"}.fa-spinner:before{content:\"\\f110\"}.fa-circle:before{content:\"\\f111\"}.fa-mail-reply:before,.fa-reply:before{content:\"\\f112\"}.fa-github-alt:before{content:\"\\f113\"}.fa-folder-o:before{content:\"\\f114\"}.fa-folder-open-o:before{content:\"\\f115\"}.fa-smile-o:before{content:\"\\f118\"}.fa-frown-o:before{content:\"\\f119\"}.fa-meh-o:before{content:\"\\f11a\"}.fa-gamepad:before{content:\"\\f11b\"}.fa-keyboard-o:before{content:\"\\f11c\"}.fa-flag-o:before{content:\"\\f11d\"}.fa-flag-checkered:before{content:\"\\f11e\"}.fa-terminal:before{content:\"\\f120\"}.fa-code:before{content:\"\\f121\"}.fa-reply-all:before{content:\"\\f122\"}.fa-mail-reply-all:before{content:\"\\f122\"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:\"\\f123\"}.fa-location-arrow:before{content:\"\\f124\"}.fa-crop:before{content:\"\\f125\"}.fa-code-fork:before{content:\"\\f126\"}.fa-unlink:before,.fa-chain-broken:before{content:\"\\f127\"}.fa-question:before{content:\"\\f128\"}.fa-info:before{content:\"\\f129\"}.fa-exclamation:before{content:\"\\f12a\"}.fa-superscript:before{content:\"\\f12b\"}.fa-subscript:before{content:\"\\f12c\"}.fa-eraser:before{content:\"\\f12d\"}.fa-puzzle-piece:before{content:\"\\f12e\"}.fa-microphone:before{content:\"\\f130\"}.fa-microphone-slash:before{content:\"\\f131\"}.fa-shield:before{content:\"\\f132\"}.fa-calendar-o:before{content:\"\\f133\"}.fa-fire-extinguisher:before{content:\"\\f134\"}.fa-rocket:before{content:\"\\f135\"}.fa-maxcdn:before{content:\"\\f136\"}.fa-chevron-circle-left:before{content:\"\\f137\"}.fa-chevron-circle-right:before{content:\"\\f138\"}.fa-chevron-circle-up:before{content:\"\\f139\"}.fa-chevron-circle-down:before{content:\"\\f13a\"}.fa-html5:before{content:\"\\f13b\"}.fa-css3:before{content:\"\\f13c\"}.fa-anchor:before{content:\"\\f13d\"}.fa-unlock-alt:before{content:\"\\f13e\"}.fa-bullseye:before{content:\"\\f140\"}.fa-ellipsis-h:before{content:\"\\f141\"}.fa-ellipsis-v:before{content:\"\\f142\"}.fa-rss-square:before{content:\"\\f143\"}.fa-play-circle:before{content:\"\\f144\"}.fa-ticket:before{content:\"\\f145\"}.fa-minus-square:before{content:\"\\f146\"}.fa-minus-square-o:before{content:\"\\f147\"}.fa-level-up:before{content:\"\\f148\"}.fa-level-down:before{content:\"\\f149\"}.fa-check-square:before{content:\"\\f14a\"}.fa-pencil-square:before{content:\"\\f14b\"}.fa-external-link-square:before{content:\"\\f14c\"}.fa-share-square:before{content:\"\\f14d\"}.fa-compass:before{content:\"\\f14e\"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:\"\\f150\"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:\"\\f151\"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:\"\\f152\"}.fa-euro:before,.fa-eur:before{content:\"\\f153\"}.fa-gbp:before{content:\"\\f154\"}.fa-dollar:before,.fa-usd:before{content:\"\\f155\"}.fa-rupee:before,.fa-inr:before{content:\"\\f156\"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:\"\\f157\"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:\"\\f158\"}.fa-won:before,.fa-krw:before{content:\"\\f159\"}.fa-bitcoin:before,.fa-btc:before{content:\"\\f15a\"}.fa-file:before{content:\"\\f15b\"}.fa-file-text:before{content:\"\\f15c\"}.fa-sort-alpha-asc:before{content:\"\\f15d\"}.fa-sort-alpha-desc:before{content:\"\\f15e\"}.fa-sort-amount-asc:before{content:\"\\f160\"}.fa-sort-amount-desc:before{content:\"\\f161\"}.fa-sort-numeric-asc:before{content:\"\\f162\"}.fa-sort-numeric-desc:before{content:\"\\f163\"}.fa-thumbs-up:before{content:\"\\f164\"}.fa-thumbs-down:before{content:\"\\f165\"}.fa-youtube-square:before{content:\"\\f166\"}.fa-youtube:before{content:\"\\f167\"}.fa-xing:before{content:\"\\f168\"}.fa-xing-square:before{content:\"\\f169\"}.fa-youtube-play:before{content:\"\\f16a\"}.fa-dropbox:before{content:\"\\f16b\"}.fa-stack-overflow:before{content:\"\\f16c\"}.fa-instagram:before{content:\"\\f16d\"}.fa-flickr:before{content:\"\\f16e\"}.fa-adn:before{content:\"\\f170\"}.fa-bitbucket:before{content:\"\\f171\"}.fa-bitbucket-square:before{content:\"\\f172\"}.fa-tumblr:before{content:\"\\f173\"}.fa-tumblr-square:before{content:\"\\f174\"}.fa-long-arrow-down:before{content:\"\\f175\"}.fa-long-arrow-up:before{content:\"\\f176\"}.fa-long-arrow-left:before{content:\"\\f177\"}.fa-long-arrow-right:before{content:\"\\f178\"}.fa-apple:before{content:\"\\f179\"}.fa-windows:before{content:\"\\f17a\"}.fa-android:before{content:\"\\f17b\"}.fa-linux:before{content:\"\\f17c\"}.fa-dribbble:before{content:\"\\f17d\"}.fa-skype:before{content:\"\\f17e\"}.fa-foursquare:before{content:\"\\f180\"}.fa-trello:before{content:\"\\f181\"}.fa-female:before{content:\"\\f182\"}.fa-male:before{content:\"\\f183\"}.fa-gittip:before{content:\"\\f184\"}.fa-sun-o:before{content:\"\\f185\"}.fa-moon-o:before{content:\"\\f186\"}.fa-archive:before{content:\"\\f187\"}.fa-bug:before{content:\"\\f188\"}.fa-vk:before{content:\"\\f189\"}.fa-weibo:before{content:\"\\f18a\"}.fa-renren:before{content:\"\\f18b\"}.fa-pagelines:before{content:\"\\f18c\"}.fa-stack-exchange:before{content:\"\\f18d\"}.fa-arrow-circle-o-right:before{content:\"\\f18e\"}.fa-arrow-circle-o-left:before{content:\"\\f190\"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:\"\\f191\"}.fa-dot-circle-o:before{content:\"\\f192\"}.fa-wheelchair:before{content:\"\\f193\"}.fa-vimeo-square:before{content:\"\\f194\"}.fa-turkish-lira:before,.fa-try:before{content:\"\\f195\"}.fa-plus-square-o:before{content:\"\\f196\"} /* */ .fa-spin::before{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}\n/* General */ .dialog { border: 1px solid; display: block; } .dialog:not(#qr):not(#thread-watcher):not(#header-bar) { box-shadow: 0 1px 2px rgba(0, 0, 0, .15); } #qr, #thread-watcher { box-shadow: -1px 2px 2px rgba(0, 0, 0, 0.25); } .captcha-img, .field { background-color: #FFF; border: 1px solid #CCC; -moz-box-sizing: border-box; box-sizing: border-box; color: #333; font: 13px sans-serif; outline: none; transition: color .25s, border-color .25s; transition: color .25s, border-color .25s; } .field::-moz-placeholder, .field:hover::-moz-placeholder { color: #AAA !important; font-size: 13px !important; opacity: 1.0 !important; } .captch-img:hover, .field:hover { border-color: #999; } .field:hover, .field:focus { color: #000; } .field[disabled] { background-color: #F2F2F2; color: #888; } .field::-webkit-search-decoration { display: none; } .move { cursor: move; overflow: hidden; } label, .watch-thread-link { cursor: pointer; } a[href=\"javascript:;\"] { text-decoration: none; } .warning { color: red; } #boardNavDesktop, #boardNavMobile { display: none !important; } body.hasDropDownNav{ margin-top: 5px; } a { outline: none !important; } .painted { border-radius: 3px; padding: 0px 2px; } .ad-plea { display: none; } /* 4chan style fixes */ .opContainer, .op { display: block !important; overflow: visible !important; } .reply > .file > .fileText { margin: 0 20px; } .hashlink::before { content: ' '; visibility: hidden; } .inline + .hashlink, [hidden] { display: none !important; } hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) { display: none !important; } .page-num { margin-right: -8px; } .fileText a { unicode-bidi: -moz-isolate; unicode-bidi: -webkit-isolate; } /* fixed, z-index */ #overlay, #fourchanx-settings, #qp, #ihover, #navlinks, .fixed #header-bar, :root.float #updater, :root.float #thread-stats, #qr { position: fixed; } #fourchanx-settings { z-index: 999; } #overlay { z-index: 900; } #notifications { z-index: 70; } #qp, #ihover { z-index: 60; } #menu, .gal-buttons { z-index: 50; } #navlinks, #updater, #thread-stats { z-index: 40; } .fixed #header-bar.autohide { z-index: 35; } #qr { z-index: 30; } #thread-watcher { z-index: 8; } :root.fixed-watcher #thread-watcher { z-index: 20; } .fixed #header-bar { z-index: 10; } /* Header */ .fixed.top-header body { padding-top: 2em; } .fixed.bottom-header body { padding-bottom: 2em; } .fixed #header-bar { right: 0; left: 0; padding: 3px 4px 4px; } .fixed.top-header #header-bar { top: 0; } .fixed.bottom-header #header-bar { bottom: 0; } #header-bar { border-width: 0; transition: all .1s .05s ease-in-out; } :root.fixed #header-bar { box-shadow: -5px 1px 10px rgba(0, 0, 0, 0.20); } #custom-board-list .current { padding: 1px 1px 4px 1px; } :root.centered-links #shortcuts { width: 300px; text-align: right; } :root.centered-links #header-bar { text-align: center; } #board-list { font-size: 13px; } :root.centered-links #custom-board-list { position: relative; left: 150px; } .fixed.top-header #header-bar { border-bottom-width: 1px; } .fixed.bottom-header #header-bar { box-shadow: 0 -1px 2px rgba(0, 0, 0, .15); border-top-width: 1px; } .fixed.bottom-header #header-bar .menu-button i { border-top: none; border-bottom: 6px solid; } #board-list { text-align: center; } .fixed #header-bar.autohide:not(:hover) { box-shadow: none; transition: all .8s .6s cubic-bezier(.55, .055, .675, .19); } .fixed.top-header #header-bar.autohide:not(:hover) { margin-bottom: -1em; -webkit-transform: translateY(-100%); transform: translateY(-100%); } .fixed.bottom-header #header-bar.autohide:not(:hover) { -webkit-transform: translateY(100%); transform: translateY(100%); } #scroll-marker { left: 0; right: 0; height: 10px; position: absolute; } :root:not(.autohide) #scroll-marker { pointer-events: none; } #header-bar #scroll-marker { display: none; } .fixed #header-bar #scroll-marker { display: block; } .fixed.top-header #header-bar #scroll-marker { top: 100%; } .fixed.bottom-header #header-bar #scroll-marker { bottom: 100%; } #header-bar a:not(.entry):not(.close) { text-decoration: none; } #header-bar a:not(.entry):not(.close):not(.current) { padding: 1px; } #shortcuts:empty { display: none; } .brackets-wrap::before { content: \"\\00a0[\"; } .brackets-wrap::after { content: \"]\\00a0\"; } .dead-thread, .disabled { opacity: .45; } #shortcuts { float: right; } .shortcut { margin-left: 3px; } #navbotright, #navtopright { display: none; } #toggleMsgBtn { display: none !important; } .current { font-weight: bold; } /* 4chan X link brackets */ .brackets-wrap::before { content: \"[\"; } .brackets-wrap::after { content: \"]\"; } /* Notifications */ #notifications { position: fixed; top: 0; height: 0; text-align: center; right: 0; left: 0; transition: all .8s .6s cubic-bezier(.55, .055, .675, .19); } .fixed.top-header #header-bar #notifications { position: absolute; top: 100%; } .notification { color: #FFF; font-weight: 700; text-shadow: 0 1px 2px rgba(0, 0, 0, .5); box-shadow: 0 1px 2px rgba(0, 0, 0, .15); border-radius: 2px; margin: 1px auto; width: 500px; max-width: 100%; position: relative; transition: all .25s ease-in-out; } .notification.error { background-color: hsla(0, 100%, 38%, .9); } .notification.warning { background-color: hsla(36, 100%, 38%, .9); } .notification.info { background-color: hsla(200, 100%, 38%, .9); } .notification.success { background-color: hsla(104, 100%, 38%, .9); } .notification a { color: white; } .notification > .close { padding: 7px; top: 0px; right: 5px; position: absolute; } .notification > .fa-times::before { font-size: 11px !important; } .message { -moz-box-sizing: border-box; box-sizing: border-box; padding: 6px 20px; max-height: 200px; width: 100%; overflow: auto; } /* Settings */ :root.fourchan-x body { -moz-box-sizing: border-box; box-sizing: border-box; } #overlay { background-color: rgba(0, 0, 0, .5); top: 0; left: 0; height: 100%; width: 100%; } #fourchanx-settings { -moz-box-sizing: border-box; box-sizing: border-box; box-shadow: 0 0 15px rgba(0, 0, 0, .15); height: 600px; max-height: 100%; width: 900px; max-width: 100%; margin: auto; padding: 3px; top: 50%; left: 50%; -moz-transform: translate(-50%, -50%); -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } #fourchanx-settings > nav { padding: 2px 2px 0; height: 15px; } #fourchanx-settings > nav a { text-decoration: underline; } #fourchanx-settings > nav a.close { text-decoration: none; padding: 0 2px; } .section-container { overflow: auto; position: absolute; top: 2.1em; right: 5px; bottom: 5px; left: 5px; padding-right: 5px; } .sections-list { padding: 0 3px; float: left; } .credits { float: right; } .tab-selected { font-weight: 700; } .section-sauce ul, .section-advanced ul { list-style: none; margin: 0; } .section-sauce ul { padding: 8px; } .section-advanced ul { padding: 0px; } .section-sauce li, .section-advanced li { padding-left: 4px; } .section-main label { text-decoration: underline; } .section-filter ul { padding: 0; } .section-filter li { margin: 10px 40px; } .section-filter textarea { height: 500px; } .section-sauce textarea { height: 350px; } .section-advanced .field[name=\"boardnav\"] { width: 100%; } .section-advanced textarea { height: 150px; } .section-advanced .archive-cell { min-width: 160px; text-align: center; } .section-advanced #archive-board-select { position: absolute; } .section-advanced .note { font-size: 0.8em; font-style: italic; margin-left: 10px; } .section-advanced .note code { font-style: normal; font-size: 11px; } .section-keybinds .field { font-family: monospace; } #fourchanx-settings fieldset { border: 1px solid; border-radius: 3px; } #fourchanx-settings legend { font-weight: 700; } #fourchanx-settings textarea { font-family: monospace; min-width: 100%; max-width: 100%; } #fourchanx-settings code { color: #000; background-color: #FFF; padding: 0 2px; } .unscroll { overflow: hidden; } /* Index */ :root.index-loading .navLinks, :root.index-loading .board, :root.index-loading .pagelist { display: none; } #index-search { padding-right: 1.5em; width: 100px; transition: color .25s, border-color .25s, width .25s; } #index-search:focus, #index-search[data-searching] { width: 200px; } #index-search-clear { color: gray; margin-left: -1em; }  #index-search:not([data-searching]) + #index-search-clear { display: none; } .summary { text-decoration: none; } /* Announcement Hiding */ :root.hide-announcement #globalMessage { display: none; } span.hide-announcement { font-size: 11px; position: relative; bottom: 5px; } .globalMessage, h2, h3 { color: inherit !important; font-size: 13px; font-weight: 100; } /* Unread */ #unread-line { margin: 0; border-color: rgb(255,0,0); } /* Thread Updater */ #updater { background: none; border: none; box-shadow: none; } #updater > .move { padding: 5px 3px 0px; margin-bottom: -3px; } #updater > div:last-child { text-align: center; } #updater input[type=\"number\"] { width: 4em; } :root.float #updater { padding: 0px 3px; } .new { color: limegreen; } #update-status.new { margin-right: 5px; } #update-timer { cursor: pointer; } /* Thread Watcher */ #thread-watcher { position: absolute; } #thread-watcher { padding-bottom: 3px; padding-left: 3px; overflow: hidden; white-space: nowrap; min-width: 146px; max-height: 92%; overflow-y: auto; } #thread-watcher .refresh { padding: 0px 3px; } :root.fixed-watcher #thread-watcher { position: fixed; } :root:not(.fixed-watcher) #thread-watcher:not(:hover) { max-height: 210px; overflow-y: hidden; } #thread-watcher > .move { padding-top: 3px; } #watched-threads > div { padding-left: 3px; padding-right: 3px; } #watched-threads .watcher-link { max-width: 250px; display: -webkit-inline-flex; display: inline-flex; -webkit-flex-direction: row; flex-direction: row; } #watched-threads .watcher-title { overflow: hidden; text-overflow: ellipsis; -webkit-flex: 0 1 auto; flex: 0 1 auto; } #watched-threads .watcher-unread { -webkit-flex: 0 0 auto; flex: 0 0 auto; } #thread-watcher a { text-decoration: none; } #thread-watcher .move > .close { position: absolute; right: 0px; top: 0px; padding: 0px 4px; } .watch-thread-link { padding-top: 18px; width: 18px; height: 0px; display: inline-block; background-repeat: no-repeat; opacity: 0.2; position: relative; top: 1px; } .watch-thread-link.watched { opacity: 1; } /* Thread Stats */ #thread-stats { background: none; border: none; box-shadow: none; } :root.float #post-count, :root.float #file-count { pointer-events: none; } :root.float #thread-stats { padding: 0px 3px; } /* Quote */ .deadlink { text-decoration: none !important; } .backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) { text-decoration: underline !important; } .inlined { opacity: .5; } #qp input, .forwarded { display: none; } .quotelink.forwardlink, .backlink.forwardlink { text-decoration: none; border-bottom: 1px dashed; } @supports (text-decoration-style: dashed) or (-moz-text-decoration-style: dashed) { .quotelink.forwardlink, .backlink.forwardlink { text-decoration: underline; -moz-text-decoration-style: dashed; text-decoration-style: dashed; border-bottom: none; } } .filtered { text-decoration: underline line-through; } :root.hide-backlinks .backlink.filtered { display: none; } .inline { border: 1px solid; display: table; margin: 2px 0; } .inline .post { border: 0 !important; background-color: transparent !important; display: table !important; margin: 0 !important; padding: 1px 2px !important; } #qp > .opContainer::after { content: ''; clear: both; display: table; } #qp .post { border: none; margin: 0; padding: 2px 2px 5px; } #qp img { max-height: 80vh; max-width: 50vw; } .qphl { outline: 2px solid rgba(216, 94, 49, .7); } :root.highlight-own .yourPost > .reply, :root.highlight-you .quotesYou > .reply { border-left: 2px solid rgba(221,0,0,.5); } /* Quote Threading */ .threadContainer { margin-left: 20px; border-left: 1px solid rgba(128,128,128,.3); } .threadOP { clear: both; } /* File */ .fnswitch:hover > .fntrunc, .fnswitch:not(:hover) > .fnfull, .expanded-image > .post > .file > .fileThumb > video[data-md5], .expanded-image > .post > .file > .fileThumb > img[data-md5] { display: none; } .full-image:not(#ihover) { display: none; } .expanded-image > .post > .file > .fileThumb > .full-image:not(#ihover) { display: inline; } .expanded-image { clear: left; } .expanding { opacity: .5; } :root.fit-height .full-image:not(#ihover) { max-height: 100vh; } :root.fit-width .full-image:not(#ihover) { max-width: 100%; } :root.gecko.fit-width .full-image:not(#ihover) { width: 100%; } .fileThumb > .warning { clear: both; } #ihover { -moz-box-sizing: border-box; box-sizing: border-box; max-height: 100%; max-width: 75%; padding-bottom: 16px; } /* Fappe Tyme */ :root.fappeTyme .thread > .noFile, :root.fappeTyme .threadContainer > .noFile { display: none; } /* Werk Tyme */ :root.werkTyme .postContainer:not(.noFile) .fileThumb { display: none; } /* Index/Reply Navigation */ #navlinks { font-size: 16px; top: 25px; right: 10px; } /* Filter */ .opContainer.filter-highlight { box-shadow: inset 5px 0 rgba(255, 0, 0, .5); } .filter-highlight > .reply { box-shadow: -5px 0 rgba(255, 0, 0, .5); } /* Spoiler text */ :root.reveal-spoilers s { color: white !important; } /* Thread & Reply Hiding */ .hide-thread-button, .hide-reply-button { float: left; margin-right: 4px; padding: 2px; } .hide-thread-button:not(:hover), .hide-reply-button:not(:hover) { opacity: 0.4; } .threadContainer .hide-reply-button { margin-left: 2px !important; position: relative; left: 1px; } .hide-thread-button { margin-top: -1px; } .stub ~ * { display: none !important; } .stub input { display: inline-block; } .thread[hidden] + hr { display: none; } /* QR */ :root.hide-original-post-form #postForm, :root.hide-original-post-form #togglePostFormLink, :root:not(.catalog) #togglePostFormLink, #qr.autohide:not(.focus):not(:hover):not(:active) > form, :root.thread-view #qr:not(.show-new-thread-option) select[data-name=\"thread\"], #file-n-submit:not(.has-file) #qr-filerm { display: none; } :root:not(.hide-original-post-form):not(.catalog) #postForm { display: table; } #qr select, #dump-button, #url-button, .remove, .captcha-img { cursor: pointer; } #qr { z-index: 20; position: fixed; padding: 1px; border: 1px solid transparent; min-width: 300px; border-radius: 3px 3px 0 0; } #qrtab { border-radius: 3px 3px 0 0; } #qrtab { margin-bottom: 1px; } #qr .close { float: right; padding: 0 3px; } #qr .warning { min-height: 1.6em; vertical-align: middle; padding: 0 1px; border-width: 1px; border-style: solid; } .qr-link-container { text-align: center; } .qr-link-container-bottom { width: 200px; position: absolute; left: -100px; margin-left: 50%; text-align: center; } .qr-link { border-radius: 3px; padding: 6px 10px 5px; font-weight: bold; vertical-align: middle; border-style: solid; border-width: 1px; font-size: 10pt; } .persona { width: 100%; display: -webkit-flex; display: flex; -webkit-flex-direction: row; flex-direction: row; } #dump-button { width: 10%; margin: 0; margin-right: 4px; font: 13px sans-serif; padding: 1px 0px 2px; opacity: 0.6; } #url-button { width: 10%; margin: 0; margin-right: 4px; font: 13px sans-serif; padding: 1px 0px 2px; opacity: 0.6; } .persona .field { -webkit-flex: 1; flex: 1; width: 0; } #qr.forced-anon input[data-name=\"name\"]:not(.force-show), #qr.forced-anon input[data-name=\"sub\"]:not(.force-show), #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show) { display: none; } #qr textarea.field { height: 14.8em; min-height: 9em; } #qr.has-captcha textarea.field { height: 9em; } input.field.tripped:not(:hover):not(:focus) { color: transparent !important; text-shadow: none !important; } #qr textarea { resize: both; } .captcha-img { margin: 0px; text-align: center; background-image: #fff; font-size: 0px; min-height: 59px; min-width: 302px; } .captcha-input{ width: 100%; margin: 1px 0 0; } .captcha-input.error:focus { border-color: rgb(255,0,0) !important; } .field { -moz-box-sizing: border-box; margin: 0px; padding: 2px 4px 3px; } #qr textarea { min-width: 100%; } #qr [type=\"submit\"] { width: 25%; vertical-align: top; } :root.webkit #qr [type=\"submit\"] { height: 24px; } #qr label input[type=\"checkbox\"] { position: relative; top: 2px; } /* Fake File Input */ input#qr-filename { border: none !important; width: 80%; padding: 0px 4px; position: relative; bottom: 1px; background: none !important; } input#qr-filename:not(.edit) { pointer-events: none; } #qr-filename, #qr-filesize, .has-file #qr-no-file { display: none; } #qr-no-file, .has-file #qr-filename, .has-file #qr-filesize { display: inline-block; margin: 0 0 2px; overflow: hidden; text-overflow: ellipsis; vertical-align: top; } #qr-no-file { color: #AAA; padding: 1px 4px; } #qr-filename-container { -moz-box-sizing: border-box; display: inline-block; position: relative; width: 100px; min-width: 74.6%; max-width: 74.6%; margin-right: 0.4%; margin-top: 1px; overflow: hidden; padding: 2px 1px 0; height: 22px; } #qr-filename-container:hover { cursor: text; } #qr-extras-container { position: absolute; right: 0px; } #qr-filerm { margin-right: 3px; z-index: 2; } #file-n-submit { height: 23px; } #qr input[type=\"file\"] { visibility: hidden; position: absolute; } /* Thread Select / Spoiler Label */ #qr select[data-name=\"thread\"] { float: right; } #qr.has-spoiler .has-file #qr-spoiler-label { width: 6.7%; min-width: 6.7%; max-width: 6.7%; display: inline-block; text-align: center; vertical-align: top; } #qr.has-spoiler #file-n-submit:not(.has-file) #qr-spoiler-label { display: none; } #qr.has-spoiler .has-file #qr-filename-container { max-width: 67.9%; min-width: 67.9%; } #qr-spoiler-label input { position: relative; top: 3px; } /* Dumping UI */ .dump #dump-list-container { display: block; } #dump-list-container { display: none; position: relative; overflow-y: hidden; margin-top: 1px; } #dump-list { overflow-x: auto; overflow-y: hidden; white-space: nowrap; width: 248px; max-width: 100%; min-width: 100%; } #dump-list:hover { overflow-x: auto; } .qr-preview { -moz-box-sizing: border-box; counter-increment: thumbnails; cursor: move; display: inline-block; height: 90px; width: 90px; padding: 2px; opacity: .5; overflow: hidden; position: relative; text-shadow: 0 0 2px #000; -moz-transition: opacity .25s ease-in-out; vertical-align: top; background-size: cover; } .qr-preview:hover, .qr-preview:focus { opacity: .9; } .qr-preview::before { content: counter(thumbnails); color: #fff; position: absolute; top: 3px; right: 3px; text-shadow: 0 0 3px #000, 0 0 8px #000; } .qr-preview#selected { opacity: 1; } .qr-preview.drag { box-shadow: 0 0 10px rgba(0,0,0,.5); } .qr-preview.over { border-color: #fff; } .qr-preview > span { color: #fff; } .remove { background: none; color: #e00; padding: 1px; } a:only-of-type > .remove { display: none; } .remove:hover::after { content: \" Remove\"; } .qr-preview > label { background: rgba(0,0,0,.5); color: #fff; right: 0; bottom: 0; left: 0; position: absolute; text-align: center; } .qr-preview > label > input { margin: 0; } #add-post { cursor: pointer; font-size: 2em; position: absolute; top: 50%; right: 10px; -moz-transform: translateY(-50%); } .textarea { position: relative; } :root.webkit .textarea { margin-bottom: -2px; } #char-count { color: #000; background: hsla(0, 0%, 100%, .5); font-size: 8pt; position: absolute; bottom: 1px; right: 1px; pointer-events: none; } /* Menu */ .menu-button:not(.fa-bars) { display: inline-block; position: relative; cursor: pointer; } #header-bar .menu-button i { border-top: 6px solid; border-right: 4px solid transparent; border-left: 4px solid transparent; display: inline-block; margin: 2px; vertical-align: middle; } .reply .menu-button, .op .menu-button, #thread-watcher .menu-button { margin-left: -1px !important; width: 20px; height: 15px; text-align: center; } .menu-button + .container:not(:empty) { margin-left: -5px !important; } #menu { position: fixed; outline: none; } #menu, .submenu { border-radius: 3px; padding-top: 1px; padding-bottom: 3px; } .entry { cursor: pointer; display: block; outline: none; padding: 2px 10px; position: relative; text-decoration: none; white-space: nowrap; min-width: 70px; text-align: left; text-shadow: none; } .left>.entry.has-submenu { padding-right: 17px !important; } .entry input[type=\"checkbox\"], .entry input[type=\"radio\"] { margin: 0px; position: relative; top: 2px; } .has-submenu::after { content: \"\"; border-left: .5em solid; border-top: .3em solid transparent; border-bottom: .3em solid transparent; display: inline-block; margin: .3em; position: absolute; right: 3px; } .left .has-submenu::after { border-left: 0; border-right: .5em solid; } .submenu { display: none; position: absolute; left: 100%; top: -1px; margin-left: 0px; margin-top: -2px; } .focused > .submenu { display: block; } .imp-exp-result { position: absolute; text-align: center; margin: auto; right: 0px; left: 0px; width: 200px; } .export, .import, .reset { cursor: pointer; text-decoration: none !important; } /* Custom Board Titles */ .boardTitle[contenteditable=\"true\"], .boardSubtitle[contenteditable=\"true\"] { cursor: text !important; } div.boardTitle { font-weight: 400 !important; } /* Link Title Favicons */ .linkify.YouTube { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAYAAABr5z2BAAABIklEQVQoz53LvUrDUBjG8bOoOammSf1IoBSvoCB4JeIqOHgBLt6AIMRBBQelWurQ2kERnMRBsBUcIp5FJSBI5oQsJVkkUHh8W0o5nhaFHvjBgef/Mq+Q46RJBMkI/vE+aOus956tnEswIZe1LV0QyJ5sE2GzgZfVMtRNIdiDpccEssdlB1mW4bvTwdvWJtRdErM7U+8S/FJykCRJX5qm+KpVce8UMNLRLbulz4iSjTAMh6Iowsd5BeNadp3nUF0VlxAEwZBotXC0Usa4ll3meZdA1iguwvf9vpvDA2wvmKgYGtSud8suDB4TyGr2PF49D/vra9jRZ1BVdknMzgwuCGSnZEObwu6sBnVTCHZiaC7BhFx2PKdxUidiAH/4lLo9Mv0DELVs9qsOHXwAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.Vimeo { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAIdZUKh6sLlLkLmr4LmsAMp88NrdYVW3MZj7Acstkrt9s1e5E7vN5EfI9JvdtKwuBijp5kpbl30eiDt8aG1uqRr7qTyNehxM+k4PCy3enB3OTg6Ovv9PXw+fz////L9U5WAAAAAXRSTlMAQObYZgAAAIFJREFUeNplz90OwiAMBWAQpAoyxclkP3je/y0H2AQXz0WT8100rRD6kNI9/cRroemQL3hXhoujZYj4OHoAmBvYGcBISwbWBvfXCrytnIDUQMkbsBpagMA7zhtQdyTFQAmIG7IkYniiZuh3XGsPqoOZkMOJOpAcLqUzNFGGu/57fwc1hgtp0mVSyQAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.SoundCloud { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.audio { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAitJREFUOE9jYCAWKJWwavr0KyXWb/FIbDtUFFyzJx6nVofE2Xo5nXsj0rqPNSR0nVkR2Hjmgmfd+U9Otdf+m5Vf/6+SfeU/R9ChVVgNYDRtlfJuuPA/rPfe/4QpD/6nznj0P27Kw/9unff/69Xf+69c/+C/SO7N/0z+OAxgMmmRCe++/r9i3ev/KWvf/vdY8PK/bt/9/wrNV3/IN5y/IVt1YqNg4pGTTP4HsbuA2bhZ2qvpyn+xjIObxAp3VwqlrgngLFyryVy5nhPmZJHANS2cwYexG8BmVC/pWn3hP4NZlzWuQDJI3dIiFnUUuwEsQAOcq87jNcC7fHeLUtJxHF4AGmBWeAavAWH1+1rUUk7giAWjOknllON4DXAs2NEiG4/DBQxAF/CFHfrPYI4jDFSLuJVjNrUJhB/B7gIGo1pJRt99GAZYJK7wLJ1z7Xzl4vu/7aqv/GRBj0bjqAX2qb0nJ7mXH17C4HcUxQA+hymWtSue/C5a9up/9Ozn/7Vr7v1nRY7GqMb91T3b3v6vWvPmf/S0p/9ZQk+DDLCBRSOz06Jqk+o7/21nvfqvsebDf7kZL/5zBaxphkezd+OFn7HzXvz3Wvjmv9a8N//5Ek//ZTBpVYUrMG2X5wjcdl68+uI/wa5Lr3hSNjczGFeywOVZ/bbcVGp//F9izfv/Ql03f3P4LC/HSEQquYwMFnUCDJ7dzBhyjGZNQpye89M5gpfnMvtNUyE2h4PUAQBovvT7lyNljwAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.LiveLeak { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAlNJREFUGBkFwU2LVmUYAODrPu8Z5x1xSpRBXQyFoLsBE+wfiO5atJOgnf9DUPwFgtGinUgEaQsRhHYuMtpEiEWuG5iNjuOcj+c8z911xXcXL/68c3Dw1fzhg0QgEQAAEYGUKXFie9vxlSs/xk/rdavjGEkmkWSih65z4osv9GfOiK6LzEyZ2uGh4dUrmzs72ddlUUhkoiMr4PT167589Mh6c1N0nSRlqrX67dat+PDyZXRT19m5edPnt28rGFHxMcJ6d9fprS1/37tneP3aemPD1uamUydPOru3p5DdGOH0tWsu3LhhxIQJM2qEpRT/Pn3q/du3AhARSmvGTH0lplKMrVkiYpVpQaJlighzhDkzhmEA0fcWoqAfyaFW4zTlgCABxlrNmY4ylUzLsiREprFWc0T2M+ZSjKWY0AEaltZUjJixZJIpuk5pTWlNP2BYFvOyKJkCAKU1tTXHrZlqVWolUxdhxsfVSj9FmJfFMM9GdICGGa01HyMstYpMIFPJVNDPmYZSTOPoOEKHzNRlKpmWWh1j6TpLa2SKTKVWU6Z+Qolwdm/P9QcPZKa2LH69e9eIMs+WCL/cv2/98CGZPrt61am+V9APq1X89eyZ/968obVYaiXT4dGREgG+vnPHeHgYMsH2+fP+efEihtVKv7SWw/6+9/v7KYLMhIywTJPamvOXLomukyRsrNf+ePzYkpl9dJ3SWgSCSCQCfz5/7pMLF2yfO6eLiAQcHRz4/cmT+HR7O+Ob3d0fNt69+7a2BiICQCJbA0EgE5lpvbXl1OXL3/8Pfax4+6SjSukAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.Vocaroo { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAw9JREFUOE9jYMABuMwYmCyTJKUCGlSnFSy02TTzeOyCiQcDViX26qVz2TAyYtWmEMwuoZ3M7V40LcB79pHkc0svpvzY8jD//87nxf+3Pyn8v/ZO8v+VNyP/2mZJumI1QCWSI8232Hjumitlfw5+qPp/9l8TCt76JP//xkdx/wsXWCzjtWFkwTCkbWFe9plPk/+ga4Txz/xt/D/hkN//gMXif21a+NbyWjIwoRiy6GDT5rP/mlFsPfyp5n/NpOj/22+0gMUXXIz/H7hC/L/bFKFbPDZMrHAD5H35OPt2J9zacDv/f3V7xv9FhwrBGubsT/1//Pjx/1GJ/mD+/nfl/1v3Ovy3KRJNQbHdOlXCvOO03/+pm1P/v3v37n90hhtYw9HPtf8Xb2v937cmHswHeWPRxYj/LvkK3igGKARwicTO07118H3V/5kbi/4vPZMJtK3s/6YH2f+Pfq1B8VbjWrdnMu5s4nAD9CNFhKwz5DTUvLl419zKvAcLtG1P84BRl/b/5M/6/6f/NPzf/qzo84yj0Uus0xUU4Zor54bm9+4OfZG02OCuoAMTb9ZkC9ull1Nvrr2Z+XvRpaRfc65H/68F+jl9svEhzyLFWoccWVc+eyTHq/twydjlKRln7jX9bNMkMJnbhoFRL1xCqmKx6/yi2fYXa/c5/e846PV/5fW0/7OPx/yfcjzop34ulxdGGvDuU8mMXaX507lBuiN6ueadmQeT/p/93vf/1O+G//sP5fw/eL3o/5JLif8zVxs+Tlir9S26UyeFQQvJGBE7FvaFZ9LfN+1y+WjbItSb3GmXvXd15v8zroH/HxgE/D+aGPx/18vi/z07PeZNPRKxe/Kh0Ae8toxscCO4zBkYXArk9C1SxJUYjBkYPPIVtbbuTftz3cz//2O9wP/75iSAXdO72/dt2HL5F6YlfBW4MiJYXMiBiW3t7azHBx+V/t89N+H/8a+1//e9K/9attDp5LQjYX8SuvVL8RoAkmxa65299Erq1FnHo0qrl7t4BddriIs4MrM3rfWcFd+pGwVSAwBZ0bKP8yrZPAAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.pastebin { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB1FBMVEUAAAAAAAAAAABWYWwAAABbY3BbYm5dZnFdZXJeZnMEBAQHCAhYYGpdZnFdZnBgaHIlJyomKCooKi09QkdESU5eZGtdYmhdYmleY2lrcXdqb3Rqb3Rqb3SSmJ+SlJeWmJutr7GtrrCWm6ChpKhbW1tmZmZvb290dHR3d3d4eHh5eXl6enp8fHx+gIJ/f3+CgoKDg4OEhISFhYWHh4eKioqKjI2Li4uMjIyOjo6Pj4+QkJCRkZGSkpKUlJSVl5mWlpaYmZqZm52ampqbm5ucnJydnZ2enp6fn5+hoaGioqKkpKSkpaalpaWmp6mmp6qnqauoqKioqquoqq2qqqqrrK2srKysra6srrCsrrGurq6vr6+wsLCxsbGysrKztLa0tLS1t7m2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr7AwMDAwsTBwcHExcfFxcXFxsnGxsbHx8fIyMjJycnMzMzNzc3Ozs7O0NLPz8/Q0NDR0dHR09XT09PV1dXV1dbV1tfV19rW1tbX19fX19jY2tzZ2dnZ2tva2tra3N3a3N7c3Nze3t7f39/f4OHg4ODi4uLl5+jm5ubs7Ozs7e3u7u7v7+/v8PDw8PDx8fHy8vLz8/P29vYSoLMZAAAAJHRSTlMABAUGCwsNHCAiLzMzMzZEYGJwgIuOnJycnqmqq9bc3+/w8fkZ0N/uAAAA/klEQVQoU2NgYGDl5YMDdgYGBmZZ3964CYFtIR3e9Q7K/AwMHI55KfaFmcHWMy3K3MwlGRg4wz0zdYpcorRbNbL0LaWAAp3ts2umV8wo6MupTauQBgqUG03VL7W3sfZSb1erAgm02M+yzYrVCXUy6zapAQlUx/dEdyX3J3ZHVUYVywAF8o2rDNN1Go2jzGLMokAC2QbuSc42mXmaOXop9iAtCXrJ5qXWjT59Abl2ESJAAX/tSIMMiyrrqQ3T6uS5gQK6kSqpqkUermGTexQFmYACflqR+hlWZSamzQpCLEDPsSmVVDT1TJw0JUhOAMRnYOARFRMTE5cQF+ZiBPIAII5B3EVG0b4AAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.gist { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABblBMVEXc3NykpKTW1tbb29ugoKCdnZ0AAAACAgIEDRcKCgoMDAwODg4QIzYRDAoTExMUDwwVAg0WICsaEw8aGhoiCBklGxUmERwwKCQ7LSU7Ozs8LSZFLyNINi1JNyxJNy1KSklMOi5VR1FXV1daQTRkZGRseYZwU0F4eHh7dnR8bWV/YE6IdGiKcGCKkJaNgYeNjY2RdGOScWCUcWCZmZmhoaGkpKSoqKirfmaurq6xsbG1tbW6urq+vr7AbmzBb23CwsLGxsbHx8fHyMjJycnJysrMzMzOiYbPi4fQ0NDRoYbT09PU1NTW1tbY2NjZqIzZ2dnb29vd3d3f39/i4uLktZrk5OTl5eXm5ubn5+fo6Ojq6urs7OzttKLu7u7wuqbw8PDx8fHz8/P4+Pj5+fn7uZj8vpz9ya79ybD/tZf/upr/wZ//w6H/xKH/xaL/xrH/yqj/y7T/zqv/z7D/07D/17n/2Lv/2Lz/3L//38n/4Mk3Q/ZuAAAABnRSTlMSFcbGzc5MNKFvAAAA1klEQVQoz2NgYPZHAswMDEwRSclwkBTBxOARn4gE4j0YXBOiJNUDg7y8Ar1UlOITXBkcY73Z2Li42dg42dn4wmIdGeyjQ7nZoEA4PNqewSZKlw0O9KJsGKwjBdl4ZeWkJGQUhNjEIq0ZrMI5+D0ri7Jz8itCRAXCrRgsQ3mUy+xicrPSbfO0REItGSyCVaVL3ONSU9LcCtQUgy0YzIJ85M1LizMzCsv9xF2CzBhMAwN99TV1DI0MtDWcAgNNGUycA5CAswkDi5kDwrMOZiwMjKzGSICVEQDhZj0UQV7PewAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.image { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAs5JREFUOE+lk/tvi1EYx98/xT8gW4REIpGFMEQWl2FiM9ZMZhm2xRAyOsmujFFmdFRHu0tWm87UypxStr69zPauN5e5rHVp3IYhbOvHy+wHEQlxkm+ek+d8nm9OznkeSfrfldmgJC7QyUlTymsJTfuTZ25z4HdWYwyLreYhtpgekGPw0+kKvo1Eo+IXRSIiEhkWZuc9tqnsJD9EqTUopCxjSGTpB0iueczSo1HyW8cpsExQ1DbxI2pt45j9cXpexul4FEd79RnZphAa/SD7WvuFtO6UItbU9LC+YQxNI2w0wwYT5LRAdhOU3oBTIXC9gXP3oUSGgz2vST3gYHejR0jptT1C332f8yrUEYHrz8CgxDnpm6DKCUfc0KnmXa/AEVPPwnDcD0cvetA2uYRk67Ive/lpjO7YBO1PPuF8Df3vwf4cbNE4tqdw7YVq8HYyHx6FvhE1hkMEg8HDUqvFkjT4aIjMqkqyqkswDSrcfBfH+Q561YLAZ/B+BLda6FXlU/cPv0AoEPhuoP1h4Av7Wbh9E/Py15NWWUjeSR3nZDfeN+N0DY9hG/7K1eGP3P0S5/EYRFUF/IOTBrUXHPm9fT6mr1xEwupkZqxbzLyiDJYUZ5NSnkdqdSHpxyrYdFpPgdmAsdfJwPMI/Yr65bf7tZLGGBQ7DNdJWFtIYvoOZmbuZE7OXpIKKli86zAr9p9gTVktWTVnKTI2U95uRWe3U2IJUDbVB5p6hVm5x5m9Vc/cnedZUNzC8lILaQesZBy6hEZ3maKzgvJWFzVWD9XtXvVGQbSWASFtMATVRlJIKbOTWtlJXaeXepuPM1f6MNp9GLt8mLvvYLmp0OhQ2Fwvk6m7xaqDTvY0eYWUVtcnllXfYlGpnfklVuraHHg8HjxuN+6fktUHlWWZPaZeUo/ILK0UKttBcbNbSB9GP0yLxWJJUxoZGUn80zD9C/vXQ/4NHY10h3M1zmQAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.InstallGentoo { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABcVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3dIYAAAAAAAAAAAAbGh4BBAcCBgoBBgoCBwsCCQ/QzucCCA7MyuXZ1eUBBQmTh8fo5/i9svIAAADh3vQAAAACCA0CCQ8CCQ4DDBQbGCUDChDr6vgAAAAAAAAREBIDCxK6tdfe2fTv7/cDCxIDDBQEDRUHDhgMJjXk4PZdXWdLUFoUNEYOKDgSMUMRLUBneI4eTGj08/QmW3onW3rTzvfOx/giU3IiVHMkWHdEaYJobHv3+PokWHpua6TNy9xZgZ+1quz8/foQKj0XPFInWn0nW38tZ4o6fqg8gq48grA9hrU/i7pAhrNAiLdBjLtEjr1FksNIjr5Il8pImMtKWnNqhL97odKFqti5q/q5rPq60+nCt/vLw/vPx/jV0vHY0/rc1/rg2/vh3fzn4fzu6/vx8vf19Pv19Pz49/v5+Pv8/Pv8/fr9/vv+/frziVtUAAAAT3RSTlMABQYHCAoNDhARGRobL0ZOV1xdXV5fYGBmZnB0eX2MjZSaoaGio6mqqqustLq7zubo6Ojo6evt7u/x8fLy9/f4+Pj5+vr6+vr6+/39/v7+XKgUSwAAAMhJREFUKM9jYGDg4OZmZgABKINT1dBAhBHIYFMxMBIDisjbhoZbCTExsCu5hoeY8DEwcOkEx8fY6MqpucTGB0izglVEplcU5/gmRYWBVQDNMK+s0hN3SvMyBpsBNJxXw0NfwTEjVQZqHQMHj5RfWW5mliSEC7TPzK6yJD/bXZQRzGdXcisqLy309okA2Q4Eis4peQWmstqBCdGW/CABraC45ERBBs3A6Fh/AbAKTwsHa34QZW8NVsGuLqwswQSjQICTmYMFQaEDAAF8JHLfKGswAAAAAElFTkSuQmCC') center left no-repeat!important; padding-left: 18px; } .linkify.video { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAxgDGAP8nNqN7AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gMZBjQQLEEqGwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAA5SURBVDjLY2AYaMDIwMDwn1JD/lPCZhpwL+B1wf///ykzgBhDiAoDfIYQZQAjIyP5BuDTPJqQqAQAvW0ZAMk8+EEAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } /* Gallery */ #a-gallery { position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: 30; display: -webkit-flex; display: flex; -webkit-flex-direction: row; flex-direction: row; background: rgba(0,0,0,0.7); } .gal-viewport { display: -webkit-flex; display: flex; -webkit-align-items: stretch; align-items: stretch; -webkit-flex-direction: row; flex-direction: row; -webkit-flex: 1 1 auto; flex: 1 1 auto; } .gal-thumbnails { -webkit-flex: 0 0 150px; flex: 0 0 150px; overflow-y: auto; display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; -webkit-align-items: stretch; align-items: stretch; text-align: center; background: rgba(0,0,0,.5); border-left: 1px solid #222; } .gal-hide-thumbnails .gal-thumbnails { display: none; } .gal-thumb img { max-width: 125px; max-height: 125px; height: auto; width: auto; } .gal-thumb { -webkit-flex: 0 0 auto; flex: 0 0 auto; padding: 3px; line-height: 0; transition: background .2s linear; } .gal-highlight { background: rgba(0, 190, 255,.8); } .gal-prev { order: 0; border-right: 1px solid #222; } .gal-next { order: 2; border-left: 1px solid #222; } .gal-prev, .gal-next { -webkit-flex: 0 0 20px; flex: 0 0 20px; position: relative; cursor: pointer; opacity: 0.7; background-color: rgba(0, 0, 0, 0.3); } .gal-prev:hover, .gal-next:hover { opacity: 1; } .gal-prev::after, .gal-next::after { position: absolute; top: 48.6%; -webkit-transform: translateY(-50%); transform: translateY(-50%); display: inline-block; border-top: 11px solid transparent; border-bottom: 11px solid transparent; content: \"\"; } .gal-prev::after { border-right: 12px solid #fff; right: 5px; } .gal-next::after { border-left: 12px solid #fff; right: 3px; } .gal-image { order: 1; -webkit-flex: 1 0 auto; flex: 1 0 auto; display: -webkit-flex; display: flex; -webkit-align-items: flex-start; align-items: flex-start; -webkit-justify-content: space-around; justify-content: space-around; overflow: hidden; /* Flex > Non-Flex child max-width and overflow fix (Firefox only?) */ width: 1%; } :root:not(.gal-fit-height):not(.gal-pdf) .gal-image { overflow-y: scroll !important; } :root:not(.gal-fit-width):not(.gal-pdf) .gal-image { overflow-x: scroll !important; } .gal-image a { margin: auto; line-height: 0; } :root.gal-pdf .gal-image a { width: 100%; height: 100%; } .gal-fit-width .gal-image img, .gal-fit-width .gal-image video { max-width: 100%; } .gal-fit-height .gal-image img, .gal-fit-height .gal-image video { /* Chrome doesn't support viewpoint units in calc() http://bugs.chromium.org/168840 \"It looks like the original author of viewport units in WebKit is not coming back to fix this stuff.\" Well, fuck. */ max-height: 95vh; max-height: calc(100vh - 25px); } .gal-image iframe { width: 100%; height: 100%; } .gal-buttons { font-size: 2em; margin-right: 3px; padding-left: 7px; padding-right: 7px; top: 5px; } :root.gal-pdf .gal-buttons { top: 40px; background: rgba(0,0,0,0.6) !important; border-radius: 3px; } .gal-buttons a { color: #ffffff; text-shadow: 0px 0px 1px #000000; } .gal-buttons i { display: inline-block; margin: 2px; position: relative; } .gal-start i { border-left: 10px solid; border-top: 6px solid transparent; border-bottom: 6px solid transparent; bottom: 1px; } .gal-stop i { border: 5px solid; bottom: 2px; } .gal-buttons.gal-playing > .gal-start, .gal-buttons:not(.gal-playing) > .gal-stop { display: none; } .gal-buttons .menu-button i { border-top: 10px solid; border-right: 6px solid transparent; border-left: 6px solid transparent; bottom: 2px; vertical-align: baseline; } .gal-buttons, .gal-name, .gal-count { position: fixed; right: 195px; } .gal-hide-thumbnails .gal-buttons, .gal-hide-thumbnails .gal-count, .gal-hide-thumbnails .gal-name { right: 44px; } .gal-name { bottom: 6px; background: rgba(0,0,0,0.6) !important; border-radius: 3px; padding: 1px 5px 2px 5px; text-decoration: none !important; color: white !important; } .gal-name:hover, .gal-buttons a:hover { color: rgb(95, 95, 101) !important; } :root.gal-pdf .gal-buttons a:hover { color: rgb(204, 204, 204) !important; } .gal-count { bottom: 27px; background: rgba(0,0,0,0.6) !important; border-radius: 3px; padding: 1px 5px 2px 5px; color: #ffffff !important; } :root:not(.gal-fit-width):not(.gal-pdf) .gal-name { bottom: 23px !important; } :root:not(.gal-fit-width):not(.gal-pdf) .gal-count { bottom: 44px !important; } :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-buttons, :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-name, :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-count { right: 178px !important; } :root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-buttons, :root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-name, :root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-count { right: 28px !important; } .field[name=\"Slide Delay\"] { width: 4em; } @media screen and (resolution: 1dppx) { .fa-bars { font-size: 14px; } #shortcuts .fa-bars { vertical-align: -1px; } }\n/* General */ :root.yotsuba .dialog { background-color: #F0E0D6; border-color: #D9BFB7; } :root.yotsuba .field:focus { border-color: #EA8; } /* Header */ :root.yotsuba #header-bar.dialog { background-color: rgba(240,224,214,0.98); } :root.yotsuba #header-bar, :root.yotsuba #notifications { font-size: 9pt; color: #B86; } :root.yotsuba #board-list a, :root.yotsuba #shortcuts a { color: #800000; } :root.yotsuba.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(178,0,0,0.2); } :root.yotsuba.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.yotsuba #fourchanx-settings fieldset { border-color: #D9BFB7; } /* Quote */ :root.yotsuba .backlink.deadlink { color: #00E !important; } :root.yotsuba .inline { border-color: #D9BFB7; background-color: rgba(255, 255, 255, .14); } /* QR */ .yotsuba #dump-list::-webkit-scrollbar-thumb { background-color: #F0E0D6; border-color: #D9BFB7; } :root.yotsuba .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.yotsuba .qr-link { border-color: rgb(225, 209, 199) rgb(225, 209, 199) rgb(210, 194, 184); background: linear-gradient(#FFEFE5, #F0E0D6) repeat scroll 0% 0% transparent; } :root.yotsuba .qr-link:hover { background: #F0E0D6; } /* Menu */ :root.yotsuba #menu { color: #800000; } :root.yotsuba .entry { font-size: 10pt; } :root.yotsuba .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.yotsuba .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(128,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.yotsuba div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(100,0,0,0.6); }\n/* General */ :root.yotsuba-b .dialog { background-color: #D6DAF0; border-color: #B7C5D9; } :root.yotsuba-b .field:focus { border-color: #98E; } /* Header */ :root.yotsuba-b #header-bar.dialog { background-color: rgba(214,218,240,0.98); } :root.yotsuba-b #header-bar, :root.yotsuba-b #notifications { font-size: 9pt; color: #89A; } :root.yotsuba-b #board-list a, :root.yotsuba-b #shortcuts a { color: #34345C; } :root.yotsuba-b.fixed #custom-board-list .current { border-bottom: 1px solid rgba(30, 30, 255, 0.2); } :root.yotsuba-b.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.yotsuba-b #fourchanx-settings fieldset { border-color: #B7C5D9; } /* Quote */ :root.yotsuba-b .backlink.deadlink { color: #34345C !important; } :root.yotsuba-b .inline { border-color: #B7C5D9; background-color: rgba(255, 255, 255, .14); } /* QR */ .yotsuba-b #dump-list::-webkit-scrollbar-thumb { background-color: #D6DAF0; border-color: #B7C5D9; } :root.yotsuba-b .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.yotsuba-b .qr-link { border-color: rgb(199, 203, 225) rgb(199, 203, 225) rgb(184, 188, 210); background: linear-gradient(#E5E9FF, #D6DAF0) repeat scroll 0% 0% transparent; } :root.yotsuba-b .qr-link:hover { background: #D9DDF3; } /* Menu */ :root.yotsuba-b #menu { color: #000; } :root.yotsuba-b .entry { font-size: 10pt; } :root.yotsuba-b .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.yotsuba-b .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(0,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.yotsuba-b div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(105,10,15,0.6); }\n/* General */ :root.futaba .dialog { background-color: #F0E0D6; border-color: #D9BFB7; } :root.futaba .field:focus { border-color: #EA8; } /* Header */ :root.futaba #header-bar.dialog { background-color: rgba(240,224,214,0.98); } :root.futaba #header-bar, :root.futaba #notifications { font-size: 11pt; color: #B86; } :root.futaba #header-bar a, :root.futaba #notifications a { color: #800000; } :root.futaba.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(178,0,0,0.2); } :root.futaba.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.futaba #fourchanx-settings fieldset { border-color: #D9BFB7; } /* Quote */ :root.futaba .backlink.deadlink { color: #00E !important; } :root.futaba .inline { border-color: #D9BFB7; background-color: rgba(255, 255, 255, .14); } /* QR */ .futaba #dump-list::-webkit-scrollbar-thumb { background-color: #F0E0D6; border-color: #D9BFB7; } :root.futaba .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.futaba .qr-link { border-color: rgb(225, 209, 199) rgb(225, 209, 199) rgb(210, 194, 184); background: linear-gradient(#FFEFE5, #F0E0D6) repeat scroll 0% 0% transparent; } :root.futaba .qr-link:hover { background: #F0E0D6; } /* Menu */ :root.futaba #menu { color: #800000; } :root.futaba .entry { font-size: 12pt; } :root.futaba .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.futaba .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(128,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); }\n/* General */ :root.burichan .dialog { background-color: #D6DAF0; border-color: #B7C5D9; } :root.burichan .field:focus { border-color: #98E; } /* Header */ :root.burichan #header-bar.dialog { background-color: rgba(214,218,240,0.98); } :root.burichan #header-bar, :root.burichan #header-bar #notifications { font-size: 11pt; color: #89A; } :root.burichan #header-bar a, :root.burichan #header-bar #notifications a { color: #34345C; } :root.burichan.fixed #custom-board-list .current { border-bottom: 1px solid rgba(30, 30, 255, 0.2); } :root.burichan.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.burichan #fourchanx-settings fieldset { border-color: #B7C5D9; } /* Quote */ :root.burichan .backlink.deadlink { color: #34345C !important; } :root.burichan .inline { border-color: #B7C5D9; background-color: rgba(255, 255, 255, .14); } /* QR */ .burichan #dump-list::-webkit-scrollbar-thumb { background-color: #D6DAF0; border-color: #B7C5D9; } :root.burichan .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.burichan .qr-link { border-color: rgb(199, 203, 225) rgb(199, 203, 225) rgb(184, 188, 210); background: linear-gradient(#E5E9FF, #D6DAF0) repeat scroll 0% 0% transparent; } :root.burichan .qr-link:hover { background: #D9DDF3; } /* Menu */ :root.burichan #menu { color: #000000; } :root.burichan .entry { font-size: 12pt; } :root.burichan .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.burichan .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(0,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); }\n/* General */ :root.tomorrow .dialog { background-color: #282A2E; border-color: #111; } /* Header */ :root.tomorrow #header-bar.dialog { background-color: rgba(40,42,46,0.9); } :root.tomorrow #header-bar, :root.tomorrow #notifications { font-size: 9pt; color: #C5C8C6; } :root.tomorrow #header-bar a, :root.tomorrow #notifications a { color: #81A2BE; } :root.tomorrow.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(83,124,160,0.4); } :root.tomorrow.fixed #custom-board-list .current:hover { border-bottom-color: rgba(95,137,172,0.4); } /* Settings */ :root.tomorrow #fourchanx-settings fieldset { border-color: #111; } /* Quote */ :root.tomorrow .backlink.deadlink { color: #81A2BE !important; } :root.tomorrow .inline { border-color: #111; background-color: rgba(0, 0, 0, .14); } /* QR */ .tomorrow #dump-list::-webkit-scrollbar-thumb { background-color: #282A2E; border-color: #111; } :root.tomorrow .qr-preview { background-color: rgba(255, 255, 255, .15); } :root.tomorrow #qr .field { background-color: rgb(26, 27, 29); color: rgb(197,200,198); border-color: rgb(40, 41, 42); } :root.tomorrow #qr .field:focus { border-color: rgb(129, 162, 190) !important; background-color: rgb(30,32,36); } :root.tomorrow .qr-link { border-color: rgb(25, 27, 31) rgb(25, 27, 31) rgb(10, 12, 16); background: linear-gradient(#37393D, #282A2E) repeat scroll 0% 0% transparent; } :root.tomorrow .qr-link:hover { background: #282A2E; } /* Menu */ :root.tomorrow #menu { color: #C5C8C6; } :root.tomorrow .entry { font-size: 10pt; } :root.tomorrow .focused.entry { background: rgba(0, 0, 0, .33); } /* Watcher Favicon */ :root.tomorrow .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(197,200,198)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.tomorrow div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(167,170,168,0.6); }\n/* General */ :root.photon .dialog { background-color: #DDD; border-color: #CCC; } :root.photon .field:focus { border-color: #EA8; } /* Header */ :root.photon #header-bar.dialog { background-color: rgba(221,221,221,0.98); } :root.photon #header-bar, :root.photon #notifications { font-size: 9pt; color: #333; } :root.photon #header-bar a, :root.photon #notifications a { color: #FF6600; } :root.photon.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(0,74,153,0.2); } :root.photon.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,51,0,0.2); } /* Settings */ :root.photon #fourchanx-settings fieldset { border-color: #CCC; } /* Quote */ :root.photon .backlink.deadlink { color: #F60 !important; } :root.photon .inline { border-color: #CCC; background-color: rgba(255, 255, 255, .14); } /* QR */ .photon #dump-list::-webkit-scrollbar-thumb { background-color: #DDD; border-color: #CCC; } :root.photon .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.photon .qr-link { border-color: rgb(206, 206, 206) rgb(206, 206, 206) rgb(191, 191, 191); background: linear-gradient(#ECECEC, #DDD) repeat scroll 0% 0% transparent; } :root.photon .qr-link:hover { background: #DDDDDD; } /* Menu */ :root.photon #menu { color: #333; } :root.photon .entry { font-size: 10pt; } :root.photon .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.photon .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(51,51,51)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.photon div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(0,74,153,0.6); }",
    features: [['Polyfill', Polyfill], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Custom CSS', CustomCSS], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply', QR], ['Menu', Menu], ['Report Link', ReportLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Thread Excerpt', ThreadExcerpt], ['Favicon', Favicon], ['Unread', Unread], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner]]
  };

  Main.init();

}).call(this);
