4chan-x/4chan_x.user.js
2013-02-21 00:51:30 +01:00

6931 lines
252 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name 4chan X Beta
// @version 3.0.0
// @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
// @copyright 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @match *://boards.4chan.org/*
// @match *://images.4chan.org/*
// @match *://sys.4chan.org/*
// @match *://api.4chan.org/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/v3/4chan_x.meta.js
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/v3/4chan_x.user.js
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
// ==/UserScript==
/* 4chan X Beta - Version 3.0.0 - 2013-02-21
* http://mayhemydg.github.com/4chan-x/
*
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
* Copyright (c) 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
* Licensed under the MIT license.
* https://github.com/MayhemYDG/4chan-x/blob/master/LICENSE
*
* Contributors:
* https://github.com/MayhemYDG/4chan-x/graphs/contributors
* Non-GitHub contributors:
* ferongr, xat-, Ongpot, thisisanon and Anonymous - favicon contributions
* e000 - cooldown sanity check
* Seiba - chrome quick reply focusing
* herpaderpderp - recaptcha fixes
* WakiMiko - recaptcha tab order http://userscripts.org/scripts/show/82657
*
* All the people who've taken the time to write bug reports.
*
* Thank you.
*/
(function() {
var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, Fourchan, Get, Header, ImageExpand, ImageHover, Keybinds, Main, Menu, Nav, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, d, doc, g,
__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; };
Config = {
main: {
Enhancing: {
'Enable 4chan\'s extension': [false, 'Compatibility between 4chan X Beta and 4chan\'s inline extension is NOT guaranteed.'],
'404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
'Time Formatting': [true, 'Localize and format timestamps arbitrarily.'],
'Relative Post Dates': [false, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'],
'File Info Formatting': [true, 'Reformat the file information.'],
'Comment Expansion': [true, 'Can expand too long comments.'],
'Thread Expansion': [true, 'Can expand threads to view all replies.'],
'Index Navigation': [false, 'Navigate to previous / next thread.'],
'Check for Updates': [true, 'Check for updated versions of 4chan X Beta.']
},
Filtering: {
'Anonymize': [false, 'Turn everyone Anonymous.'],
'Filter': [true, 'Self-moderation placebo.'],
'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'],
'Thread Hiding': [true, 'Hide entire threads.'],
'Reply Hiding': [true, 'Hide single replies.'],
'Thread/Reply Hiding Buttons': [true, 'Make buttons to hide threads / replies, in addition to menu links.'],
'Stubs': [true, 'Make stubs of hidden threads / replies.']
},
Imaging: {
'Auto-GIF': [false, 'Animate GIF thumbnails.'],
'Image Expansion': [true, 'Expand images.'],
'Image Hover': [false, 'Show full image on mouseover.'],
'Sauce': [true, 'Add sauce links to images.'],
'Reveal Spoilers': [false, 'Reveal spoiler thumbnails.']
},
Menu: {
'Menu': [true, 'Add a drop-down menu in posts.'],
'Report Link': [true, 'Add a report link to the menu.'],
'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. Chrome-only currently.'],
'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.'],
'Unread Tab Icon': [true, 'Show a different favicon when there are unread posts.'],
'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'],
'Thread Stats': [true, 'Display reply and image count.'],
'Thread Watcher': [true, 'Bookmark threads.'],
'Auto Watch': [true, 'Automatically watch threads you start.'],
'Auto Watch Reply': [false, 'Automatically watch threads you reply to.']
},
Posting: {
'Quick Reply': [true, 'Weapon of mass destruction.'],
'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'],
'Auto Hide QR': [false, 'Automatically hide the quick reply when posting.'],
'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'],
'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'],
'Hide Original Post Form': [true, 'Hide the normal post form.']
},
Quoting: {
'Quote Backlinks': [true, 'Add quote backlinks.'],
'OP Backlinks': [false, 'Add backlinks to the OP.'],
'Quote Inline': [true, 'Inline quoted post on click.'],
'Forward Hiding': [true, 'Hide original posts of inlined backlinks.'],
'Quote Preview': [true, 'Show quoted post on hover.'],
'Quote Highlighting': [true, 'Highlight the previewed post.'],
'Resurrect Quotes': [true, 'Linkify dead quotes to archives.'],
'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'],
'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.']
}
},
imageExpansion: {
'Fit width': [true, null],
'Fit height': [false, null],
'Expand spoilers': [false, 'Expand all images along with spoilers.'],
'Expand from here': [true, 'Expand all images only from current position to thread end.']
},
filter: {
name: ['# Filter any namefags:', '#/^(?!Anonymous$)/'].join('\n'),
uniqueID: ['# Filter a specific ID:', '#/Txhvk1Tl/'].join('\n'),
tripcode: ['# Filter any tripfag', '#/^!/'].join('\n'),
capcode: ['# Set a custom class for mods:', '#/Mod$/;highlight:mod;op:yes', '# Set a custom class for moot:', '#/Admin$/;highlight:moot;op:yes'].join('\n'),
email: ['# Filter any e-mails that are not `sage` on /a/ and /jp/:', '#/^(?!sage$)/;boards:a,jp'].join('\n'),
subject: ['# Filter Generals on /v/:', '#/general/i;boards:v;op:only'].join('\n'),
comment: ['# Filter Stallman copypasta on /g/:', '#/what you\'re refer+ing to as linux/i;boards:g'].join('\n'),
flag: [''].join('\n'),
filename: [''].join('\n'),
dimensions: ['# Highlight potential wallpapers:', '#/1920x1080/;op:yes;highlight;top:no;boards:w,wg'].join('\n'),
filesize: [''].join('\n'),
MD5: [''].join('\n')
},
sauces: ['http://iqdb.org/?url=%turl', 'http://www.google.com/searchbyimage?image_url=%turl', '#http://tineye.com/search?url=%turl', '#http://saucenao.com/search.php?db=999&url=%turl', '#http://3d.iqdb.org/?url=%turl', '#http://regex.info/exif.cgi?imgurl=%url', '# uploaders:', '#http://imgur.com/upload?url=%url;text:Upload to imgur', '#http://omploader.org/upload?url1=%url;text:Upload to omploader', '# "View Same" in archives:', '#//archive.foolz.us/_/search/image/%md5/;text:View same on foolz', '#//archive.foolz.us/%board/search/image/%md5/;text:View same on foolz /%board/', '#//archive.installgentoo.net/%board/image/%md5;text:View same on installgentoo /%board/'].join('\n'),
time: '%m/%d/%y(%a)%H:%M:%S',
backlink: '>>%id',
fileInfo: '%l (%p%s, %r)',
favicon: 'ferongr',
hotkeys: {
'Open empty QR': ['q', 'Open QR without post number inserted.'],
'Open QR': ['Shift+q', 'Open QR with post number inserted.'],
'Open options': ['Alt+o', 'Open Options.'],
'Close': ['Esc', 'Close Settings, Notifications or QR.'],
'Spoiler tags': ['Ctrl+s', 'Insert spoiler tags.'],
'Code tags': ['Alt+c', 'Insert code tags.'],
'Math tags': ['Alt+m', 'Insert math tags.'],
'Submit QR': ['Alt+s', 'Submit post.'],
'Watch': ['w', 'Watch thread.'],
'Update': ['u', 'Update the thread now.'],
'Expand image': ['Shift+e', 'Expand selected image.'],
'Expand images': ['e', 'Expand all images.'],
'Front page': ['0', 'Jump to page 0.'],
'Open front page': ['Shift+0', 'Open page 0 in a new tab.'],
'Next page': ['Right', 'Jump to the next page.'],
'Previous page': ['Left', 'Jump to the previous page.'],
'Next thread': ['Down', 'See next thread.'],
'Previous thread': ['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.'],
'Hide': ['x', 'Hide thread.']
},
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.']
},
'Interval': 30
}
};
if (!/^(boards|images|sys)\.4chan\.org$/.test(location.hostname)) {
return;
}
Conf = {};
d = document;
doc = d.documentElement;
g = {
VERSION: '3.0.0',
NAMESPACE: "4chan X Beta.",
boards: {},
threads: {},
posts: {}
};
UI = (function() {
var Menu, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
dialog = function(id, position, html) {
var el, move;
el = d.createElement('div');
el.className = 'dialog';
el.innerHTML = html;
el.id = id;
el.style.cssText = localStorage.getItem("" + g.NAMESPACE + id + ".position") || position;
move = el.querySelector('.move');
move.addEventListener('touchstart', dragstart, false);
move.addEventListener('mousedown', dragstart, false);
return el;
};
Menu = (function() {
var close, currentMenu, lastToggledButton;
currentMenu = null;
lastToggledButton = null;
function Menu(type) {
this.type = type;
$.on(d, 'AddMenuEntry', this.addEntry.bind(this));
this.close = close.bind(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.bind(this));
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, cHeight, cWidth, entry, left, mRect, menu, prevEntry, style, top, _i, _len, _ref;
menu = this.makeMenu();
currentMenu = menu;
lastToggledButton = button;
_ref = this.entries;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
this.insertEntry(entry, menu, data);
}
entry = $('.entry', menu);
while (prevEntry = this.findNextEntry(entry, -1)) {
entry = prevEntry;
}
this.focus(entry);
$.on(d, 'click', this.close);
$.on(d, 'CloseMenu', this.close);
$.add(d.body, menu);
mRect = menu.getBoundingClientRect();
bRect = button.getBoundingClientRect();
bTop = doc.scrollTop + d.body.scrollTop + bRect.top;
bLeft = doc.scrollLeft + d.body.scrollLeft + bRect.left;
cHeight = doc.clientHeight;
cWidth = doc.clientWidth;
top = bRect.top + bRect.height + mRect.height < cHeight ? bTop + bRect.height + 2 : bTop - mRect.height - 2;
left = bRect.left + mRect.width < cWidth ? bLeft : bLeft + bRect.width - mRect.width;
style = menu.style;
style.top = top + 'px';
style.left = left + 'px';
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);
};
close = function() {
$.rm(currentMenu);
currentMenu = null;
lastToggledButton = null;
$.off(d, 'click', this.close);
return $.off(d, 'CloseMenu', this.close);
};
Menu.prototype.findNextEntry = function(entry, direction) {
var entries;
entries = Array.prototype.slice.call(entry.parentNode.children);
entries.sort(function(first, second) {
return +(first.style.order || first.style.webkitOrder) - +(second.style.order || second.style.webkitOrder);
});
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.focus = function(entry) {
var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref;
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;
if (eRect.top + sRect.height < cHeight) {
top = '0px';
bottom = 'auto';
} else {
top = 'auto';
bottom = '0px';
}
if (eRect.right + sRect.width < cWidth) {
left = '100%';
right = 'auto';
} else {
left = 'auto';
right = '100%';
}
style = submenu.style;
style.top = top;
style.bottom = bottom;
style.left = left;
return style.right = right;
};
Menu.prototype.addEntry = function(e) {
var entry;
entry = e.detail;
if (entry.type !== this.type) {
return;
}
this.parseEntry(entry);
return this.entries.push(entry);
};
Menu.prototype.parseEntry = function(entry) {
var el, style, subEntries, subEntry, _i, _len;
el = entry.el, subEntries = entry.subEntries;
$.addClass(el, 'entry');
$.on(el, 'focus mouseover', (function(e) {
e.stopPropagation();
return this.focus(el);
}).bind(this));
style = el.style;
style.webkitOrder = 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;
e.preventDefault();
el = this.parentNode;
if (isTouching = e.type === 'touchstart') {
e = e.changedTouches[e.changedTouches.length - 1];
}
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
};
if (isTouching) {
o.identifier = e.identifier;
o.move = touchmove.bind(o);
o.up = touchend.bind(o);
d.addEventListener('touchmove', o.move, false);
d.addEventListener('touchend', o.up, false);
return d.addEventListener('touchcancel', o.up, false);
} else {
o.move = drag.bind(o);
o.up = dragend.bind(o);
d.addEventListener('mousemove', o.move, false);
return d.addEventListener('mouseup', o.up, false);
}
};
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 ? 0 : this.height - top < 10 ? null : top / this.screenHeight * 100 + '%';
right = left === null ? 0 : null;
bottom = top === null ? 0 : 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) {
d.removeEventListener('touchmove', this.move, false);
d.removeEventListener('touchend', this.up, false);
d.removeEventListener('touchcancel', this.up, false);
} else {
d.removeEventListener('mousemove', this.move, false);
d.removeEventListener('mouseup', this.up, false);
}
return localStorage.setItem("" + g.NAMESPACE + this.id + ".position", this.style.cssText);
};
hoverstart = function(_arg) {
var asap, asapTest, cb, el, endEvents, event, latestEvent, o, root, _i, _len, _ref;
root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, cb = _arg.cb;
o = {
root: root,
el: el,
style: el.style,
cb: cb,
endEvents: endEvents.split(' '),
latestEvent: latestEvent,
clientHeight: doc.clientHeight,
clientWidth: doc.clientWidth
};
o.hover = hover.bind(o);
o.hoverend = hoverend.bind(o);
asap = function() {
if (asapTest()) {
return o.hover(o.latestEvent);
} else {
return o.timeout = setTimeout(asap, 25);
}
};
asap();
_ref = o.endEvents;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
event = _ref[_i];
root.addEventListener(event, o.hoverend, false);
}
return root.addEventListener('mousemove', o.hover, false);
};
hover = function(e) {
var clientX, clientY, height, left, right, style, top;
this.latestEvent = e;
height = this.el.offsetHeight;
clientX = e.clientX, clientY = e.clientY;
top = clientY - 120;
top = this.clientHeight <= height || top <= 0 ? 0 : top + height >= this.clientHeight ? this.clientHeight - height : top;
if (clientX <= this.clientWidth - 400) {
left = clientX + 45 + 'px';
right = null;
} else {
left = null;
right = this.clientWidth - clientX + 45 + 'px';
}
style = this.style;
style.top = top + 'px';
style.left = left;
return style.right = right;
};
hoverend = function() {
var event, _i, _len, _ref;
this.el.parentNode.removeChild(this.el);
_ref = this.endEvents;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
event = _ref[_i];
this.root.removeEventListener(event, this.hoverend, false);
}
this.root.removeEventListener('mousemove', this.hover, false);
clearTimeout(this.timeout);
if (this.cb) {
return this.cb.call(this);
}
};
return {
dialog: dialog,
Menu: Menu,
hover: hoverstart
};
})();
$ = function(selector, root) {
if (root == null) {
root = d.body;
}
return root.querySelector(selector);
};
$$ = function(selector, root) {
if (root == null) {
root = d.body;
}
return Array.prototype.slice.call(root.querySelectorAll(selector));
};
$.extend = function(object, properties) {
var key, val;
for (key in properties) {
val = properties[key];
object[key] = val;
}
};
$.extend($, {
SECOND: 1000,
MINUTE: 1000 * 60,
HOUR: 1000 * 60 * 60,
DAY: 1000 * 60 * 60 * 24,
log: console.log.bind(console),
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(),
id: function(id) {
return d.getElementById(id);
},
ready: function(fc) {
var cb, _ref;
if ((_ref = d.readyState) === 'interactive' || _ref === 'complete') {
$.queueTask(fc);
return;
}
cb = function() {
$.off(d, 'DOMContentLoaded', cb);
return fc();
};
return $.on(d, 'DOMContentLoaded', cb);
},
sync: function(key, cb) {
key = "" + g.NAMESPACE + key;
return $.on(window, 'storage', function(e) {
if (e.key === key) {
return cb(JSON.parse(e.newValue));
}
});
},
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) {
fd.append(key, val);
}
}
return fd;
},
ajax: function(url, callbacks, opts) {
var form, headers, key, r, type, upCallbacks, val;
if (opts == null) {
opts = {};
}
type = opts.type, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form;
r = new XMLHttpRequest();
type || (type = form && 'post' || 'get');
r.open(type, url, true);
for (key in headers) {
val = headers[key];
r.setRequestHeader(key, val);
}
$.extend(r, callbacks);
$.extend(r.upload, upCallbacks);
r.withCredentials = type === 'post';
r.send(form);
return r;
},
cache: (function() {
var reqs;
reqs = {};
return function(url, cb) {
var req;
if (req = reqs[url]) {
if (req.readyState === 4) {
cb.call(req);
} else {
req.callbacks.push(cb);
}
return;
}
req = $.ajax(url, {
onload: function() {
var _i, _len, _ref;
_ref = this.callbacks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
cb = _ref[_i];
cb.call(this);
}
return delete this.callbacks;
},
onabort: function() {
return delete reqs[url];
},
onerror: function() {
return delete reqs[url];
}
});
req.callbacks = [cb];
return reqs[url] = req;
};
})(),
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, type) {
var style;
style = type === 'style' || !window.URL ? $.el('style', {
textContent: css
}) : $.el('link', {
rel: 'stylesheet',
href: URL.createObjectURL(new Blob([css], {
type: 'text/css'
}))
});
$.asap((function() {
return d.head;
}), function() {
return $.add(d.head, style);
});
return style;
},
x: function(path, root) {
if (root == null) {
root = d.body;
}
return d.evaluate(path, root, null, 8, null).singleNodeValue;
},
addClass: function(el, className) {
return el.classList.add(className);
},
rmClass: function(el, className) {
return el.classList.remove(className);
},
hasClass: function(el, className) {
return el.classList.contains(className);
},
rm: function(el) {
return el.parentNode.removeChild(el);
},
tn: function(s) {
return d.createTextNode(s);
},
nodes: function(nodes) {
var frag, node, _i, _len;
if (!(nodes instanceof Array)) {
return nodes;
}
frag = d.createDocumentFragment();
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;
}
return root.dispatchEvent(new CustomEvent(event, {
bubbles: true,
detail: detail
}));
},
open: function(url) {
return (GM_openInTab || window.open)(url, '_blank');
},
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, script);
return $.rm(script);
},
unsafeWindow: window.opera ? window : unsafeWindow !== window ? unsafeWindow : (function() {
var p;
p = d.createElement('p');
p.setAttribute('onclick', 'return window');
return p.onclick();
})(),
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];
}
});
if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) {
$["delete"] = function(name) {
return GM_deleteValue(g.NAMESPACE + name);
};
$.get = function(name, defaultValue) {
var value;
if (value = GM_getValue(g.NAMESPACE + name)) {
return JSON.parse(value);
} else {
return defaultValue;
}
};
$.set = function(name, value) {
name = g.NAMESPACE + name;
value = JSON.stringify(value);
localStorage.setItem(name, value);
return GM_setValue(name, value);
};
} else if (window.opera) {
(function() {
var scriptStorage;
scriptStorage = opera.scriptStorage;
$["delete"] = function(name) {
return delete scriptStorage[g.NAMESPACE + name];
};
$.get = function(name, defaultValue) {
var value;
if (value = scriptStorage[g.NAMESPACE + name]) {
return JSON.parse(value);
} else {
return defaultValue;
}
};
return $.set = function(name, value) {
name = g.NAMESPACE + name;
value = JSON.stringify(value);
localStorage.setItem(name, value);
return scriptStorage[name] = value;
};
})();
} else {
$["delete"] = function(name) {
return localStorage.removeItem(g.NAMESPACE + name);
};
$.get = function(name, defaultValue) {
var value;
if (value = localStorage.getItem(g.NAMESPACE + name)) {
return JSON.parse(value);
} else {
return defaultValue;
}
};
$.set = function(name, value) {
return localStorage.setItem(g.NAMESPACE + name, JSON.stringify(value));
};
}
Polyfill = {
init: function() {
return Polyfill.visibility();
},
visibility: function() {
var event, prefix, property;
if ('visibilityState' in document) {
return;
}
if ('webkitVisibilityState' in document) {
prefix = 'webkit';
} else if ('mozVisibilityState' in document) {
prefix = 'moz';
} else {
return;
}
property = prefix + 'VisibilityState';
event = prefix + 'visibilitychange';
d.visibilityState = d[property];
d.hidden = d.visibilityState === 'hidden';
return $.on(d, event, function() {
d.visibilityState = d[property];
d.hidden = d.visibilityState === 'hidden';
return $.event('visibilitychange');
});
}
};
Header = {
init: function() {
var boardList, boardListButton, boardTitle, catalogToggler, headerBar, menuButton, toggleBar;
this.menu = new UI.Menu('header');
this.headerEl = $.el('div', {
id: 'header',
innerHTML: '<div id=header-bar class=dialog></div><div id=notifications></div>'
});
headerBar = $('#header-bar', this.headerEl);
if ($.get('autohideHeaderBar', false)) {
$.addClass(headerBar, 'autohide');
}
menuButton = $.el('a', {
className: 'menu-button',
innerHTML: '[<span></span>]',
href: 'javascript:;'
});
$.on(menuButton, 'click', this.menuToggle);
boardListButton = $.el('span', {
className: 'show-board-list-button',
innerHTML: '[<a href=javascript:;>+</a>]',
title: 'Toggle the board list.'
});
$.on(boardListButton, 'click', this.toggleBoardList);
boardTitle = $.el('a', {
className: 'board-name',
innerHTML: "<span class=board-path>/" + g.BOARD + "/</span> - <span class=board-title>...</span>",
href: "/" + g.BOARD + "/" + (g.VIEW === 'catalog' ? 'catalog' : '')
});
boardList = $.el('span', {
className: 'board-list',
hidden: true
});
toggleBar = $.el('div', {
id: 'toggle-header-bar',
title: 'Toggle the header bar position.'
});
$.on(toggleBar, 'click', this.toggleBar);
$.prepend(headerBar, [menuButton, boardListButton, $.tn(' '), boardTitle, boardList, toggleBar]);
catalogToggler = $.el('label', {
innerHTML: "<input type=checkbox " + (g.VIEW === 'catalog' ? 'checked' : '') + "> Use catalog links"
});
$.on(catalogToggler.firstElementChild, 'change', this.toggleCatalogLinks);
$.event('AddMenuEntry', {
type: 'header',
el: catalogToggler,
order: 105
});
$.asap((function() {
return d.body;
}), function() {
if ($('link[href*="favicon-status.ico"]', d.head)) {
return;
}
return $.prepend(d.body, Header.headerEl);
});
return $.asap((function() {
return $.id('boardNavDesktop');
}), this.setBoardList);
},
setBoardList: function() {
var a, nav;
if (nav = $.id('boardNavDesktop')) {
if (a = $("a[href*='/" + g.BOARD + "/']", nav)) {
a.className = 'current';
$('.board-title', Header.headerEl).textContent = a.title;
}
return $.add($('.board-list', Header.headerEl), Array.prototype.slice.call(nav.childNodes));
}
},
toggleBoardList: function() {
var headerEl, node, showBoardList;
node = this.firstElementChild.firstChild;
if (showBoardList = $.hasClass(this, 'show-board-list-button')) {
this.className = 'hide-board-list-button';
node.data = node.data.replace('+', '-');
} else {
this.className = 'show-board-list-button';
node.data = node.data.replace('-', '+');
}
headerEl = Header.headerEl;
$('.board-name', headerEl).hidden = showBoardList;
return $('.board-list', headerEl).hidden = !showBoardList;
},
toggleCatalogLinks: function() {
var a, as, root, useCatalog, _i, _len;
useCatalog = this.checked;
root = $('.board-list', Header.headerEl);
as = $$('a[href*="boards.4chan.org"]', root);
as.push($('.board-name', Header.headerEl));
for (_i = 0, _len = as.length; _i < _len; _i++) {
a = as[_i];
a.pathname = "/" + (a.pathname.split('/')[1]) + "/" + (useCatalog ? 'catalog' : '');
}
},
toggleBar: function() {
var isAutohiding, message;
message = (isAutohiding = $.id('header-bar').classList.toggle('autohide')) ? 'The header bar will automatically hide itself.' : 'The header bar will remain visible.';
new Notification('info', message, 2);
return $.set('autohideHeaderBar', isAutohiding);
},
menuToggle: function(e) {
return Header.menu.toggle(e, this, g);
}
};
Notification = (function() {
function Notification(type, content, timeout) {
var el;
this.type = type;
this.el = $.el('div', {
className: "notification " + type,
innerHTML: '<a href=javascript:; class=close title=Close>×</a><div class=message></div>'
});
$.on(this.el.firstElementChild, 'click', this.close.bind(this));
if (typeof content === 'string') {
content = $.tn(content);
}
$.add(this.el.lastElementChild, content);
if (timeout) {
setTimeout(this.close.bind(this), timeout * $.SECOND);
}
el = this.el;
$.ready(function() {
return $.add($.id('notifications'), el);
});
}
Notification.prototype.setType = function(type) {
$.rmClass(this.el, this.type);
$.addClass(this.el, type);
return this.type = type;
};
Notification.prototype.close = function() {
if (this.el.parentNode) {
return $.rm(this.el);
}
};
return Notification;
})();
Settings = {
init: function() {
var link, settings;
link = $.el('a', {
className: 'settings-link',
textContent: '4chan X Beta Settings',
href: 'javascript:;'
});
$.on(link, 'click', Settings.open);
$.event('AddMenuEntry', {
type: 'header',
el: link,
order: 110
});
link = $.el('a', {
className: 'fourchan-settings-link',
textContent: '4chan Settings',
href: 'javascript:;'
});
$.on(link, 'click', function() {
return $.id('settingsWindowLink').click();
});
$.event('AddMenuEntry', {
type: 'header',
el: link,
order: 111,
open: function() {
return Conf['Enable 4chan\'s extension'];
}
});
if (!$.get('previousversion')) {
$.set('previousversion', g.VERSION);
$.on(d, '4chanXInitFinished', Settings.open);
}
Settings.addSection('Main', Settings.main);
Settings.addSection('Filter', Settings.filter);
Settings.addSection('Sauce', Settings.sauce);
Settings.addSection('Rice', Settings.rice);
Settings.addSection('Keybinds', Settings.keybinds);
$.on(d, 'AddSettingsSection', Settings.addSection);
$.on(d, 'OpenSettings', function(e) {
return Settings.open(e.detail);
});
if (Conf['Enable 4chan\'s extension']) {
return;
}
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 html, link, links, overlay, section, sectionToOpen, _i, _len, _ref;
if (Settings.dialog) {
return;
}
$.event('CloseMenu');
html = "<div id=settings class=dialog>\n <nav>\n <div class=sections-list></div>\n <div class=credits>\n <a href='http://mayhemydg.github.com/4chan-x/' target=_blank>4chan X Beta</a> |\n <a href='https://github.com/MayhemYDG/4chan-x/blob/v3/changelog' target=_blank>" + g.VERSION + "</a> |\n <a href='https://github.com/MayhemYDG/4chan-x/issues' target=_blank>Issues</a> |\n <a href=javascript:; class=close title=Close>×</a>\n </div>\n </nav>\n <hr>\n <div class=section-container><section></section></div>\n</div>";
Settings.dialog = overlay = $.el('div', {
id: 'overlay',
innerHTML: html
});
links = [];
_ref = Settings.sections;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
section = _ref[_i];
link = $.el('a', {
textContent: section.title,
href: 'javascript:;'
});
$.on(link, 'click', Settings.openSection.bind(section));
links.push(link, $.tn(' | '));
if (section.title === openSection) {
sectionToOpen = link;
}
}
links.pop();
if (sectionToOpen) {
sectionToOpen.click();
} else {
links[0].click();
}
$.add($('.sections-list', overlay), links);
$.on($('.close', overlay), 'click', Settings.close);
$.on(overlay, 'click', Settings.close);
$.on(overlay.firstElementChild, 'click', function(e) {
return e.stopPropagation();
});
d.body.style.width = "" + d.body.clientWidth + "px";
$.addClass(d.body, 'unscroll');
return $.add(d.body, overlay);
},
close: function() {
if (!Settings.dialog) {
return;
}
d.body.style.removeProperty('width');
$.rmClass(d.body, 'unscroll');
$.rm(Settings.dialog);
return delete Settings.dialog;
},
sections: [],
addSection: function(title, open) {
var _ref;
if (typeof title !== 'string') {
_ref = title.detail, title = _ref.title, open = _ref.open;
}
return Settings.sections.push({
title: title,
open: open
});
},
openSection: function() {
var section;
section = $('section', Settings.dialog);
section.innerHTML = null;
section.className = "section-" + (this.title.toLowerCase().replace(/\s+/g, '-'));
this.open(section, g);
return section.scrollTop = 0;
},
main: function(section) {
var ID, arr, checked, description, hiddenNum, key, li, obj, post, thread, ul, _ref, _ref1, _ref2;
_ref = Config.main;
for (key in _ref) {
obj = _ref[key];
ul = $.el('ul', {
textContent: key
});
for (key in obj) {
arr = obj[key];
checked = $.get(key, Conf[key]) ? 'checked' : '';
description = arr[1];
li = $.el('li', {
innerHTML: "<label><input type=checkbox name=\"" + key + "\" " + checked + ">" + key + "</label><span class=description>: " + description + "</span>"
});
$.on($('input', li), 'click', $.cb.checked);
$.add(ul, li);
}
$.add(section, ul);
}
hiddenNum = 0;
_ref1 = ThreadHiding.getHiddenThreads().threads;
for (ID in _ref1) {
thread = _ref1[ID];
hiddenNum++;
}
_ref2 = ReplyHiding.getHiddenPosts().threads;
for (ID in _ref2) {
thread = _ref2[ID];
for (ID in thread) {
post = thread[ID];
$.log(post);
hiddenNum++;
}
}
li = $.el('li', {
innerHTML: "<button>Hidden: " + hiddenNum + "</button><span class=description>: Clear manually hidden threads and posts on /" + g.BOARD + "/."
});
$.on($('button', li), 'click', function() {
this.textContent = 'Hidden: 0';
$["delete"]("hiddenThreads." + g.BOARD);
return $["delete"]("hiddenPosts." + g.BOARD);
});
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, li);
},
filter: function(section) {
var select;
section.innerHTML = "<div class=warning " + (Conf['Sauce'] ? 'hidden' : '') + "><code>Filter</code> is disabled.</div>\n<select name=filter>\n <option value=guide>Guide</option>\n <option value=name>Name</option>\n <option value=uniqueID>Unique ID</option>\n <option value=tripcode>Tripcode</option>\n <option value=capcode>Capcode</option>\n <option value=email>E-mail</option>\n <option value=subject>Subject</option>\n <option value=comment>Comment</option>\n <option value=flag>Flag</option>\n <option value=filename>Filename</option>\n <option value=dimensions>Image dimensions</option>\n <option value=filesize>Filesize</option>\n <option value=MD5>Image MD5</option>\n</select>\n<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') {
div.innerHTML = null;
ta = $.el('textarea', {
name: name,
className: 'field',
value: $.get(name, Conf[name])
});
$.on(ta, 'change', $.cb.value);
$.add(div, ta);
return;
}
return div.innerHTML = "<p>\n Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>\n Lines starting with a <code>#</code> will be ignored.<br>\n For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>\n MD5 filtering uses exact string matching, not regular expressions.\n</p>\n<ul>You can use these settings with each regular expression, separate them with semicolons:\n <li>\n Per boards, separate them with commas. It is global if not specified.<br>\n For example: <code>boards:a,jp;</code>.\n </li>\n <li>\n Filter OPs only along with their threads (`only`), replies only (`no`, this is default), or both (`yes`).<br>\n For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.\n </li>\n <li>\n Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>\n For example: <code>stub:yes;</code> or <code>stub:no;</code>.\n </li>\n <li>\n Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>\n For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.\n </li>\n <li>\n Highlighted OPs will have their threads put on top of board pages by default.<br>\n For example: <code>top:yes;</code> or <code>top:no;</code>.\n </li>\n</ul>";
},
sauce: function(section) {
var sauce;
section.innerHTML = "<div class=warning " + (Conf['Sauce'] ? 'hidden' : '') + "><code>Sauce</code> is disabled.</div>\n<div>Lines starting with a <code>#</code> will be ignored.</div>\n<div>You can specify a display text by appending <code>;text:[text]</code> to the url.</div>\n<ul>These parameters will be replaced by their corresponding values:\n <li><code>%turl</code>: Thumbnail url.</li>\n <li><code>%url</code>: Full image url.</li>\n <li><code>%md5</code>: MD5 hash.</li>\n <li><code>%board</code>: Current board.</li>\n</ul>\n<textarea name=sauces class=field></textarea>";
sauce = $('textarea', section);
sauce.value = $.get('sauces', Conf['sauces']);
return $.on(sauce, 'change', $.cb.value);
},
rice: function(section) {
var event, input, name, _i, _len, _ref;
section.innerHTML = "<div class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + "><code>Time Formatting</code> is disabled.</div>\n<ul>Time formatting\n <li><input name=time class=field>: <span class=time-preview></span></li>\n <li>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</li>\n <li>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></li>\n <li>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></li>\n <li>Year: <code>%y</code></li>\n <li>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></li>\n <li>Minute: <code>%M</code></li>\n <li>Second: <code>%S</code></li>\n</ul>\n<div class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + "><code>Quote Backlinks</code> are disabled.</div>\n<ul>Backlink formatting\n <li><input name=backlink class=field>: <span class=backlink-preview></span></li>\n</ul>\n<div class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + "><code>File Info Formatting</code> is disabled.</div>\n<ul>File Info Formatting\n <li><input name=fileInfo class=field>: <span class='fileText file-info-preview'></span></li>\n <li>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</li>\n <li>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</li>\n <li>Spoiler indicator: <code>%p</code></li>\n <li>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</li>\n <li>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</li>\n</ul>\n<div class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + "><code>Unread Tab Icon</code> is disabled.</div>\n<div>Unread favicons</div>\n<select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n</select>\n<span class=favicon-preview></span>";
_ref = ['time', 'backlink', 'fileInfo', 'favicon'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
input = $("[name=" + name + "]", section);
input.value = $.get('name', Conf[name]);
event = input.nodeName === 'SELECT' ? 'change' : 'input';
$.on(input, event, $.cb.value);
$.on(input, event, Settings[name]);
Settings[name].call(input);
}
},
time: function() {
var funk;
funk = Time.createFunc(this.value);
return this.nextElementSibling.textContent = funk(Time, new Date());
},
backlink: function() {
return this.nextElementSibling.textContent = Conf['backlink'].replace(/%id/, '123456789');
},
fileInfo: function() {
var data, funk;
data = {
isReply: true,
file: {
URL: '//images.4chan.org/g/src/1334437723720.jpg',
name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
size: '276 KB',
sizeInBytes: 276 * 1024,
dimensions: '1280x720',
isImage: true,
isSpoiler: true
}
};
funk = FileInfo.createFunc(this.value);
return this.nextElementSibling.innerHTML = funk(FileInfo, data);
},
favicon: function() {
Favicon["switch"]();
if (g.VIEW === 'thread' && Conf['Unread Tab Icon']) {
Unread.update();
}
return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">";
},
keybinds: function(section) {
var arr, input, key, tbody, tr, _ref, _results;
section.innerHTML = "<div class=warning " + (Conf['Keybinds'] ? 'hidden' : '') + "><code>Keybinds</code> are disabled.</div>\n<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>\n<div>Press <kbd>Return</kbd> to disable a keybind.</div>\n<table><tbody>\n <tr><th>Actions</th><th>Keybinds</th></tr>\n</tbody></table>";
tbody = $('tbody', section);
_ref = Config.hotkeys;
_results = [];
for (key in _ref) {
arr = _ref[key];
tr = $.el('tr', {
innerHTML: "<td>" + arr[1] + "</td><td><input class=field></td>"
});
input = $('input', tr);
input.name = key;
input.value = $.get(key, Conf[key]);
$.on(input, 'keydown', Settings.keybind);
_results.push($.add(tbody, tr));
}
return _results;
},
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);
}
};
Fourchan = {
init: function() {
var board;
if (g.VIEW === 'catalog') {
return;
}
board = g.BOARD.ID;
if (board === 'g') {
Post.prototype.callbacks.push({
name: 'Parse /g/ code',
cb: this.code
});
}
if (board === 'sci') {
return Post.prototype.callbacks.push({
name: 'Parse /sci/ math',
cb: this.math
});
}
},
code: function() {
var pre, _i, _len, _ref;
if (this.isClone) {
return;
}
_ref = $$('.prettyprint', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
pre = _ref[_i];
pre.innerHTML = $.unsafeWindow.prettyPrintOne(pre.innerHTML);
}
},
math: function() {
var jsMath;
if (this.isClone || !$('.math', this.nodes.comment)) {
return;
}
jsMath = $.unsafeWindow.jsMath;
if (jsMath) {
if (jsMath.loaded) {
return jsMath.ProcessBeforeShowing(this.nodes.post);
} else {
return $.globalEval("jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\njsMath.Autoload.LoadJsMath();");
}
}
},
parseThread: function(threadID, offset, limit) {
return $.event('4chanParsingDone', {
threadId: threadID,
offset: offset,
limit: limit
});
}
};
Filter = {
filters: {},
init: function() {
var boards, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (g.VIEW === 'catalog' || !Conf['Filter']) {
return;
}
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 (err) {
new Notification('warning', err.message, 60);
continue;
}
}
op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1] : void 0) || 'no';
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.prototype.callbacks.push({
name: 'Thread Hiding',
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, firstThread, key, result, thisThread, value, _i, _len, _ref;
if (this.isClone) {
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) {
ReplyHiding.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 && g.VIEW === 'index') {
thisThread = this.nodes.root.parentNode;
if (firstThread = $('div[class="postContainer opContainer"]')) {
if (firstThread !== this.nodes.root) {
$.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]);
}
}
}
}
}
},
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;
},
email: function(post) {
if ('email' in post.info) {
return post.info.email;
}
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) {
if (post.file && post.file.isImage) {
return post.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 = {
type: 'post',
el: div,
order: 50,
open: function(post) {
Filter.menu.post = post;
return true;
},
subEntries: []
};
_ref = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['E-mail', 'email'], ['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 $.event('AddMenuEntry', entry);
},
createSubEntry: function(text, type) {
var el;
el = $.el('a', {
href: 'javascript:;',
textContent: text
});
el.setAttribute('data-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, save, section, select, ta, tl, 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 + "$/";
if (!Filter.menu.post.isReply) {
re += ';op:yes';
}
save = $.get(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();
}
}
};
ThreadHiding = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Hiding']) {
return;
}
this.getHiddenThreads();
this.syncFromCatalog();
this.clean();
return Thread.prototype.callbacks.push({
name: 'Thread Hiding',
cb: this.node
});
},
node: function() {
var data;
if (data = ThreadHiding.hiddenThreads.threads[this]) {
ThreadHiding.hide(this, data.makeStub);
}
if (!Conf['Thread/Reply Hiding Buttons']) {
return;
}
return $.prepend(this.posts[this].nodes.root, ThreadHiding.makeButton(this, 'hide'));
},
getHiddenThreads: function() {
var hiddenThreads;
hiddenThreads = $.get("hiddenThreads." + g.BOARD);
if (!hiddenThreads) {
hiddenThreads = {
threads: {},
lastChecked: Date.now()
};
$.set("hiddenThreads." + g.BOARD, hiddenThreads);
}
return ThreadHiding.hiddenThreads = hiddenThreads;
},
syncFromCatalog: function() {
var hiddenThreadsOnCatalog, threadID, threads;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
threads = ThreadHiding.hiddenThreads.threads;
for (threadID in hiddenThreadsOnCatalog) {
if (threadID in threads) {
continue;
}
threads[threadID] = {};
}
for (threadID in threads) {
if (threadID in threads) {
continue;
}
delete threads[threadID];
}
return $.set("hiddenThreads." + g.BOARD, ThreadHiding.hiddenThreads);
},
clean: function() {
var hiddenThreads, lastChecked, now;
hiddenThreads = ThreadHiding.hiddenThreads;
lastChecked = hiddenThreads.lastChecked;
hiddenThreads.lastChecked = now = Date.now();
if (lastChecked > now - $.DAY) {
return;
}
if (!Object.keys(hiddenThreads.threads).length) {
$.set("hiddenThreads." + g.BOARD, hiddenThreads);
return;
}
return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", {
onload: function() {
var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
threads = {};
_ref = JSON.parse(this.response);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
obj = _ref[_i];
_ref1 = obj.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no in hiddenThreads.threads) {
threads[thread.no] = hiddenThreads.threads[thread.no];
}
}
}
hiddenThreads.threads = threads;
return $.set("hiddenThreads." + g.BOARD, hiddenThreads);
}
});
},
menu: {
init: function() {
var apply, div, makeStub;
if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding']) {
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 = $.el('label', {
innerHTML: "<input type=checkbox checked=" + Conf['Stubs'] + "> Make stub"
});
return $.event('AddMenuEntry', {
type: 'post',
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
}
]
});
},
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');
}
},
makeButton: function(thread, type) {
var a;
a = $.el('a', {
className: "" + type + "-thread-button",
innerHTML: "<span>[&nbsp;" + (type === 'hide' ? '-' : '+') + "&nbsp;]</span>",
href: 'javascript:;'
});
$.on(a, 'click', function() {
return ThreadHiding.toggle(thread);
});
return a;
},
saveHiddenState: function(thread, makeStub) {
var hiddenThreads, hiddenThreadsCatalog;
hiddenThreads = ThreadHiding.getHiddenThreads();
hiddenThreadsCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) {
hiddenThreads.threads[thread] = {
makeStub: makeStub
};
hiddenThreadsCatalog[thread] = true;
} else {
delete hiddenThreads.threads[thread];
delete hiddenThreadsCatalog[thread];
}
$.set("hiddenThreads." + g.BOARD, hiddenThreads);
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsCatalog));
},
toggle: function(thread) {
if (thread.isHidden) {
ThreadHiding.show(thread);
} else {
ThreadHiding.hide(thread);
}
return ThreadHiding.saveHiddenState(thread);
},
hide: function(thread, makeStub) {
var a, numReplies, op, opInfo, span, threadRoot;
if (makeStub == null) {
makeStub = Conf['Stubs'];
}
if (thread.hidden) {
return;
}
op = thread.posts[thread];
threadRoot = op.nodes.root.parentNode;
threadRoot.hidden = thread.isHidden = true;
if (!makeStub) {
threadRoot.nextElementSibling.hidden = true;
return;
}
numReplies = 0;
if (span = $('.summary', threadRoot)) {
numReplies = +span.textContent.match(/\d+/);
}
numReplies += $$('.opContainer ~ .replyContainer', threadRoot).length;
numReplies = numReplies === 1 ? '1 reply' : "" + numReplies + " replies";
opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', op.nodes.info).textContent;
a = ThreadHiding.makeButton(thread, 'show');
$.add(a, $.tn(" " + opInfo + " (" + numReplies + ")"));
thread.stub = $.el('div', {
className: 'stub'
});
$.add(thread.stub, a);
if (Conf['Menu']) {
$.add(thread.stub, [$.tn(' '), Menu.makeButton(op)]);
}
return $.before(threadRoot, thread.stub);
},
show: function(thread) {
var threadRoot;
if (thread.stub) {
$.rm(thread.stub);
delete thread.stub;
}
threadRoot = thread.posts[thread].nodes.root.parentNode;
return threadRoot.nextElementSibling.hidden = threadRoot.hidden = thread.isHidden = false;
}
};
ReplyHiding = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Reply Hiding']) {
return;
}
this.getHiddenPosts();
this.clean();
return Post.prototype.callbacks.push({
name: 'Reply Hiding',
cb: this.node
});
},
node: function() {
var data, thread;
if (!this.isReply || this.isClone) {
return;
}
if (thread = ReplyHiding.hiddenPosts.threads[this.thread]) {
if (data = thread[this]) {
if (data.thisPost) {
ReplyHiding.hide(this, data.makeStub, data.hideRecursively);
} else {
Recursive.hide(this, data.makeStub);
}
}
}
if (!Conf['Thread/Reply Hiding Buttons']) {
return;
}
return $.replace($('.sideArrows', this.nodes.root), ReplyHiding.makeButton(this, 'hide'));
},
getHiddenPosts: function() {
var hiddenPosts;
hiddenPosts = $.get("hiddenPosts." + g.BOARD);
if (!hiddenPosts) {
hiddenPosts = {
threads: {},
lastChecked: Date.now()
};
$.set("hiddenPosts." + g.BOARD, hiddenPosts);
}
return ReplyHiding.hiddenPosts = hiddenPosts;
},
clean: function() {
var hiddenPosts, lastChecked, now;
hiddenPosts = ReplyHiding.hiddenPosts;
lastChecked = hiddenPosts.lastChecked;
hiddenPosts.lastChecked = now = Date.now();
if (lastChecked > now - $.DAY) {
return;
}
if (!Object.keys(hiddenPosts.threads).length) {
$.set("hiddenPosts." + g.BOARD, hiddenPosts);
return;
}
return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", {
onload: function() {
var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
threads = {};
_ref = JSON.parse(this.response);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
obj = _ref[_i];
_ref1 = obj.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no in hiddenPosts.threads) {
threads[thread.no] = hiddenPosts.threads[thread.no];
}
}
}
hiddenPosts.threads = threads;
return $.set("hiddenPosts." + g.BOARD, hiddenPosts);
}
});
},
menu: {
init: function() {
var apply, div, makeStub, replies, thisPost;
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding']) {
return;
}
div = $.el('div', {
className: 'hide-reply-link',
textContent: 'Hide reply'
});
apply = $.el('a', {
textContent: 'Apply',
href: 'javascript:;'
});
$.on(apply, 'click', ReplyHiding.menu.hide);
thisPost = $.el('label', {
innerHTML: '<input type=checkbox name=thisPost checked=true> This post'
});
replies = $.el('label', {
innerHTML: "<input type=checkbox name=replies checked=" + Conf['Recursive Hiding'] + "> Hide replies"
});
makeStub = $.el('label', {
innerHTML: "<input type=checkbox name=makeStub checked=" + Conf['Stubs'] + "> Make stub"
});
return $.event('AddMenuEntry', {
type: 'post',
el: div,
order: 20,
open: function(post) {
if (!post.isReply || post.isClone) {
return false;
}
ReplyHiding.menu.post = post;
return true;
},
subEntries: [
{
el: apply
}, {
el: thisPost
}, {
el: replies
}, {
el: makeStub
}
]
});
},
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 = ReplyHiding.menu.post;
if (thisPost) {
ReplyHiding.hide(post, makeStub, replies);
} else if (replies) {
Recursive.hide(post, makeStub);
} else {
return;
}
ReplyHiding.saveHiddenState(post, true, thisPost, makeStub, replies);
return $.event('CloseMenu');
}
},
makeButton: function(post, type) {
var a;
a = $.el('a', {
className: "" + type + "-reply-button",
innerHTML: "<span>[&nbsp;" + (type === 'hide' ? '-' : '+') + "&nbsp;]</span>",
href: 'javascript:;'
});
$.on(a, 'click', function() {
return ReplyHiding.toggle(post);
});
return a;
},
saveHiddenState: function(post, isHiding, thisPost, makeStub, hideRecursively) {
var hiddenPosts, thread;
hiddenPosts = ReplyHiding.getHiddenPosts();
if (isHiding) {
if (!(thread = hiddenPosts.threads[post.thread])) {
thread = hiddenPosts.threads[post.thread] = {};
}
thread[post] = {
thisPost: thisPost !== false,
makeStub: makeStub,
hideRecursively: hideRecursively
};
} else {
thread = hiddenPosts.threads[post.thread];
delete thread[post];
if (!Object.keys(thread).length) {
delete hiddenPosts.threads[post.thread];
}
}
return $.set("hiddenPosts." + g.BOARD, hiddenPosts);
},
toggle: function(post) {
if (post.isHidden) {
ReplyHiding.show(post);
} else {
ReplyHiding.hide(post);
}
return ReplyHiding.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.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 = ReplyHiding.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, [$.tn(' '), Menu.makeButton(post)]);
}
return $.prepend(post.nodes.root, post.nodes.stub);
},
show: function(post) {
var quotelink, _i, _len, _ref;
if (post.nodes.stub) {
$.rm(post.nodes.stub);
delete post.nodes.stub;
} else {
post.nodes.root.hidden = false;
}
post.isHidden = false;
_ref = Get.allQuotelinksLinkingTo(post);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quotelink = _ref[_i];
$.rmClass(quotelink, 'filtered');
}
}
};
Recursive = {
toHide: [],
init: function() {
return Post.prototype.callbacks.push({
name: 'Recursive',
cb: this.node
});
},
node: function() {
var board, postID, quote, quotelink, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
if (this.isClone) {
return;
}
_ref = this.quotes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i];
if (__indexOf.call(Recursive.toHide, quote) >= 0) {
ReplyHiding.hide(this, !!g.posts[quote].nodes.stub, true);
}
}
_ref1 = this.nodes.quotelinks;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
quotelink = _ref1[_j];
_ref2 = Get.postDataFromLink(quotelink), board = _ref2.board, postID = _ref2.postID;
if ((_ref3 = g.posts["" + board + "." + postID]) != null ? _ref3.isHidden : void 0) {
$.addClass(quotelink, 'filtered');
}
}
},
hide: function(post, makeStub) {
var ID, fullID, quote, _i, _len, _ref, _ref1;
fullID = post.fullID;
Recursive.toHide.push(fullID);
_ref = g.posts;
for (ID in _ref) {
post = _ref[ID];
if (!post.isReply) {
continue;
}
_ref1 = post.quotes;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
quote = _ref1[_i];
if (quote === fullID) {
ReplyHiding.hide(post, makeStub, true);
break;
}
}
}
}
};
Menu = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Menu']) {
return;
}
this.menu = new UI.Menu('post');
return Post.prototype.callbacks.push({
name: 'Menu',
cb: this.node
});
},
node: function() {
var button;
button = Menu.makeButton(this);
if (this.isClone) {
$.replace($('.menu-button', this.nodes.info), button);
return;
}
return $.add(this.nodes.info, [$.tn('\u00A0'), button]);
},
makeButton: function(post) {
var a;
a = $.el('a', {
className: 'menu-button',
innerHTML: '[<span></span>]',
href: 'javascript:;'
});
a.setAttribute('data-postid', post.fullID);
if (post.isClone) {
a.setAttribute('data-clone', true);
}
$.on(a, 'click', Menu.toggle);
return a;
},
toggle: function(e) {
var post;
post = this.dataset.clone ? Get.postFromNode(this) : g.posts[this.dataset.postid];
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 $.event('AddMenuEntry', {
type: 'post',
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);
}
};
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;
fileEl.textContent = 'File';
$.on(fileEl, 'click', DeleteLink["delete"]);
return !!file;
}
};
$.event('AddMenuEntry', {
type: 'post',
el: div,
order: 40,
open: function(post) {
var node, seconds;
if (post.isDead) {
return false;
}
DeleteLink.post = post;
node = div.firstChild;
if (seconds = DeleteLink.cooldown[post.fullID]) {
node.textContent = "Delete (" + seconds + ")";
DeleteLink.cooldown.el = node;
} else {
node.textContent = 'Delete';
delete DeleteLink.cooldown.el;
}
return true;
},
subEntries: [postEntry, fileEntry]
});
return $.on(d, 'QRPostSuccessful', this.cooldown.start);
},
"delete": function() {
var form, link, m, post, pwd;
post = DeleteLink.post;
if (DeleteLink.cooldown[post.fullID]) {
return;
}
$.off(this, 'click', DeleteLink["delete"]);
this.textContent = "Deleting " + this.textContent + "...";
pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $.id('delPassword').value;
form = {
mode: 'usrdel',
onlyimgdel: $.hasClass(this, 'delete-file'),
pwd: pwd
};
form[post.ID] = 'delete';
link = this;
return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), {
onload: function() {
return DeleteLink.load(link, this.response);
},
onerror: function() {
return DeleteLink.error(link);
}
}, {
form: $.formData(form)
});
},
load: function(link, html) {
var msg, s, tmpDoc;
tmpDoc = d.implementation.createHTMLDocument('');
tmpDoc.documentElement.innerHTML = html;
if (tmpDoc.title === '4chan - Banned') {
s = 'Banned!';
} else if (msg = tmpDoc.getElementById('errmsg')) {
s = msg.textContent;
$.on(link, 'click', DeleteLink["delete"]);
} else {
s = 'Deleted';
}
return link.textContent = s;
},
error: function(link) {
link.textContent = 'Connection error, please retry.';
return $.on(link, 'click', DeleteLink["delete"]);
},
cooldown: {
start: function(e) {
var fullID, seconds;
seconds = g.BOARD.ID === 'q' ? 600 : 30;
fullID = "" + g.BOARD + "." + e.detail.postID;
return DeleteLink.cooldown.count(fullID, seconds, seconds);
},
count: function(fullID, seconds, length) {
var el;
if (!((0 <= seconds && seconds <= length))) {
return;
}
setTimeout(DeleteLink.cooldown.count, 1000, fullID, seconds - 1, length);
el = DeleteLink.cooldown.el;
if (seconds === 0) {
if (el != null) {
el.textContent = 'Delete';
}
delete DeleteLink.cooldown[fullID];
delete DeleteLink.cooldown.el;
return;
}
if (el != null) {
el.textContent = "Delete (" + seconds + ")";
}
return DeleteLink.cooldown[fullID] = seconds;
}
}
};
DownloadLink = {
init: function() {
var a;
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Download Link']) {
return;
}
if ($.el('a').download === void 0) {
return;
}
a = $.el('a', {
className: 'download-link',
textContent: 'Download file'
});
return $.event('AddMenuEntry', {
type: 'post',
el: a,
order: 70,
open: function(_arg) {
var file;
file = _arg.file;
if (!file) {
return false;
}
a.href = file.URL;
a.download = file.name;
return true;
}
});
}
};
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 = {
type: 'post',
el: div,
order: 90,
open: function(_arg) {
var board, postID, redirect, threadID;
postID = _arg.ID, threadID = _arg.thread, board = _arg.board;
redirect = Redirect.to({
postID: postID,
threadID: threadID,
board: board
});
return redirect !== ("//boards.4chan.org/" + board + "/");
},
subEntries: []
};
_ref = [['Post', 'post'], ['Name', 'name'], ['Tripcode', 'tripcode'], ['E-mail', 'email'], ['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 $.event('AddMenuEntry', entry);
},
createSubEntry: function(text, type) {
var el, open;
el = $.el('a', {
textContent: text,
target: '_blank'
});
if (type === 'post') {
open = function(_arg) {
var board, postID, threadID;
postID = _arg.ID, threadID = _arg.thread, board = _arg.board;
el.href = Redirect.to({
postID: postID,
threadID: threadID,
board: board
});
return true;
};
} else {
open = function(post) {
var value;
value = Filter[type](post);
if (!value) {
return false;
}
el.href = Redirect.to({
board: post.board,
type: type,
value: value,
isSearch: true
});
return true;
};
}
return {
el: el,
open: open
};
}
};
Keybinds = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Keybinds']) {
return;
}
$.on(d, 'keydown', Keybinds.keydown);
return $.on(d, '4chanXInitFinished', function() {
var node, _i, _len, _ref, _results;
_ref = $$('[accesskey]');
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
_results.push(node.removeAttribute('accesskey'));
}
return _results;
});
},
keydown: function(e) {
var form, key, notification, notifications, target, thread, threadRoot, _i, _len, _ref;
if (!(key = Keybinds.keyCode(e))) {
return;
}
target = e.target;
if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') {
if (!((key === 'Esc') || (/(Alt|Ctrl|Meta)\+/.test(key)))) {
return;
}
}
threadRoot = Nav.getThread();
thread = Get.postFromNode($('.op', threadRoot)).thread;
switch (key) {
case Conf['Open empty QR']:
Keybinds.qr(threadRoot);
break;
case Conf['Open QR']:
Keybinds.qr(threadRoot, true);
break;
case Conf['Open options']:
Settings.open();
break;
case Conf['Close']:
if ($.id('settings')) {
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.el) {
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['Math tags']:
if (target.nodeName !== 'TEXTAREA') {
return;
}
Keybinds.tags('math', target);
break;
case Conf['Submit QR']:
if (QR.el && !QR.status()) {
QR.submit();
}
break;
case Conf['Watch']:
ThreadWatcher.toggle(thread);
break;
case Conf['Update']:
ThreadUpdater.update();
break;
case Conf['Expand image']:
Keybinds.img(threadRoot);
break;
case Conf['Expand images']:
Keybinds.img(threadRoot, true);
break;
case Conf['Front page']:
window.location = "/" + g.BOARD + "/0#delform";
break;
case Conf['Open front page']:
$.open("/" + g.BOARD + "/#delform");
break;
case Conf['Next page']:
if (form = $('.next form')) {
window.location = form.action;
}
break;
case Conf['Previous page']:
if (form = $('.prev form')) {
window.location = form.action;
}
break;
case Conf['Next thread']:
if (g.VIEW === 'thread') {
return;
}
Nav.scroll(+1);
break;
case Conf['Previous thread']:
if (g.VIEW === 'thread') {
return;
}
Nav.scroll(-1);
break;
case Conf['Expand thread']:
ExpandThread.toggle(thread);
break;
case Conf['Open thread']:
Keybinds.open(thread);
break;
case Conf['Open thread tab']:
Keybinds.open(thread, true);
break;
case Conf['Next reply']:
Keybinds.hl(+1, threadRoot);
break;
case Conf['Previous reply']:
Keybinds.hl(-1, threadRoot);
break;
case Conf['Hide']:
ThreadHiding.toggle(thread);
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, quote) {
if (!Conf['Quick Reply']) {
return;
}
QR.open();
if (quote) {
QR.quote.call($('input', $('.post.highlight', thread) || thread));
}
return $('textarea', QR.el).focus();
},
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);
},
img: function(thread, all) {
var input, post;
if (all) {
input = ImageExpand.expandAllInput;
input.checked = !input.checked;
return ImageExpand.cb.all.call(input);
} 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 = "//boards.4chan.org/" + thread.board + "/res/" + thread;
if (tab) {
return $.open(url);
} else {
return location.href = url;
}
},
hl: function(delta, thread) {
var headRect, next, postEl, rect, replies, reply, root, topMargin, _i, _len;
headRect = $.id('header-bar').getBoundingClientRect();
topMargin = headRect.top + headRect.height;
if (postEl = $('.reply.highlight', thread)) {
$.rmClass(postEl, 'highlight');
rect = postEl.getBoundingClientRect();
if (rect.bottom >= topMargin && rect.top <= d.documentElement.clientHeight) {
root = postEl.parentNode;
next = $.x('child::div[contains(@class,"post reply")]', delta === +1 ? root.nextElementSibling : root.previousElementSibling);
if (!next) {
this.focus(postEl);
return;
}
if (!(g.VIEW === 'thread' || $.x('ancestor::div[parent::div[@class="board"]]', next) === thread)) {
return;
}
rect = next.getBoundingClientRect();
if (rect.top < 0 || rect.bottom > d.documentElement.clientHeight) {
if (delta === -1) {
window.scrollBy(0, rect.top - topMargin);
} else {
next.scrollIntoView(false);
}
}
this.focus(next);
return;
}
}
replies = $$('.reply', thread);
if (delta === -1) {
replies.reverse();
}
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
rect = reply.getBoundingClientRect();
if (delta === +1 && rect.top >= topMargin || delta === -1 && rect.bottom <= d.documentElement.clientHeight) {
this.focus(reply);
return;
}
}
},
focus: function(post) {
$.addClass(post, 'highlight');
return $('a[title="Highlight this post"]', post).focus();
}
};
Nav = {
init: function() {
var next, prev, span;
if (g.VIEW !== 'index' || !Conf['Index Navigation']) {
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]);
return $.on(d, '4chanXInitFinished', function() {
return $.add(d.body, span);
});
},
prev: function() {
return Nav.scroll(-1);
},
next: function() {
return Nav.scroll(+1);
},
getThread: function(full) {
var headRect, i, rect, thread, threads, topMargin, _i, _len;
headRect = $.id('header-bar').getBoundingClientRect();
topMargin = headRect.top + headRect.height;
threads = $$('.thread:not([hidden])');
for (i = _i = 0, _len = threads.length; _i < _len; i = ++_i) {
thread = threads[i];
rect = thread.getBoundingClientRect();
if (rect.bottom > topMargin) {
if (full) {
return [threads, thread, i, rect, topMargin];
} else {
return thread;
}
}
}
return $('.board');
},
scroll: function(delta) {
var i, rect, thread, threads, top, topMargin, _ref, _ref1;
_ref = Nav.getThread(true), threads = _ref[0], thread = _ref[1], i = _ref[2], rect = _ref[3], topMargin = _ref[4];
top = rect.top - topMargin;
if (!((delta === -1 && Math.ceil(top) < 0) || (delta === +1 && top > 1))) {
i += delta;
}
top = ((_ref1 = threads[i]) != null ? _ref1.getBoundingClientRect().top : void 0) - topMargin;
return window.scrollBy(0, top);
}
};
Redirect = {
image: function(board, filename) {
switch ("" + board) {
case 'a':
case 'co':
case 'jp':
case 'm':
case 'q':
case 'sp':
case 'tg':
case 'tv':
case 'v':
case 'vg':
case 'wsg':
return "//archive.foolz.us/" + board + "/full_image/" + filename;
case 'u':
return "//nsfw.foolz.us/" + board + "/full_image/" + filename;
case 'po':
return "//archive.thedarkcave.org/" + board + "/full_image/" + filename;
case 'ck':
case 'lit':
return "//fuuka.warosu.org/" + board + "/full_image/" + filename;
case 'diy':
case 'sci':
return "//archive.installgentoo.net/" + board + "/full_image/" + filename;
case 'cgl':
case 'g':
case 'mu':
case 'w':
return "//rbt.asia/" + board + "/full_image/" + filename;
case 'an':
case 'fit':
case 'k':
case 'mlp':
case 'r9k':
case 'toy':
case 'x':
return "http://archive.heinessen.com/" + board + "/full_image/" + filename;
case 'c':
return "//archive.nyafuu.org/" + board + "/full_image/" + filename;
}
},
post: function(board, postID) {
switch ("" + board) {
case 'a':
case 'co':
case 'jp':
case 'm':
case 'q':
case 'sp':
case 'tg':
case 'tv':
case 'v':
case 'vg':
case 'wsg':
return "//archive.foolz.us/_/api/chan/post/?board=" + board + "&num=" + postID;
case 'u':
return "//nsfw.foolz.us/_/api/chan/post/?board=" + board + "&num=" + postID;
case 'c':
case 'int':
case 'po':
return "//archive.thedarkcave.org/_/api/chan/post/?board=" + board + "&num=" + postID;
}
},
to: function(data) {
var board, url;
board = data.board;
switch ("" + board) {
case 'a':
case 'co':
case 'jp':
case 'm':
case 'q':
case 'sp':
case 'tg':
case 'tv':
case 'v':
case 'vg':
case 'wsg':
case 'dev':
case 'foolz':
url = Redirect.path('//archive.foolz.us', 'foolfuuka', data);
break;
case 'u':
case 'kuku':
url = Redirect.path('//nsfw.foolz.us', 'foolfuuka', data);
break;
case 'int':
case 'po':
url = Redirect.path('//archive.thedarkcave.org', 'foolfuuka', data);
break;
case 'ck':
case 'lit':
url = Redirect.path('//fuuka.warosu.org', 'fuuka', data);
break;
case 'diy':
case 'sci':
url = Redirect.path('//archive.installgentoo.net', 'fuuka', data);
break;
case 'cgl':
case 'g':
case 'mu':
case 'w':
url = Redirect.path('//rbt.asia', 'fuuka', data);
break;
case 'an':
case 'fit':
case 'k':
case 'mlp':
case 'r9k':
case 'toy':
case 'x':
url = Redirect.path('http://archive.heinessen.com', 'fuuka', data);
break;
case 'c':
url = Redirect.path('//archive.nyafuu.org', 'fuuka', data);
break;
default:
if (data.threadID) {
url = "//boards.4chan.org/" + board + "/";
}
}
return url || '';
},
path: function(base, archiver, data) {
var board, path, postID, threadID, type, value;
if (data.isSearch) {
board = data.board, type = data.type, value = data.value;
type = type === 'name' ? 'username' : type === 'MD5' ? 'image' : type;
value = encodeURIComponent(value);
if (archiver === 'foolfuuka') {
return "" + base + "/" + board + "/search/" + type + "/" + value;
} else if (type === 'image') {
return "" + base + "/" + board + "/?task=search2&search_media_hash=" + value;
} else {
return "" + base + "/" + board + "/?task=search2&search_" + type + "=" + value;
}
}
board = data.board, threadID = data.threadID, postID = data.postID;
if (postID && typeof postID === 'string') {
postID = postID.match(/\d+/)[0];
}
path = threadID ? "" + board + "/thread/" + threadID : "" + board + "/post/" + postID;
if (archiver === 'foolfuuka') {
path += '/';
}
if (threadID && postID) {
path += archiver === 'foolfuuka' ? "#" + postID : "#p" + postID;
}
return "" + base + "/" + path;
}
};
Build = {
spoilerRange: {},
shortFilename: function(filename, isReply) {
var threshold;
threshold = isReply ? 30 : 40;
if (filename.length - 4 > threshold) {
return "" + filename.slice(0, threshold - 5) + "(...)." + filename.slice(-3);
} else {
return filename;
}
},
postFromObject: function(data, board) {
var o;
o = {
postID: data.no,
threadID: data.resto || data.no,
board: board,
name: data.name,
capcode: data.capcode,
tripcode: data.trip,
uniqueID: data.id,
email: data.email ? encodeURI(data.email.replace(/&quot;/g, '"')) : '',
subject: data.sub,
flagCode: data.country,
flagName: data.country_name,
date: data.now,
dateUTC: data.time,
comment: data.com,
isSticky: !!data.sticky,
isClosed: !!data.closed
};
if (data.ext || data.filedeleted) {
o.file = {
name: data.filename + data.ext,
timestamp: "" + data.tim + data.ext,
url: "//images.4chan.org/" + board + "/src/" + data.tim + data.ext,
height: data.h,
width: data.w,
MD5: data.md5,
size: data.fsize,
turl: "//thumbs.4chan.org/" + board + "/thumb/" + data.tim + "s.jpg",
theight: data.tn_h,
twidth: data.tn_w,
isSpoiler: !!data.spoiler,
isDeleted: !!data.filedeleted
};
}
return Build.post(o);
},
post: function(o, isArchived) {
/*
This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS).
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*/
var a, board, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
postID = o.postID, threadID = o.threadID, board = o.board, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file;
isOP = postID === threadID;
staticPath = '//static.4chan.org';
if (email) {
emailStart = '<a href="mailto:' + email + '" class="useremail">';
emailEnd = '</a>';
} else {
emailStart = '';
emailEnd = '';
}
subject = "<span class=subject>" + (subject || '') + "</span>";
userID = !capcode && uniqueID ? (" <span class='posteruid id_" + uniqueID + "'>(ID: ") + ("<span class=hand title='Highlight posts by this ID'>" + uniqueID + "</span>)</span> ") : '';
switch (capcode) {
case 'admin':
case 'admin_highlight':
capcodeClass = " capcodeAdmin";
capcodeStart = " <strong class='capcode hand id_admin'" + "title='Highlight posts by the Administrator'>## Admin</strong>";
capcode = (" <img src='" + staticPath + "/image/adminicon.gif' ") + "alt='This user is the 4chan Administrator.' " + "title='This user is the 4chan Administrator.' class=identityIcon>";
break;
case 'mod':
capcodeClass = " capcodeMod";
capcodeStart = " <strong class='capcode hand id_mod' " + "title='Highlight posts by Moderators'>## Mod</strong>";
capcode = (" <img src='" + staticPath + "/image/modicon.gif' ") + "alt='This user is a 4chan Moderator.' " + "title='This user is a 4chan Moderator.' class=identityIcon>";
break;
case 'developer':
capcodeClass = " capcodeDeveloper";
capcodeStart = " <strong class='capcode hand id_developer' " + "title='Highlight posts by Developers'>## Developer</strong>";
capcode = (" <img src='" + staticPath + "/image/developericon.gif' ") + "alt='This user is a 4chan Developer.' " + "title='This user is a 4chan Developer.' class=identityIcon>";
break;
default:
capcodeClass = '';
capcodeStart = '';
capcode = '';
}
flag = flagCode ? (" <img src='" + staticPath + "/image/country/" + (board === 'pol' ? 'troll/' : '')) + flagCode.toLowerCase() + (".gif' alt=" + flagCode + " title='" + flagName + "' class=countryFlag>") : '';
if (file != null ? file.isDeleted : void 0) {
fileHTML = isOP ? ("<div id=f" + postID + " class=file><div class=fileInfo></div><span class=fileThumb>") + ("<img src='" + staticPath + "/image/filedeleted.gif' alt='File deleted.' class='fileDeleted retina'>") + "</span></div>" : ("<div id=f" + postID + " class=file><span class=fileThumb>") + ("<img src='" + staticPath + "/image/filedeleted-res.gif' alt='File deleted.' class='fileDeletedRes retina'>") + "</span></div>";
} else if (file) {
ext = file.name.slice(-3);
if (!file.twidth && !file.theight && ext === 'gif') {
file.twidth = file.width;
file.theight = file.height;
}
fileSize = $.bytesToString(file.size);
fileThumb = file.turl;
if (file.isSpoiler) {
fileSize = "Spoiler Image, " + fileSize;
if (!isArchived) {
fileThumb = '//static.4chan.org/image/spoiler';
if (spoilerRange = Build.spoilerRange[board]) {
fileThumb += ("-" + board) + Math.floor(1 + spoilerRange * Math.random());
}
fileThumb += '.png';
file.twidth = file.theight = 100;
}
}
if (board !== 'f') {
imgSrc = ("<a class='fileThumb" + (file.isSpoiler ? ' imgspoiler' : '') + "' href='" + file.url + "' target=_blank>") + ("<img src='" + fileThumb + "' alt='" + fileSize + "' data-md5=" + file.MD5 + " style='height: " + file.theight + "px; width: " + file.twidth + "px;'></a>");
}
a = $.el('a', {
innerHTML: file.name
});
filename = a.textContent.replace(/%22/g, '"');
a.textContent = Build.shortFilename(filename);
shortFilename = a.innerHTML;
a.textContent = filename;
filename = a.innerHTML.replace(/'/g, '&apos;');
fileDims = ext === 'pdf' ? 'PDF' : "" + file.width + "x" + file.height;
fileInfo = ("<span class=fileText id=fT" + postID + (file.isSpoiler ? " title='" + filename + "'" : '') + ">File: <a href='" + file.url + "' target=_blank>" + file.timestamp + "</a>") + ("-(" + fileSize + ", " + fileDims + (file.isSpoiler ? '' : ", <span title='" + filename + "'>" + shortFilename + "</span>")) + ")</span>";
fileHTML = "<div id=f" + postID + " class=file><div class=fileInfo>" + fileInfo + "</div>" + imgSrc + "</div>";
} else {
fileHTML = '';
}
tripcode = tripcode ? " <span class=postertrip>" + tripcode + "</span>" : '';
sticky = isSticky ? ' <img src=//static.4chan.org/image/sticky.gif alt=Sticky title=Sticky style="height:16px;width:16px">' : '';
closed = isClosed ? ' <img src=//static.4chan.org/image/closed.gif alt=Closed title=Closed style="height:16px;width:16px">' : '';
container = $.el('div', {
id: "pc" + postID,
className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
innerHTML: (isOP ? '' : "<div class=sideArrows id=sa" + postID + ">&gt;&gt;</div>") + ("<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'>") + ("<div class='postInfoM mobile' id=pim" + postID + ">") + ("<span class='nameBlock" + capcodeClass + "'>") + ("<span class=name>" + (name || '') + "</span>") + tripcode + capcodeStart + capcode + userID + flag + sticky + closed + ("<br>" + subject) + ("</span><span class='dateTime postNum' data-utc=" + dateUTC + ">" + date) + ("<a href=" + ("/" + board + "/res/" + threadID + "#p" + postID) + ">No.</a>") + ("<a href='" + (g.VIEW === 'thread' && g.THREAD === +threadID ? "javascript:quote(" + postID + ")" : "/" + board + "/res/" + threadID + "#q" + postID) + "'>" + postID + "</a>") + '</span>' + '</div>' + (isOP ? fileHTML : '') + ("<div class='postInfo desktop' id=pi" + postID + ">") + ("<input type=checkbox name=" + postID + " value=delete> ") + ("" + subject + " ") + ("<span class='nameBlock" + capcodeClass + "'>") + emailStart + ("<span class=name>" + (name || '') + "</span>") + tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + ' </span> ' + ("<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span> ") + "<span class='postNum desktop'>" + ("<a href=" + ("/" + board + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a>") + ("<a href='" + (g.VIEW === 'thread' && g.THREAD === +threadID ? "javascript:quote(" + postID + ")" : "/" + board + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>") + '</span>' + '</div>' + (isOP ? '' : fileHTML) + ("<blockquote class=postMessage id=m" + postID + ">" + (comment || '') + "</blockquote> ") + '</div>'
});
_ref = $$('.quotelink', container);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i];
href = quote.getAttribute('href');
if (href[0] === '/') {
continue;
}
quote.href = "/" + board + "/res/" + href;
}
return container;
}
};
Get = {
threadExcerpt: function(thread) {
var excerpt, op, _ref;
op = thread.posts[thread];
excerpt = ((_ref = op.info.subject) != null ? _ref.trim() : void 0) || op.info.comment.replace(/\n+/g, ' // ') || Conf['Anonymize'] && 'Anonymous' || $('.nameBlock', op.nodes.info).textContent.trim();
return "/" + thread.board + "/ - " + excerpt;
},
postFromRoot: function(root) {
var board, index, link, post, postID;
link = $('a[title="Highlight this post"]', root);
board = link.pathname.split('/')[1];
postID = link.hash.slice(2);
index = root.dataset.clone;
post = g.posts["" + board + "." + postID];
if (index) {
return post.clones[index];
} else {
return post;
}
},
postFromNode: function(root) {
return Get.postFromRoot($.x('ancestor::div[contains(@class,"postContainer")][1]', root));
},
contextFromLink: function(quotelink) {
return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', quotelink));
},
postDataFromLink: function(link) {
var board, path, postID, threadID;
if (link.hostname === 'boards.4chan.org') {
path = link.pathname.split('/');
board = path[1];
threadID = path[3];
postID = link.hash.slice(2);
} else {
board = link.dataset.board;
threadID = link.dataset.threadid || 0;
postID = link.dataset.postid;
}
return {
board: board,
threadID: +threadID,
postID: +postID
};
},
allQuotelinksLinkingTo: function(post) {
var ID, quote, quotedPost, quotelinks, quoterPost, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3;
quotelinks = [];
_ref = g.posts;
for (ID in _ref) {
quoterPost = _ref[ID];
if (-1 !== quoterPost.quotes.indexOf(post.fullID)) {
_ref1 = [quoterPost].concat(quoterPost.clones);
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
quoterPost = _ref1[_i];
quotelinks.push.apply(quotelinks, quoterPost.nodes.quotelinks);
}
}
}
if (Conf['Quote Backlinks']) {
_ref2 = post.quotes;
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
quote = _ref2[_j];
if (!(quotedPost = g.posts[quote])) {
continue;
}
_ref3 = [quotedPost].concat(quotedPost.clones);
for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
quotedPost = _ref3[_k];
quotelinks.push.apply(quotelinks, Array.prototype.slice.call(quotedPost.nodes.backlinks));
}
}
}
return quotelinks.filter(function(quotelink) {
var board, postID, _ref4;
_ref4 = Get.postDataFromLink(quotelink), board = _ref4.board, postID = _ref4.postID;
return board === post.board.ID && postID === post.ID;
});
},
postClone: function(board, threadID, postID, root, context) {
var post, url;
if (post = g.posts["" + board + "." + postID]) {
Get.insert(post, root, context);
return;
}
root.textContent = "Loading post No." + postID + "...";
if (threadID) {
return $.cache("//api.4chan.org/" + board + "/res/" + threadID + ".json", function() {
return Get.fetchedPost(this, board, threadID, postID, root, context);
});
} else if (url = Redirect.post(board, postID)) {
return $.cache(url, function() {
return Get.archivedPost(this, board, postID, root, context);
});
}
},
insert: function(post, root, context) {
var clone, nodes;
if (!root.parentNode) {
return;
}
clone = post.addClone(context);
Main.callbackNodes(Post, [clone]);
nodes = clone.nodes;
nodes.root.innerHTML = null;
$.add(nodes.root, nodes.post);
root.innerHTML = null;
return $.add(root, nodes.root);
},
fetchedPost: function(req, board, threadID, postID, root, context) {
var post, posts, status, thread, url, _i, _len;
if (post = g.posts["" + board + "." + postID]) {
Get.insert(post, root, context);
return;
}
status = req.status;
if (status !== 200) {
if (url = Redirect.post(board, postID)) {
$.cache(url, function() {
return Get.archivedPost(this, board, postID, root, context);
});
} else {
$.addClass(root, 'warning');
root.textContent = status === 404 ? "Thread No." + threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ").";
}
return;
}
posts = JSON.parse(req.response).posts;
Build.spoilerRange[board] = 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 (url = Redirect.post(board, postID)) {
$.cache(url, function() {
return Get.archivedPost(this, board, postID, root, context);
});
} else {
$.addClass(root, 'warning');
root.textContent = "Post No." + postID + " was not found.";
}
return;
}
}
board = g.boards[board] || new Board(board);
thread = g.threads["" + board + "." + threadID] || new Thread(threadID, board);
post = new Post(Build.postFromObject(post, board), thread, board);
Main.callbackNodes(Post, [post]);
return Get.insert(post, root, context);
},
archivedPost: function(req, board, postID, root, context) {
var bq, comment, data, o, post, thread, threadID, _ref;
if (post = g.posts["" + board + "." + postID]) {
Get.insert(post, root, context);
return;
}
data = JSON.parse(req.response);
if (data.error) {
$.addClass(root, 'warning');
root.textContent = data.error;
return;
}
bq = $.el('blockquote', {
textContent: data.comment
});
bq.innerHTML = bq.innerHTML.replace(/\n|\[\/?b\]|\[\/?spoiler\]|\[\/?code\]|\[\/?moot\]|\[\/?banned\]/g, function(text) {
switch (text) {
case '\n':
return '<br>';
case '[b]':
return '<b>';
case '[/b]':
return '</b>';
case '[spoiler]':
return '<span class=spoiler>';
case '[/spoiler]':
return '</span>';
case '[code]':
return '<pre class=prettyprint>';
case '[/code]':
return '</pre>';
case '[moot]':
return '<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">';
case '[/moot]':
return '</div>';
case '[banned]':
return '<b style="color: red;">';
case '[/banned]':
return '</b>';
}
});
comment = bq.innerHTML.replace(/(^|>)(&gt;[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3').replace(/((&gt;){2}(&gt;\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>');
threadID = data.thread_num;
o = {
postID: "" + postID,
threadID: "" + threadID,
board: board,
name: data.name_processed,
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 ? encodeURI(data.email) : '',
subject: data.title_processed,
flagCode: data.poster_country,
flagName: data.poster_country_name_processed,
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_processed,
timestamp: data.media.media_orig,
url: data.media.media_link || data.media.remote_media_link,
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 || ("//thumbs.4chan.org/" + board + "/thumb/" + data.media.preview_orig),
theight: data.media.preview_h,
twidth: data.media.preview_w,
isSpoiler: data.media.spoiler === '1'
};
}
board = g.boards[board] || new Board(board);
thread = g.threads["" + board + "." + threadID] || new Thread(threadID, board);
post = new Post(Build.post(o, true), thread, board, {
isArchived: true
});
Main.callbackNodes(Post, [post]);
return Get.insert(post, root, context);
}
};
Quotify = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Resurrect Quotes']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Resurrect Quotes',
cb: this.node
});
},
node: function() {
var ID, a, board, deadlink, m, post, quote, quoteID, redirect, _i, _len, _ref, _ref1;
if (this.isClone) {
return;
}
_ref = $$('.deadlink', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
deadlink = _ref[_i];
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, Array.prototype.slice.call(deadlink.childNodes));
continue;
}
quote = deadlink.textContent;
if (!(ID = (_ref1 = quote.match(/\d+$/)) != null ? _ref1[0] : void 0)) {
continue;
}
board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + board + "." + ID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: "/" + board + "/" + post.thread + "/res/#p" + ID,
className: 'quotelink',
textContent: quote
});
} else if (redirect = Redirect.to({
board: board,
threadID: post.thread.ID,
postID: ID
})) {
a = $.el('a', {
href: redirect,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
a.setAttribute('data-board', board);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', ID);
}
} else if (redirect = Redirect.to({
board: board,
threadID: 0,
postID: ID
})) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(board, ID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-board', board);
a.setAttribute('data-postid', ID);
}
}
if (__indexOf.call(this.quotes, quoteID) < 0) {
this.quotes.push(quoteID);
}
if (!a) {
deadlink.textContent += "\u00A0(Dead)";
continue;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
this.nodes.quotelinks.push(a);
}
}
}
};
QuoteInline = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Quote Inline']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Quote Inline',
cb: this.node
});
},
node: function() {
var link, _i, _j, _len, _len1, _ref, _ref1;
_ref = this.nodes.quotelinks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
link = _ref[_i];
$.on(link, 'click', QuoteInline.toggle);
}
_ref1 = this.nodes.backlinks;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
link = _ref1[_j];
$.on(link, 'click', QuoteInline.toggle);
}
},
toggle: function(e) {
var board, context, postID, threadID, _ref;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return;
}
e.preventDefault();
_ref = Get.postDataFromLink(this), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID;
context = Get.contextFromLink(this);
if ($.hasClass(this, 'inlined')) {
QuoteInline.rm(this, board, threadID, postID, context);
} else {
if ($.x("ancestor::div[@id='p" + postID + "']", this)) {
return;
}
QuoteInline.add(this, board, 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, board, threadID, postID, context) {
var i, inline, isBacklink, post;
isBacklink = $.hasClass(quotelink, 'backlink');
inline = $.el('div', {
id: "i" + postID,
className: 'inline'
});
$.after(QuoteInline.findRoot(quotelink, isBacklink), inline);
Get.postClone(board, threadID, postID, inline, context);
if (!((post = g.posts["" + board + "." + postID]) && context.thread === post.thread)) {
return;
}
if (isBacklink && Conf['Forward Hiding']) {
$.addClass(post.nodes.root, 'forwarded');
post.forwarded++ || (post.forwarded = 1);
}
if (Unread.posts && (i = Unread.posts.indexOf(post)) !== -1) {
Unread.posts.splice(i, 1);
return Unread.update();
}
},
rm: function(quotelink, board, threadID, postID, context) {
var el, inlined, isBacklink, post, root, _ref;
isBacklink = $.hasClass(quotelink, 'backlink');
root = QuoteInline.findRoot(quotelink, isBacklink);
root = $.x("following-sibling::div[@id='i" + postID + "'][1]", root);
$.rm(root);
if (!(el = root.firstElementChild)) {
return;
}
post = g.posts["" + board + "." + postID];
post.rmClone(el.dataset.clone);
if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads["" + board + "." + threadID] && !--post.forwarded) {
delete post.forwarded;
$.rmClass(post.nodes.root, 'forwarded');
}
while (inlined = $('.inlined', el)) {
_ref = Get.postDataFromLink(inlined), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID;
QuoteInline.rm(inlined, board, threadID, postID, context);
$.rmClass(inlined, 'inlined');
}
}
};
QuotePreview = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Quote Preview']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Quote Preview',
cb: this.node
});
},
node: function() {
var link, _i, _j, _len, _len1, _ref, _ref1;
_ref = this.nodes.quotelinks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
link = _ref[_i];
$.on(link, 'mouseover', QuotePreview.mouseover);
}
_ref1 = this.nodes.backlinks;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
link = _ref1[_j];
$.on(link, 'mouseover', QuotePreview.mouseover);
}
},
mouseover: function(e) {
var board, clone, origin, post, postID, posts, qp, quote, quoterID, threadID, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2;
if ($.hasClass(this, 'inlined')) {
return;
}
_ref = Get.postDataFromLink(this), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID;
qp = $.el('div', {
id: 'qp',
className: 'dialog'
});
$.add(d.body, qp);
Get.postClone(board, threadID, postID, qp, Get.contextFromLink(this));
UI.hover({
root: this,
el: qp,
latestEvent: e,
endEvents: 'mouseout click',
cb: QuotePreview.mouseout,
asapTest: function() {
return qp.firstElementChild;
}
});
if (!(origin = g.posts["" + board + "." + 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;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
quote = _ref1[_j];
if (quote.hash.slice(2) === quoterID) {
$.addClass(quote, 'forwardlink');
}
}
_ref2 = clone.nodes.backlinks;
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
quote = _ref2[_k];
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');
}
}
};
QuoteBacklink = {
init: function() {
var format;
if (g.VIEW === 'catalog' || !Conf['Quote Backlinks']) {
return;
}
format = Conf['backlink'].replace(/%id/g, "' + id + '");
this.funk = Function('id', "return '" + format + "'");
this.containers = {};
Post.prototype.callbacks.push({
name: 'Quote Backlinking Part 1',
cb: this.firstNode
});
return Post.prototype.callbacks.push({
name: 'Quote Backlinking Part 2',
cb: this.secondNode
});
},
firstNode: function() {
var a, clone, container, containers, link, post, quote, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
if (this.isClone || !this.quotes.length) {
return;
}
a = $.el('a', {
href: "/" + this.board + "/res/" + this.thread + "#p" + this,
className: this.isHidden ? 'filtered backlink' : 'backlink',
textContent: QuoteBacklink.funk(this.ID)
});
_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];
link = a.cloneNode(true);
if (Conf['Quote Preview']) {
$.on(link, 'mouseover', QuotePreview.mouseover);
}
if (Conf['Quote Inline']) {
$.on(link, 'click', QuoteInline.toggle);
}
$.add(container, [$.tn(' '), link]);
}
}
},
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'
}));
}
};
QuoteOP = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Mark OP Quotes']) {
return;
}
this.text = '\u00A0(OP)';
return Post.prototype.callbacks.push({
name: 'Mark OP Quotes',
cb: this.node
});
},
node: function() {
var board, op, postID, quote, quotelinks, quotes, _i, _j, _len, _len1, _ref;
if (this.isClone && this.thread === this.context.thread) {
return;
}
if (!(quotes = this.quotes).length) {
return;
}
quotelinks = this.nodes.quotelinks;
if (this.isClone && -1 < quotes.indexOf(this.fullID)) {
for (_i = 0, _len = quotelinks.length; _i < _len; _i++) {
quote = quotelinks[_i];
quote.textContent = quote.textContent.replace(QuoteOP.text, '');
}
}
op = (this.isClone ? this.context : this).thread.fullID;
if (!(-1 < quotes.indexOf(op))) {
return;
}
for (_j = 0, _len1 = quotelinks.length; _j < _len1; _j++) {
quote = quotelinks[_j];
_ref = Get.postDataFromLink(quote), board = _ref.board, postID = _ref.postID;
if (("" + board + "." + postID) === op) {
$.add(quote, $.tn(QuoteOP.text));
}
}
}
};
QuoteCT = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Mark Cross-thread Quotes']) {
return;
}
this.text = '\u00A0(Cross-thread)';
return Post.prototype.callbacks.push({
name: 'Mark Cross-thread Quotes',
cb: this.node
});
},
node: function() {
var board, data, quote, quotelinks, quotes, thread, _i, _len, _ref;
if (this.isClone && this.thread === this.context.thread) {
return;
}
if (!(quotes = this.quotes).length) {
return;
}
quotelinks = this.nodes.quotelinks;
_ref = this.isClone ? this.context : this, board = _ref.board, thread = _ref.thread;
for (_i = 0, _len = quotelinks.length; _i < _len; _i++) {
quote = quotelinks[_i];
data = Get.postDataFromLink(quote);
if (!data.threadID) {
continue;
}
if (this.isClone) {
quote.textContent = quote.textContent.replace(QuoteCT.text, '');
}
if (data.board === this.board.ID && data.threadID !== thread.ID) {
$.add(quote, $.tn(QuoteCT.text));
}
}
}
};
Anonymize = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Anonymize']) {
return;
}
return Post.prototype.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) {
if (/sage/i.test(this.info.email)) {
return email.href = 'mailto:sage';
} else {
$.replace(email, name);
return delete this.nodes.email;
}
}
}
};
Time = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Time Formatting']) {
return;
}
this.funk = this.createFunc(Conf['time']);
return Post.prototype.callbacks.push({
name: 'Time Formatting',
cb: this.node
});
},
node: function() {
if (this.isClone) {
return;
}
return this.nodes.date.textContent = Time.funk(Time, this.info.date);
},
createFunc: function(format) {
var code;
code = format.replace(/%([A-Za-z])/g, function(s, c) {
if (c in Time.formatters) {
return "' + Time.formatters." + c + ".call(date) + '";
} else {
return s;
}
});
return Function('Time', 'date', "return '" + code + "'");
},
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() - 2000;
}
}
};
RelativeDates = {
INTERVAL: $.MINUTE / 2,
init: function() {
if (g.VIEW === 'catalog' || !Conf['Relative Post Dates']) {
return;
}
$.on(d, 'visibilitychange ThreadUpdate', this.flush);
this.flush();
return Post.prototype.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.setUpdate(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 now, update, _i, _len, _ref;
if (d.hidden) {
return;
}
now = new Date();
_ref = RelativeDates.stale;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
update = _ref[_i];
update(now);
}
RelativeDates.stale = [];
clearTimeout(RelativeDates.timeout);
return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL);
},
setUpdate: function(post) {
var markStale, setOwnTimeout, update;
setOwnTimeout = function(diff) {
var delay;
delay = diff < $.MINUTE ? diff % $.SECOND : diff < $.HOUR ? diff % $.MINUTE : diff < $.DAY ? diff % $.HOUR : diff % $.DAY;
return setTimeout(markStale, delay);
};
update = function(now) {
var date, diff, relative, singlePost, _i, _len, _ref;
date = post.info.date;
diff = now - date;
relative = RelativeDates.relative(diff, now, date);
_ref = [post].concat(post.clones);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
singlePost = _ref[_i];
singlePost.nodes.date.textContent = relative;
}
return setOwnTimeout(diff);
};
markStale = function() {
return RelativeDates.stale.push(update);
};
return update(new Date());
}
};
FileInfo = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['File Info Formatting']) {
return;
}
this.funk = this.createFunc(Conf['fileInfo']);
return Post.prototype.callbacks.push({
name: 'File Info Formatting',
cb: this.node
});
},
node: function() {
if (!this.file || this.isClone) {
return;
}
return this.file.text.innerHTML = FileInfo.funk(FileInfo, this);
},
createFunc: function(format) {
var code;
code = format.replace(/%(.)/g, function(s, c) {
if (c in FileInfo.formatters) {
return "' + FileInfo.formatters." + c + ".call(post) + '";
} else {
return s;
}
});
return Function('FileInfo', 'post', "return '" + code + "'");
},
convertUnit: function(size, unit) {
var i;
if (unit === 'B') {
return "" + (size.toFixed()) + " Bytes";
}
i = 1 + ['KB', 'MB'].indexOf(unit);
while (i--) {
size /= 1024;
}
size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed();
return "" + size + " " + unit;
},
escape: function(name) {
return name.replace(/<|>/g, function(c) {
return c === '<' && '&lt;' || '&gt;';
});
},
formatters: {
t: function() {
return this.file.URL.match(/\d+\..+$/)[0];
},
T: function() {
return "<a href=" + this.file.URL + " target=_blank>" + (FileInfo.formatters.t.call(this)) + "</a>";
},
l: function() {
return "<a href=" + this.file.URL + " target=_blank>" + (FileInfo.formatters.n.call(this)) + "</a>";
},
L: function() {
return "<a href=" + this.file.URL + " target=_blank>" + (FileInfo.formatters.N.call(this)) + "</a>";
},
n: function() {
var fullname, shortname;
fullname = this.file.name;
shortname = Build.shortFilename(this.file.name, this.isReply);
if (fullname === shortname) {
return FileInfo.escape(fullname);
} else {
return "<span class=fntrunc>" + (FileInfo.escape(shortname)) + "</span><span class=fnfull>" + (FileInfo.escape(fullname)) + "</span>";
}
},
N: function() {
return FileInfo.escape(this.file.name);
},
p: function() {
if (this.file.isSpoiler) {
return 'Spoiler, ';
} else {
return '';
}
},
s: function() {
return this.file.size;
},
B: function() {
return FileInfo.convertUnit(this.file.sizeInBytes, 'B');
},
K: function() {
return FileInfo.convertUnit(this.file.sizeInBytes, 'KB');
},
M: function() {
return FileInfo.convertUnit(this.file.sizeInBytes, 'MB');
},
r: function() {
if (this.file.isImage) {
return this.file.dimensions;
} else {
return 'PDF';
}
}
}
};
Sauce = {
init: function() {
var 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];
if (link[0] === '#') {
continue;
}
links.push(this.createSauceLink(link.trim()));
}
if (!links.length) {
return;
}
this.links = links;
this.link = $.el('a', {
target: '_blank'
});
return Post.prototype.callbacks.push({
name: 'Sauce',
cb: this.node
});
},
createSauceLink: function(link) {
var m, text;
link = link.replace(/%(t?url|md5|board)/g, function(parameter) {
switch (parameter) {
case '%turl':
return "' + post.file.thumbURL + '";
case '%url':
return "' + post.file.URL + '";
case '%md5':
return "' + encodeURIComponent(post.file.MD5) + '";
case '%board':
return "' + post.board + '";
default:
return parameter;
}
});
text = (m = link.match(/;text:(.+)$/)) ? m[1] : link.match(/(\w+)\.\w+\//)[1];
link = link.replace(/;text:.+$/, '');
return Function('post', 'a', "a.href = '" + link + "';\na.textContent = '" + text + "';\nreturn a;");
},
node: function() {
var link, 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];
nodes.push($.tn('\u00A0'), link(this, Sauce.link.cloneNode(true)));
}
return $.add(this.file.info, nodes);
}
};
ImageExpand = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Image Expansion',
cb: this.node
});
},
node: function() {
if (!(this.file && this.file.isImage)) {
return;
}
$.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle);
if (ImageExpand.on && !this.isHidden) {
return ImageExpand.expand(this);
}
},
cb: {
toggle: function(e) {
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return;
}
e.preventDefault();
return ImageExpand.toggle(Get.postFromNode(this));
},
all: function() {
var ID, file, func, post, posts, _i, _j, _len, _len1, _ref, _ref1;
$.event('CloseMenu');
ImageExpand.on = this.checked;
posts = [];
_ref = g.posts;
for (ID in _ref) {
post = _ref[ID];
_ref1 = [post].concat(post.clones);
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
post = _ref1[_i];
file = post.file;
if (!(file && file.isImage && doc.contains(post.nodes.root))) {
continue;
}
if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || Conf['Expand from here'] && file.thumb.getBoundingClientRect().top < 0)) {
continue;
}
posts.push(post);
}
}
func = ImageExpand.on ? ImageExpand.expand : ImageExpand.contract;
for (_j = 0, _len1 = posts.length; _j < _len1; _j++) {
post = posts[_j];
func(post);
}
},
setFitness: function() {
var checked;
checked = this.checked;
(checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-'));
if (this.name !== 'Fit height') {
return;
}
if (checked) {
$.on(window, 'resize', ImageExpand.resize);
if (!ImageExpand.style) {
ImageExpand.style = $.addStyle(null, 'style');
}
return ImageExpand.resize();
} else {
return $.off(window, 'resize', ImageExpand.resize);
}
}
},
toggle: function(post) {
var headRect, postRect, rect, thumb, top;
thumb = post.file.thumb;
if (!thumb.hidden) {
ImageExpand.expand(post);
return;
}
rect = thumb.parentNode.getBoundingClientRect();
if (rect.bottom > 0) {
postRect = post.nodes.root.getBoundingClientRect();
headRect = $.id('header-bar').getBoundingClientRect();
top = postRect.top - headRect.top - headRect.height - 2;
if ($.engine === 'webkit') {
if (rect.top < 0) {
d.body.scrollTop += top;
}
if (rect.left < 0) {
d.body.scrollLeft = 0;
}
} else {
if (rect.top < 0) {
d.documentElement.scrollTop += top;
}
if (rect.left < 0) {
d.documentElement.scrollLeft = 0;
}
}
}
return ImageExpand.contract(post);
},
contract: function(post) {
var img, thumb;
thumb = post.file.thumb;
thumb.hidden = false;
if (img = $('.full-image', thumb.parentNode)) {
img.hidden = true;
}
return $.rmClass(post.nodes.root, 'expanded-image');
},
expand: function(post) {
var img, thumb;
thumb = post.file.thumb;
if (post.isHidden || thumb.hidden) {
return;
}
thumb.hidden = true;
$.addClass(post.nodes.root, 'expanded-image');
if (img = $('.full-image', thumb.parentNode)) {
img.hidden = false;
return;
}
img = $.el('img', {
className: 'full-image',
src: post.file.URL
});
$.on(img, 'error', ImageExpand.error);
return $.after(thumb, img);
},
error: function() {
var URL, post, src, timeoutID;
post = Get.postFromNode(this);
$.rm(this);
ImageExpand.contract(post);
if (this.hidden) {
return;
}
src = this.src.split('/');
if (!(src[2] === 'images.4chan.org' && (URL = Redirect.image(src[3], src[5])))) {
if (g.DEAD) {
return;
}
URL = post.file.URL;
}
if ($.engine !== 'webkit' && URL.split('/')[2] === 'images.4chan.org') {
return;
}
timeoutID = setTimeout(ImageExpand.expand, 10000, post);
if ($.engine !== 'webkit' || URL.split('/')[2] !== 'images.4chan.org') {
return;
}
return $.ajax(URL, {
onreadystatechange: (function() {
if (this.status === 404) {
return clearTimeout(timeoutID);
}
})
}, {
type: 'head'
});
},
menu: {
init: function() {
var conf, createSubEntry, el, key, 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 = [];
subEntries.push(createSubEntry('Expand all'));
_ref = Config.imageExpansion;
for (key in _ref) {
conf = _ref[key];
subEntries.push(createSubEntry(key, conf));
}
return $.event('AddMenuEntry', {
type: 'header',
el: el,
order: 20,
subEntries: subEntries
});
},
createSubEntry: function(type, config) {
var input, label;
label = $.el('label', {
innerHTML: "<input type=checkbox name='" + type + "'> " + type
});
input = label.firstElementChild;
switch (type) {
case 'Expand all':
$.on(input, 'change', ImageExpand.cb.all);
ImageExpand.expandAllInput = input;
break;
case 'Fit width':
case 'Fit height':
$.on(input, 'change', ImageExpand.cb.setFitness);
}
if (config) {
label.title = config[1];
input.checked = Conf[type];
$.event('change', null, input);
$.on(input, 'change', $.cb.checked);
}
return {
el: label
};
}
},
resize: function() {
return ImageExpand.style.textContent = ":root.fit-height .full-image {max-height:" + doc.clientHeight + "px}";
}
};
RevealSpoilers = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Reveal Spoilers']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Reveal Spoilers',
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');
return thumb.src = this.file.thumbURL;
}
};
AutoGIF = {
init: function() {
var _ref;
if (g.VIEW === 'catalog' || !Conf['Auto-GIF'] || ((_ref = g.BOARD.ID) === 'gif' || _ref === 'wsg')) {
return;
}
return Post.prototype.callbacks.push({
name: 'Auto-GIF',
cb: this.node
});
},
node: function() {
var URL, gif, style, thumb, _ref, _ref1;
if (this.isClone || this.isHidden || this.thread.isHidden || !((_ref = this.file) != null ? _ref.isImage : void 0)) {
return;
}
_ref1 = this.file, thumb = _ref1.thumb, URL = _ref1.URL;
if (!(/gif$/.test(URL) && !/spoiler/.test(thumb.src))) {
return;
}
if (this.file.isSpoiler) {
style = thumb.style;
style.maxHeight = style.maxWidth = this.isReply ? '125px' : '250px';
}
gif = $.el('img');
$.on(gif, 'load', function() {
return thumb.src = URL;
});
return gif.src = URL;
}
};
ImageHover = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Image Hover']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Auto-GIF',
cb: this.node
});
},
node: function() {
var _ref;
if (!((_ref = this.file) != null ? _ref.isImage : void 0)) {
return;
}
return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover);
},
mouseover: function(e) {
var el;
el = $.el('img', {
id: 'ihover',
src: this.parentNode.href
});
$.add(d.body, el);
UI.hover({
root: this,
el: el,
latestEvent: e,
endEvents: 'mouseout click',
asapTest: function() {
return el.naturalHeight;
}
});
return $.on(el, 'error', ImageHover.error);
},
error: function() {
var URL, src, timeoutID,
_this = this;
if (!doc.contains(this)) {
return;
}
src = this.src.split('/');
if (!(src[2] === 'images.4chan.org' && (URL = Redirect.image(src[3], src[5])))) {
if (g.DEAD) {
return;
}
URL = post.file.URL;
}
if ($.engine !== 'webkit' && URL.split('/')[2] === 'images.4chan.org') {
return;
}
timeoutID = setTimeout((function() {
return _this.src = URL;
}), 3000);
if ($.engine !== 'webkit' || URL.split('/')[2] !== 'images.4chan.org') {
return;
}
return $.ajax(URL, {
onreadystatechange: (function() {
if (this.status === 404) {
return clearTimeout(timeoutID);
}
})
}, {
type: 'head'
});
}
};
ExpandComment = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Comment Expansion']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Comment Expansion',
cb: this.node
});
},
node: function() {
var a;
if (a = $('.abbr > a', this.nodes.comment)) {
return $.on(a, 'click', ExpandComment.cb);
}
},
cb: function(e) {
var post;
e.preventDefault();
post = Get.postFromNode(this);
return ExpandComment.expand(post);
},
expand: function(post) {
var a;
if (post.nodes.longComment) {
$.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("//api.4chan.org" + a.pathname + ".json", function() {
return ExpandComment.parse(this, a, post);
});
},
contract: function(post) {
var a;
if (!post.nodes.shortComment) {
return;
}
a = 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 clone, comment, href, postObj, posts, quote, spoilerRange, _i, _j, _len, _len1, _ref;
if (req.status !== 200) {
a.textContent = "Error " + req.statusText + " (" + req.status + ")";
return;
}
posts = JSON.parse(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;
}
quote.href = "/" + post.board + "/res/" + href;
}
post.nodes.shortComment = comment;
$.replace(comment, clone);
post.nodes.comment = post.nodes.longComment = clone;
post.parseComment();
post.parseQuotes();
if (Conf['Resurrect Quotes']) {
Quotify.node.call(post);
}
if (Conf['Quote Preview']) {
QuotePreview.node.call(post);
}
if (Conf['Quote Inline']) {
QuoteInline.node.call(post);
}
if (Conf['Mark OP Quotes']) {
QuoteOP.node.call(post);
}
if (Conf['Mark Cross-thread Quotes']) {
QuoteCT.node.call(post);
}
if (g.BOARD.ID === 'g') {
Fourchan.code.call(post);
}
if (g.BOARD.ID === 'sci') {
return Fourchan.math.call(post);
}
}
};
ExpandThread = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Expansion']) {
return;
}
return Thread.prototype.callbacks.push({
name: 'Thread Expansion',
cb: this.node
});
},
node: function() {
var a, op, span;
op = this.posts[this];
if (!(span = $('.summary', op.nodes.root.parentNode))) {
return;
}
a = $.el('a', {
textContent: "+ " + span.textContent,
className: 'summary',
href: 'javascript:;'
});
$.on(a, 'click', ExpandThread.cbToggle);
return $.replace(span, a);
},
cbToggle: function() {
var op;
op = Get.postFromRoot(this.previousElementSibling);
return ExpandThread.toggle(op.thread);
},
toggle: function(thread) {
var a, inlined, num, replies, reply, text, threadRoot, url, _i, _len;
threadRoot = thread.posts[thread].nodes.root.parentNode;
url = "//api.4chan.org/" + thread.board + "/res/" + thread + ".json";
a = $('.summary', threadRoot);
text = a.textContent;
switch (text[0]) {
case '+':
a.textContent = text.replace('+', '× Loading...');
$.cache(url, function() {
return ExpandThread.parse(this, thread, a);
});
ExpandComment.expand(thread.posts[thread]);
break;
case '×':
a.textContent = text.replace('× Loading...', '+');
break;
case '-':
a.textContent = text.replace('-', '+');
ExpandComment.contract(thread.posts[thread]);
num = (function() {
switch (g.BOARD) {
case 'b':
case 'vg':
case 'q':
return 3;
case 't':
return 1;
default:
return 5;
}
})();
replies = $$('.thread > .replyContainer', threadRoot).slice(0, -num);
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
if (Conf['Quote Inline']) {
while (inlined = $('.inlined', reply)) {
inlined.click();
}
}
$.rm(reply);
}
}
},
parse: function(req, thread, a) {
var link, node, nodes, post, posts, replies, reply, spoilerRange, _i, _len;
if (a.textContent[0] === '+') {
return;
}
if (req.status !== 200) {
a.textContent = "Error " + req.statusText + " (" + req.status + ")";
$.off(a, 'click', ExpandThread.cb.toggle);
return;
}
a.textContent = a.textContent.replace('× Loading...', '-');
posts = JSON.parse(req.response).posts;
if (spoilerRange = posts[0].custom_spoiler) {
Build.spoilerRange[g.BOARD] = spoilerRange;
}
replies = posts.slice(1);
posts = [];
nodes = [];
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
if (post = thread.posts[reply.no]) {
nodes.push(post.nodes.root);
continue;
}
node = Build.postFromObject(reply, thread.board);
post = new Post(node, thread, thread.board);
link = $('a[title="Highlight this post"]', node);
link.href = "res/" + thread + "#p" + post;
link.nextSibling.href = "res/" + thread + "#q" + post;
posts.push(post);
nodes.push(node);
}
Main.callbackNodes(Post, posts);
$.after(a, nodes);
if (Conf['Enable 4chan\'s extension']) {
return $.unsafeWindow.Parser.parseThread(thread.ID, 1, nodes.length);
} else {
return Fourchan.parseThread(thread.ID, 1, nodes.length);
}
}
};
ThreadExcerpt = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
return;
}
return Thread.prototype.callbacks.push({
name: 'Thread Excerpt',
cb: this.node
});
},
node: function() {
return d.title = Get.threadExcerpt(this);
}
};
Unread = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Tab Icon']) {
return;
}
$.on(d, 'ThreadUpdate', this.onUpdate);
$.on(d, 'QRPostSuccessful', this.post);
$.on(d, 'scroll visibilitychange', this.read);
return Thread.prototype.callbacks.push({
name: 'Unread',
cb: this.node
});
},
node: function() {
var ID, post, posts, _ref;
Unread.yourPosts = [];
Unread.posts = [];
Unread.title = d.title;
posts = [];
_ref = this.posts;
for (ID in _ref) {
post = _ref[ID];
if (post.isReply) {
posts.push(post);
}
}
Unread.addPosts(posts);
return Unread.update();
},
addPosts: function(newPosts) {
var height, index, post, _i, _len;
if (!d.hidden) {
height = doc.clientHeight;
}
for (_i = 0, _len = newPosts.length; _i < _len; _i++) {
post = newPosts[_i];
if ((index = Unread.yourPosts.indexOf(post.ID)) !== -1) {
Unread.yourPosts.splice(index, 1);
} else if (!post.isHidden && (d.hidden || post.nodes.root.getBoundingClientRect().bottom > height)) {
Unread.posts.push(post);
}
}
},
onUpdate: function(e) {
if (!e.detail[404]) {
Unread.addPosts(e.detail.newPosts);
}
return Unread.update();
},
post: function(e) {
return Unread.yourPosts.push(+e.detail.postID);
},
read: function() {
var bottom, height, i, post, _i, _len, _ref;
height = doc.clientHeight;
_ref = Unread.posts;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
post = _ref[i];
bottom = post.nodes.root.getBoundingClientRect().bottom;
if (bottom > height) {
break;
}
}
if (!i) {
return;
}
Unread.posts = Unread.posts.slice(i);
return Unread.update();
},
update: function() {
var count;
count = Unread.posts.length;
if (Conf['Unread Count']) {
d.title = g.DEAD ? "(" + Unread.posts.length + ") /" + g.BOARD + "/ - 404" : "(" + Unread.posts.length + ") " + Unread.title;
}
if (!Conf['Unread Tab Icon']) {
return;
}
Favicon.el.href = g.DEAD ? count ? Favicon.unreadDead : Favicon.dead : count ? Favicon.unread : Favicon["default"];
return $.add(d.head, Favicon.el);
}
};
Favicon = {
init: function() {
return $.ready(function() {
var href;
Favicon.el = $('link[rel="shortcut icon"]', d.head);
Favicon.el.type = 'image/x-icon';
href = Favicon.el.href;
Favicon.SFW = /ws\.ico$/.test(href);
Favicon["default"] = href;
return Favicon["switch"]();
});
},
"switch": function() {
switch (Conf['favicon']) {
case 'ferongr':
Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
break;
case 'xat-':
Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC';
Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg==';
Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII=';
break;
case 'Mayhem':
Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg==';
Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC';
Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII=';
break;
case 'Original':
Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
}
return Favicon.unread = Favicon.SFW ? Favicon.unreadSFW : Favicon.unreadNSFW;
},
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
};
ThreadStats = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Thread Stats']) {
return;
}
this.dialog = UI.dialog('thread-stats', 'bottom: 0; left: 0;', "<div class=move><span id=post-count>0</span> / <span id=file-count>0</span></div>");
this.postCount = this.fileCount = 0;
this.postCountEl = $('#post-count', this.dialog);
this.fileCountEl = $('#file-count', this.dialog);
this.fileLimit = (function() {
switch (g.BOARD.ID) {
case 'a':
case 'b':
case 'v':
case 'co':
case 'mlp':
return 251;
case 'vg':
return 376;
default:
return 151;
}
})();
return Thread.prototype.callbacks.push({
name: 'Thread Stats',
cb: this.node
});
},
node: function() {
var ID, post, _ref;
_ref = this.posts;
for (ID in _ref) {
post = _ref[ID];
ThreadStats.postCount++;
if (post.file) {
ThreadStats.fileCount++;
}
}
ThreadStats.update();
$.on(d, 'ThreadUpdate', ThreadStats.onUpdate);
return $.add(d.body, ThreadStats.dialog);
},
onUpdate: function(e) {
var post, _i, _len, _ref;
if (e.detail[404]) {
return;
}
_ref = e.detail.newPosts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
post = _ref[_i];
ThreadStats.postCount++;
if (post.file) {
ThreadStats.fileCount++;
}
}
ThreadStats.postCount -= e.detail.deletedPosts.length;
ThreadStats.fileCount -= e.detail.deletedFiles.length;
return ThreadStats.update();
},
update: function() {
this.postCountEl.textContent = ThreadStats.postCount;
this.fileCountEl.textContent = ThreadStats.fileCount;
return (ThreadStats.fileCount > ThreadStats.fileLimit ? $.addClass : $.rmClass)(ThreadStats.fileCountEl, 'warning');
}
};
ThreadUpdater = {
init: function() {
var checked, conf, html, name, _ref;
if (g.VIEW !== 'thread' || !Conf['Thread Updater']) {
return;
}
html = '';
_ref = Config.updater.checkbox;
for (name in _ref) {
conf = _ref[name];
checked = Conf[name] ? 'checked' : '';
html += "<div><label title='" + conf[1] + "'><input name='" + name + "' type=checkbox " + checked + "> " + name + "</label></div>";
}
checked = Conf['Auto Update'] ? 'checked' : '';
html = "<div class=move><span id=update-status></span> <span id=update-timer></span></div>\n" + html + "\n<div><label title='Controls whether *this* thread automatically updates or not'><input type=checkbox name='Auto Update This' " + checked + "> Auto Update This</label></div>\n<div><label><input type=number name=Interval class=field min=5 value=" + Conf['Interval'] + "> Refresh rate (s)</label></div>\n<div><input value='Update Now' type=button name='Update Now'></div>";
this.dialog = UI.dialog('updater', 'bottom: 0; right: 0;', html);
this.timer = $('#update-timer', this.dialog);
this.status = $('#update-status', this.dialog);
return Thread.prototype.callbacks.push({
name: 'Thread Updater',
cb: this.node
});
},
node: function() {
var input, _i, _len, _ref;
ThreadUpdater.thread = this;
ThreadUpdater.root = this.posts[this].nodes.root.parentNode;
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0];
ThreadUpdater.outdateCount = 0;
ThreadUpdater.lastModified = '0';
_ref = $$('input', ThreadUpdater.dialog);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
input = _ref[_i];
if (input.type === 'checkbox') {
$.on(input, 'change', $.cb.checked);
}
switch (input.name) {
case 'Scroll BG':
$.on(input, 'change', ThreadUpdater.cb.scrollBG);
ThreadUpdater.cb.scrollBG();
break;
case 'Auto Update This':
$.on(input, 'change', ThreadUpdater.cb.autoUpdate);
$.event('change', null, input);
break;
case 'Interval':
$.on(input, 'change', ThreadUpdater.cb.interval);
ThreadUpdater.cb.interval.call(input);
break;
case 'Update Now':
$.on(input, 'click', ThreadUpdater.update);
}
}
$.on(window, 'online offline', ThreadUpdater.cb.online);
$.on(d, 'QRPostSuccessful', ThreadUpdater.cb.post);
$.on(d, 'visibilitychange', ThreadUpdater.cb.visibility);
ThreadUpdater.cb.online();
return $.add(d.body, ThreadUpdater.dialog);
},
/*
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.online = navigator.onLine) {
ThreadUpdater.outdateCount = 0;
ThreadUpdater.set('timer', ThreadUpdater.getInterval());
if (Conf['Auto Update This']) {
ThreadUpdater.update();
}
ThreadUpdater.set('status', null, null);
} else {
ThreadUpdater.set('timer', null);
ThreadUpdater.set('status', 'Offline', 'warning');
}
return ThreadUpdater.cb.autoUpdate();
},
post: function(e) {
if (!(Conf['Auto Update This'] && +e.detail.threadID === ThreadUpdater.thread.ID)) {
return;
}
ThreadUpdater.outdateCount = 0;
if (ThreadUpdater.seconds > 2) {
return setTimeout(ThreadUpdater.update, 1000);
}
},
visibility: function() {
if (d.hidden) {
return;
}
ThreadUpdater.outdateCount = 0;
if (ThreadUpdater.seconds > ThreadUpdater.interval) {
return ThreadUpdater.set('timer', ThreadUpdater.getInterval());
}
},
scrollBG: function() {
return ThreadUpdater.scrollBG = Conf['Scroll BG'] ? function() {
return true;
} : function() {
return !d.hidden;
};
},
autoUpdate: function() {
if (Conf['Auto Update This'] && ThreadUpdater.online) {
return ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
} else {
return clearTimeout(ThreadUpdater.timeoutID);
}
},
interval: function() {
var val;
val = Math.max(5, parseInt(this.value, 10));
ThreadUpdater.interval = this.value = val;
return $.cb.value.call(this);
},
load: function() {
var klass, req, text, _ref, _ref1;
req = ThreadUpdater.req;
switch (req.status) {
case 200:
g.DEAD = false;
ThreadUpdater.parse(JSON.parse(req.response).posts);
ThreadUpdater.lastModified = req.getResponseHeader('Last-Modified');
ThreadUpdater.set('timer', ThreadUpdater.getInterval());
break;
case 404:
g.DEAD = true;
ThreadUpdater.set('timer', null);
ThreadUpdater.set('status', '404', 'warning');
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.thread.kill();
$.event('ThreadUpdate', {
404: true,
thread: ThreadUpdater.thread
});
break;
default:
ThreadUpdater.outdateCount++;
ThreadUpdater.set('timer', ThreadUpdater.getInterval());
/*
Status Code 304: Not modified
By sending the `If-Modified-Since` header we get a proper status code, and no response.
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
*/
_ref1 = (_ref = req.status) === 0 || _ref === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref1[0], klass = _ref1[1];
ThreadUpdater.set('status', text, klass);
}
return delete ThreadUpdater.req;
}
},
getInterval: function() {
var i, j;
i = ThreadUpdater.interval;
j = Math.min(ThreadUpdater.outdateCount, 10);
if (!d.hidden) {
j = Math.min(j, 7);
}
return ThreadUpdater.seconds = Math.max(i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]);
},
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;
}
},
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 url;
if (!ThreadUpdater.online) {
return;
}
ThreadUpdater.seconds = 0;
ThreadUpdater.set('timer', '...');
if (ThreadUpdater.req) {
ThreadUpdater.req.onloadend = null;
ThreadUpdater.req.abort();
}
url = "//api.4chan.org/" + ThreadUpdater.thread.board + "/res/" + ThreadUpdater.thread + ".json";
return ThreadUpdater.req = $.ajax(url, {
onloadend: ThreadUpdater.cb.load
}, {
headers: {
'If-Modified-Since': ThreadUpdater.lastModified
}
});
},
parse: function(postObjects) {
var ID, count, deletedFiles, deletedPosts, files, index, node, nodes, num, post, postObject, posts, scroll, _i, _len, _ref;
Build.spoilerRange[ThreadUpdater.thread.board] = postObjects[0].custom_spoiler;
nodes = [];
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);
nodes.push(node);
posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board));
}
deletedPosts = [];
deletedFiles = [];
_ref = ThreadUpdater.thread.posts;
for (ID in _ref) {
post = _ref[ID];
if (post.isDead) {
continue;
}
ID = +ID;
if (-1 === index.indexOf(ID)) {
post.kill();
deletedPosts.push(post);
} else if (post.file && !post.file.isDead && -1 === files.indexOf(ID)) {
post.kill(true);
deletedFiles.push(post);
}
}
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;
$.add(ThreadUpdater.root, nodes);
if (scroll) {
if (Conf['Bottom scroll']) {
d.body.scrollTop = d.body.clientHeight;
} else {
nodes[0].scrollIntoView();
}
}
$.queueTask(function() {
var length, threadID;
threadID = ThreadUpdater.thread.ID;
length = ThreadUpdater.root.children.length;
if (Conf['Enable 4chan\'s extension']) {
return $.unsafeWindow.Parser.parseThread(threadID, -count);
} else {
return Fourchan.parseThread(threadID, length - count, length);
}
});
}
return $.event('ThreadUpdate', {
404: false,
thread: ThreadUpdater.thread,
newPosts: posts,
deletedPosts: deletedPosts,
deletedFiles: deletedFiles
});
}
};
ThreadWatcher = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Thread Watcher']) {
return;
}
this.dialog = UI.dialog('watcher', 'top: 50px; left: 0px;', '<div class=move>Thread Watcher</div>');
$.on(d, 'QRPostSuccessful', this.cb.post);
$.on(d, '4chanXInitFinished', this.ready);
$.sync('WatchedThreads', this.refresh);
return Thread.prototype.callbacks.push({
name: 'Thread Watcher',
cb: this.node
});
},
node: function() {
var favicon, op;
op = this.posts[this];
favicon = $.el('img', {
className: 'favicon'
});
$.on(favicon, 'click', ThreadWatcher.cb.toggle);
$.before($('input', op.nodes.post), favicon);
if (g.VIEW === 'thread' && this.ID === $.get('AutoWatch', 0)) {
ThreadWatcher.watch(this);
return $["delete"]('AutoWatch');
}
},
ready: function() {
ThreadWatcher.refresh();
return $.add(d.body, ThreadWatcher.dialog);
},
refresh: function(watched) {
var ID, board, div, favicon, id, link, nodes, op, props, thread, x, _ref, _ref1;
watched || (watched = $.get('WatchedThreads', {}));
nodes = [$('.move', ThreadWatcher.dialog)];
for (board in watched) {
_ref = watched[board];
for (id in _ref) {
props = _ref[id];
x = $.el('a', {
textContent: '×',
href: 'javascript:;'
});
$.on(x, 'click', ThreadWatcher.cb.x);
link = $.el('a', props);
link.title = link.textContent;
div = $.el('div');
$.add(div, [x, $.tn(' '), link]);
nodes.push(div);
}
}
ThreadWatcher.dialog.innerHTML = '';
$.add(ThreadWatcher.dialog, nodes);
watched = watched[g.BOARD] || {};
_ref1 = g.BOARD.threads;
for (ID in _ref1) {
thread = _ref1[ID];
op = thread.posts[thread];
favicon = $('.favicon', op.nodes.post);
favicon.src = ID in watched ? Favicon["default"] : Favicon.empty;
}
},
cb: {
toggle: function() {
return ThreadWatcher.toggle(Get.postFromNode(this).thread);
},
x: function() {
var thread;
thread = this.nextElementSibling.pathname.split('/');
return ThreadWatcher.unwatch(thread[1], thread[3]);
},
post: function(e) {
var postID, threadID, _ref;
_ref = e.detail, postID = _ref.postID, threadID = _ref.threadID;
if (threadID === '0') {
if (Conf['Auto Watch']) {
return $.set('AutoWatch', +postID);
}
} else if (Conf['Auto Watch Reply']) {
return ThreadWatcher.watch(g.BOARD.threads[threadID]);
}
}
},
toggle: function(thread) {
var op;
op = thread.posts[thread];
if ($('.favicon', op.nodes.post).src === Favicon.empty) {
return ThreadWatcher.watch(thread);
} else {
return ThreadWatcher.unwatch(thread.board, thread.ID);
}
},
unwatch: function(board, threadID) {
var watched;
watched = $.get('WatchedThreads', {});
delete watched[board][threadID];
ThreadWatcher.refresh(watched);
return $.set('WatchedThreads', watched);
},
watch: function(thread) {
var watched, _name;
watched = $.get('WatchedThreads', {});
watched[_name = thread.board] || (watched[_name] = {});
watched[thread.board][thread] = {
href: "/" + thread.board + "/res/" + thread,
textContent: Get.threadExcerpt(thread)
};
ThreadWatcher.refresh(watched);
return $.set('WatchedThreads', watched);
}
};
QR = {
init: function() {
var link;
if (g.VIEW === 'catalog' || !Conf['Quick Reply']) {
return;
}
if (Conf['Hide Original Post Form']) {
$.addClass(doc, 'hide-original-post-form');
}
link = $.el('a', {
className: 'qr-shortcut',
textContent: 'Quick Reply',
href: 'javascript:;'
});
$.on(link, 'click', function() {
$.event('CloseMenu');
QR.open();
if (g.BOARD.ID === 'f') {
if (g.VIEW === 'index') {
QR.threadSelector.value = '9999';
}
} else if (g.VIEW === 'thread') {
QR.threadSelector.value = g.THREAD;
} else {
QR.threadSelector.value = 'new';
}
return $('textarea', QR.el).focus();
});
$.event('AddMenuEntry', {
type: 'header',
el: link,
order: 10
});
$.on(d, 'dragover', QR.dragOver);
$.on(d, 'drop', QR.dropFile);
$.on(d, 'dragstart dragend', QR.drag);
if (Conf['Persistent QR']) {
$.on(d, '4chanXInitFinished', QR.persist);
}
$.on(d, 'ThreadUpdate', function() {
if (g.DEAD) {
return QR.abort();
} else {
return QR.status();
}
});
return Post.prototype.callbacks.push({
name: 'Quick Reply',
cb: this.node
});
},
node: function() {
return $.on($('a[title="Quote this post"]', this.nodes.info), 'click', QR.quote);
},
persist: function() {
QR.open();
if (Conf['Auto Hide QR']) {
return QR.hide();
}
},
open: function() {
if (QR.el) {
QR.el.hidden = false;
QR.unhide();
return;
}
try {
return QR.dialog();
} catch (err) {
delete QR.el;
return Main.handleErrors({
message: 'Quick Reply dialog creation crashed.',
error: err
});
}
},
close: function() {
var i, spoiler, _i, _len, _ref;
QR.el.hidden = true;
QR.abort();
d.activeElement.blur();
$.rmClass(QR.el, 'dump');
_ref = QR.replies;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
i = _ref[_i];
QR.replies[0].rm();
}
QR.cooldown.auto = false;
QR.status();
QR.resetFileInput();
if (!Conf['Remember Spoiler'] && (spoiler = $.id('spoiler')).checked) {
spoiler.click();
}
return QR.cleanNotifications();
},
hide: function() {
d.activeElement.blur();
$.addClass(QR.el, 'autohide');
return $.id('autohide').checked = true;
},
unhide: function() {
$.rmClass(QR.el, 'autohide');
return $.id('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)) {
$('[autocomplete]', QR.el).focus();
}
if (d.hidden) {
alert(el.textContent);
}
return QR.lastNotifications.push(new Notification('warning', el));
},
lastNotifications: [],
cleanNotifications: function() {
var notification, _i, _len, _ref;
_ref = QR.lastNotifications;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
notification = _ref[_i];
notification.close();
}
return QR.lastNotification = [];
},
status: function(data) {
var disabled, input, value;
if (data == null) {
data = {};
}
if (!QR.el) {
return;
}
if (g.DEAD) {
value = 404;
disabled = true;
QR.cooldown.auto = false;
}
value = data.progress || QR.cooldown.seconds || value;
input = QR.status.input;
input.value = QR.cooldown.auto ? value ? "Auto " + value : 'Auto' : value || 'Submit';
return input.disabled = disabled || false;
},
cooldown: {
init: function() {
QR.cooldown.types = {
thread: (function() {
switch (g.BOARD) {
case 'q':
return 86400;
case 'b':
case 'soc':
case 'r9k':
return 600;
default:
return 300;
}
})(),
sage: g.BOARD === 'q' ? 600 : 60,
file: g.BOARD === 'q' ? 300 : 30,
post: g.BOARD === 'q' ? 60 : 30
};
QR.cooldown.cooldowns = $.get("" + g.BOARD + ".cooldown", {});
QR.cooldown.start();
return $.sync("" + g.BOARD + ".cooldown", QR.cooldown.sync);
},
start: function() {
if (QR.cooldown.isCounting) {
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, hasFile, isReply, isSage, start, type;
start = Date.now();
if (data.delay) {
cooldown = {
delay: data.delay
};
} else {
isSage = /sage/i.test(data.post.email);
hasFile = !!data.post.file;
isReply = data.isReply;
type = !isReply ? 'thread' : isSage ? 'sage' : hasFile ? 'file' : 'post';
cooldown = {
isReply: isReply,
isSage: isSage,
hasFile: hasFile,
timeout: start + QR.cooldown.types[type] * $.SECOND
};
}
QR.cooldown.cooldowns[start] = cooldown;
$.set("" + g.BOARD + ".cooldown", QR.cooldown.cooldowns);
return QR.cooldown.start();
},
unset: function(id) {
delete QR.cooldown.cooldowns[id];
return $.set("" + g.BOARD + ".cooldown", QR.cooldown.cooldowns);
},
count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, isSage, now, post, seconds, start, type, types, update, _ref;
if (Object.keys(QR.cooldown.cooldowns).length) {
setTimeout(QR.cooldown.count, 1000);
} else {
$["delete"]("" + g.BOARD + ".cooldown");
delete QR.cooldown.isCounting;
delete QR.cooldown.seconds;
QR.status();
return;
}
isReply = g.BOARD.ID === 'f' && g.VIEW === 'thread' ? true : QR.threadSelector.value !== 'new';
if (isReply) {
post = QR.replies[0];
isSage = /sage/i.test(post.email);
hasFile = !!post.file;
}
now = Date.now();
seconds = null;
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns;
for (start in cooldowns) {
cooldown = cooldowns[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) {
type = !isReply ? 'thread' : isSage && cooldown.isSage ? 'sage' : hasFile && cooldown.hasFile ? 'file' : 'post';
elapsed = Math.floor((now - start) / 1000);
if (elapsed >= 0) {
seconds = Math.max(seconds, types[type] - elapsed);
}
}
if (!((start <= now && now <= cooldown.timeout))) {
QR.cooldown.unset(start);
}
}
update = seconds !== null || !!QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
if (update) {
QR.status();
}
if (seconds === 0 && QR.cooldown.auto) {
return QR.submit();
}
}
},
quote: function(e) {
var caretPos, post, range, s, sel, selectionRoot, ta, text, thread;
if (e != null) {
e.preventDefault();
}
text = "";
sel = d.getSelection();
selectionRoot = $.x('ancestor::div[contains(@class,"postContainer")][1]', sel.anchorNode);
post = Get.postFromNode(this);
thread = g.BOARD.posts[Get.contextFromLink(this).thread];
if ((s = sel.toString().trim()) && post.nodes.root === selectionRoot) {
s = s.replace(/\n/g, '\n>');
text += ">" + s + "\n";
}
text = !text && post === thread && (!QR.el || QR.el.hidden) ? "" : ">>" + post + "\n" + text;
QR.open();
ta = $('textarea', QR.el);
if (QR.threadSelector && !ta.value && g.BOARD.ID !== 'f') {
QR.threadSelector.value = thread.ID;
}
caretPos = ta.selectionStart;
ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd);
range = caretPos + text.length;
ta.setSelectionRange(range, range);
ta.focus();
return ta.dispatchEvent(new Event('input'));
},
characterCount: function() {
var count, counter;
counter = QR.charaCounter;
count = this.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();
QR.fileInput.call(e.dataTransfer);
return $.addClass(QR.el, 'dump');
},
fileInput: function() {
var file, _i, _len, _ref;
QR.cleanNotifications();
if (this.files.length === 1) {
file = this.files[0];
if (file.size > this.max) {
QR.error("File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(this.max)) + ").");
QR.resetFileInput();
} else if (-1 === QR.mimeTypes.indexOf(file.type)) {
QR.error('Unsupported file type.');
QR.resetFileInput();
} else {
QR.selected.setFile(file);
}
return;
}
_ref = this.files;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.size > this.max) {
QR.error("File " + file.name + " is too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(this.max)) + ").");
} else if (-1 === QR.mimeTypes.indexOf(file.type)) {
QR.error("" + file.name + ": Unsupported file type.");
}
if (!QR.replies[QR.replies.length - 1].file) {
QR.replies[QR.replies.length - 1].setFile(file);
} else {
new QR.reply().setFile(file);
}
}
$.addClass(QR.el, 'dump');
return QR.resetFileInput();
},
resetFileInput: function() {
return $('[type=file]', QR.el).value = null;
},
replies: [],
reply: (function() {
function _Class() {
var persona, prev,
_this = this;
prev = QR.replies[QR.replies.length - 1];
persona = $.get('QR.persona', {});
this.name = prev ? prev.name : persona.name || null;
this.email = prev && !/^sage$/.test(prev.email) ? prev.email : persona.email || null;
this.sub = prev && Conf['Remember Subject'] ? prev.sub : Conf['Remember Subject'] ? persona.sub : null;
this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
this.com = null;
this.el = $.el('a', {
className: 'qrpreview',
draggable: true,
href: 'javascript:;',
innerHTML: '<a class=remove>×</a><label hidden><input type=checkbox> Spoiler</label><span></span>'
});
$('input', this.el).checked = this.spoiler;
$.on(this.el, 'click', function() {
return _this.select();
});
$.on($('.remove', this.el), 'click', function(e) {
e.stopPropagation();
return _this.rm();
});
$.on($('label', this.el), 'click', function(e) {
return e.stopPropagation();
});
$.on($('input', this.el), 'change', function(e) {
_this.spoiler = e.target.checked;
if (_this.el.id === 'selected') {
return $.id('spoiler').checked = _this.spoiler;
}
});
$.before($('#addReply', QR.el), this.el);
$.on(this.el, 'dragstart', this.dragStart);
$.on(this.el, 'dragenter', this.dragEnter);
$.on(this.el, 'dragleave', this.dragLeave);
$.on(this.el, 'dragover', this.dragOver);
$.on(this.el, 'dragend', this.dragEnd);
$.on(this.el, 'drop', this.drop);
QR.replies.push(this);
}
_Class.prototype.setFile = function(file) {
var fileURL, img,
_this = this;
this.file = file;
this.el.title = "" + file.name + " (" + ($.bytesToString(file.size)) + ")";
if (QR.spoiler) {
$('label', this.el).hidden = false;
}
if (!/^image/.test(file.type)) {
this.el.style.backgroundImage = null;
return;
}
if (!window.URL) {
return;
}
URL.revokeObjectURL(this.url);
fileURL = URL.createObjectURL(file);
img = $.el('img');
$.on(img, 'load', function() {
var c, data, i, l, s, ui8a, _i;
s = 90 * 3;
if (img.height < s || img.width < s) {
_this.url = fileURL;
_this.el.style.backgroundImage = "url(" + _this.url + ")";
return;
}
if (img.height <= img.width) {
img.width = s / img.height * img.width;
img.height = s;
} else {
img.height = s / img.width * img.height;
img.width = s;
}
c = $.el('canvas');
c.height = img.height;
c.width = img.width;
c.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
data = atob(c.toDataURL().split(',')[1]);
l = data.length;
ui8a = new Uint8Array(l);
for (i = _i = 0; 0 <= l ? _i < l : _i > l; i = 0 <= l ? ++_i : --_i) {
ui8a[i] = data.charCodeAt(i);
}
_this.url = URL.createObjectURL(new Blob([ui8a], {
type: 'image/png'
}));
_this.el.style.backgroundImage = "url(" + _this.url + ")";
return URL.revokeObjectURL(fileURL);
});
return img.src = fileURL;
};
_Class.prototype.rmFile = function() {
QR.resetFileInput();
delete this.file;
this.el.title = null;
this.el.style.backgroundImage = null;
if (QR.spoiler) {
$('label', this.el).hidden = true;
}
if (!window.URL) {
return;
}
return URL.revokeObjectURL(this.url);
};
_Class.prototype.select = function() {
var data, rectEl, rectList, _i, _len, _ref, _ref1;
if ((_ref = QR.selected) != null) {
_ref.el.id = null;
}
QR.selected = this;
this.el.id = 'selected';
rectEl = this.el.getBoundingClientRect();
rectList = this.el.parentNode.getBoundingClientRect();
this.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
_ref1 = ['name', 'email', 'sub', 'com'];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
data = _ref1[_i];
$("[name=" + data + "]", QR.el).value = this[data];
}
QR.characterCount.call($('textarea', QR.el));
return $('#spoiler', QR.el).checked = this.spoiler;
};
_Class.prototype.dragStart = function() {
return $.addClass(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, reply;
el = $('.drag', this.parentNode);
index = function(el) {
return Array.prototype.slice.call(el.parentNode.children).indexOf(el);
};
oldIndex = index(el);
newIndex = index(this);
if (oldIndex < newIndex) {
$.after(this, el);
} else {
$.before(this, el);
}
reply = QR.replies.splice(oldIndex, 1)[0];
return QR.replies.splice(newIndex, 0, reply);
};
_Class.prototype.dragEnd = function() {
var el;
$.rmClass(this, 'drag');
if (el = $('.over', this.parentNode)) {
return $.rmClass(el, 'over');
}
};
_Class.prototype.rm = function() {
var index;
QR.resetFileInput();
$.rm(this.el);
index = QR.replies.indexOf(this);
if (QR.replies.length === 1) {
new QR.reply().select();
} else if (this.el.id === 'selected') {
(QR.replies[index - 1] || QR.replies[index + 1]).select();
}
QR.replies.splice(index, 1);
if (!window.URL) {
return;
}
return URL.revokeObjectURL(this.url);
};
return _Class;
})(),
captcha: {
init: function() {
var _this = this;
if (-1 !== d.cookie.indexOf('pass_enabled=')) {
return;
}
if (!(this.isEnabled = !!$.id('captchaFormPart'))) {
return;
}
if ($.id('recaptcha_challenge_field_holder')) {
return this.ready();
} else {
this.onready = function() {
return _this.ready();
};
return $.on($.id('recaptcha_widget_div'), 'DOMNodeInserted', this.onready);
}
},
ready: function() {
var _this = this;
if (this.challenge = $.id('recaptcha_challenge_field_holder')) {
$.off($.id('recaptcha_widget_div'), 'DOMNodeInserted', this.onready);
delete this.onready;
} else {
return;
}
$.addClass(QR.el, 'captcha');
$.after($('.textarea', QR.el), $.el('div', {
className: 'captchaimg',
title: 'Reload',
innerHTML: '<img>'
}));
$.after($('.captchaimg', QR.el), $.el('div', {
className: 'captchainput',
innerHTML: '<input title=Verification class=field autocomplete=off size=1>'
}));
this.img = $('.captchaimg > img', QR.el);
this.input = $('.captchainput > input', QR.el);
$.on(this.img.parentNode, 'click', this.reload);
$.on(this.input, 'keydown', this.keydown);
$.on(this.challenge, 'DOMNodeInserted', function() {
return _this.load();
});
$.sync('captchas', function(arr) {
return _this.count(arr.length);
});
this.count($.get('captchas', []).length);
return this.reload();
},
save: function() {
var captcha, captchas, response;
if (!(response = this.input.value)) {
return;
}
captchas = $.get('captchas', []);
while ((captcha = captchas[0]) && captcha.time < Date.now()) {
captchas.shift();
}
captchas.push({
challenge: this.challenge.firstChild.value,
response: response,
time: this.timeout
});
$.set('captchas', captchas);
this.count(captchas.length);
return this.reload();
},
load: function() {
var challenge;
this.timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE;
challenge = this.challenge.firstChild.value;
this.img.alt = challenge;
this.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge;
return this.input.value = null;
},
count: function(count) {
this.input.placeholder = (function() {
switch (count) {
case 0:
return 'Verification (Shift + Enter to cache)';
case 1:
return 'Verification (1 cached captcha)';
default:
return "Verification (" + count + " cached captchas)";
}
})();
return this.input.alt = count;
},
reload: function(focus) {
$.unsafeWindow.Recaptcha.reload('t');
if (focus) {
return QR.captcha.input.focus();
}
},
keydown: function(e) {
var c;
c = QR.captcha;
if (e.keyCode === 8 && !c.input.value) {
c.reload();
} else if (e.keyCode === 13 && e.shiftKey) {
c.save();
} else {
return;
}
return e.preventDefault();
}
},
dialog: function() {
var fileInput, key, mimeTypes, name, span, spoiler, ta, thread, threads, _i, _len, _ref, _ref1;
QR.el = UI.dialog('qr', 'top:0;right:0;', "<div class=move>Quick Reply <input type=checkbox id=autohide title=Auto-hide><span> <a href=javascript:; class=close title=Close>×</a></span></div>\n<form>\n <div class=persona><input id=dump type=button title='Dump list' value=+><input name=name title=Name placeholder=Name class=field size=1><input name=email title=E-mail placeholder=E-mail class=field size=1><input name=sub title=Subject placeholder=Subject class=field size=1></div>\n <div id=replies><div id=repliesList><a id=addReply href=javascript:; title=\"Add a reply\">+</a></div></div>\n <div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span></div>\n <div><input type=file title=\"Shift+Click to remove the selected file.\" multiple size=16><input type=submit></div>\n <label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image</label>\n</form>");
mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace(/\w+/g, function(type) {
switch (type) {
case 'jpg':
return 'image/jpeg';
case 'pdf':
return 'application/pdf';
case 'swf':
return 'application/x-shockwave-flash';
default:
return "image/" + type;
}
});
QR.mimeTypes = mimeTypes.split(', ');
QR.mimeTypes.push('');
fileInput = $('input[type=file]', QR.el);
fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
if ($.engine !== 'presto') {
fileInput.accept = mimeTypes;
}
QR.spoiler = !!$('input[name=spoiler]');
spoiler = $('#spoilerLabel', QR.el);
spoiler.hidden = !QR.spoiler;
QR.charaCounter = $('#charCount', QR.el);
ta = $('textarea', QR.el);
span = $('.move > span', QR.el);
if (g.BOARD.ID === 'f') {
if (g.VIEW === 'index') {
QR.threadSelector = $('select[name=filetag]').cloneNode(true);
}
} else {
QR.threadSelector = $.el('select', {
title: 'Create a new thread / Reply to a thread'
});
threads = '<option value=new>New thread</option>';
_ref = g.BOARD.threads;
for (key in _ref) {
thread = _ref[key];
threads += "<option value=" + thread.ID + ">Thread No." + thread.ID + "</option>";
}
QR.threadSelector.innerHTML = threads;
if (g.VIEW === 'thread') {
QR.threadSelector.value = g.THREAD;
}
}
if (QR.threadSelector) {
$.prepend(span, QR.threadSelector);
}
$.on(span, 'mousedown', function(e) {
return e.stopPropagation();
});
$.on($('#autohide', QR.el), 'change', QR.toggleHide);
$.on($('.close', QR.el), 'click', QR.close);
$.on($('#dump', QR.el), 'click', function() {
return QR.el.classList.toggle('dump');
});
$.on($('#addReply', QR.el), 'click', function() {
return new QR.reply().select();
});
$.on($('form', QR.el), 'submit', QR.submit);
$.on(ta, 'input', function() {
return QR.selected.el.lastChild.textContent = this.value;
});
$.on(ta, 'input', QR.characterCount);
$.on(fileInput, 'change', QR.fileInput);
$.on(fileInput, 'click', function(e) {
if (e.shiftKey) {
return QR.selected.rmFile() || e.preventDefault();
}
});
$.on(spoiler.firstChild, 'change', function() {
return $('input', QR.selected.el).click();
});
new QR.reply().select();
_ref1 = ['name', 'email', 'sub', 'com'];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
name = _ref1[_i];
$.on($("[name=" + name + "]", QR.el), 'input', function() {
var _ref2;
QR.selected[this.name] = this.value;
if (QR.cooldown.auto && QR.selected === QR.replies[0] && (0 < (_ref2 = QR.cooldown.seconds) && _ref2 <= 5)) {
return QR.cooldown.auto = false;
}
});
}
QR.status.input = $('input[type=submit]', QR.el);
QR.status();
QR.cooldown.init();
QR.captcha.init();
$.add(d.body, QR.el);
return $.event('QRDialogCreation', null, QR.el);
},
submit: function(e) {
var callbacks, captcha, captchas, challenge, err, filetag, m, opts, post, reply, response, textOnly, threadID, _ref;
if (e != null) {
e.preventDefault();
}
if (QR.cooldown.seconds) {
QR.cooldown.auto = !QR.cooldown.auto;
QR.status();
return;
}
QR.abort();
reply = QR.replies[0];
if (g.BOARD.ID === 'f' && g.VIEW === 'index') {
filetag = QR.threadSelector.value;
threadID = 'new';
} else {
threadID = QR.threadSelector.value;
}
if (threadID === 'new') {
threadID = null;
if (((_ref = g.BOARD.ID) === 'vg' || _ref === 'q') && !reply.sub) {
err = 'New threads require a subject.';
} else if (!(reply.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) {
err = 'No file selected.';
} else if (g.BOARD.ID === 'f' && filetag === '9999') {
err = 'Invalid tag specified.';
}
} else if (!(reply.com || reply.file)) {
err = 'No file selected.';
}
if (QR.captcha.isEnabled && !err) {
captchas = $.get('captchas', []);
while ((captcha = captchas[0]) && captcha.time < Date.now()) {
captchas.shift();
}
if (captcha = captchas.shift()) {
challenge = captcha.challenge;
response = captcha.response;
} else {
challenge = QR.captcha.img.alt;
if (response = QR.captcha.input.value) {
QR.captcha.reload();
}
}
$.set('captchas', captchas);
QR.captcha.count(captchas.length);
if (!response) {
err = 'No valid captcha.';
} else {
response = response.trim();
if (!/\s/.test(response)) {
response = "" + response + " " + response;
}
}
}
if (err) {
QR.cooldown.auto = false;
QR.status();
QR.error(err);
return;
}
QR.cleanNotifications();
QR.cooldown.auto = QR.replies.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();
}
QR.status({
progress: '...'
});
post = {
resto: threadID,
name: reply.name,
email: reply.email,
sub: reply.sub,
com: reply.com,
upfile: reply.file,
filetag: filetag,
spoiler: reply.spoiler,
textonly: textOnly,
mode: 'regist',
pwd: (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value,
recaptcha_challenge_field: challenge,
recaptcha_response_field: response
};
callbacks = {
onload: function() {
return QR.response(this.response);
},
onerror: function() {
delete QR.ajax;
QR.cooldown.auto = false;
QR.status();
return QR.error($.el('a', {
href: '//www.4chan.org/banned',
target: '_blank',
textContent: 'Connection error, or you are banned.'
}));
}
};
opts = {
form: $.formData(post),
upCallbacks: {
onload: function() {
return QR.status({
progress: '...'
});
},
onprogress: function(e) {
return QR.status({
progress: "" + (Math.round(e.loaded / e.total * 100)) + "%"
});
}
}
};
return QR.ajax = $.ajax($.id('postForm').parentNode.action, callbacks, opts);
},
response: function(html) {
var ban, board, err, h1, persona, postID, reply, threadID, tmpDoc, _, _ref, _ref1;
delete QR.ajax;
tmpDoc = d.implementation.createHTMLDocument('');
tmpDoc.documentElement.innerHTML = html;
if (ban = $('.banType', tmpDoc)) {
board = $('.board', tmpDoc).innerHTML;
err = $.el('span', {
innerHTML: ban.textContent.toLowerCase() === 'banned' ? ("You are banned on " + board + "! ;_;<br>") + "Click <a href=//www.4chan.org/banned target=_blank>here</a> to see the reason." : ("You were issued a warning on " + board + " as " + ($('.nameBlock', tmpDoc).innerHTML) + ".<br>") + ("Reason: " + ($('.reason', tmpDoc).innerHTML))
});
} else if (err = tmpDoc.getElementById('errmsg')) {
if ((_ref = $('a', err)) != null) {
_ref.target = '_blank';
}
} else if (tmpDoc.title !== 'Post successful!') {
err = 'Connection error with sys.4chan.org.';
}
if (err) {
if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') {
if (/mistyped/i.test(err.textContent)) {
err = 'Error: You seem to have mistyped the CAPTCHA.';
}
QR.cooldown.auto = QR.captcha.isEnabled ? !!$.get('captchas', []).length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({
delay: 2
});
} else {
QR.cooldown.auto = false;
}
QR.status();
QR.error(err);
return;
}
h1 = $('h1', tmpDoc);
QR.cleanNotifications();
QR.lastNotifications.push(new Notification('success', h1.textContent, 5));
reply = QR.replies[0];
persona = $.get('QR.persona', {});
persona = {
name: reply.name,
email: /^sage$/.test(reply.email) ? persona.email : reply.email,
sub: Conf['Remember Subject'] ? reply.sub : null
};
$.set('QR.persona', persona);
_ref1 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref1[0], threadID = _ref1[1], postID = _ref1[2];
$.event('QRPostSuccessful', {
threadID: threadID,
postID: postID
}, QR.el);
QR.cooldown.set({
post: reply,
isReply: threadID !== '0'
});
QR.cooldown.auto = QR.replies.length > 1;
if (threadID === '0') {
$.open("/" + g.BOARD + "/res/" + postID);
} else if (g.VIEW === 'reply' && !QR.cooldown.auto) {
$.open("//boards.4chan.org/" + g.BOARD + "/res/" + threadID + "#p" + postID);
}
if (Conf['Persistent QR'] || QR.cooldown.auto) {
reply.rm();
} else {
QR.close();
}
QR.status();
return QR.resetFileInput();
},
abort: function() {
if (QR.ajax) {
QR.ajax.abort();
delete QR.ajax;
QR.error('QR upload aborted.');
}
return QR.status();
}
};
Board = (function() {
Board.prototype.toString = function() {
return this.ID;
};
function Board(ID) {
this.ID = ID;
this.threads = {};
this.posts = {};
g.boards[this] = this;
}
return Board;
})();
Thread = (function() {
Thread.prototype.callbacks = [];
Thread.prototype.toString = function() {
return this.ID;
};
function Thread(ID, board) {
this.board = board;
this.ID = +ID;
this.fullID = "" + this.board + "." + this.ID;
this.posts = {};
g.threads["" + board + "." + this] = board.threads[this] = this;
}
Thread.prototype.kill = function() {
this.isDead = true;
return this.timeOfDeath = Date.now();
};
return Thread;
})();
Post = (function() {
Post.prototype.callbacks = [];
Post.prototype.toString = function() {
return this.ID;
};
function Post(root, thread, board, that) {
var alt, anchor, capcode, date, email, file, fileInfo, flag, info, name, post, size, subject, thumb, tripcode, uniqueID, unit;
this.thread = thread;
this.board = board;
if (that == null) {
that = {};
}
this.ID = +root.id.slice(2);
this.fullID = "" + this.board + "." + this.ID;
post = $('.post', root);
info = $('.postInfo', post);
this.nodes = {
root: root,
post: post,
info: info,
comment: $('.postMessage', post),
quotelinks: [],
backlinks: info.getElementsByClassName('backlink')
};
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', info)) {
this.nodes.capcode = capcode;
this.info.capcode = capcode.textContent;
}
if (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();
if ((file = $('.file', post)) && (thumb = $('img[data-md5]', file))) {
alt = thumb.alt;
anchor = thumb.parentNode;
fileInfo = file.firstElementChild;
this.file = {
info: fileInfo,
text: fileInfo.firstElementChild,
thumb: thumb,
URL: anchor.href,
size: 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 = that.isArchived ? thumb.src : "" + location.protocol + "//thumbs.4chan.org/" + board + "/thumb/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg";
this.file.name = $('span[title]', fileInfo).title.replace(/%22/g, '"');
if (this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name)) {
this.file.dimensions = this.file.text.textContent.match(/\d+x\d+/)[0];
}
}
this.isReply = $.hasClass(post, 'reply');
this.clones = [];
g.posts["" + board + "." + this] = thread.posts[this] = board.posts[this] = this;
if (that.isArchived) {
this.kill();
}
}
Post.prototype.parseComment = function() {
var bq, data, i, node, nodes, text, _i, _j, _len, _ref, _ref1;
bq = this.nodes.comment.cloneNode(true);
_ref = $$('.abbr, .capcodeReplies, .exif, b', bq);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
$.rm(node);
}
text = [];
nodes = d.evaluate('.//br|.//text()', bq, null, 7, null);
for (i = _j = 0, _ref1 = nodes.snapshotLength; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
text.push((data = nodes.snapshotItem(i).data) ? data : '\n');
}
return this.info.comment = text.join('').trim().replace(/\s+$/gm, '');
};
Post.prototype.parseQuotes = function() {
var hash, pathname, quotelink, quotes, _i, _len, _ref;
quotes = {};
_ref = $$('.quotelink', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quotelink = _ref[_i];
hash = quotelink.hash;
if (!hash) {
continue;
}
pathname = quotelink.pathname;
if (/catalog$/.test(pathname)) {
continue;
}
if (quotelink.hostname !== 'boards.4chan.org') {
continue;
}
this.nodes.quotelinks.push(quotelink);
if (quotelink.parentNode.parentNode.className === 'capcodeReplies') {
continue;
}
quotes["" + (pathname.split('/')[1]) + "." + hash.slice(2)] = true;
}
if (this.isClone) {
return;
}
return this.quotes = Object.keys(quotes);
};
Post.prototype.kill = function(file, now) {
var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1;
now || (now = new Date());
if (file) {
this.file.isDead = true;
this.file.timeOfDeath = now;
$.addClass(this.nodes.root, 'deleted-file');
} else {
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: '[Deleted]'
});
$.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;
}
$.add(quotelink, $.tn('\u00A0(Dead)'));
$.addClass(quotelink, 'deadlink');
}
};
Post.prototype.addClone = function(context) {
return new Clone(this, context);
};
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.setAttribute('data-clone', index++);
}
};
return Post;
})();
Clone = (function(_super) {
__extends(Clone, _super);
function Clone(origin, context) {
var file, index, info, inline, inlined, key, nodes, post, root, val, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3;
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 = 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');
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.info = file.firstElementChild;
this.file.text = this.file.info.firstElementChild;
this.file.thumb = $('img[data-md5]', file);
}
if (origin.isDead) {
this.isDead = true;
}
this.isClone = true;
index = origin.clones.push(this) - 1;
root.setAttribute('data-clone', index);
}
return Clone;
})(Post);
Main = {
init: function() {
var flatten, initFeature, key, pathname, val;
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);
for (key in Conf) {
val = Conf[key];
Conf[key] = $.get(key, val);
}
pathname = location.pathname.split('/');
g.BOARD = new Board(pathname[1]);
g.VIEW = (function() {
switch (pathname[2]) {
case 'res':
return 'thread';
case 'catalog':
return 'catalog';
default:
return 'index';
}
})();
if (g.VIEW === 'thread') {
g.THREAD = +pathname[3];
}
switch (location.hostname) {
case 'sys.4chan.org':
return;
case 'images.4chan.org':
$.ready(function() {
var url;
if (Conf['404 Redirect'] && d.title === '4chan - 404 Not Found') {
url = Redirect.image(pathname[1], pathname[3]);
if (url) {
return location.href = url;
}
}
});
return;
}
initFeature = function(name, module) {
console.time("" + name + " initialization");
try {
return module.init();
} catch (err) {
return Main.handleErrors({
message: "\"" + name + "\" initialization crashed.",
error: err
});
} finally {
console.timeEnd("" + name + " initialization");
}
};
console.time('All initializations');
initFeature('Polyfill', Polyfill);
initFeature('Header', Header);
initFeature('Settings', Settings);
initFeature('Fourchan thingies', Fourchan);
initFeature('Resurrect Quotes', Quotify);
initFeature('Filter', Filter);
initFeature('Thread Hiding', ThreadHiding);
initFeature('Reply Hiding', ReplyHiding);
initFeature('Recursive', Recursive);
initFeature('Quick Reply', QR);
initFeature('Menu', Menu);
initFeature('Report Link', ReportLink);
initFeature('Thread Hiding (Menu)', ThreadHiding.menu);
initFeature('Reply Hiding (Menu)', ReplyHiding.menu);
initFeature('Delete Link', DeleteLink);
initFeature('Filter (Menu)', Filter.menu);
initFeature('Download Link', DownloadLink);
initFeature('Archive Link', ArchiveLink);
initFeature('Quote Inline', QuoteInline);
initFeature('Quote Preview', QuotePreview);
initFeature('Quote Backlinks', QuoteBacklink);
initFeature('Mark OP Quotes', QuoteOP);
initFeature('Mark Cross-thread Quotes', QuoteCT);
initFeature('Anonymize', Anonymize);
initFeature('Time Formatting', Time);
initFeature('Relative Post Dates', RelativeDates);
initFeature('File Info Formatting', FileInfo);
initFeature('Sauce', Sauce);
initFeature('Image Expansion', ImageExpand);
initFeature('Image Expansion (Menu)', ImageExpand.menu);
initFeature('Reveal Spoilers', RevealSpoilers);
initFeature('Auto-GIF', AutoGIF);
initFeature('Image Hover', ImageHover);
initFeature('Comment Expansion', ExpandComment);
initFeature('Thread Expansion', ExpandThread);
initFeature('Thread Excerpt', ThreadExcerpt);
initFeature('Favicon', Favicon);
initFeature('Unread', Unread);
initFeature('Thread Stats', ThreadStats);
initFeature('Thread Updater', ThreadUpdater);
initFeature('Thread Watcher', ThreadWatcher);
initFeature('Index Navigation', Nav);
initFeature('Keybinds', Keybinds);
console.timeEnd('All initializations');
$.on(d, '4chanMainInit', Main.initStyle);
return $.ready(Main.initReady);
},
initStyle: function() {
var MutationObserver, mainStyleSheet, observer, setStyle, style, styleSheets, _ref;
if ($('link[href*="favicon-status.ico"]', d.head)) {
return;
}
if ((_ref = $('link[href*=mobile]', d.head)) != null) {
_ref.disabled = true;
}
$.addClass(doc, $.engine);
$.addClass(doc, 'fourchan-x');
$.addStyle(Main.css);
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;
}
if (MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.OMutationObserver) {
observer = new MutationObserver(setStyle);
return observer.observe(mainStyleSheet, {
attributes: true,
attributeFilter: ['href']
});
} else {
return $.on(mainStyleSheet, 'DOMAttrModified', setStyle);
}
},
initReady: function() {
var board, boardChild, errors, posts, thread, threadChild, threads, _i, _j, _len, _len1, _ref, _ref1;
if (!$.hasClass(doc, 'fourchan-x')) {
Main.initStyle();
}
if (d.title === '4chan - 404 Not Found') {
if (Conf['404 Redirect'] && g.VIEW === 'thread') {
location.href = Redirect.to({
board: g.BOARD,
threadID: g.THREAD,
postID: location.hash
});
}
return;
}
if (board = $('.board')) {
threads = [];
posts = [];
_ref = board.children;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
boardChild = _ref[_i];
if (!$.hasClass(boardChild, 'thread')) {
continue;
}
thread = new Thread(boardChild.id.slice(1), g.BOARD);
threads.push(thread);
_ref1 = boardChild.children;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
threadChild = _ref1[_j];
if (!$.hasClass(threadChild, 'postContainer')) {
continue;
}
try {
posts.push(new Post(threadChild, thread, g.BOARD));
} catch (err) {
if (!errors) {
errors = [];
}
errors.push({
message: "Parsing of Post No." + (threadChild.id.match(/\d+/)) + " failed. Post will be skipped.",
error: err
});
}
}
}
if (errors) {
Main.handleErrors(errors);
}
Main.callbackNodes(Thread, threads);
Main.callbackNodes(Post, posts);
}
return $.event('4chanXInitFinished');
},
callbackNodes: function(klass, nodes) {
var callback, errors, i, len, node, _i, _j, _len, _ref;
len = nodes.length;
_ref = klass.prototype.callbacks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
callback = _ref[_i];
for (i = _j = 0; 0 <= len ? _j < len : _j > len; i = 0 <= len ? ++_j : --_j) {
node = nodes[i];
try {
callback.cb.call(node);
} catch (err) {
if (!errors) {
errors = [];
}
errors.push({
message: "\"" + callback.name + "\" crashed on " + klass.name + " No." + node + " (/" + node.board + "/).",
error: err
});
}
}
}
if (errors) {
return Main.handleErrors(errors);
}
},
handleErrors: function(errors) {
var div, error, logs, _i, _len;
if (!('length' in errors)) {
error = errors;
} else if (errors.length === 1) {
error = errors[0];
}
if (error) {
new Notification('error', Main.parseError(error), 15);
return;
}
div = $.el('div', {
innerHTML: "" + errors.length + " errors occured. [<a href=javascript:;>show</a>]"
});
$.on(div.lastElementChild, 'click', function() {
if (this.textContent === 'show') {
this.textContent = 'hide';
return logs.hidden = false;
} else {
this.textContent = 'show';
return logs.hidden = true;
}
});
logs = $.el('div', {
hidden: true
});
for (_i = 0, _len = errors.length; _i < _len; _i++) {
error = errors[_i];
$.add(logs, Main.parseError(error));
}
return new Notification('error', [div, logs], 30);
},
parseError: function(data) {
var error, message;
message = data.message, error = data.error;
$.log(message, error.stack);
message = $.el('div', {
textContent: message
});
error = $.el('div', {
textContent: error
});
return [message, error];
},
css: "/* General */\n.dialog {\nbox-shadow: 0 1px 2px rgba(0, 0, 0, .15);\nborder: 1px solid;\ndisplay: block;\npadding: 0;\n}\n.field {\nborder: 1px solid #CCC;\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\ncolor: #333;\nfont: 13px sans-serif;\nmargin: 0;\npadding: 2px 4px 3px;\noutline: none;\n-webkit-transition: color .25s, border-color .25s;\ntransition: color .25s, border-color .25s;\n}\n.field::-moz-placeholder,\n.field:hover::-moz-placeholder {\ncolor: #AAA !important;\n}\n.field:hover {\nborder-color: #999;\n}\n.field:hover, .field:focus {\ncolor: #000;\n}\n.move {\ncursor: move;\n}\nlabel {\ncursor: pointer;\n}\na[href=\"javascript:;\"] {\ntext-decoration: none;\n}\n.warning {\ncolor: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\ndisplay: block !important;\n}\n.post {\noverflow: visible !important;\n}\n[hidden] {\ndisplay: none !important;\n}\n\n/* fixed, z-index */\n#overlay,\n#qp, #ihover,\n#updater, #thread-stats,\n#navlinks, #header,\n#qr, #watcher {\nposition: fixed;\n}\n#overlay {\nz-index: 999;\n}\n#notifications {\nz-index: 80;\n}\n#qp, #ihover {\nz-index: 70;\n}\n#menu {\nz-index: 60;\n}\n#navlinks, #updater, #thread-stats {\nz-index: 50;\n}\n#header:hover {\nz-index: 40;\n}\n#qr {\nz-index: 30;\n}\n#header {\nz-index: 20;\n}\n#watcher {\nz-index: 10;\n}\n\n/* Header */\n.fourchan-x body {\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\nmargin-top: 2em;\n}\n.fourchan-x #boardNavDesktop,\n.fourchan-x #navtopright,\n.fourchan-x #boardNavDesktopFoot {\ndisplay: none !important;\n}\n#header {\ntop: 0;\nright: 0;\nleft: 0;\n}\n#header-bar {\nborder-width: 0 0 1px;\npadding: 4px;\nposition: relative;\n-webkit-transition: all .1s ease-in-out;\ntransition: all .1s ease-in-out;\n}\n#header-bar.autohide:not(:hover) {\nbox-shadow: none;\nmargin-bottom: -1em;\n-webkit-transform: translateY(-100%);\ntransform: translateY(-100%);\n-webkit-transition: all .75s .25s ease-in-out;\ntransition: all .75s .25s ease-in-out;\n}\n#toggle-header-bar {\ncursor: n-resize;\nleft: 0;\nright: 0;\nbottom: -8px;\nheight: 10px;\nposition: absolute;\n}\n#header-bar.autohide #toggle-header-bar {\ncursor: s-resize;\n}\n#header-bar a {\ntext-decoration: none;\npadding: 1px;\n}\n#header-bar > .menu-button {\nfloat: right;\npadding: 0;\n}\n\n/* Notifications */\n#notifications {\ntext-align: center;\n}\n.notification {\ncolor: #FFF;\nfont-weight: 700;\ntext-shadow: 0 1px 2px rgba(0, 0, 0, .5);\nbox-shadow: 0 1px 2px rgba(0, 0, 0, .15);\nborder-radius: 2px;\nmargin: 1px auto;\nwidth: 500px;\nmax-width: 100%;\nposition: relative;\n-webkit-transition: all .25s ease-in-out;\ntransition: all .25s ease-in-out;\n}\n.notification.error {\nbackground-color: hsla(0, 100%, 40%, .9);\n}\n.notification.warning {\nbackground-color: hsla(36, 100%, 40%, .9);\n}\n.notification.info {\nbackground-color: hsla(200, 100%, 40%, .9);\n}\n.notification.success {\nbackground-color: hsla(104, 100%, 40%, .9);\n}\n.notification > .close {\ncolor: white;\npadding: 4px 6px;\ntop: 0;\nright: 0;\nposition: absolute;\n}\n.message {\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\npadding: 4px 20px;\nmax-height: 200px;\nwidth: 100%;\noverflow: auto;\n}\n\n/* Settings */\n#overlay {\nbackground-color: rgba(0, 0, 0, .5);\ndisplay: -webkit-flex;\ndisplay: flex;\n-webkit-align-items: center;\nalign-items: center;\n-webkit-justify-content: center;\njustify-content: center;\nposition: fixed;\ntop: 0;\nleft: 0;\nheight: 100%;\nwidth: 100%;\n}\n#settings {\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\nbox-shadow: 0 0 15px rgba(0, 0, 0, .15);\nheight: 600px;\nmin-height: 0;\nmax-height: 100%;\nwidth: 900px;\nmin-width: 0;\nmax-width: 100%;\npadding: 3px;\ndisplay: -webkit-flex;\ndisplay: flex;\n-webkit-flex-direction: column;\nflex-direction: column;\n}\n#settings > nav {\ndisplay: -webkit-flex;\ndisplay: flex;\npadding: 2px 2px 0;\n}\n#settings > nav a {\ntext-decoration: underline;\n}\n#settings > nav a.close {\ntext-decoration: none;\npadding: 2px;\n}\n.sections-list {\n-webkit-flex: 1;\nflex: 1;\n}\n.section-container {\n-webkit-flex: 1;\nflex: 1;\nposition: relative;\n}\n.section-container > section {\nposition: absolute;\ntop: 0;\nright: 0;\nbottom: 0;\nleft: 0;\noverflow: auto;\n}\n.section-main ul,\n.section-sauce ul,\n.section-rice ul {\nlist-style: none;\nmargin: 0;\npadding: 8px;\n}\n.section-main li,\n.section-sauce li,\n.section-rice li {\npadding-left: 4px;\n}\n.section-main label {\ntext-decoration: underline;\n}\n.section-filter ul {\npadding: 0;\n}\n.section-filter li {\nmargin: 10px 40px;\n}\n.section-filter textarea {\nheight: 500px;\n}\n.section-sauce textarea {\nheight: 350px;\n}\n#settings textarea {\nfont-family: monospace;\nmin-width: 100%;\nmax-width: 100%;\n}\n#settings code {\ncolor: #000;\nbackground-color: #FFF;\npadding: 0 2px;\n}\n.unscroll {\noverflow: hidden;\n}\n\n/* Thread Updater */\n#updater:not(:hover) {\nbackground: none;\nborder: none;\nbox-shadow: none;\n}\n#updater > .move {\npadding: 0 3px;\n}\n#updater > div:last-child {\ntext-align: center;\n}\n#updater input[type=number] {\nwidth: 4em;\n}\n#updater:not(:hover) > div:not(.move) {\ndisplay: none;\n}\n.new {\ncolor: limegreen;\n}\n\n/* Thread Watcher */\n#watcher {\npadding-bottom: 3px;\nposition: absolute;\noverflow: hidden;\nwhite-space: nowrap;\n}\n#watcher:not(:hover) {\nmax-height: 220px;\n}\n#watcher > .move {\npadding-top: 3px;\n}\n#watcher > div {\nmax-width: 200px;\noverflow: hidden;\npadding-left: 3px;\npadding-right: 3px;\ntext-overflow: ellipsis;\n}\n#watcher a {\ntext-decoration: none;\n}\n\n/* Thread Stats */\n#thread-stats {\nbackground: none;\nborder: none;\nbox-shadow: none;\n}\n\n/* Quote */\n.deadlink {\ntext-decoration: none !important;\n}\n.backlink.deadlink, .quotelink.deadlink {\ntext-decoration: underline !important;\n}\n.inlined {\nopacity: .5;\n}\n#qp input, .forwarded {\ndisplay: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\ntext-decoration: none;\nborder-bottom: 1px dashed;\n}\n.filtered {\ntext-decoration: underline line-through;\n}\n.inline {\nborder: 1px solid;\ndisplay: table;\nmargin: 2px 0;\n}\n.inline .post {\nborder: 0 !important;\nbackground-color: transparent !important;\ndisplay: table !important;\nmargin: 0 !important;\npadding: 1px 2px !important;\n}\n#qp {\npadding: 2px 2px 5px;\n}\n#qp .post {\nborder: none;\nmargin: 0;\npadding: 0;\n}\n#qp img {\nmax-height: 300px;\nmax-width: 500px;\n}\n.qphl {\nbox-shadow: 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* File */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\ndisplay: none;\n}\n:root.fit-width .full-image {\nmax-width: 100%;\n}\n:root.gecko.fit-width .full-image,\n:root.presto.fit-width .full-image {\nwidth: 100%;\n}\n.expanded-image > .op > .file::after {\ncontent: '';\nclear: both;\ndisplay: table;\n}\n#ihover {\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\nmax-height: 100%;\nmax-width: 75%;\npadding-bottom: 16px;\n}\n\n/* Index/Reply Navigation */\n#navlinks {\nfont-size: 16px;\ntop: 25px;\nright: 10px;\n}\n\n/* Filter */\n.opContainer.filter-highlight {\nbox-shadow: inset 5px 0 rgba(255, 0, 0, .5);\n}\n.opContainer.filter-highlight.qphl {\nbox-shadow: inset 5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n.filter-highlight > .reply {\nbox-shadow: -5px 0 rgba(255, 0, 0, .5);\n}\n.filter-highlight > .reply.qphl {\nbox-shadow: -5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* Thread & Reply Hiding */\n.hide-thread-button,\n.hide-reply-button {\nfloat: left;\nmargin-right: 2px;\n}\n.stub ~ .sideArrows,\n.stub ~ .hide-reply-button,\n.stub ~ .post {\ndisplay: none !important;\n}\n\n/* QR */\n.hide-original-post-form #postForm,\n.hide-original-post-form .postingMode {\ndisplay: none;\n}\n#qr > .move {\nmin-width: 300px;\noverflow: hidden;\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\npadding: 0 2px;\n}\n#qr > .move > span {\nfloat: right;\n}\n#autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning {\ncursor: pointer;\n}\n#qr select {\nmargin: 0;\n}\n#dump {\nbackground: -webkit-linear-gradient(#EEE, #CCC);\nbackground: linear-gradient(#EEE, #CCC);\nborder: 1px solid #CCC;\nmargin: 0;\npadding: 2px 4px 3px;\noutline: none;\nwidth: 30px;\n}\n.gecko #dump {\npadding: 1px 0 2px;\nwidth: 10%;\n}\n#dump:hover, #dump:focus {\nbackground: -webkit-linear-gradient(#FFF, #DDD);\nbackground: linear-gradient(#FFF, #DDD);\n}\n#dump:active, .dump #dump:not(:hover):not(:focus) {\nbackground: -webkit-linear-gradient(#CCC, #DDD);\nbackground: linear-gradient(#CCC, #DDD);\n}\n#qr:not(.dump) #replies, .dump > form > label {\ndisplay: none;\n}\n#replies {\ndisplay: block;\nheight: 100px;\nposition: relative;\n-webkit-user-select: none;\n-moz-user-select: none;\n-o-user-select: none;\nuser-select: none;\n}\n#replies > div {\ncounter-reset: qrpreviews;\ntop: 0; right: 0; bottom: 0; left: 0;\nmargin: 0; padding: 0;\noverflow: hidden;\nposition: absolute;\nwhite-space: pre;\n}\n#replies > div:hover {\nbottom: -10px;\noverflow-x: auto;\nz-index: 1;\n}\n.qrpreview {\nbackground-position: 50% 20%;\nbackground-size: cover;\nborder: 1px solid #808080;\ncolor: #FFF !important;\nfont-size: 12px;\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\ncursor: move;\ndisplay: inline-block;\nheight: 90px; width: 90px;\nmargin: 5px; padding: 2px;\nopacity: .6;\noutline: none;\noverflow: hidden;\nposition: relative;\ntext-shadow: 0 1px 1px #000;\n-webkit-transition: opacity .25s ease-in-out;\ntransition: opacity .25s ease-in-out;\nvertical-align: top;\n}\n.qrpreview:hover, .qrpreview:focus {\nopacity: .9;\ncolor: #FFF !important;\n}\n.qrpreview#selected {\nopacity: 1;\n}\n.qrpreview::before {\ncounter-increment: qrpreviews;\ncontent: counter(qrpreviews);\nfont-weight: 700;\ntext-shadow: 0 0 3px #000, 0 0 5px #000;\nposition: absolute;\ntop: 3px; right: 3px;\n}\n.qrpreview.drag {\nborder-color: red;\nborder-style: dashed;\n}\n.qrpreview.over {\nborder-color: #FFF;\nborder-style: dashed;\n}\n.remove {\ncolor: #E00 !important;\nfont-weight: 700;\npadding: 3px;\n}\n.remove:hover::after {\ncontent: ' Remove';\n}\n.qrpreview > label {\nbackground: rgba(0, 0, 0, .5);\nright: 0; bottom: 0; left: 0;\nposition: absolute;\ntext-align: center;\n}\n.qrpreview > label > input {\nmargin: 1px 0;\nvertical-align: bottom;\n}\n#addReply {\nfont-size: 3.5em;\nline-height: 100px;\n}\n.persona {\ndisplay: -webkit-flex;\ndisplay: flex;\n}\n.persona .field {\n-webkit-flex: 1;\nflex: 1;\n}\n.gecko .persona .field {\nwidth: 30%;\n}\n#qr textarea.field {\ndisplay: -webkit-box;\nmin-height: 160px;\nmin-width: 100%;\n}\n#qr.captcha textarea.field {\nmin-height: 120px;\n}\n.textarea {\nposition: relative;\n}\n#charCount {\ncolor: #000;\nbackground: hsla(0, 0%, 100%, .5);\nfont-size: 8pt;\nmargin: 1px;\nposition: absolute;\nbottom: 0;\nright: 0;\npointer-events: none;\n}\n#charCount.warning {\ncolor: red;\n}\n.captchainput > .field {\nmin-width: 100%;\n}\n.captchaimg {\nbackground: #FFF;\noutline: 1px solid #CCC;\noutline-offset: -1px;\ntext-align: center;\n}\n.captchaimg > img {\ndisplay: block;\nheight: 57px;\nwidth: 300px;\n}\n#qr [type=file] {\nmargin: 1px 0;\nwidth: 70%;\n}\n#qr [type=submit] {\nmargin: 1px 0;\npadding: 1px; /* not Gecko */\nwidth: 30%;\n}\n.gecko #qr [type=submit] {\npadding: 0 1px; /* Gecko does not respect box-sizing: border-box */\n}\n\n/* Menu */\n.menu-button {\ndisplay: inline-block;\n}\n.menu-button > span {\nborder-top: 6px solid;\nborder-right: 4px solid transparent;\nborder-left: 4px solid transparent;\ndisplay: inline-block;\nmargin: 2px;\nvertical-align: middle;\n}\n#menu {\nborder-bottom: 0;\ndisplay: -webkit-flex;\ndisplay: flex;\n-webkit-flex-direction: column;\nflex-direction: column;\nposition: absolute;\noutline: none;\n}\n.entry {\ncursor: pointer;\noutline: none;\npadding: 3px 7px;\nposition: relative;\ntext-decoration: none;\nwhite-space: nowrap;\n/* XXX firefox fix */\ntop: 0;\nright: 0;\nbottom: 0;\nleft: 0;\n}\n.entry.has-submenu {\npadding-right: 20px;\n}\n.has-submenu::after {\ncontent: '';\nborder-left: 6px solid;\nborder-top: 4px solid transparent;\nborder-bottom: 4px solid transparent;\ndisplay: inline-block;\nmargin: 4px;\nposition: absolute;\nright: 3px;\n}\n.has-submenu:not(.focused) > .submenu {\ndisplay: none;\n}\n.submenu {\nborder-bottom: 0;\ndisplay: -webkit-flex;\ndisplay: flex;\n-webkit-flex-direction: column;\nflex-direction: column;\nposition: absolute;\nmargin: -1px 0;\n}\n.entry input {\nmargin: 0;\n}\n\n/* General */\n:root.yotsuba .dialog {\nbackground-color: #F0E0D6;\nborder-color: #D9BFB7;\n}\n:root.yotsuba .field:focus {\nborder-color: #EA8;\n}\n\n/* Header */\n:root.yotsuba #header-bar {\nfont-size: 9pt;\ncolor: #B86;\n}\n:root.yotsuba #header-bar a {\ncolor: #800000;\n}\n\n/* Quote */\n:root.yotsuba .backlink.deadlink {\ncolor: #00E !important;\n}\n:root.yotsuba .inline {\nborder-color: #D9BFB7;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.yotsuba .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.yotsuba .entry {\nborder-bottom: 1px solid #D9BFB7;\n}\n:root.yotsuba .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* General */\n:root.yotsuba-b .dialog {\nbackground-color: #D6DAF0;\nborder-color: #B7C5D9;\n}\n:root.yotsuba-b .field:focus {\nborder-color: #98E;\n}\n\n/* Header */\n:root.yotsuba-b #header-bar {\nfont-size: 9pt;\ncolor: #89A;\n}\n:root.yotsuba-b #header-bar a {\ncolor: #34345C;\n}\n\n/* Quote */\n:root.yotsuba-b .backlink.deadlink {\ncolor: #34345C !important;\n}\n:root.yotsuba-b .inline {\nborder-color: #B7C5D9;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.yotsuba-b .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.yotsuba-b .entry {\nborder-bottom: 1px solid #B7C5D9;\n}\n:root.yotsuba-b .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* General */\n:root.futaba .dialog {\nbackground-color: #F0E0D6;\nborder-color: #D9BFB7;\n}\n:root.futaba .field:focus {\nborder-color: #EA8;\n}\n\n/* Header */\n:root.futaba #header-bar {\nfont-size: 11pt;\ncolor: #B86;\n}\n:root.futaba #header-bar a {\ncolor: #800000;\n}\n\n/* Quote */\n:root.futaba .backlink.deadlink {\ncolor: #00E !important;\n}\n:root.futaba .inline {\nborder-color: #D9BFB7;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.futaba .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.futaba .entry {\nborder-bottom: 1px solid #D9BFB7;\n}\n:root.futaba .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* General */\n:root.burichan .dialog {\nbackground-color: #D6DAF0;\nborder-color: #B7C5D9;\n}\n:root.burichan .field:focus {\nborder-color: #98E;\n}\n\n/* Header */\n:root.burichan #header-bar {\nfont-size: 11pt;\ncolor: #89A;\n}\n:root.burichan #header-bar a {\ncolor: #34345C;\n}\n\n/* Quote */\n:root.burichan .backlink.deadlink {\ncolor: #34345C !important;\n}\n:root.burichan .inline {\nborder-color: #B7C5D9;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.burichan .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.burichan .entry {\nborder-bottom: 1px solid #B7C5D9;\n}\n:root.burichan .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* General */\n:root.tomorrow .dialog {\nbackground-color: #282A2E;\nborder-color: #111;\n}\n:root.tomorrow .field:focus {\nborder-color: #000;\n}\n\n/* Header */\n:root.tomorrow #header-bar {\nfont-size: 9pt;\ncolor: #C5C8C6;\n}\n:root.tomorrow #header-bar a {\ncolor: #81A2BE;\n}\n\n/* Quote */\n:root.tomorrow .backlink.deadlink {\ncolor: #81A2BE !important;\n}\n:root.tomorrow .inline {\nborder-color: #111;\nbackground-color: rgba(0, 0, 0, .14);\n}\n\n/* QR */\n:root.tomorrow .qrpreview {\nbackground-color: rgba(255, 255, 255, .15);\n}\n\n/* Menu */\n:root.tomorrow .entry {\nborder-bottom: 1px solid #111;\n}\n:root.tomorrow .focused.entry {\nbackground: rgba(0, 0, 0, .33);\n}\n\n/* General */\n:root.photon .dialog {\nbackground-color: #DDD;\nborder-color: #CCC;\n}\n:root.photon .field:focus {\nborder-color: #EA8;\n}\n\n/* Header */\n:root.photon #header-bar {\nfont-size: 9pt;\ncolor: #333;\n}\n:root.photon #header-bar a {\ncolor: #FF6600;\n}\n\n/* Quote */\n:root.photon .backlink.deadlink {\ncolor: #F60 !important;\n}\n:root.photon .inline {\nborder-color: #CCC;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.photon .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.photon .entry {\nborder-bottom: 1px solid #CCC;\n}\n:root.photon .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n"
};
Main.init();
}).call(this);