7877 lines
305 KiB
JavaScript
7877 lines
305 KiB
JavaScript
// ==UserScript==
|
||
// @name 4chan x
|
||
// @namespace aeosynth
|
||
// @version 1.0.11
|
||
// @description Old v2 version to point to new 4chan X.
|
||
// @copyright 2013-2013 Zixaphir <zixaphirmoxphar@gmail.com>
|
||
// @copyright 2013-2013 Jordan Bates <saudrapsmann@gmail.com>
|
||
// @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 *://*.4chan.org/*
|
||
// @grant GM_getValue
|
||
// @grant GM_setValue
|
||
// @grant GM_deleteValue
|
||
// @grant GM_openInTab
|
||
// @run-at document-start
|
||
// @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/4chan_x.meta.js
|
||
// @downloadURL https://github.com/seaweedchan/4chan-x/raw/stable/4chan_x.user.js
|
||
// @icon data:image/gif;base64,R0lGODlhYAAQAJEAAGbMM////wAAAP///yH5BAEAAAMALAAAAABgABAAAAL8nI+py+0Po5y02ruEFmh7PnxbJ3KecR4pqoWr2iajIgR2AIb3bu43WPPhdENiLmg7Jn25EODZFDwBTSIPJRwir1tuElvshrNf1RR4lv2QPZzmG9zA16c4uO3+WeVl55Qq9RdVZlc4p3VYdcfXBpSo0ufyJ1gVx2j0huhmWKSW1Xhpd0czSZVBhucl1Hj6acVkBNnZIcggdgh7y0qTFIoaOypTqoiZCTpjiRwp66jp8RhsVmrq2bzbsbz3AcxpDfztBxUIVT0qij0LtsoN3c1MSx5eSQiNTqzL7mzed+4khfZPBrIRL1jEoFHCRYw6A1usKGiiQwgMFCtalFAAADs=
|
||
// ==/UserScript==
|
||
|
||
/*
|
||
* 4chan x - Version 1.0.11 - 2013-04-25
|
||
*
|
||
* Licensed under the MIT license.
|
||
* https://github.com/seaweedchan/4chan-x/blob/4chanX/LICENSE
|
||
*
|
||
* Appchan X Copyright © 2013-2013 Zixaphir <zixaphirmoxphar@gmail.com>
|
||
* http://zixaphir.github.io/appchan-x/
|
||
* 4chan x Copyright © 2009-2011 James Campos <james.r.campos@gmail.com>
|
||
* https://github.com/aeosynth/4chan-x
|
||
* 4chan x Copyright © 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||
* https://4chan-x.just-believe.in/
|
||
* 4chan x Copyright © 2013-2013 Jordan Bates <saudrapsmann@gmail.com>
|
||
* http://seaweedchan.github.io/4chan-x/
|
||
* 4chan x Copyright © 2012-2013 ihavenoface
|
||
* http://ihavenoface.github.io/4chan-x/
|
||
* OneeChan Copyright © 2011-2013 Jordan Bates <saudrapsmann@gmail.com>
|
||
* http://seaweedchan.github.io/oneechan/
|
||
* 4chan SS Copyright © 2011-2013 Ahodesuka
|
||
* https://github.com/ahodesuka/4chan-Style-Script/
|
||
* Raphael Icons Copyright © 2013 Dmitry Baranovskiy
|
||
* http://raphaeljs.com/icons/
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person
|
||
* obtaining a copy of this software and associated documentation
|
||
* files (the "Software"), to deal in the Software without
|
||
* restriction, including without limitation the rights to use,
|
||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
* copies of the Software, and to permit persons to whom the
|
||
* Software is furnished to do so, subject to the following
|
||
* conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be
|
||
* included in all copies or substantial portions of the Software.
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||
* OTHER DEALINGS IN THE SOFTWARE.
|
||
*
|
||
* Contributors:
|
||
* aeosynth
|
||
* mayhemydg
|
||
* noface
|
||
* !K.WeEabo0o
|
||
* blaise
|
||
* that4chanwolf
|
||
* desuwa
|
||
* seaweed
|
||
* e000
|
||
* ahodesuka
|
||
* Shou
|
||
* ferongr
|
||
* xat
|
||
* Ongpot
|
||
* thisisanon
|
||
* Anonymous
|
||
* Seiba
|
||
* herpaderpderp
|
||
* WakiMiko
|
||
* btmcsweeney
|
||
* AppleBloom
|
||
*
|
||
* All the people who've taken the time to write bug reports.
|
||
*
|
||
* Thank you.
|
||
*/
|
||
|
||
/*
|
||
* Linkify based on:
|
||
* http://downloads.mozdev.org/greasemonkey/linkify.user.js
|
||
* https://github.com/MayhemYDG/LinkifyPlusFork
|
||
*
|
||
* Originally written by Anthony Lieuallen of http://arantius.com/
|
||
* Licensed for unlimited modification and redistribution as long as
|
||
* this notice is kept intact.
|
||
*/
|
||
|
||
/*
|
||
* JSColor, JavaScript Color Picker
|
||
*
|
||
* @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
|
||
* @author Jan Odvarko, http://odvarko.cz
|
||
* @link http://JSColor.com
|
||
*/
|
||
|
||
(function() {
|
||
var $, $$, AnnouncementHiding, Anonymize, ArchiveLink, BanChecker, Build, CatalogLinks, Conf, Config, CustomNavigation, DeleteLink, DownloadLink, EmbedLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Get, IDColor, ImageExpand, ImageHover, ImageReplace, Keybinds, Linkify, Main, MarkOwn, Markdown, Menu, MutationObserver, Nav, Navigation, Options, Prefetch, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, RelativeDates, RemoveSpoilers, ReplyHideLink, ReplyHiding, ReportLink, RevealSpoilers, Sauce, StrikethroughQuotes, Style, ThreadHideLink, ThreadHiding, ThreadStats, Time, TitlePost, UI, Unread, UpdateAlert, Updater, Watcher, d, g, userNavigation, _base,
|
||
__slice = [].slice;
|
||
|
||
Config = {
|
||
main: {
|
||
Enhancing: {
|
||
'Catalog Links': [true, 'Turn Navigation links into links to each board\'s catalog.'],
|
||
'External Catalog': [false, 'Link to external catalog instead of the internal one.'],
|
||
'404 Redirect': [true, 'Redirect dead threads and images'],
|
||
'Keybinds': [true, 'Binds actions to keys'],
|
||
'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'],
|
||
'Relative Post Dates': [false, 'Display dates as "3 minutes ago" f.e., tooltip shows the timestamp'],
|
||
'File Info Formatting': [true, 'Reformats the file information'],
|
||
'Comment Expansion': [true, 'Expand too long comments'],
|
||
'Thread Expansion': [true, 'View all replies'],
|
||
'Index Navigation': [false, 'Navigate to previous / next thread'],
|
||
'Reply Navigation': [false, 'Navigate to top / bottom of thread'],
|
||
'Custom Navigation': [false, 'Customize your Navigation bar.'],
|
||
'Append Delimiters': [false, 'Adds delimiters before and after custom navigation.'],
|
||
'Check for Updates': [true, 'Check for updated versions of 4chan X'],
|
||
'Check for Bans': [false, 'Check ban status and prepend it to the top of the page.'],
|
||
'Check for Bans constantly': [false, 'Optain ban status on every refresh. Note that this will cause delay on getting the result.'],
|
||
'Custom CSS': [false, 'Add your own CSS to 4chan.'],
|
||
'Emoji': [false, 'Adds icons next to names for different emails']
|
||
},
|
||
Linkification: {
|
||
'Linkify': [true, 'Convert text into links where applicable.'],
|
||
'Embedding': [true, 'Embed supported services.'],
|
||
'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud']
|
||
},
|
||
Filtering: {
|
||
'Announcement Hiding': [false, 'Allow toggling of announcement. New announcements will unhide automatically.'],
|
||
'Anonymize': [false, 'Make everybody anonymous'],
|
||
'Filter': [true, 'Self-moderation placebo'],
|
||
'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'],
|
||
'Reply Hiding': [false, 'Hide single replies'],
|
||
'Thread Hiding': [false, 'Hide entire threads'],
|
||
'Show Stubs': [true, 'Of hidden threads / replies']
|
||
},
|
||
Imaging: {
|
||
'Image Expansion': [true, 'Expand images'],
|
||
'Image Hover': [false, 'Show full image on mouseover'],
|
||
'Sauce': [true, 'Add sauce to images'],
|
||
'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail'],
|
||
'Don\'t Expand Spoilers': [true, 'Don\'t expand spoilers when using ImageExpand.'],
|
||
'Expand From Current': [false, 'Expand images from current position to thread end.'],
|
||
'Fappe Tyme': [false, 'Toggle display of posts without images.'],
|
||
'Prefetch': [false, 'Prefetch images.'],
|
||
'Replace GIF': [false, 'Replace thumbnail of gifs with its actual image.'],
|
||
'Replace PNG': [false, 'Replace pngs.'],
|
||
'Replace JPG': [false, 'Replace jpgs.']
|
||
},
|
||
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': [false, 'Add a download with original filename link to the menu. Chrome-only currently.'],
|
||
'Archive Link': [true, 'Add an archive link to the menu.'],
|
||
'Embed Link': [true, 'Add an embed link to the menu to embed all supported formats in a post.'],
|
||
'Thread Hiding Link': [true, 'Add a link to hide entire threads.'],
|
||
'Reply Hiding Link': [true, 'Add a link to hide single replies.']
|
||
},
|
||
Monitoring: {
|
||
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'],
|
||
'Optional Increase': [false, 'Increase value of Updater over time.'],
|
||
'Interval per board': [false, 'Change the intervals of updates on a board-by-board basis.'],
|
||
'Unread Count': [true, 'Show unread post count in tab title'],
|
||
'Unread Favicon': [true, 'Show a different favicon when there are unread posts'],
|
||
'Post in Title': [true, 'Show the op\'s post in the tab title'],
|
||
'Thread Stats': [true, 'Display reply and image count'],
|
||
'Merged Updater and Stats': [false, 'Merges the updater and thread stats into one container.'],
|
||
'Thread Watcher': [false, 'Bookmark threads'],
|
||
'Auto Watch': [true, 'Automatically watch threads that you start'],
|
||
'Auto Watch Reply': [false, 'Automatically watch threads that you reply to'],
|
||
'Color user IDs': [false, 'Assign unique colors to user IDs on boards that use them'],
|
||
'Remove Spoilers': [false, 'Remove all spoilers in text.'],
|
||
'Indicate Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled.']
|
||
},
|
||
Posting: {
|
||
'Quick Reply': [true, 'Reply without leaving the page'],
|
||
'Cooldown': [true, 'Prevent "flood detected" errors.'],
|
||
'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'],
|
||
'Auto Hide QR': [false, 'Automatically hide the quick reply when posting.'],
|
||
'Open Reply in New Tab': [false, 'Open replies in a new tab that are made from the main board.'],
|
||
'Per Board Persona': [false, 'Remember Name, Email, Subject, etc per board instead of globally.'],
|
||
'Remember QR size': [false, 'Remember the size of the Quick reply (Firefox only).'],
|
||
'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'],
|
||
'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'],
|
||
'Remember Sage': [false, 'Remember email even if it contains sage.'],
|
||
'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR'],
|
||
'Markdown': [false, 'Code, italic, bold, italic bold, double struck - `, *, **, ***, ||, respectively. _ can be used instead of *.']
|
||
},
|
||
Quoting: {
|
||
'Quote Backlinks': [true, 'Add quote backlinks'],
|
||
'OP Backlinks': [false, 'Add backlinks to the OP'],
|
||
'Quote Highlighting': [true, 'Highlight the previewed post'],
|
||
'Quote Inline': [true, 'Show quoted post inline on quote click'],
|
||
'Quote Hash Navigation': [false, 'Show a "#" to jump around the thread as if Quote Inline were disabled.'],
|
||
'Quote Preview': [true, 'Show quote content on hover'],
|
||
'Resurrect Quotes': [true, 'Linkify dead quotes to archives'],
|
||
'Indicate OP quote': [true, 'Add \'(OP)\' to OP quotes'],
|
||
'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes'],
|
||
'Indicate Own Posts': [true, 'Add (you) to posts you\'ve authored.'],
|
||
'Forward Hiding': [true, 'Hide original posts of inlined backlinks']
|
||
}
|
||
},
|
||
filter: {
|
||
name: "# Filter any namefags:\n#/^(?!Anonymous$)/",
|
||
uniqueid: "# Filter a specific ID:\n#/Txhvk1Tl/",
|
||
tripcode: "# Filter any tripfags\n#/^!/",
|
||
mod: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for moot:\n#/Admin$/;highlight:moot;op:yes",
|
||
email: "# Filter any e-mails that are not `sage` on /a/ and /jp/:\n#/^(?!sage$)/;boards:a,jp",
|
||
subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only'",
|
||
comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g",
|
||
country: '',
|
||
filename: '',
|
||
dimensions: "# Highlight potential wallpapers:\n#/1920x1080/;op:yes;highlight;top:no;boards:w,wg",
|
||
filesize: '',
|
||
md5: ''
|
||
},
|
||
sauces: "http://iqdb.org/?url=$1\nhttp://www.google.com/searchbyimage?image_url=$1\n#http://tineye.com/search?url=$1\n#http://saucenao.com/search.php?db=999&url=$1\n#http://3d.iqdb.org/?url=$1\n#http://regex.info/exif.cgi?imgurl=$2\n# uploaders:\n#http://imgur.com/upload?url=$2;text:Upload to imgur\n#http://omploader.org/upload?url1=$2;text:Upload to omploader\n# \"View Same\" in archives:\n#http://archive.foolz.us/_/search/image/$3/;text:View same on foolz\n#http://archive.foolz.us/$4/search/image/$3/;text:View same on foolz /$4/\n#https://archive.installgentoo.net/$4/image/$3;text:View same on installgentoo /$4/",
|
||
time: '%m/%d/%y(%a)%H:%M',
|
||
backlink: '>>%id',
|
||
fileInfo: '%L (%p%s, %r)',
|
||
favicon: 'ferongr',
|
||
sageEmoji: 'appchan',
|
||
emojiPos: 'before',
|
||
updateIncrease: '5,10,15,20,30,60,90,120,240,300',
|
||
updateIncreaseB: '5,10,15,20,30,60,90,120,240,300',
|
||
customCSS: "/* Block Ads */\n/*\ndiv.center {\n display: none;\n}\n*/\n/* Tripcode Italics: */\n/*\nspan.postertrip {\n font-style: italic;\n}\n*/\n\n/* Add a rounded border to thumbnails (but not expanded images): */\n/*\n.fileThumb > img:first-child {\n border: solid 2px rgba(0,0,100,0.5);\n border-radius: 10px;\n}\n*/\n\n/* Make highlighted posts look inset on the page: */\n/*\ndiv.post:target,\ndiv.post.highlight {\n box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);\n}\n*/",
|
||
hotkeys: {
|
||
openQR: ['I', 'Open QR with post number inserted'],
|
||
openEmptyQR: ['i', 'Open QR without post number inserted'],
|
||
openOptions: ['ctrl+o', 'Open Options'],
|
||
close: ['Esc', 'Close Options or QR'],
|
||
spoiler: ['ctrl+s', 'Quick spoiler tags'],
|
||
math: ['ctrl+m', 'Quick math tags'],
|
||
eqn: ['ctrl+e', 'Quick eqn tags'],
|
||
code: ['alt+c', 'Quick code tags'],
|
||
sageru: ['alt+n', 'Sage keybind'],
|
||
submit: ['alt+s', 'Submit post'],
|
||
hideQR: ['h', 'Toggle hide status of QR'],
|
||
toggleCatalog: ['alt+t', 'Toggle links in nav bar'],
|
||
watch: ['w', 'Watch thread'],
|
||
update: ['u', 'Update now'],
|
||
unreadCountTo0: ['z', 'Mark thread as read'],
|
||
expandImage: ['m', 'Expand selected image'],
|
||
expandAllImages: ['M', 'Expand all images'],
|
||
fappeTyme: ['F', 'Toggle Fappe Tyme'],
|
||
zero: ['0', 'Jump to page 0'],
|
||
nextPage: ['L', 'Jump to the next page'],
|
||
previousPage: ['H', 'Jump to the previous page'],
|
||
nextThread: ['n', 'See next thread'],
|
||
previousThread: ['p', 'See previous thread'],
|
||
expandThread: ['e', 'Expand thread'],
|
||
openThreadTab: ['o', 'Open thread in new tab'],
|
||
openThread: ['O', 'Open thread in current tab'],
|
||
nextReply: ['J', 'Select next reply'],
|
||
previousReply: ['K', 'Select previous reply'],
|
||
hide: ['x', 'Hide thread']
|
||
},
|
||
updater: {
|
||
checkbox: {
|
||
'Beep': [false, 'Beep on new post to completely read thread'],
|
||
'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'],
|
||
'Scroll BG': [false, 'Scroll background tabs'],
|
||
'Verbose': [true, 'Show countdown timer, new post count'],
|
||
'Auto Update': [true, 'Automatically fetch new posts']
|
||
},
|
||
Interval: 30,
|
||
BGInterval: 60
|
||
},
|
||
embedWidth: 640,
|
||
embedHeight: 390
|
||
};
|
||
|
||
if (!/^[a-z]+\.4chan\.org$/.test(location.hostname)) {
|
||
return;
|
||
}
|
||
|
||
Conf = {};
|
||
|
||
g = {};
|
||
|
||
d = document;
|
||
|
||
g.TYPE = 'sfw';
|
||
|
||
userNavigation = {};
|
||
|
||
MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.OMutationObserver;
|
||
|
||
/*
|
||
loosely follows the jquery api:
|
||
http://api.jquery.com/
|
||
not chainable
|
||
*/
|
||
|
||
|
||
$ = function(selector, root) {
|
||
var result;
|
||
|
||
root || (root = d.body);
|
||
if (result = root.querySelector(selector)) {
|
||
return result;
|
||
}
|
||
};
|
||
|
||
$.extend = function(object, properties) {
|
||
var key, val;
|
||
|
||
for (key in properties) {
|
||
val = properties[key];
|
||
object[key] = val;
|
||
}
|
||
};
|
||
|
||
$.extend(Array.prototype, {
|
||
add: function(object, position) {
|
||
var keep;
|
||
|
||
keep = this.slice(position);
|
||
this.length = position;
|
||
this.push(object);
|
||
return this.pushArrays(keep);
|
||
},
|
||
contains: function(object) {
|
||
return this.indexOf(object) > -1;
|
||
},
|
||
indexOf: function(object) {
|
||
var i;
|
||
|
||
i = this.length;
|
||
while (i--) {
|
||
if (this[i] === object) {
|
||
break;
|
||
}
|
||
}
|
||
return i;
|
||
},
|
||
pushArrays: function() {
|
||
var arg, args, _i, _len;
|
||
|
||
args = arguments;
|
||
for (_i = 0, _len = args.length; _i < _len; _i++) {
|
||
arg = args[_i];
|
||
this.push.apply(this, arg);
|
||
}
|
||
return this;
|
||
},
|
||
remove: function(object) {
|
||
var index;
|
||
|
||
if ((index = this.indexOf(object)) > -1) {
|
||
return this.splice(index, 1);
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
});
|
||
|
||
$.extend(String.prototype, {
|
||
capitalize: function() {
|
||
return this.charAt(0).toUpperCase() + this.slice(1);
|
||
},
|
||
contains: function(string) {
|
||
return this.indexOf(string) > -1;
|
||
}
|
||
});
|
||
|
||
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
|
||
|
||
$.extend($, {
|
||
NBSP: '\u00A0',
|
||
minmax: function(value, min, max) {
|
||
return (value < min ? min : value > max ? max : value);
|
||
},
|
||
log: typeof (_base = console.log).bind === "function" ? _base.bind(console) : void 0,
|
||
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(),
|
||
ready: function(fc) {
|
||
var cb;
|
||
|
||
if (['interactive', 'complete'].contains(d.readyState)) {
|
||
return setTimeout(fc);
|
||
}
|
||
if (!$.callbacks) {
|
||
$.callbacks = [];
|
||
cb = function() {
|
||
var callback, _i, _len, _ref;
|
||
|
||
_ref = $.callbacks;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
callback = _ref[_i];
|
||
callback();
|
||
}
|
||
return $.off(d, 'DOMContentLoaded', cb);
|
||
};
|
||
$.on(d, 'DOMContentLoaded', cb);
|
||
}
|
||
$.callbacks.push(fc);
|
||
return $.on(d, 'DOMContentLoaded', cb);
|
||
},
|
||
sync: function(key, cb) {
|
||
var parse;
|
||
|
||
key = Main.namespace + key;
|
||
parse = JSON.parse;
|
||
return $.on(window, 'storage', function(e) {
|
||
if (e.key === key) {
|
||
return cb(parse(e.newValue));
|
||
}
|
||
});
|
||
},
|
||
id: function(id) {
|
||
return d.getElementById(id);
|
||
},
|
||
formData: function(arg) {
|
||
var fd, key, val;
|
||
|
||
if (arg instanceof HTMLFormElement) {
|
||
fd = new FormData(arg);
|
||
} else {
|
||
fd = new FormData();
|
||
for (key in arg) {
|
||
val = arg[key];
|
||
if (val) {
|
||
fd.append(key, val);
|
||
}
|
||
}
|
||
}
|
||
return fd;
|
||
},
|
||
ajax: function(url, callbacks, opts) {
|
||
var form, headers, key, r, type, upCallbacks, val;
|
||
|
||
if (!opts) {
|
||
opts = {};
|
||
}
|
||
type = opts.type, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form;
|
||
r = new XMLHttpRequest();
|
||
r.overrideMimeType('text/html');
|
||
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);
|
||
if (type === 'post') {
|
||
r.withCredentials = true;
|
||
}
|
||
r.send(form);
|
||
return r;
|
||
},
|
||
cache: function(url, cb) {
|
||
var req;
|
||
|
||
if (req = $.cache.requests[url]) {
|
||
if (req.readyState === 4) {
|
||
return cb.call(req);
|
||
} else {
|
||
return req.callbacks.push(cb);
|
||
}
|
||
} else {
|
||
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);
|
||
}
|
||
},
|
||
onabort: function() {
|
||
return delete $.cache.requests[url];
|
||
},
|
||
onerror: function() {
|
||
return delete $.cache.requests[url];
|
||
}
|
||
});
|
||
req.callbacks = [cb];
|
||
return $.cache.requests[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;
|
||
}
|
||
},
|
||
addStyle: function(css, identifier) {
|
||
var style;
|
||
|
||
style = $.el('style', {
|
||
innerHTML: css,
|
||
id: identifier
|
||
});
|
||
$.add(d.head, style);
|
||
return style;
|
||
},
|
||
x: function(path, root) {
|
||
root || (root = d.body);
|
||
return d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
|
||
},
|
||
X: function(path, root) {
|
||
root || (root = d.body);
|
||
return d.evaluate(path, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
|
||
},
|
||
addClass: function(el, className) {
|
||
return el.classList.add(className);
|
||
},
|
||
rmClass: function(el, className) {
|
||
return el.classList.remove(className);
|
||
},
|
||
toggleClass: function(el, className) {
|
||
return el.classList.toggle(className);
|
||
},
|
||
hasClass: function(el, className) {
|
||
return el.classList.contains(className);
|
||
},
|
||
rm: function(el) {
|
||
return el.parentNode.removeChild(el);
|
||
},
|
||
rmAll: function(root) {
|
||
var node;
|
||
|
||
while (node = root.firstChild) {
|
||
$.rm(node);
|
||
}
|
||
},
|
||
tn: function(string) {
|
||
return d.createTextNode(string);
|
||
},
|
||
nodes: function(nodes) {
|
||
var frag, node, _i, _len;
|
||
|
||
if (!(nodes instanceof Array)) {
|
||
return nodes;
|
||
}
|
||
frag = $.frag();
|
||
for (_i = 0, _len = nodes.length; _i < _len; _i++) {
|
||
node = nodes[_i];
|
||
$.add(frag, node);
|
||
}
|
||
return frag;
|
||
},
|
||
frag: function() {
|
||
return d.createDocumentFragment();
|
||
},
|
||
add: function(parent, children) {
|
||
return parent.appendChild($.nodes(children));
|
||
},
|
||
prepend: function(parent, children) {
|
||
return parent.insertBefore($.nodes(children), 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(el, e) {
|
||
return el.dispatchEvent(e);
|
||
},
|
||
globalEval: function(code) {
|
||
var script;
|
||
|
||
script = $.el('script', {
|
||
textContent: "(" + code + ")()"
|
||
});
|
||
$.add(d.head, script);
|
||
return $.rm(script);
|
||
},
|
||
shortenFilename: function(filename, isOP) {
|
||
var threshold;
|
||
|
||
threshold = 30 + 10 * isOP;
|
||
if (filename.replace(/\.\w+$/, '').length > threshold) {
|
||
return "" + filename.slice(0, threshold - 5) + "(...)" + (filename.match(/\.\w+$/));
|
||
} else {
|
||
return filename;
|
||
}
|
||
},
|
||
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];
|
||
},
|
||
debounce: function(wait, fn) {
|
||
var timeout;
|
||
|
||
timeout = null;
|
||
return function() {
|
||
if (timeout) {
|
||
clearTimeout(timeout);
|
||
} else {
|
||
fn.apply(this, arguments);
|
||
}
|
||
return timeout = setTimeout((function() {
|
||
return timeout = null;
|
||
}), wait);
|
||
};
|
||
}
|
||
});
|
||
|
||
$.cache.requests = {};
|
||
|
||
$.extend($, typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null ? {
|
||
"delete": function(name) {
|
||
name = Main.namespace + name;
|
||
return GM_deleteValue(name);
|
||
},
|
||
get: function(name, defaultValue) {
|
||
var value;
|
||
|
||
name = Main.namespace + name;
|
||
if ((value = GM_getValue(name)) && value !== 'undefined') {
|
||
return JSON.parse(value);
|
||
} else {
|
||
return defaultValue;
|
||
}
|
||
},
|
||
set: function(name, value) {
|
||
name = Main.namespace + name;
|
||
localStorage.setItem(name, JSON.stringify(value));
|
||
return GM_setValue(name, JSON.stringify(value));
|
||
},
|
||
open: function(url) {
|
||
return GM_openInTab(location.protocol + url, true);
|
||
}
|
||
} : {
|
||
"delete": function(name) {
|
||
return localStorage.removeItem(Main.namespace + name);
|
||
},
|
||
get: function(name, defaultValue) {
|
||
var value;
|
||
|
||
if (value = localStorage.getItem(Main.namespace + name)) {
|
||
return JSON.parse(value);
|
||
} else {
|
||
return defaultValue;
|
||
}
|
||
},
|
||
set: function(name, value) {
|
||
return localStorage.setItem(Main.namespace + name, JSON.stringify(value));
|
||
},
|
||
open: function(url) {
|
||
return window.open(location.protocol + url, '_blank');
|
||
}
|
||
});
|
||
|
||
$$ = function(selector, root) {
|
||
var result;
|
||
|
||
root || (root = d.body);
|
||
if (result = __slice.call(root.querySelectorAll(selector))) {
|
||
return result;
|
||
}
|
||
return null;
|
||
};
|
||
|
||
UI = {
|
||
dialog: function(id, position, html) {
|
||
var el, move;
|
||
|
||
el = $.el('div', {
|
||
className: 'reply dialog',
|
||
innerHTML: html,
|
||
id: id
|
||
});
|
||
el.style.cssText = $.get("" + id + ".coords", position);
|
||
if (move = $('.move', el)) {
|
||
move.addEventListener('mousedown', UI.dragstart, false);
|
||
}
|
||
return el;
|
||
},
|
||
dragstart: function(e) {
|
||
var el, rect;
|
||
|
||
e.preventDefault();
|
||
UI.el = el = this.parentNode;
|
||
d.addEventListener('mousemove', UI.drag, false);
|
||
d.addEventListener('mouseup', UI.dragend, false);
|
||
rect = el.getBoundingClientRect();
|
||
UI.dx = e.clientX - rect.left;
|
||
UI.dy = e.clientY - rect.top;
|
||
UI.width = d.documentElement.clientWidth - rect.width;
|
||
return UI.height = d.documentElement.clientHeight - rect.height;
|
||
},
|
||
drag: function(e) {
|
||
var left, style, top;
|
||
|
||
left = e.clientX - UI.dx;
|
||
top = e.clientY - UI.dy;
|
||
left = left < 10 ? '0px' : UI.width - left < 10 ? null : left + 'px';
|
||
top = top < 10 ? '0px' : UI.height - top < 10 ? null : top + 'px';
|
||
style = UI.el.style;
|
||
style.left = left;
|
||
style.top = top;
|
||
style.right = left === null ? '0px' : null;
|
||
return style.bottom = top === null ? '0px' : null;
|
||
},
|
||
dragend: function() {
|
||
$.set("" + UI.el.id + ".coords", UI.el.style.cssText);
|
||
d.removeEventListener('mousemove', UI.drag, false);
|
||
d.removeEventListener('mouseup', UI.dragend, false);
|
||
return delete UI.el;
|
||
},
|
||
hover: function(e, mode) {
|
||
var clientHeight, clientWidth, clientX, clientY, height, style, top, _ref;
|
||
|
||
clientX = e.clientX, clientY = e.clientY;
|
||
style = UI.el.style;
|
||
_ref = d.documentElement, clientHeight = _ref.clientHeight, clientWidth = _ref.clientWidth;
|
||
height = UI.el.offsetHeight;
|
||
if ((mode || 'default') === 'default') {
|
||
top = clientY - 120;
|
||
style.top = "" + (clientHeight <= height || top <= 0 ? 0 : top + height >= clientHeight ? clientHeight - height : top) + "px";
|
||
if (clientX <= clientWidth - 400) {
|
||
style.left = clientX + 45 + 'px';
|
||
return style.right = null;
|
||
} else {
|
||
style.left = null;
|
||
style.right = clientWidth - clientX + 20 + 'px';
|
||
return top = clientY - 120;
|
||
}
|
||
} else {
|
||
if (clientX <= clientWidth - 400) {
|
||
style.left = clientX + 20 + 'px';
|
||
style.right = null;
|
||
top = clientY;
|
||
} else {
|
||
style.left = null;
|
||
style.right = clientWidth - clientX + 20 + 'px';
|
||
top = clientY - 120;
|
||
}
|
||
return style.top = "" + (clientHeight <= height || top <= 0 ? 0 : top + height >= clientHeight ? clientHeight - height : top) + "px";
|
||
}
|
||
},
|
||
hoverend: function() {
|
||
$.rm(UI.el);
|
||
return delete UI.el;
|
||
}
|
||
};
|
||
|
||
Options = {
|
||
init: function() {
|
||
var a, setting, settings, _i, _len, _ref, _results;
|
||
|
||
if (!$.get('firstrun')) {
|
||
$.set('firstrun', true);
|
||
if (!Favicon.el) {
|
||
Favicon.init();
|
||
}
|
||
Options.dialog();
|
||
}
|
||
_ref = ['navtopright', 'navbotright'];
|
||
_results = [];
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
settings = _ref[_i];
|
||
a = $.el('a', {
|
||
className: 'settingsWindowLink',
|
||
textContent: '4chan X Settings',
|
||
href: 'javascript:;'
|
||
});
|
||
$.on(a, 'click', function() {
|
||
return Options.dialog();
|
||
});
|
||
setting = $.id(settings);
|
||
if (Conf['Disable 4chan\'s extension']) {
|
||
$.replace(setting.childNodes[1], a);
|
||
continue;
|
||
}
|
||
_results.push($.prepend(setting, [$.tn('['), a, $.tn('] ')]));
|
||
}
|
||
return _results;
|
||
},
|
||
dialog: function() {
|
||
var archiver, arr, back, checked, customCSS, description, dialog, div, emojiPos, favicon, fieldset, fileInfo, filter, height, hiddenNum, hiddenThreads, input, key, label, legend, name, obj, overlay, sageEmoji, sauce, time, toSelect, tr, updateIncrease, updateIncreaseB, value, width, _i, _j, _len, _len1, _ref, _ref1, _ref2;
|
||
|
||
dialog = Options.el = $.el('div', {
|
||
id: 'options',
|
||
className: 'reply dialog',
|
||
innerHTML: '<div id=optionsbar>\
|
||
<div id=credits>\
|
||
<label for=apply>Apply</label>\
|
||
| <a class=export>Export</a>\
|
||
| <a class=import>Import</a>\
|
||
| <a target=_blank href=http://seaweedchan.github.io/4chan-x/>4chan X</a>\
|
||
| <a target=_blank href=https://raw.github.com/seaweedchan/4chan-x/4chanX/changelog>' + Main.version + '</a>\
|
||
</div>\
|
||
\
|
||
<div class=imp-exp>\
|
||
<div class=placeholder></div>\
|
||
<input type=file style="visibility:hidden; position: absolute;">\
|
||
<p class=imp-exp-result></p>\
|
||
</div>\
|
||
\
|
||
<div class=tabs>\
|
||
<label for=main_tab id=selected_tab>Main</label>\
|
||
| <label for=filter_tab>Filter</label>\
|
||
| <label for=sauces_tab>Sauce</label>\
|
||
| <label for=keybinds_tab>Keybinds</label>\
|
||
| <label for=rice_tab>Rice</label>\
|
||
</div>\
|
||
</div>\
|
||
<div id=content>\
|
||
<input type=radio name=tab hidden id=main_tab checked>\
|
||
<div class=main_tab>\
|
||
</div>\
|
||
<input type=radio name=tab hidden id=sauces_tab>\
|
||
<div class=sauces_tab>\
|
||
<div class=warning><code>Sauce</code> is disabled.</div>\
|
||
Lines starting with a <code>#</code> will be ignored.<br>\
|
||
You can specify a certain display text by appending <code>;text:[text]</code> to the url.\
|
||
<ul>These parameters will be replaced by their corresponding values:\
|
||
<li>$1: Thumbnail url.</li>\
|
||
<li>$2: Full image url.</li>\
|
||
<li>$3: MD5 hash.</li>\
|
||
<li>$4: Current board.</li>\
|
||
</ul>\
|
||
<textarea name=sauces id=sauces class=field></textarea>\
|
||
</div>\
|
||
<input type=radio name=tab hidden id=filter_tab>\
|
||
<div class=filter_tab>\
|
||
<div class=warning><code>Filter</code> is disabled.</div>\
|
||
<select name=filter>\
|
||
<option value=guide>Guide</option>\
|
||
<option value=name>Name</option>\
|
||
<option value=uniqueid>Unique ID</option>\
|
||
<option value=tripcode>Tripcode</option>\
|
||
<option value=mod>Admin/Mod</option>\
|
||
<option value=email>E-mail</option>\
|
||
<option value=subject>Subject</option>\
|
||
<option value=comment>Comment</option>\
|
||
<option value=country>Country</option>\
|
||
<option value=filename>Filename</option>\
|
||
<option value=dimensions>Image dimensions</option>\
|
||
<option value=filesize>Filesize</option>\
|
||
<option value=md5>Image MD5 (uses exact string matching, not regular expressions)</option>\
|
||
</select>\
|
||
</div>\
|
||
<input type=radio name=tab hidden id=rice_tab>\
|
||
<div class=rice_tab>\
|
||
<fieldset style="margin-top: 15px;">\
|
||
<legend>Icons</legend>\
|
||
<div class=warning style="width: 35%"><code>Emoji</code> is disabled.</div>\
|
||
<div style="display: inline-block; margin-right: 20px;">\
|
||
Emoji Position<br>\
|
||
<span></span>\
|
||
<select name=emojiPos>\
|
||
<option value=before>Before</option>\
|
||
<option value=after>After</option>\
|
||
</select>\
|
||
</div>\
|
||
<div style="display: inline-block; margin-right: 20px;">\
|
||
Sage Emoji<br>\
|
||
<span></span>\
|
||
<select name=sageEmoji>\
|
||
<option value=appchan>Appchan</option>\
|
||
<option value=4chanSS>4chan SS</option>\
|
||
</select>\
|
||
</div>\
|
||
<div style="display: inline-block;">\
|
||
<div class=warning><code>Unread Favicon</code> is disabled.</div>\
|
||
Unread favicons<br>\
|
||
<span></span>\
|
||
<select name=favicon>\
|
||
<option value=ferongr>ferongr</option>\
|
||
<option value=xat->xat-</option>\
|
||
<option value=Mayhem>Mayhem</option>\
|
||
<option value=4chanJS>4chanJS</option>\
|
||
<option value=Original>Original</option>\
|
||
</select>\
|
||
</div>\
|
||
</fieldset>\
|
||
<span></span>\
|
||
<fieldset>\
|
||
<legend>Archiver</legend>\
|
||
<div>\
|
||
Select an Archiver for this board:\
|
||
<select name=archiver></select>\
|
||
</div>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<legend>Updater\'s Dynamic Increase</legend>\
|
||
<div>Visible tab</div>\
|
||
<div><input name=updateIncrease class=field></div>\
|
||
<div>Background tab</div>\
|
||
<div><input name=updateIncreaseB class=field></div>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<legend>Backlink formatting</legend>\
|
||
<div class=warning><code>Quote Backlinks</code> are disabled.</div>\
|
||
<div><input name=backlink class=field> : <span id=backlinkPreview></span></div>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<legend>Time Formatting</legend>\
|
||
<div class=warning><code>Time Formatting</code> is disabled.</div>\
|
||
<input name=time class=field> : <span id=timePreview></span><br>\
|
||
Supported <a href=http://en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:<br>\
|
||
Day: %a, %A, %d, %e<br>\
|
||
Month: %m, %b, %B<br>\
|
||
Year: %y<br>\
|
||
Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P<br>\
|
||
Minutes: %M<br>\
|
||
Seconds: %S<br>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<div class=warning><code>File Info Formatting</code> is disabled.</div>\
|
||
<legend>File Info Formatting</legend>\
|
||
<input name=fileInfo class=field> : <span id=fileInfoPreview class=fileText></span><br>\
|
||
Link: %l (lowercase L, truncated), %L (untruncated), %t (Unix timestamp)<br>\
|
||
Original file name: %n (truncated), %N (untruncated), %T (Unix timestamp)<br>\
|
||
Spoiler indicator: %p<br>\
|
||
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)<br>\
|
||
Resolution: %r (Displays PDF on /po/, for PDFs)<br>\
|
||
</fieldset>\
|
||
<fieldset id=persona>\
|
||
<div class=warning style="margin-bottom: 5px;"><code>Per Board Persona</code> is disabled.</div>\
|
||
<legend>Per Board Persona</legend>\
|
||
<select name=personaboards></select>\
|
||
<br><br>\
|
||
<div class=option>\
|
||
Name:\
|
||
</div>\
|
||
<div class=option>\
|
||
<input name=name>\
|
||
</div>\
|
||
<div class=option>\
|
||
Email:\
|
||
</div>\
|
||
<div class=option>\
|
||
<input name=email>\
|
||
</div>\
|
||
<div class=option>\
|
||
Subject:\
|
||
</div>\
|
||
<div class=option>\
|
||
<input name=sub>\
|
||
</div>\
|
||
<br>\
|
||
<button></button>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<legend>Embedding</legend>\
|
||
Specify size of video embeds<br>\
|
||
Height: <input name=embedHeight type=number />px\
|
||
|\
|
||
Width: <input name=embedWidth type=number />px\
|
||
<button name=resetSize>Reset</button>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<legend>Custom Navigation</legend>\
|
||
<div class=warning style="margin-bottom: 5px;"><code>Custom Navigation</code> is disabled.</div>\
|
||
<div id=customNavigation>\
|
||
</div>\
|
||
</fieldset>\
|
||
<fieldset>\
|
||
<legend>Custom CSS</legend>\
|
||
<div class=warning><code>Custom CSS</code> is disabled.</div><br>\
|
||
Remove Comment blocks to use! ( "/*" and "*/" around CSS blocks )\
|
||
<textarea name=customCSS id=customCSS class=field></textarea>\
|
||
</div>\
|
||
<input type=radio name=tab hidden id=keybinds_tab>\
|
||
<div class=keybinds_tab>\
|
||
<div class=warning><code>Keybinds</code> are disabled.</div>\
|
||
<div>Allowed keys: Ctrl, Alt, Meta, a-z, A-Z, 0-9, Up, Down, Right, Left.</div>\
|
||
<table><tbody>\
|
||
<tr><th>Actions</th><th>Keybinds</th></tr>\
|
||
</tbody></table>\
|
||
</div>\
|
||
<input type=radio name=tab hidden onClick="document.location.reload()" id=apply>\
|
||
<div>Reloading page with new settings.</div>\
|
||
</div>'
|
||
});
|
||
_ref = $$('label[for]', dialog);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
label = _ref[_i];
|
||
$.on(label, 'click', function() {
|
||
var previous;
|
||
|
||
if (previous = $.id('selected_tab')) {
|
||
previous.id = '';
|
||
}
|
||
return this.id = 'selected_tab';
|
||
});
|
||
}
|
||
$.on($('#credits .export', dialog), 'click', Options["export"]);
|
||
$.on($('#credits .import', dialog), 'click', Options["import"]);
|
||
$.on($('.imp-exp>input', dialog), 'change', Options.onImport);
|
||
_ref1 = Config.main;
|
||
for (key in _ref1) {
|
||
obj = _ref1[key];
|
||
fieldset = $.el('fieldset');
|
||
legend = $.el('legend', {
|
||
textContent: key
|
||
});
|
||
for (key in obj) {
|
||
arr = obj[key];
|
||
checked = $.get(key, Conf[key]) ? 'checked' : '';
|
||
description = arr[1];
|
||
div = $.el('div', {
|
||
innerHTML: "<label><input type=checkbox name=\"" + key + "\" " + checked + "><span class=\"optionlabel\">" + key + "</span></label><span>: " + description + "</span>"
|
||
});
|
||
$.on($('input', div), 'click', $.cb.checked);
|
||
$.add(fieldset, legend);
|
||
$.add(fieldset, div);
|
||
}
|
||
$.add($('#main_tab + div', dialog), fieldset);
|
||
}
|
||
hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
|
||
hiddenNum = Object.keys(g.hiddenReplies).length + Object.keys(hiddenThreads).length;
|
||
div = $.el('div', {
|
||
innerHTML: "<span class=\"optionlabel\"><button>hidden: " + hiddenNum + "</button></span><div style=\"display: none\">Forget all hidden posts. Useful if you accidentally hide a post and have \"Show Stubs\" disabled.</div>"
|
||
});
|
||
$.on($('button', div), 'click', Options.clearHidden);
|
||
$.on($('.optionlabel', div), 'mouseover', Options.mouseover);
|
||
$.add($('fieldset:nth-child(3)', dialog), div);
|
||
filter = $('select[name=filter]', dialog);
|
||
$.on(filter, 'change', Options.filter);
|
||
archiver = $('select[name=archiver]', dialog);
|
||
toSelect = Redirect.select(g.BOARD);
|
||
if (!toSelect[0]) {
|
||
toSelect = ['No Archive Available'];
|
||
}
|
||
for (_j = 0, _len1 = toSelect.length; _j < _len1; _j++) {
|
||
name = toSelect[_j];
|
||
$.add(archiver, $.el('option', {
|
||
textContent: name
|
||
}));
|
||
}
|
||
if (toSelect[1]) {
|
||
archiver.value = $.get(value = "archiver/" + g.BOARD + "/", toSelect[0]);
|
||
$.on(archiver, 'change', function() {
|
||
return $.set(value, this.value);
|
||
});
|
||
}
|
||
sauce = $('#sauces', dialog);
|
||
sauce.value = $.get(sauce.name, Conf[sauce.name]);
|
||
$.on(sauce, 'change', $.cb.value);
|
||
(back = $('[name=backlink]', dialog)).value = $.get('backlink', Conf['backlink']);
|
||
(time = $('[name=time]', dialog)).value = $.get('time', Conf['time']);
|
||
(fileInfo = $('[name=fileInfo]', dialog)).value = $.get('fileInfo', Conf['fileInfo']);
|
||
$.on(back, 'input', $.cb.value);
|
||
$.on(back, 'input', Options.backlink);
|
||
$.on(time, 'input', $.cb.value);
|
||
$.on(time, 'input', Options.time);
|
||
$.on(fileInfo, 'input', $.cb.value);
|
||
$.on(fileInfo, 'input', Options.fileInfo);
|
||
this.persona.select = $('[name=personaboards]', dialog);
|
||
this.persona.button = $('#persona button', dialog);
|
||
this.persona.data = $.get('persona', {
|
||
global: {}
|
||
});
|
||
if (!this.persona.data[g.BOARD]) {
|
||
this.persona.data[g.BOARD] = JSON.parse(JSON.stringify(this.persona.data.global));
|
||
}
|
||
for (name in this.persona.data) {
|
||
this.persona.select.innerHTML += "<option value=" + name + ">" + name + "</option>";
|
||
}
|
||
this.persona.select.value = Conf['Per Board Persona'] ? g.BOARD : 'global';
|
||
this.persona.init();
|
||
$.on(this.persona.select, 'change', Options.persona.change);
|
||
customCSS = $('#customCSS', dialog);
|
||
customCSS.value = $.get(customCSS.name, Conf[customCSS.name]);
|
||
$.on(customCSS, 'change', function() {
|
||
$.cb.value.call(this);
|
||
return Style.addStyle();
|
||
});
|
||
(width = $('[name=embedWidth]', dialog)).value = $.get('embedWidth', Conf['embedWidth']);
|
||
(height = $('[name=embedHeight]', dialog)).value = $.get('embedHeight', Conf['embedHeight']);
|
||
$.on(width, 'input', $.cb.value);
|
||
$.on(height, 'input', $.cb.value);
|
||
$.on($('[name=resetSize]', dialog), 'click', function() {
|
||
$.set('embedWidth', width.value = Config.embedWidth);
|
||
return $.set('embedHeight', height.value = Config.embedHeight);
|
||
});
|
||
favicon = $('select[name=favicon]', dialog);
|
||
favicon.value = $.get('favicon', Conf['favicon']);
|
||
$.on(favicon, 'change', $.cb.value);
|
||
$.on(favicon, 'change', Options.favicon);
|
||
sageEmoji = $('select[name=sageEmoji]', dialog);
|
||
sageEmoji.value = $.get('sageEmoji', Conf['sageEmoji']);
|
||
$.on(sageEmoji, 'change', $.cb.value);
|
||
$.on(sageEmoji, 'change', Options.sageEmoji);
|
||
emojiPos = $('select[name=emojiPos]', dialog);
|
||
emojiPos.value = $.get('emojiPos', Conf['emojiPos']);
|
||
$.on(emojiPos, 'change', $.cb.value);
|
||
$.on(emojiPos, 'change', Options.emojiPos);
|
||
(updateIncrease = $('[name=updateIncrease]', dialog)).value = $.get('updateIncrease', Conf['updateIncrease']);
|
||
(updateIncreaseB = $('[name=updateIncreaseB]', dialog)).value = $.get('updateIncreaseB', Conf['updateIncreaseB']);
|
||
$.on(updateIncrease, 'input', $.cb.value);
|
||
$.on(updateIncreaseB, 'input', $.cb.value);
|
||
this.customNavigation.dialog(dialog);
|
||
_ref2 = Config.hotkeys;
|
||
for (key in _ref2) {
|
||
arr = _ref2[key];
|
||
tr = $.el('tr', {
|
||
innerHTML: "<td>" + arr[1] + "</td><td><input name=" + key + " class=field></td>"
|
||
});
|
||
input = $('input', tr);
|
||
input.value = $.get(key, Conf[key]);
|
||
$.on(input, 'keydown', Options.keybind);
|
||
$.add($('#keybinds_tab + div tbody', dialog), tr);
|
||
}
|
||
overlay = $.el('div', {
|
||
id: 'overlay'
|
||
});
|
||
$.on(dialog, 'click', function(e) {
|
||
return e.stopPropagation();
|
||
});
|
||
$.on(overlay, 'click', Options.close);
|
||
$.on(dialog, 'click', function(e) {
|
||
return e.stopPropagation();
|
||
});
|
||
$.add(overlay, dialog);
|
||
$.add(d.body, overlay);
|
||
d.body.style.setProperty('width', "" + d.body.clientWidth + "px", null);
|
||
$.addClass(d.body, 'unscroll');
|
||
this.indicators(dialog);
|
||
Options.filter.call(filter);
|
||
Options.backlink.call(back);
|
||
Options.time.call(time);
|
||
Options.fileInfo.call(fileInfo);
|
||
Options.favicon.call(favicon);
|
||
return Options.sageEmoji.call(sageEmoji);
|
||
},
|
||
indicators: function(dialog) {
|
||
var indicator, indicators, key, _i, _j, _len, _len1, _ref, _ref1;
|
||
|
||
indicators = {};
|
||
_ref = $$('.warning', dialog);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
indicator = _ref[_i];
|
||
key = indicator.firstChild.textContent;
|
||
indicator.hidden = $.get(key, Conf[key]);
|
||
indicators[key] = indicator;
|
||
$.on($("[name='" + key + "']", dialog), 'click', function() {
|
||
return indicators[this.name].hidden = this.checked;
|
||
});
|
||
}
|
||
_ref1 = $$('.disabledwarning', dialog);
|
||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||
indicator = _ref1[_j];
|
||
key = indicator.firstChild.textContent;
|
||
indicator.hidden = !$.get(key, Conf[key]);
|
||
indicators[key] = indicator;
|
||
$.on($("[name='" + key + "']", dialog), 'click', function() {
|
||
return Options.indicators(dialog);
|
||
});
|
||
}
|
||
},
|
||
customNavigation: {
|
||
dialog: function(dialog) {
|
||
var addLink, div, index, input, item, itemIndex, li, link, navOptions, removeLink, ul, _ref;
|
||
|
||
div = $("#customNavigation", dialog);
|
||
ul = $.el("div");
|
||
li = $.el("div", {
|
||
className: "delimiter",
|
||
textContent: "Delimiter: "
|
||
});
|
||
input = $.el("input", {
|
||
className: "field",
|
||
name: "delimiter"
|
||
});
|
||
input.setAttribute("value", userNavigation.delimiter);
|
||
input.setAttribute("placeholder", "delimiter");
|
||
input.setAttribute("type", "text");
|
||
$.on(input, "change", function() {
|
||
if (this.value === "") {
|
||
alert("Custom Navigation options cannot be blank.");
|
||
return;
|
||
}
|
||
userNavigation.delimiter = this.value;
|
||
return $.set("userNavigation", userNavigation);
|
||
});
|
||
$.add(li, input);
|
||
$.add(ul, li);
|
||
li = $.el("div", {
|
||
innerHTML: "Navigation Syntax:<br>Display Name | Title / Alternate Text | URL"
|
||
});
|
||
$.add(ul, li);
|
||
navOptions = ["Display Name", "Title / Alt Text", "URL"];
|
||
_ref = userNavigation.links;
|
||
for (index in _ref) {
|
||
link = _ref[index];
|
||
if (typeof link !== 'object') {
|
||
continue;
|
||
}
|
||
li = $.el("div");
|
||
input = $.el("input", {
|
||
className: "hidden",
|
||
value: index,
|
||
type: "hidden",
|
||
hidden: "hidden"
|
||
});
|
||
$.add(li, input);
|
||
for (itemIndex in link) {
|
||
item = link[itemIndex];
|
||
if (typeof item !== 'string') {
|
||
continue;
|
||
}
|
||
input = $.el("input", {
|
||
className: "field",
|
||
name: itemIndex,
|
||
value: item,
|
||
placeholder: navOptions[itemIndex],
|
||
type: "text"
|
||
});
|
||
$.on(input, "change", function() {
|
||
if (this.value === "") {
|
||
alert("Custom Navigation options cannot be blank.");
|
||
return;
|
||
}
|
||
userNavigation.links[this.parentElement.firstChild.value][this.name] = this.value;
|
||
return $.set("userNavigation", userNavigation);
|
||
});
|
||
$.add(li, [input, $.tn(' ')]);
|
||
}
|
||
addLink = $.el("a", {
|
||
textContent: " + ",
|
||
href: "javascript:;"
|
||
});
|
||
$.on(addLink, "click", function() {
|
||
var blankLink;
|
||
|
||
blankLink = ["ex", "example", "http://www.example.com/"];
|
||
userNavigation.links.add(blankLink, this.parentElement.firstChild.value);
|
||
return Options.customNavigation.cleanup();
|
||
});
|
||
removeLink = $.el("a", {
|
||
textContent: " x ",
|
||
href: "javascript:;"
|
||
});
|
||
$.on(removeLink, "click", function() {
|
||
userNavigation.links.remove(userNavigation.links[this.parentElement.firstChild.value]);
|
||
return Options.customNavigation.cleanup();
|
||
});
|
||
$.add(li, addLink);
|
||
$.add(li, removeLink);
|
||
$.add(ul, li);
|
||
}
|
||
li = $.el("div", {
|
||
innerHTML: "<a name='add' href='javascript:;'>+</a> | <a name='reset' href='javascript:;'>Reset</a>"
|
||
});
|
||
$.on($('a[name=add]', li), "click", function() {
|
||
var blankLink;
|
||
|
||
blankLink = ["ex", "example", "http://www.example.com/"];
|
||
userNavigation.links.push(blankLink);
|
||
return Options.customNavigation.cleanup();
|
||
});
|
||
$.on($('a[name=reset]', li), "click", function() {
|
||
userNavigation = JSON.parse(JSON.stringify(Navigation));
|
||
return Options.customNavigation.cleanup();
|
||
});
|
||
$.add(ul, li);
|
||
return $.add(div, ul);
|
||
},
|
||
cleanup: function() {
|
||
$.set("userNavigation", userNavigation);
|
||
$.rm($("#customNavigation > div", d.body));
|
||
return Options.customNavigation.dialog($("#options", d.body));
|
||
}
|
||
},
|
||
persona: {
|
||
init: function() {
|
||
var input, item, key, _i, _len, _ref;
|
||
|
||
key = Conf['Per Board Persona'] ? g.BOARD : 'global';
|
||
Options.persona.newButton();
|
||
_ref = Options.persona.array;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
item = _ref[_i];
|
||
input = $("input[name=" + item + "]", Options.el);
|
||
input.value = this.data[key][item] || "";
|
||
$.on(input, 'blur', function() {
|
||
var pers;
|
||
|
||
pers = Options.persona;
|
||
pers.data[pers.select.value][this.name] = this.value;
|
||
return $.set('persona', pers.data);
|
||
});
|
||
}
|
||
return $.on(Options.persona.button, 'click', Options.persona.copy);
|
||
},
|
||
array: ['name', 'email', 'sub'],
|
||
change: function() {
|
||
var input, item, key, _i, _len, _ref;
|
||
|
||
key = this.value;
|
||
Options.persona.newButton();
|
||
_ref = Options.persona.array;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
item = _ref[_i];
|
||
input = $("input[name=" + item + "]", Options.el);
|
||
input.value = Options.persona.data[key][item];
|
||
}
|
||
},
|
||
copy: function() {
|
||
var change, data, select, _ref;
|
||
|
||
_ref = Options.persona, select = _ref.select, data = _ref.data, change = _ref.change;
|
||
if (select.value === 'global') {
|
||
data.global = JSON.parse(JSON.stringify(data[select.value]));
|
||
} else {
|
||
data[select.value] = JSON.parse(JSON.stringify(data.global));
|
||
}
|
||
$.set('persona', Options.persona.data = data);
|
||
return change.call(select);
|
||
},
|
||
newButton: function() {
|
||
return Options.persona.button.textContent = "Copy from " + (Options.persona.select.value === 'global' ? 'current board' : 'global');
|
||
}
|
||
},
|
||
close: function() {
|
||
$.rm(this);
|
||
d.body.style.removeProperty('width');
|
||
return $.rmClass(d.body, 'unscroll');
|
||
},
|
||
clearHidden: function() {
|
||
$["delete"]("hiddenReplies/" + g.BOARD + "/");
|
||
$["delete"]("hiddenThreads/" + g.BOARD + "/");
|
||
this.textContent = "hidden: 0";
|
||
return g.hiddenReplies = {};
|
||
},
|
||
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);
|
||
},
|
||
filter: function() {
|
||
var article, el, name, ta;
|
||
|
||
el = this.nextSibling;
|
||
if ((name = this.value) !== 'guide') {
|
||
ta = $.el('textarea', {
|
||
name: name,
|
||
className: 'field',
|
||
value: $.get(name, Conf[name])
|
||
});
|
||
$.on(ta, 'change', $.cb.value);
|
||
$.replace(el, ta);
|
||
return;
|
||
}
|
||
article = $.el('article', {
|
||
innerHTML: "<p>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.</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>"
|
||
});
|
||
if (el) {
|
||
return $.replace(el, article);
|
||
} else {
|
||
return $.after(this, article);
|
||
}
|
||
},
|
||
time: function() {
|
||
Time.foo();
|
||
Time.date = new Date();
|
||
return $.id('timePreview').textContent = Time.funk(Time);
|
||
},
|
||
backlink: function() {
|
||
return $.id('backlinkPreview').textContent = Conf['backlink'].replace(/%id/, '123456789');
|
||
},
|
||
fileInfo: function() {
|
||
FileInfo.data = {
|
||
link: '//images.4chan.org/g/src/1334437723720.jpg',
|
||
spoiler: true,
|
||
size: '276',
|
||
unit: 'KB',
|
||
resolution: '1280x720',
|
||
fullname: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
|
||
shortname: 'd9bb2efc98dd0df141a94399ff5880(...).jpg'
|
||
};
|
||
FileInfo.setFormats();
|
||
return $.id('fileInfoPreview').innerHTML = FileInfo.funk(FileInfo);
|
||
},
|
||
favicon: function() {
|
||
Favicon["switch"]();
|
||
Unread.update(true);
|
||
return this.previousElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">";
|
||
},
|
||
sageEmoji: function() {
|
||
Conf['sageEmoji'] = this.value;
|
||
return this.previousElementSibling.innerHTML = "<img src=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA" + (Emoji.icons[Emoji.icons.length - 1][1] = Emoji.sageValue(this.value)) + ">";
|
||
},
|
||
"export": function() {
|
||
var a, data, now, output;
|
||
|
||
now = Date.now();
|
||
data = {
|
||
version: Main.version,
|
||
date: now,
|
||
Conf: Conf,
|
||
WatchedThreads: $.get('watched', {})
|
||
};
|
||
a = $.el('a', {
|
||
className: 'warning',
|
||
textContent: 'Save me!',
|
||
download: "4chan X v" + Main.version + "-" + now + ".json",
|
||
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data))))),
|
||
target: '_blank'
|
||
});
|
||
if ($.engine !== 'gecko') {
|
||
a.click();
|
||
return;
|
||
}
|
||
output = $('.imp-exp>.placeholder');
|
||
$.rmAll(output);
|
||
$.rmAll($('.imp-exp-result'));
|
||
return $.add(output, a);
|
||
},
|
||
"import": function() {
|
||
$('.imp-exp>input').click();
|
||
return $.rmAll($('.imp-exp>.placeholder'));
|
||
},
|
||
onImport: function() {
|
||
var file, output, reader;
|
||
|
||
if (!(file = this.files[0])) {
|
||
return;
|
||
}
|
||
output = $('.imp-exp-result');
|
||
if (!confirm('Your current settings will be entirely overwritten, are you sure?')) {
|
||
output.textContent = 'Import aborted.';
|
||
return;
|
||
}
|
||
reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
var data, err;
|
||
|
||
try {
|
||
data = JSON.parse(e.target.result);
|
||
Options.loadSettings(data);
|
||
if (confirm('Import successful. Refresh now?')) {
|
||
return window.location.reload();
|
||
}
|
||
} catch (_error) {
|
||
err = _error;
|
||
return output.textContent = 'Import failed due to an error.';
|
||
}
|
||
};
|
||
return reader.readAsText(file);
|
||
},
|
||
loadSettings: function(data) {
|
||
var key, val, _ref;
|
||
|
||
_ref = data.Conf;
|
||
for (key in _ref) {
|
||
val = _ref[key];
|
||
$.set(key, val);
|
||
}
|
||
return $.set('watched', data.WatchedThreads);
|
||
}
|
||
};
|
||
|
||
BanChecker = {
|
||
init: function() {
|
||
var reason;
|
||
|
||
this.now = Date.now();
|
||
if (!Conf['Check for Bans constantly'] && (reason = $.get('isBanned'))) {
|
||
return BanChecker.prepend(reason);
|
||
} else if (Conf['Check for Bans constantly'] || $.get('lastBanCheck', 0) < this.now - 6 * $.HOUR) {
|
||
return BanChecker.load();
|
||
}
|
||
},
|
||
load: function() {
|
||
this.url = 'https://www.4chan.org/banned';
|
||
return $.ajax(this.url, {
|
||
onloadend: function() {
|
||
var doc, msg, reason;
|
||
|
||
if (this.status === 200 || 304) {
|
||
if (!Conf['Check for Bans constantly']) {
|
||
$.set('lastBanCheck', BanChecker.now);
|
||
}
|
||
doc = d.implementation.createHTMLDocument('');
|
||
doc.documentElement.innerHTML = this.response;
|
||
if (/no entry in our database/i.test((msg = $('.boxcontent', doc).textContent.trim()))) {
|
||
if ($.get('isBanned', false)) {
|
||
$["delete"]('isBanned');
|
||
$.rm(BanChecker.el);
|
||
delete BanChecker.el;
|
||
}
|
||
return;
|
||
}
|
||
$.set('isBanned', reason = /This ban will not expire/i.test(msg) ? 'You are permabanned.' : 'You are banned.');
|
||
return BanChecker.prepend(reason);
|
||
}
|
||
}
|
||
});
|
||
},
|
||
prepend: function(reason) {
|
||
var el;
|
||
|
||
if (!BanChecker.el) {
|
||
Banchecker.el = el = $.el('h2', {
|
||
id: 'banmessage',
|
||
"class": 'warning',
|
||
innerHTML: " <span>" + reason + "</span> <a href=" + BanChecker.url + " title='Click to find out why.' target=_blank>Click to find out why.</a>",
|
||
title: 'Click to recheck.'
|
||
}, $.on(el.lastChild, 'click', function() {
|
||
if (!Conf['Check for Bans constantly']) {
|
||
$["delete"]('lastBanCheck');
|
||
}
|
||
$["delete"]('isBanned');
|
||
this.parentNode.style.opacity = '.5';
|
||
return BanChecker.load();
|
||
}));
|
||
return $.before($.id('delform'), el);
|
||
} else {
|
||
return Banchecker.el.firstChild.textContent = reason;
|
||
}
|
||
}
|
||
};
|
||
|
||
CatalogLinks = {
|
||
init: function() {
|
||
var a, el;
|
||
|
||
el = $.el('span', {
|
||
className: 'toggleCatalog',
|
||
innerHTML: '[<a href=javascript:;></a>]'
|
||
});
|
||
$.on((a = el.firstElementChild), 'click', this.toggle);
|
||
$.add($.id('boardNavDesktop'), [$.tn(' '), el]);
|
||
return this.toggle.call(a, true);
|
||
},
|
||
toggle: function(onLoad) {
|
||
var a, board, useCatalog, _i, _len, _ref;
|
||
|
||
if (onLoad === true) {
|
||
useCatalog = $.get('CatalogIsToggled', g.CATALOG);
|
||
} else {
|
||
$.set('CatalogIsToggled', useCatalog = this.textContent === 'Catalog Off');
|
||
}
|
||
_ref = $$('a', $.id('boardNavDesktop'));
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
a = _ref[_i];
|
||
board = a.pathname.split('/')[1];
|
||
if (['f', 'status', '4chan'].contains(board) || !board) {
|
||
if (board === 'f') {
|
||
a.pathname = '/f/';
|
||
}
|
||
continue;
|
||
}
|
||
if (Conf['External Catalog']) {
|
||
a.href = useCatalog ? CatalogLinks.external(board) : "//boards.4chan.org/" + board + "/";
|
||
} else {
|
||
a.pathname = "/" + board + "/" + (useCatalog ? 'catalog' : '');
|
||
}
|
||
a.title = useCatalog ? "" + a.title + " - Catalog" : a.title.replace(/\ -\ Catalog$/, '');
|
||
}
|
||
this.textContent = "Catalog " + (useCatalog ? 'On' : 'Off');
|
||
return this.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + ".";
|
||
},
|
||
external: function(board) {
|
||
return (['a', 'c', 'g', 'co', 'k', 'm', 'o', 'p', 'v', 'vg', 'w', 'cm', '3', 'adv', 'an', 'cgl', 'ck', 'diy', 'fa', 'fit', 'int', 'jp', 'mlp', 'lit', 'mu', 'n', 'po', 'sci', 'toy', 'trv', 'tv', 'vp', 'x', 'q'].contains(board) ? "http://catalog.neet.tv/" + board : ['d', 'e', 'gif', 'h', 'hr', 'hc', 'r9k', 's', 'pol', 'soc', 'u', 'i', 'ic', 'hm', 'r', 'w', 'wg', 'wsg', 't', 'y'].contains(board) ? "http://4index.gropes.us/" + board : "//boards.4chan.org/" + board + "/catalog");
|
||
}
|
||
};
|
||
|
||
CustomNavigation = {
|
||
init: function() {
|
||
var a, i, len, link, navNodes, navigation, node, nodes;
|
||
|
||
navigation = $("#boardNavDesktop", d.body);
|
||
navNodes = navigation.childNodes;
|
||
i = navNodes.length;
|
||
nodes = Conf['Append Delimiters'] ? [$.tn("" + userNavigation.delimiter + " ")] : [];
|
||
while (i--) {
|
||
if ((node = navNodes[i]).id) {
|
||
continue;
|
||
}
|
||
$.rm(node);
|
||
}
|
||
len = userNavigation.links.length - 1;
|
||
while (i++ < len) {
|
||
link = userNavigation.links[i];
|
||
a = $.el('a', {
|
||
textContent: link[0],
|
||
title: link[1],
|
||
href: link[2]
|
||
});
|
||
if (a.href.contains("/" + g.BOARD + "/")) {
|
||
$.addClass(a, 'current');
|
||
}
|
||
nodes[nodes.length] = a;
|
||
if (Conf['Append Delimiters'] || i !== len) {
|
||
nodes[nodes.length] = $.tn(" " + userNavigation.delimiter + " ");
|
||
}
|
||
}
|
||
$.prepend(navigation, nodes);
|
||
}
|
||
};
|
||
|
||
Navigation = {
|
||
delimiter: "/",
|
||
links: [["a", "Anime & Manga", "//boards.4chan.org/a/"], ["b", "Random", "//boards.4chan.org/b/"], ["c", "Cute/Anime", "//boards.4chan.org/c/"], ["d", "Hentai/Alternative", "//boards.4chan.org/d/"], ["e", "Ecchi", "//boards.4chan.org/e/"], ["f", "Flash", "//boards.4chan.org/f/"], ["g", "Technology", "//boards.4chan.org/g/"], ["gif", "Animated Gifs", "//boards.4chan.org/gif/"], ["h", "Hentai", "//boards.4chan.org/h/"], ["hr", "High Resolution", "//boards.4chan.org/hr/"], ["k", "Weapons", "//boards.4chan.org/k/"], ["l", "Lolicon", "http://7chan.org/cake/"], ["m", "Mecha", "//boards.4chan.org/m/"], ["o", "Auto", "//boards.4chan.org/o/"], ["p", "Pictures", "//boards.4chan.org/p/"], ["r", "Requests", "//boards.4chan.org/r/"], ["s", "Sexy Beautiful Women", "//boards.4chan.org/s/"], ["t", "Torrents", "//boards.4chan.org/t/"], ["u", "Yuri", "//boards.4chan.org/u/"], ["v", "Video Games", "//boards.4chan.org/v/"], ["vg", "Video Game Generals", "//boards.4chan.org/vg/"], ["vr", "Retro Games", "//boards.4chan.org/vr/"], ["w", "Anime/Wallpapers", "//boards.4chan.org/w/"], ["wg", "Wallpapers/General", "//boards.4chan.org/wg/"], ["i", "Oekaki", "//boards.4chan.org/i/"], ["ic", "Artwork/Critique", "//boards.4chan.org/ic/"], ["r9k", "ROBOT9001", "//boards.4chan.org/r9k/"], ["s4s", "Shit 4chan Says", "//boards.4chan.org/s4s/"], ["cm", "Cute/Male", "//boards.4chan.org/cm/"], ["hm", "Handsome Men", "//boards.4chan.org/hm/"], ["lgbt", "LGBT", "//boards.4chan.org/lgbt/"], ["y", "Yaoi", "//boards.4chan.org/y/"], ["3", "3DCG", "//boards.4chan.org/3/"], ["adv", "Advice", "//boards.4chan.org/adv/"], ["an", "Animals", "//boards.4chan.org/an/"], ["asp", "Alternative Sports", "//boards.4chan.org/asp/"], ["cgl", "Cosplay & EGL", "//boards.4chan.org/cgl/"], ["ck", "Food & Cooking", "//boards.4chan.org/ck/"], ["co", "Comics & Cartoons", "//boards.4chan.org/co/"], ["diy", "Do It Yourself", "//boards.4chan.org/diy/"], ["fa", "Fashion", "//boards.4chan.org/fa/"], ["fit", "Health & Fitness", "//boards.4chan.org/fit/"], ["gd", "Graphic Design", "//boards.4chan.org/gd/"], ["hc", "Hardcore", "//boards.4chan.org/hc/"], ["int", "International", "//boards.4chan.org/int/"], ["jp", "Otaku Culture", "//boards.4chan.org/jp/"], ["lit", "Literature", "//boards.4chan.org/lit/"], ["mlp", "My Little Pony", "//boards.4chan.org/mlp/"], ["mu", "Music", "//boards.4chan.org/mu/"], ["n", "Transportation", "//boards.4chan.org/n/"], ["out", "Outdoors", "//boards.4chan.org/out/"], ["po", "Papercraft & Origami", "//boards.4chan.org/po/"], ["pol", "Politically Incorrect", "//boards.4chan.org/pol/"], ["sci", "Science & Math", "//boards.4chan.org/sci/"], ["soc", "Social", "//boards.4chan.org/soc/"], ["sp", "Sports", "//boards.4chan.org/sp/"], ["tg", "Traditional Games", "//boards.4chan.org/tg/"], ["toy", "Toys", "//boards.4chan.org/toys/"], ["trv", "Travel", "//boards.4chan.org/trv/"], ["tv", "Television & Film", "//boards.4chan.org/tv/"], ["vp", "Pokémon", "//boards.4chan.org/vp/"], ["wsg", "Worksafe GIF", "//boards.4chan.org/wsg/"], ["x", "Paranormal", "//boards.4chan.org/x/"], ["rs", "Rapidshares", "http://rs.4chan.org/"], ["status", "4chan Status", "http://status.4chan.org/"], ["q", "4chan Discussion", "//boards.4chan.org/q/"], ["@", "4chan Twitter", "http://www.twitter.com/4chan"]]
|
||
};
|
||
|
||
Emoji = {
|
||
init: function() {
|
||
this.icons.push(['PlanNine', Emoji.icons[0][1]]);
|
||
return this.icons.push(['sage', this.sageValue(Conf['sageEmoji'])]);
|
||
},
|
||
sageValue: function(check) {
|
||
return (check === 'appchan' ? 'A4AAAAOCAMAAAAolt3jAAABa1BMVEUAAACqrKiCgYIAAAAAAAAAAACHmX5pgl5NUEx/hnx4hXRSUVMiIyKwrbFzn19SbkZ1d3OvtqtpaWhcX1ooMyRsd2aWkZddkEV8vWGcpZl+kHd7jHNdYFuRmI4bHRthaV5WhUFsfGZReUBFZjdJazpGVUBnamYfHB9TeUMzSSpHgS1cY1k1NDUyOC8yWiFywVBoh1lDSEAZHBpucW0ICQgUHhBjfFhCRUA+QTtEQUUBAQFyo1praWspKigWFRZHU0F6j3E9Oz5VWFN0j2hncWONk4sAAABASDxJWkJKTUgAAAAvNC0fJR0DAwMAAAA9QzoWGhQAAAA8YytvrFOJsnlqyT9oqExqtkdrsExpsUsqQx9rpVJDbzBBbi5utk9jiFRuk11iqUR64k5Wf0JIZTpadk5om1BkyjmF1GRNY0FheFdXpjVXhz86XSp2yFJwslR3w1NbxitbtDWW5nNnilhFXTtYqDRwp1dSijiJ7H99AAAAUnRSTlMAJTgNGQml71ypu3cPEN/RDh8HBbOwQN7wVg4CAQZ28vs9EDluXjo58Ge8xwMy0P3+rV8cT73sawEdTv63NAa3rQwo4cUdAl3hWQSWvS8qqYsjEDiCzAAAAIVJREFUeNpFx7GKAQAYAOD/A7GbZVAWZTBZFGQw6LyCF/MIkiTdcOmWSzYbJVE2u1KX0J1v+8QDv/EkyS0yXF/NgeEILiHfyc74mICTQltqYXBeAWU9HGxU09YqqEvAElGjyZYjPyLqitjzHSEiGkrsfMWr0VLe+oy/djGP//YwfbeP8bN3Or0bkqEVblAAAAAASUVORK5CYII=' : 'A4AAAANCAYAAACZ3F9/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAa9JREFUKFOdkt0rg2EUwM95b2zlL0CRRLngksznXrJsNtYW1tjYhM3mY6+IXZAbikhTKJp8XZAp81UmWYhIRHHhUi60e7s6ntdCa2449es8PfU7z+k5B6AbyuE/wQlc4BcO2d06unAUBCgFE0hianOd3NHIcy8NPwrUf9NBPZcOEi7ayXZiea/1V7+ljaXeYAfOgg2So2TOwQWGnwQafOgi962TnMFmatozUeNu4yetASspVvgXiUvii5K5Nm6z56ol3Hdtpy+cwSYy+HRUt1nLsoEato0kXyh6wTac+24brThWv6MNOYNW9prlG/uxmbRrFaT0VrCspZoNPSUNJNyCBcoiLZuhLH0o9U6UrAfGKCz7RlLM81Q8XUwqr4oKPLIQmnA8IupBigacVy7yrya/2JouhryJHJJNykg+UxLGOtz6+SQNpEiMcduls4Wvoli9WklVKz+ol5SU4U6ngql8Qj2eRI+GyajBhSRH4r3cUxhSeRVhsYBmWUWiyM+UMDmDUI2nsfuSC1I27nLgYZJlP8jhjJ3PY8iE+L8tWx4kQC6MQA5b1D9HNiRCFhx8AF/e2qh92VnKAAAAAElFTkSuQmCC');
|
||
},
|
||
icons: [['Plan9', 'AwAAAAPCAYAAAGn5h7fAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoYAzE15J1s7QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAACAElEQVQoz3WSz4sSARTHvzMjygpqYg3+KIhkE83DKtKlf0C9SrTRuZNEx0VowU6CuSeJNlwwpEO2kJ6SQBiIUAzFjRDF4wrjKosnGx3HmdehFDfpe/2+z/s++D5gU7IsEwRByICIiAEAIiIAYAFAXsjYVr/fLxMRNVvN+prJ5/OA3+/XERFNf02JyeVyDx0OxyvLNQsnimLKfcf9KRQKXQAAnE6nlf5qMpnQycnbP/kAoKoqsSwLAJhOp+AAwOv1otvtpqxWq73dbt/r9XqvEQ6HUalUEvF4XLd5IpvNZqlerzd5nlf6/f6tTCZjBACk0+nb+XxeW4UrikLJZPImAGA0Gq0NIqJyuSyyANDr9Q5Wu1utFvR6/SULAI1G4+vK8Pv90DTtGwsAJpPpaGUYDAZ0Op3PHAAEg8H3tVqtbrtu21sqyxuRSOQJk0ql9IvF4r7b7f7pcrlejkaj57IsH58Pzp8dvjhc/lsBk0gkbLFYrFqtVvd27+4qOk733ePxPDCbzVBVFfP5fCiK4rvhxfDN/qP9wSasGwwGMv1HiqJQsVg8ZlfTHMepkiR1t05gGJBGmM/nMBqNj9nN9kql0lNN064ARISzH2cQBAGz2ewLu2na7XYLwzBbvxYIBBCNRrFj3BmsAZ/PZ+J5/kOhUIAkSVeA8XiMZqt5efrx9OA3GfcgvyVno9cAAAAASUVORK5CYII='], ['Neko', 'BMAAAARCAMAAAAIRmf1AAACoFBMVEUAAABnUFZoUVddU1T6+PvFwLzn4eFXVlT/+vZpZGCgm5dKU1Cfnpz//flbWljr5uLp5OCalpNZWFb//f3r6+n28ff9+PRaVVH59Pr//vr38vj57/Dp7eyjn5zq8O5aVVJbYV9nVFhjUFRiWFlZVlFgZGOboJzm5uZhamfz9/bt8fDw6+drb26bl5j/8/lkX1z06uldWFS5r61UT0tfWlbDwr3Ew76moqNRTU7Mx8P75OpeY19pWl1XW1qzr6x5eHaLiojv7+1UT0xIU0uzqadVS0nV0MxkZGT5+PPk497///ra29Xq5eFtY2H28e2hnJignJlUUE1dXV2vrqxkY2FkYF/m3d5vZmfDuruhl5aZlJHx8O75+PZWVVP29vT/9fTj3trv6ubh5eRdXFqTkpBOTUtqZmX88/RMQ0T78vPEvr7HwcHDwsDq6ef///3Gx8H++fXEv7tZWVedmZZXXVudnJp0c3FZU1f79fnb1dlXUVVjXWFrZmy8t7359/qLj455e3q4s69vamZjX1zy4+avpaReWFz/+f1NR0vu6Ozp4+f48/lnYmi8ur3Iw7/69fHz7+xbV1SZmJZVUk1ZV1zq5ez++f/c196uqbDn4uj9+P7z7vRVVVXt6ORiXl/OycXHw8CPi4ihoJ5aWF3/+v/k3+axrLOsp67LzMZYU1m2sq9dWF5WUU1WUk/Au7eYlJGqpqObmphYVV749f7p5Or38fPu6OpiXFz38fH79vLz7urv6+hhYF5cWWKal6D//f/Z09Xg29exraqbl5RqaW6kpKTq5uPv7Of/+PDj29D//vP18Ozs5+OloJymoZ1ZVVJZWVlkYF2hnpmblIyspJmVjYKQi4enop5STUlRTUpcWUhqY1BgWT9ZUjhcV1NiXVkkhke3AAAABHRSTlMA5vjapJ+a9wAAAP9JREFUGBk9wA1EAwEAhuHv3dTQAkLiUlJFJWF0QDLFYDRXIMkomBgxNIYxhOk4wwCqQhQjxgxSGIsALFA5BiYbMZHajz1oJlx51sBJpf6Gd3zONcrqm/r1W8ByK0r+XV1LXyOLLnjW6hMGpu0u1IzPSdO17DgrGC6AadrVodGcDQYbhguP6wAvAaC0BRZQalkUQ8UQDz5tAof0XbejOFcV5xiUoCfjj3O/nf0ZbqAMPYmzU18KSDaRQ08qnfw+B2JNdAEQt2O5vctUGjhoIBU4ygPsj2Vh5zYopDK73hsirdkPTwGCbSHpiYFwYVVC/17pCFSBeUmoqwYQuZtWxx+BVEz0LeVKIQAAAABJRU5ErkJggg=='], ['Madotsuki', 'BQAAAAPCAMAAADTRh9nAAAALVBMVEUAAAC3iopWLTtWPkHnvqUcBxx5GCZyAAARERGbdXJrRUyGRUyYbY23coZFGDRFGEYfAAAAAXRSTlMAQObYZgAAAGhJREFUeF5Vy1kOQyEMQ1Fshzd12P9y61AixLX4yJFo1cvVUfT23GaflF0HPLln6bhnZVKCcrIWGqpCUcKYSP3JSIRySKTtULPNwMaD8/NC8tsyqsd1hR+6qeqIDHc3LD0B3KdtV1f2A+LJBBIHSgcEAAAAAElFTkSuQmCC'], ['Sega', 'CwAAAALBAMAAAD2A3K8AAAAMFBMVEUAAACMjpOChImytLmdnqMrKzDIyM55dnkODQ94foQ7PkXm5Olsb3VUUVVhZmw8Sl6klHLxAAAAAXRSTlMAQObYZgAAANFJREFUGJVjYIACRiUlJUUGDHBk4syTkxQwhO3/rQ/4ZYsuymi3YEFUqAhC4LCJZJGIi1uimKKjk3KysbOxsaMnAwNLyqoopaXhttf2it1anrJqke1pr1DlBAZhicLnM5YXZ4RWlIYoezx0zrjYqG6czCDsYRzxIko6Q/qFaKy0690Ij0MxN8K2MIhJXF+hsfxJxuwdpYGVaUU3Mm5bqgKFOZOFit3Vp23J3pgsqLxFUXpLtlD5bgcGBs45794dn6mkOVFQUOjNmXPPz8ysOcAAANw6SHLtrqolAAAAAElFTkSuQmCC'], ['Sakamoto', 'BEAAAAQCAYAAADwMZRfAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAxVJREFUOE+Nk19IU1EYwK+GQQTVQ39egh6ibKlzw91z7rn3bvfOmddNszl1bjKXc5rJJGmBUr7Yg9qTD0IalFgRBEYg6EDQQB+GovQyQgiaUZsoLcgHMcr069w7MgcGXfi453zn+37fv3MYZt/n99e76tzVj4JN/hP79fvXnV3hnNabwUBjoOHcgTYOu/JQspgTzsqKgn9BfD4vkWTzur287PqLVy+zM+yePB7KsRXLywTjnSpnZctBkPCdW8ccDuU55vBO8RXbkC/oP5ph19V5+7LIky0OY1BKbZEbLcFSt7u6pN7jLmltCVrr3DV5jY3+KovFEsccB1KJNVpefe10BqS2tqqO4/AuphBB4L/LkrRqNgtJs1lMypLls1kU38mytMLz/E8VIlutqVqX6/weZG52OttRXjbE0cP/FYLRlpVjDXuQ/r77x2XZPKkCHA4HBAIBkCQpAygIAvh8Pu2MZgO0Lz+QSa/sQfwN9RfpVN66XC6Ynp6GhYUFGBwczAC1t7fD0tISxONx6O7upgHILmsqvLcHodOggfiV/v5+SCaT4HQ6IRaLgdfr1bIRRREmJyfBZrNBNBqF+fl5sNsdgE2GiAbp6bmbdbXC7qWQbxMTE7C2tgY6nQ5SqRSEw2ENopaoZpCXlwdTU1NaoECgCbgiU6y8QH+ECYWaTymK7TWdys7MzIwGaWtrg42NDejo6AB1WjU1NZo+FArB2NgYrK6uQrAlCASxn2z6wkuMp87VIAhkE2MEAwMDkEgkYHx8HBYXF0HtkQpRy1BLiEQisLy8rPVNKSsFjEzrXH4+z1hlS4xDhKadNu7t7YPR0VHweDzAEVWfHru6HxkZgeHhYVAURYNjkylVWKArZjjMzqmdVi+QCsLUkQiEjvDvncEkvU7/qQ0Vgukeo48Go87IiCJnZNmipxiz7wXEbVDnbUxQOgM12h9n6qTq6NvapRdtkwaP0XK8RmPuYSbxYfaQ/sJJhjfknuFRURUi7AMOozcCwl94hLZp5F+EioDQVwqYI6jomZU1NFtM+rOSxZjVazcyvwHr/p/Kws1jegAAAABJRU5ErkJggg=='], ['Baka', 'BAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA0pJREFUOE91k3tI01EUx39JOpA0H4jNx0pbD3XTalISWf8YFlEgldqDsBLLyqjEKBCiLLWiggh6/KEV1WZ7OaelLZvDdDafNW1JFraWe/32+01FrUZ9uy4ylLpw4Z5z7/nc77n3HIqaMRIjZJyEcNX+uFCFeGmI/GZciEIsCFJUTvoAzDz+1y7K76MSwhX5hXl6z+WSbrzU2KB8YEGDwgrTaxZ3b7xHcaHhR3xw7Z5/UviB1ReP5XSg3+TAqYJOxMzWISFIC0GQDomhTVA9skCnsaAwp/vnMq66dBokNuBR9uFd7T9Z1zCunjci0qcRJUVdoJ3DYOhRnC/qBZ+jQbfeCc+37yjY2UEg0iwvJE0k9l8Z+8xqHmTgot0QLdQgTaQFQ2AsOzlHvOu1S5pwOLsHHo8HjHMCq2MazNvTlByKHyrJLDvdR25jMWRxYx5HjeMH2r1BDOOeguRua4OI14jx8a8YH5tA+al3EHKlW6mYOapb2oZBOOwMbEMseAE12L+jjUh3w+VipyAZ65oxn1NP/GMYGR6Ftn4Qsf7qa9S82Y/l/X122G0uL2TbxmZEz1WhXW8mUol8moXu+SCi/OoQ6VsDh3UUwyQ1k9GOaI5MTkX4yWTGHutvgI1F28sviAlRgxeoRm62HvsyW8En9pZ1TYgi6TntoyQtFm86rVgUoJZRvDnKMmXVAGxWmkAYOBwudBqGcHCvHulrGpGT2Uy+z4yT+QYsCXtCUpp8GxbKhx8gDK0ro+KjJGvzdjfDZnN6VdisLD5/JjArQ2zW66PJOj2lEZtStaBphkwah7K6kMJ/GEulp1bMWhAmMbTozOQRaWRtfoZVgjo4iRra4SYgGi26TwjxVeDKhR7Y7U606ixICq9tr7hd7+OthRWL7yUnJ1WPmXotqLhpRICPHCePtuFV6xdUPTAhcWEtRHEqfHpPyto4hPXLXnzflSEJnFaN3OCKDcsFsrEntR9RUmxARLAUgT5iBPuJsXWDBj0dZjRU9yNV+PTbpjTp9OA/pOSk24nRkXf1J462oPxcJ65f6ULlHSMulepRerYDgvj7A0cKpNz/tyTZqbzXO4t0ZZGQJ34RH11lFHIlA8LIqreCCMUZRY3cd2bwL/5/RmjNSXqtAAAAAElFTkSuQmCC'], ['Ponyo', 'BAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAuNJREFUOE+Nk3tI01EUx39BTytConQTt1am07m5abi5KT5S8z2dj1yOEMUC7aUgIoimlmUEWX9kBZGWaamEmE6U1BI1XNPNGTrnHs33IwuSXrL4NgcJ0mNdOHDh3PPhnPP9XoKwcroJYvMQiRSicHCQKCgUyZC9/T5rNet5KUFs0zCZbZMsFmZ9fTEjEEBDp4/KSSSb/4JoGIyWaTYbiykpWEhOxhSHAzWD0aqkUGhWAcVkW58xlvuPhfh4zItEmOHxYDR3MhcdDaNAsKJydAz5IySKRNjEUmy88vjOVaU8F0iPCqCNjEBHkC/UYaGYFwqxmJoKLYOhkxPElg0QsbNtTlmox9yjRD9UCbnoOR+J/lwRWtOCcdXfDc2BPpg0d7CQlIQZPh9KKlVkAQjJ2x2zmOSsQu7hpzUJfBhLjsNQmADjxcT10Bcl4rE4EHc5LjBEhEPn7f1WTqXSLQB/s1Tp7vslsoIkyPPiMJAbi86McBguiaHKjoEqR4jJy2K0nAxApzMN5iUGrclrKVaz2fUvuF4tRbxDKA90w5VjTFyLZKHpTBSq4/1QnxGB2qxoVIZx0JopRCPHFSNOThfWZzfrXDcZEowH4iA05ATg68hDtBaL0HAuCm3lJ9Bfcx2fFNUoi/DCjRgfNHHd1wCZA2TyXjNkE6F0cBDpPFiojeNi8EkJdFoN3vXch0nbBJOhDd907dANv8JITxNqziag3ZsJbUDAwLin50Q9QWwl1qSYoNOVvUcOoqOqAAa9Fu9H2/F9+B5WZLcwOyxFX18flLI+VASyMGVeoJHD+Tzq5BS1PoaKRrNT8127P74swsq4FCa9FKvqBqwaOiz3hdEuLKueYSyECT2LNW0eIfo3E/WmEbvnG1MUJnWdpWhDGDvxQXZHo+RR0uW2tnv+auPX+TvtJm7zKpaen/4y2yjBUlcxlvtvmvT16ZWDpQeoVv3/60F/NrHjTf4ugazIXtJ8ivjnz/sJ+yGQRjcqUdIAAAAASUVORK5CYII='], ['Rabite', 'BIAAAAQCAYAAAAbBi9cAAAD/0lEQVR4Xl2MXUxbdQDFz/9+9Lb3tkBLCxTKhzgoOOZAsokbJmZxDFHnd+LL4hKVzBgfNCY++ODbjDEaZowvErOM6HRu6hKZY2rIAOkCY4OSDTpFaAsrlJa2t5+39+NvjT7tnJzknIfzI98Nf/C6TuXdguWBd1q9rcb8/CwsZiu2Ywm4nDVo3VWLZCKDaDwJq9mCg31PgjAMKKUwmcyYvTbek9iJRDm6M/XswEDjwNz6plWW6wdZhjUAintFCEEhn0N04zYskljaDLaj8ar49oUrsYR6mrFJNj322w46H8y+mitM/ZJKZmyE4XAvjJSsazpyuSzslVZIkgWKOvvRgQ6Xrdlhqmds7o7bFZoLkctreKxf7GtuCE7IyUQjBQcQ8j/lvxCGQJZz0IoCVpamTtzfIh9nwiaIrCQyjNg8mq11oDLUhNXRJfT1Ozr3tS/PqpnQ80qRgjAmKIqBfK4ItbSLKoOZqR/6neLkENlSUAIhlktvEf+sD2rkm8nWTHtvZCGMVON1ePuaoBER31/MXGly1wSqq9Uug6FluYyWXJiPqFXmjd4Dh9oF9ZKKimYXRtYCx8lmMIDIxlIPGz591av0mtanF7FcCEN6iMXeox2wOJ0QJAmUAoRQaIqCnWAQY1/ewKNGNeQuYXkm0d2NC2e+wvmRr/Hx+6+8PHayrbDyyQBNDb9As3PHKDWG6MTM23RoeJAWsqeoWvyUUv0UHf7pBB0fe4OeeXe3/vmHbx3+8dwIGJ4IsFpMMFe0fbtAn+nwZePr1u4MBK8XIALG/Rt479wYrs2vgeNNAMNgMbiNzybuoKVvn+Gs9kbr6qpBfJfGYHFIkJUCoGwfqcoMX/b27EGhwgOjoCADDlP+CA51ugFFRzoB8FYNaQ1oqKD44+eNL+wNj7zJGQSIhe8+jgQ9thk+27v/KRY6L4FSCkVOwtlQj6P73Qgt/o1ERoKt4iUkE7+jrZMHyzIoK9cOBFfT4LbWAk+0a7ZLnvqHcTNdACgFScfAcjxEdy00VQclHGo7dqGeYxHbvIo6hwhSghCehb3G5p6eW7VxXC5/xGWToMgrKKoaCnIalI9CIARasQAqloMI/x4BWrLLYwE1AEPTwCGHaGjz7pw/leZUNV8wNm9BLy6CxsvxZ1kMbaY4TKIIXlNBsynoVjvAC4CuAoYOVi+CMfLYCUfg95tPHuzZB0YtKzsb58RMucWE/fZmhCbdOP9rNnLnxko6GVoB8lFwyVVw8b/AyeulHoJyN4Rb19dTFyeqBlu6njvfsWcvOJvLs7DMmw/7bvpeE4pU2OIcgcqmp4fGAgt2Txwvqr7lTp5V7LquZxXC6+BqEvGcY5pyjaM1tffJbk89NE3FP5VQ6y7a+paZAAAAAElFTkSuQmCC'], ['Arch', 'BAAAAAQCAMAAAAoLQ9TAAABCFBMVEUAAAAA//8rqtVAqtUQj88tpdIYks46otwVldUbktEaldMjldM2qNcXk9IWktQZkdIYlc8mnNUXlNEZktEZlNIYktIWlNMXktE7o9klmdMXktFHqdkXk9EWk9EYk9IlmtQXlNEXktAWk9AWlNEYlNFDptkZldMYk9E4otg/p9kXktEXk9AXlNA4otclmdQXk9IYktEXlNEwn9YXk9IXk9FFp9o3otgXk9FPrdwXk9E2otdCptkXk9E/ptkcldIXk9Edl9IXk9EjmdUXk9EXk9EXk9EbldIcldIjmdMmmtQsndUvntYyn9YyoNYzoNc0odc1odc2odc6pNg7pNg9pdlDp9pJqttOrdzlYlFbAAAARXRSTlMAAQYMEBEVFhgcHR0mLS8zNTY3PT4/RU1kdXp6e3+Cg4WIiYqMjZGXl5mbnqSnrbS3zMzV3OPk7Ozv8fT29vf4+fz8/f7SyXIjAAAAmUlEQVR4XlXI1WLCUBQF0YM3SHB3a1B3l7Bx1///E6ANkDtva0jKbCW2XIH1z2hiZEZ4uUgxo7JedTQye/KN/Sb5tbJ+7V9OXd1n+O+38257TL+tah3mADAwSMM7wzQWF4Hff6ubQIZIAIb6vxEF4CZyATXhZa4HwEnEA+2QgoiyQDnIEWkjVSBBZBqXbCRlKYo8+Rwkyx54AOYfFe7HhFa7AAAAAElFTkSuQmCC'], ['CentOS', 'BAAAAAQCAMAAAAoLQ9TAAAB5lBMVEUAAADy8tng4Ovs9tnk5O3c7bX44LLduNO1tdDh7r/eutj43q2kocX23az07N+qqsvUqcmXl7331ZXJj7r40o/Pn8T42qP63KjNw9n21p3Y387Ml7732JzR55z05MSxtMLGn8TC4Hx8eqt8e62Af6/B4HnG4oPC4HzH44fBf7LCgbOkoMTcsrmtn8PWqcfFtKrj4Jvs2ZOz2FnMqLXT3KfY5p60Z6NUU5XRuqHzwWSywqDn3JaiiLWahrWhkry5zJjRmqm1Z6P1wmb1y319fK632mK5cKi5nH+73Gu73Gy73W283W+9eK17e6y1yZS3aqRZWJdcW5ldXJplXZppaKBwb6VwcKV5eKswL306OYNPTpGkfK+m0kGpUJWq1EnEqIuXK3+Xh7ahP4qhkryMfK6BgK+CdpGMaKKMa6O9ea2+eq6+oYW/eq+NbqWVlL2Wlr7AjanA4HnA4HrBkqbBlafB33rCgbLCmKjCxIzC1mSs1UytV5mtxIWt1lCuz2evWpuvXJywxYzHjrvH4oXIjrrN2HXO5pTO5pXUlYnUlYvVl5Hb0G7e0XTg03rhr5fpzHPpzXTp0Hvtz3/wrDHytknyt0zyuE3yuVHzvVr0wGP1x3T1yHf1yXe0ZaL2zYP30o730pD31ZeRIcF5AAAAQ3RSTlMAFBkbHEhJS0xMTk5UWWBsd4SEiIiPkJCVlZaam6CjpK29wMPDxMTFxcnK193e3+Dg4uTn5+fo6e/v8/P4+fn7/P7+J4XBAAAAAOBJREFUeF5Vj1OvAwEYBb/yGlu717atLW0b17Zt2/6nze42TTpvMw8nOZCAmwUpiIY6c5IiLi9tPX64GairqszHQ4X2VB64v1Cs6PxMPJSdHM777s6/jyaMRGiRLyyrb88OpjZ3CzAXrm1sqzSNNeN7kVBPNgB7cG51abE5l9cXDces7emQ1uadHhutFUg6gpPKkSIqQGavwz7r7O/+/3t/rSdjI9XDM3qz4fr3B/3iA0aJTG9x71+9oR/PLDwUe2wm19bly+fTIxHyEETatbPewGEw6Mk/tKZCEqSQQUlIHB/QNBEjjVN1AAAAAElFTkSuQmCC'], ['Debian', 'A0AAAAQCAYAAADNo/U5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAZ5JREFUOE+Nkk0oBHEYxv8fu5GQj3JwcaDkIAc5IpR87M7MKnIVJVKclaIQ5Sy5OLkgR7n5OigcSNpmd2c2Vyfl4KT8/muWiVU79TTv+7zv837NCBF6PG1X+NpZyEYSD9mIc+tHnBPe23B9xKrCuTmbQA/JKfABrhBswa1hH4A38IwfOxPdX1qcjiCQxO5NyrjKV70TnSbeRPwJvGN3i4yyqnEucPY8ZZX9GSEgGK+RvFfyjk2VKZxzBNG8wJWWgh/xtDOeUXZ7Slr6TrSLYL9N4SMgYTTcwdc2ArvJcElhSVcM6mCNSV8n9hA59yTU5UWMG6HIbLhIWlglgWiC2L4Z79qTdo40D6ISuOWwKCWHyk9Fv8ldpUHOuGTuynwSBUynddPdlbEosVpP9Eu4FnOsRzUYNTsdmZN/d5LDiqM0w+2CMdAFFsFGWgfXxZnheqe/z+0puwEM0HHYV3Z9Sgz8TEz7GkQvpuJ/36ggj2AaHLrSlkULWV5x+h2E8xkZL16YVjGNaAUscfZ/f6c/k9ywLKI2MMcRWl0RLy007idmRbQJ7RIfDAAAAABJRU5ErkJggg=='], ['Fedora', 'BAAAAAQCAMAAAAoLQ9TAAABPlBMVEUAAAApQXIpQXIpQXIqQ3UpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIqQ3QpQXIpQXIqRHYpQXIpQXIqQ3QqRHYpQXI8brT///8uTYMpQnM5Zqg5ZqnS1+I4ZaY4ZactSn8uRnYrQ3MrRXgsRHUsR3s8bbM8brMtSX4wUosxVI01XZw2X50vUIguToQvR3c6X5o6aKs6aq08Un8qQnM9VIFDWINJXohKcKlXapEqQ3UvUIc2X55bhcBdcJVgcpdhfapmd5tuk8dxgqJ1hKR5jbB6iah/m8Shudq3v9C4wNG/x9bFy9nFzNnFzNrIz9zK0NzK0t/O2+3P1eA2YaDU2eTb3+jb4Oje4urj6fHm6e/s7/Tz9fj3+fz7/P38/f3+/v83YaEa/NNxAAAAHnRSTlMABAoVGyY1SVlpeIuQsLfDzdHW4+3y8/b39/n6+vr4+ns8AAAAyklEQVR4XiWN5XrDMAxF75KOknYdZJS0klNmHjMzMzO9/wvMcH7I37mSJShsJ+5NjMT6umDoHyXDcI/2qJadh++P3cle1de+9yPe3/bTY92wzfzr7wGtP3JrAI72BZGVtcAdQlwHy+JS1pDbBE9qamZF3BYrjQxPEXwKc6dC8bXFm0QIpmt8kn0Rn093q82UCtK8oXZckwFJzuulV8bHkajPyXdbnJnARfDHs0trz+JQ+5AFvzp/L0+cL2qPAINUPrq5OC6p/64F/AMnrST+Dq/r7QAAAABJRU5ErkJggg=='], ['FreeBSD', 'BAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0QA/wD/AP+gvaeTAAADXklEQVQYGQXBS2wUZQDA8f83j33M9rF9d7u4loaWklaDpkSo9KDGaIKUaGxshD2YSPRiuDVeTDyhBxosJCoa40ktpAkPDcUqAYVIpUSUPrAulEdD2bbb7e7ObGcfM/P5+4kwKDvq6yJ1FYYcvb+YAkqAHo/HQ7FYrFIoCiurq9ZXJ06YSOkA+kBzfX06bys3zHxS9EL0tXDVyZfefacqV+X/ZSJx5+qLbx98LhaL9RiGEZWlEsWC/Thd9q6Pf3vs2u6Orc83rFsvTwwfLf5obgywT1Vjh2Hh+rbNsnTssJdNLedK5aIrpSuldKVXKsnH4+Pyn6FDXn5tMef9O+3NvdkvP1V4+EYw2AoQ+KSx8dRYS6NXXnwovaItXduSrrkinWxGOmZWJi9OyOK9m1LmsjIz9IH8QUMOd3WfAQwNKCy2tJwbHB5+XasPaxIHmc4g7WWEZ1MquBiRFlJTf1E7+Tl/H/8asavPzTY1nWd2ZkMDRPeBeHPz5ojwsilEQCBvTSKunCF3M8FSNkBGVTHDYYrLj8jVNhDZ2SMa2zo3MTamaIC/u6Ojr3DtrOrvP0BpdATnyBeIhTxpR5ABUlKSUlXS1dWstbVxdz6hPL0l1quGqkLaKwNvVcjEXNRd/4mit4Z19DjefBEPyCKxgQJQcF28dBrHNDGTSZSezsjeff0hraa2Vs2vrvit81O4vj9xLJcC4ADrQA7YAGqBGsAql/EtLdFQE/L7dF1XZmdnSrbPMJfXoLDmolQK8gJyQBowgQhQDRQBD+hsraVhd4e5MH+/oExfvWLJ9q3/3S7qMpNH2hsS40kFS4EUUAMA2IANRIBXv4uzuO67c2PykqkA5YmZ6bN18YPi0Yoknxc4AsJPCMLVAk2BLKDosCWqs/PZaulkuxk9fekcUBAAQGDks5FT0W++3NuYuC0DVUL4DIEdlIQDAj0IRkigaMjArkFx0tf523sffrQHyKsAgHPhwoXLL+yP9/kePNhk5ExUTyKFkJVAUAiCFZrQup4Rv9ftuLV/6ONBYBVABQAArMvJ5MXW7duD6P62sD8UrPAFRU1TpeCpCnGvPZr7WW///v0jpw+VC9ZdAAABAAAAAMLo7drWrmQyPWG/r8tnaGIjaM05ujr16x/ZBFh5AACA/wGZnIuw4Z4A3AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMi0wNy0wNFQxMDowOTo0OS0wNDowMOPVpFwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTItMDctMDRUMTA6MDk6NDktMDQ6MDCSiBzgAAAAAElFTkSuQmCC'], ['Gentoo', 'BAAAAAQCAMAAAAoLQ9TAAAB9VBMVEUAAAD///+AgICqqv+AgIC/v9+Ojqqii9GAgKptYZKQkOmPj/ddUYBgW4eVjeCTgfiWjO5wbJaZkvPBvepkXomYkNldV4Bzbpl6dJ+Uj7ynoO6Vi+1qZI63se2mnudjXYjOy+GCfaqZjvWlm/Pc2e+Oh7NeWIOWjfeXjeW1sd+gl+diXIfp5/KHgKnn5/F2cZx6c6ZgWoXc2e6dltrAvNu0scrX1eTOyujCvup4c5qpovVpY43///+6uPPJyPXq6fvm5vrz8/z8/P7+/v/d3PixqvmxrPSyrfe0sPO0sfS3tMve2/3r6vy6ufPz8/3d3fi3tM63tPO4tsu5tsu5tvO6tfe6t/Vva5KRjKy7tvW7t/W9vPO/vM+/vvPCwfPEw/TFwvTFxOfGxfTGxvTHxvTIx/TJx/aTiOrNzPXNzfXQzfnRzuHS0fbS0vbT0uHU0e/U0uTU0/bW0+zW1ffX1vfY1/jZ2Pjb2/jc2uSTiemVkLSlnvbe3PTe3vng3fzg3f3g4Pnh4Pnh4fri4enj4/nk5Prl5Prm4/ymn/bn5vro5/rp6O/p6funoPWsqs3t7Pvt7fXv7vzv7v3w7/nx7/3y8f3y8v3z8vytqPWuqPX09P319P319P719f339v739/34+P35+f37+/+uqev9/f6vqvSwrPQAR0dcAAAAPHRSTlMAAQIDBAgJCwwVFyAsNUFHSVBneH+Bh4mVmZmanKCxsrK2tr3ExtDW19rb4ODl5u3t7u/w8/T6+/z9/f4MkNJ1AAAA8ElEQVR4XjXNw5aDURSE0YrRtm3b54+dtm3btm3bz9k3Wek9+2pSYFwT8ibzE93hwAtdJqK3nZo4J9hFXbP+vFHOthV6gnGzstZq94wdCs4UCCDymQ2v7X0LdYoSQ0MIENRYzJbRlPTTHu73ZNAL8vivmVui98PpzuqffX0mIPHJGtOQenukteJ+aS3b9htNpDnT9TeZH1bHAwBRMhGpd6e6uNrLoRgxBKmsX47nBlp678ojpEA40fejcmW4e/No0V8IIPfj6eKgbEJ3ZUnzgE1OqWp9Q3VeWRAsg51f1dZ8c31RmAsc+N5JGbG+zvj3BzDCPrzMDC9SAAAAAElFTkSuQmCC'], ['Mint', 'BAAAAAQCAMAAAAoLQ9TAAACVVBMVEUAAADh4eEAAAAAAAAAAAAAAAAAAAAsLCyXl5dgYGCnp6eTk5N3d3fBwcGqqqq8vLzNzc3Ozs7Ozs7Pz8/Pz9DQ0NHR0dLS0tLS0tPT09Pf3t/Pz8/i4eLb29vZ2drZ2tna2dra2trf3t/u7O/u7e/u7O/r6+vt7O/w7/Lw8PDy8fTz8fXz8fbx8fHz8/P19fb49/j49/n6+vuPxlmWyGOx437h9NDr9eD6/fj////+/v75/vTA5Jv6/fb7/fnL5bDL5q+AxjeDxUCEzTyGxUaGzjyHxkiHzz6J0D+Kxk6K0kCLyE2M00WNy06P00mSz1OUyF+W2FGX1FiY0F6Z02CZ21ac0Wiez2yfz2+f2mOh4GCi4GOi4WKi4mOk12+k3Wul32um1Hin0nun4G6n5Gin5Wmo23Op2Huq1n+q43Cr526s4Hit23+v6XSw34Cw34Gw6nWx4IKy4IOy44Cy63ez146z34az4IWz4YW03Y217nu38H2625e645G74pK83pu98Iq984W+4ZjA4px0tzDA5ZrB8ZDC5p7D55/E947F6KHF+JHH4qvH6qTI46/K5LLL5LN1tzLL5bN1uTDL57DM5bPM6qzM66/N5rTP6LbP6bTR6rfS573T67vT7LrV7r3X68XX7MHX773Y77/Y9rvZ8cHa7cjd88bi88/j8tTk8djk9tHm8trn89vo89zo9N3p9N3p9d7p9tvq9d/s+93s/dzy+erz+O73+vT4/PX5/fT5/fX5/vN1uzB3vTD6/ff6/fh5uTj8/fv9/vr9/vx8wjV/xDmrMRH0AAAAOXRSTlMAAAECAwQJDzk/RUlNU3F0kpSVlpeYmpucnaKjpKWqqqqtu8LExMTEzdTU1NXY4evy8vP+/v7+/v6LaR1mAAABD0lEQVR4XiXI03bEABAA0KltW9kaW3eSZW3btm3btm3b/q4mp/fxgqKOtpamhrqaqoqykrQYABh+PVMU9fjE5Xp8o54kgPHN0EBHU2N5YXZykiua0HHd2759VF2Sk5IYE5GGsmCEWLV1kVWwt5O+3x/qpgsy8k4ja+cJl2/v5C22tlgCAHtw9TQSa4s+AzfPSm0BRNl9SydhWJzLC567KrNhgrNwHIJ5qTz/2f9w7Jw/DNqIjVr04exW0AEOXcN3Ab7enr9eDW2VTJgehONyc2Z8XP5YdD0Tcuhcc4/r45OjGX51TEjYPbh8THRPvbz+CHusgSZlT7rP8PkCwfQKaQUi9Igr6JsRBMFiWZgb/AHKElRzKopZJQAAAABJRU5ErkJggg=='], ['Osx', 'BAAAAAQCAMAAAAoLQ9TAAABrVBMVEUAAAD///////+qqqr///+ZmZn///+qqqqAgID///////+tra339/eAgICoqKjx8fGMjIzm5ubh4eGPj4/g4ODIyMiAgICSkpKLi4vS1tbPz8+Xl5eMjIypqanIyMjW1tZ2dnbR0dGamprFxcV3d3d+fn60tbV3d3dcXFx3d3epqal7fHxxcXF+foCnp6hYWFhyc3Ojo6SMjI5fX196enp+fn6Li4xERERqamqgoKFpaWmFhoeen6A/Pz9QUFCWlpeSk5SUlZWUlZaOjo+Tk5RHR0cuLi5YWFgwMDAeHh40NDQ3Nzc6OjpcXF1rbG0XFxdSU1NVVVVXV1dZWVlbW1tnZ2lwcHABAQEEBAQXFxchISI+P0BISUpaW1xHR0kNDg4qKyszNDU1NTY9Pj8NDQ1cXF4XFxhSU1QSEhIDAwMrKywtLS4uLi4wMDFHSElISEggISE0NDVJSktNTU1FRUVWVlhGRkYEBAVBQUE0NTZQUVJQUVMFBQUqKitWV1lXV1daWlpaWlw+Pj8bGxtcXV9dXV1fX19fYGFgYGBkZGRlZmhpaWlsbGxwcHB2dna844Y9AAAAV3RSTlMAAQIDAwUFBggMDhkeICMkKCgqMDIzPj9ERFBib4CCg4iMjZCcnp+jqamrw83W1tvb3ePl6Ojp6+vs7u7v8PHy9PT09PT3+vr7/f39/f39/v7+/v7+/v50ou7NAAAA30lEQVR4XkXIY3vDYABG4SepMdq2bRSz/capzdm2fvOuDO397Rw0Ly4tz2QAQPbcxuZ2E/STJwfxPhWgG355fRrVAIVb1zeP9UDLfiSwkAcADe8fn7tFxWuEXFRDoer/OgoMTRBCumj8yJwPBo8Zhpk14U856/HI8n0ZUtpZ1udrSzfVneA4roNKjdrwpcMRilb8d8G60+lKnrpWcn9bO+B23w2O8Tzfq4aiNSZJqzn5O4Kw16h06fPZ+VUlUHfo97+VAEb7rSh2UgDd4/U+TBlQY7FMj5gBIGvcarVVfQPVPTG94D0j9QAAAABJRU5ErkJggg=='], ['Rhel', 'BAAAAAQCAMAAAAoLQ9TAAABj1BMVEUAAAD///////8AAAD///////8AAAD///8AAAD///////8AAAD///8AAAD+/v4AAAAAAAAAAAArKysAAAD///////8AAAAAAAAAAAAAAAD///8AAAAAAAAAAAD///8AAAD///8AAAAAAAAAAAAAAAB5eXn+/v5JSUnKysrS0tJ5eXmqqqqxsrL+/v4ZCgknJyeHh4eIiIjo6OgZCAdOTk7t7e3///8GCwwPAAArKyv19fX29vb9/f0EAAD////+/v4AAAAGBgYHAAAJAAAMAAANAQAPAQAVAQFyCQV9fX2pIRzmEQjn5+cBAAAFAAAAAADnEQjvEgn////uEQjyEgnsEQjzEgnxEgljBwPaEAj9EwnwEglHBQJHBQNNBQIBAAB3CQR5CQSHCgWLCgWRCgWTCwadDAWmDAapDAa/DgfKDwjWEAgGAADh4eHiEQjmEQjmEQkKAADoEQgLAQDtEQgMAQDuEQnvEQjvEQkPAQAfAgEuAwEvAwE8BAL1Egn3Egn4Egn6Egk+BAL+/v5CBQJrB0muAAAAT3RSTlMAAAMEBAkYGhsbMTRLUmpvcHeIjLe6vcHCxM3P0NbW3Ojp6u/w9ff5+fn6+vr6+/v7+/v8/Pz9/f39/f39/f7+/v7+/v7+/v7+/v7+/v7+Q8UoNAAAAO5JREFUeF4tiwVPA0EYRL9SXIsWl+LuxfcOd2Z3764quLu788NZNrxkksmbDP2R7vH6GioLs+iffEzNXd4+TqPErUUpVqMOvwgdzMPn1rv5vPsVeufBTaBK/bH2FPvkEUuIG5jIIc+sHYn/HJ3dC/Hxuo4y8s44dzwBbFkisHN8bVIdXs6jb+H97aCwbHEIqgcml64CD7YllNkAVQC940MLYe5YzvIeQAXNrd19Roc5MdzfdQLUUKaUYyuG9I8y1g4gj6hIak4X5cBIT2MquZJrJdOqpY11ZpAiqVwbY/C7KY1cRCrZxX4pWXVuiuq/hs49kg4OyP4AAAAASUVORK5CYII='], ['Sabayon', 'BAAAAAQCAMAAAAoLQ9TAAABvFBMVEUAAAAcUaYdVKwAAAAAAAUABAwWRY4YSZYhZtIhaNYHDx0KCgoFDBcKCgoRMmYSNm0fXL0fXb8AAAAYS5gaTp8fXLwgXsEGBgYFBQUZSpgZTZ4JFSgODg4IEiIOJkwOKVIkW7EnXbQLGzUTExMKGC8LHjwMIkITExMiIiIPEBEPJ00QEhMXOXAaPncOJEgoXbApXbEcHBwwMDAEAgAfHRgQDgo3NC8AAAAHBwcKCgoLCwsJCQkaGhofHx8lJSUwMDA0NDQ4ODiRkZEICQocHBweHh4GBgYHCg8mJiYnJycpKSkrKystLS0uLi4ICAgODg43NzcRERF1dXUUFBSjo6O1tbUbGxsEBAMLGS8MDA0iIiIjIyMkJCQNDQ0NHTYKCQkoKCgPDw8QEBArMDkKCgkRERIREhMxMTEyMjISIz00Njk1NTU2NjYCAgIVFRU5OTo5P0c8PD0+Pj4/QURAQEBHR0dKSkpMTExSUlJiYmJlZWVnZ2cWFhZ2dnZ4eHh8fHx9fX2FhYUXFxeVlZWXl5eYmJiZmZmcnJwZGRmlpaWrq6usrKyvr68KFiq/v7/FxcXY2Nji4uLn5+ft7e0yif9uAAAAN3RSTlMAAAApKSkqKioqg4OEhISEhoa1tra3t7y9vr7S09PT09TU+Pj5+fn5+/v7+/v7+/v7/v7+/v7+70RY/wAAAPpJREFUeF4dyWNjw2AUBeC7dfYyorM6rx1exKltzLZt2/rDa/J8OgBVVlFDX39jcTZoUqCse251a2dvu6ccUtWlanLQ4Vpel+ThlWq1l3wEz58tx4dOt1dMlAJk9A5gMjG75LHwo46hzkwosGOMbejumoRvubC9EOrMviT0E0Us9fvN9dA6zxJCNv6+ECGsb6oNWsgmpZT9/UTUZo3Em6AW34guTL4jiAudiCM1kLcw8/SmHERfT1/eueBiDqR1GK1n9w+K8nglxYxd6QAML4ztXoQuj8YFgWcgqdJp8qzty26vaboCNIxBCshyQDKov0aXr29v1ufq1PwPx5Q7bCoh6eoAAAAASUVORK5CYII='], ['Slackware', 'BAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AcEDi0qZWWDgAAAAx1JREFUOMt9kktoXHUchb/ffc1M7rySSdJMOknFPMRitLgoNKKI8ZHGKkgrjU8SitidimSh2UkXoQmoO1dGQSxJjdvOtqSaqlR0USEGSjVJGxuSmWR6M3fu4/93YX0g4rc9HA6cc4Q7DI+fpzz7PA8++2mxvZAeBZ4xhHtFcJRmXWsWvb36/OLcyxf5B/KHeYHy7DmGx1+YSDjmWTdlobTGMAStQGkNoLXS4tXDq7u7tUcWz49tA8jR8QUuzB5n5NTCV13F9JEo1JJwTLKuzU61QiOMcd0UDb+BncwQK3Rl15eNja3ui/Njq8aF2eMcO/XlBz0H8oO2ZUkum6A13WB99TtyzXlaCi24SaFa+ZFCzsG2DNnfkdbFjsI1APPhk+d6ujqznycdCxFozadYWvyMpx47wa+bPkGksKwUNnsk3TaCGASRXDZh5LpHXPPg4Rcni+3uYBxrtBbQghlscOVKmYHeEm0ZIZ9xyLffw41ND6VAa43SmjiMByzHYtjzwr9arfshxf5jOKlvKZfn8es77N2uks24PPfSFD/9Uvt7AtPKWmEU9d645eHYJo5tcKi/FX/zG+zmQxQH+rANk862DOW5N/hhaY64cJSa5xNFCgDDILZACMKYWAmh73HmzFsMlBQJ06LeiMinE1S3KzRCm5rXIIoUIoKIYCVM36urZFbEoiBLNMIhAE6/NsSB7h6SKZdL8xsUOnpx9j1KbTdARACIowArYe1ergfNT2i0mIbJys0GI6PT3N1/hJvrPxOFdRJNBQIy/FapI4Bpgohgcjuw+jq8jy8tV55MNBWI4ohS802CpizKv8q+FgALZAfYgSyAZtNro1oLaU1VvxCA029Oraxs7u/tKnXiNjn8HyKwur6lI++6vPK4V7IA7u+1Dyu1tr183ddNbkHuXP8/zEIYeFqiLRl6YO/p0bHJdflT/PD9qZa1W+ry99fcvlAlcZwUpuUAglIRYVgnDEIOlna4q0M/NPnuO1/PzMwg/045O/XeibUt5/Xangx6viSVFpK2jtMpvdyWCz+5ryf10clX3/amp6eZmJjgd441URWWJY8BAAAAAElFTkSuQmCC'], ['Trisquel', 'BAAAAAQCAMAAAAoLQ9TAAABjFBMVEX///8AAAAAAAAAAAAAADMAAGYAAAAAHFUAGWYAF10AImYAIGAAHloAHGMAKGsAGmYAJmYAJGEAKnUAJ1gAMXYAJnEAJGQAI2EAK28AK3cAGTEAMHgALXEALXgALG0AFUAAI2oAK3EAMngANoYALXMANIAAM4IANIIAL3gANIcANokANoQANYQAOY0ANIYANooAN4kAN40AOY0APZMANIUAOY0AO5AAPZUAPJAAP5MAPpQAQJUAOYsAPpYANoUAPpoAPpUAM4AAQJkAPZIAPJEAQpgAN4cAPpQAPZUAPJEAO4oAOosAOo8AQJoAOYsAO44AQpsAO48AQp0AP5UAQpoARJwAQ58ARaAAQZgAQ54AQ50AQpgARaIARqMARaMARaIAR6QARaIARaEASakARKEAR6MASqsARKEASKcAR6MARqYAR6UATbEATa8ARqUARKAAR6oARqMASKgATK8AR6QATbIATbAASq0AR6cASKgASqwAR6UASKcATa8ASqoASqwAS6wASKoAS60ATbHn4CTpAAAAhHRSTlMAAQIFBQUGCQoLDxAREhMUFBUYGhobHB0eHh8gIiIjJCQkJCYoLC0xMTE0NDo6Oz1BQUNHSUxOVFVVVldaWl5iY2RkZWZoamtsb3FycnR1ent9f4KDhIiJioyNkJGYm5+foqOkpqamqKmqrKytsLKzs7e4uLy8v8TFxcXGx8rO0NXY2eZc4XYcAAAA00lEQVR4XkWN1VoCUQAG/3NWtwh7CTsQJOyk7BaDxuxA6bbrxf32gt25m7kZqDRYxziooDV7+1AalMUavQh2AsEZoWvzigLun+T17/c8QiJZ7qu2QKiNmyZthdcR1/as353jIeU1GxMHo5XHdqPFeX8IaDMdHPYN6dRN7LR4qQewdTa35HWkyh+fbxERAMjwlAWJv3CPSKDQ+H7XvHdkV4Pua3Gtm4sPKIF/WV8dop4VKBw/NU33B3x1JbTt+XwhkJQoqRfWvHOy28uqH8JIdomR/R+s9yR3Cso77AAAAABJRU5ErkJggg=='], ['Ubuntu', 'BAAAAAQCAMAAAAoLQ9TAAABKVBMVEX////ojFzplGf1zbnqnHLvs5P10b3yuZv1xKrytZXvtJXys5LysI32waT0n3HxiVHwg0jxhk31kFn0h0zxf0P0hUrveTv2iU3yfkD1hEfyejv5eDLybSX0aR7zZxvyayH6ZxnxZBj4YhH7XAb5WALlUQLeTwHgUAHeTgHfTwD65NzdTQDdTQHdTgD31MfcTgLcTADcTQD////xt5/31Mf54dfmfE/dUAbeVQ/jcUDcTgHeWBnnflHohFvpjGbqkGztnX342Mz53dLgXiP65d399PHdUgrtoYLyu6Xzvaf76eLfXB/rkm/fWhvupojwrpTeVhTgYSfgYynzwa30xbL1ybnngFT31snngljhZS3539XhZzDiajbibDn77OX88Ovrl3X99vTjbz1fisGCAAAAMHRSTlMABgYGBwcHJiorMDA1NXGHjY2Nl5mZmZyfn6O5u8XHzc3X193j9fj4+vr6/f39/f08OUojAAAAx0lEQVR4Xi3HZVbDYBhGwQctWqzFPXiQ+36pu+LubvtfBKcN82/UEhld2vWXxyL6F92gbTPabse8hU/uHMx1SZoyyJWPTwq1Rs7GpYE9+Cg+OJcs1MHvU9y4fnrN31yUm18vMCIPjtw3QMndw4rs8ieVzAAcBlewpe1KM3uaBuD3Dda1BhWXAsi6AFY1a2SqifxZ+rnxWYcJDRkUS3fO1R5vwe+XZgw4D4L3RAJiknoXCVX3WeiUpJ5pIxTvVmg45pl5k4Ot/AGV2iqZBWgJJAAAAABJRU5ErkJggg=='], ['Windows', 'BIAAAAQCAYAAAAbBi9cAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA+pJREFUOE+F0n84FHYcB3CWSsL9ojo/6ik64c6PnTjmSS0limmrpBm2G002y++xzXRz6zE0R4nbw+RnTj/WD4sbanLkkAe55ccYlyNme4SrO9u9d13PI3/saZ+/vs/3831ez+f9eb5aWsuqy2mjRYeNUa7YmtjfTico7jNJ8z0eG24NB9vvnDrvufzpq89Npnr8VjMddNmuRh9rDfp36mFg91oM7qPIc5JdbDJq3An/JfCu7Hl53W2lpS220pP2OuniN299jAYbYizSENIoAgbCTdrTKtxOJVdvGo8psUwKy7Vxe4ez1YEVudGP8YEZzyveInFJ6mZRHHqYazDspw/pJwTIuERM5JIwmUdGdyo9K7/BszGzzg6fXzZHGJ8KvzQqXKOpoIeZLjofWR++BPWyCEnPY4xFGEKWQcLjMjKmr1MwfcMYwmz/Y4KOgNki0V5k1dkjUWCK93Kp2PMFFawos8cm1gZ2GqjLXktL4mbQPHLQ4B9ZDFE5+S356fQlyuJMqzH++HnTo6ui2OO1ko9Ul+4fxfd3d4F7k4YTReqpuFS88bGZUE2QNNDobuIq8Q5CduHb7lFJaTnvnym9ergjMWD/FG8zf+aKS3G9JO5C01Asah6wUXrvALKEDoitMMHhDKrKJdg8RU2s0EB2EWWur8dd7PDPFv6dUC0Gv3kAN36VPRGP/5k5NS6lljWxG0TDiSr1VKhoPwhevRMSqkwRxDObc/DavGtpP6zoi8XOyZfhnyNEvKANBU0P8VPfI/wyNCGXSn7wlEmyA9KrgmOKGth3eDVvPfyywq2dnUEv2R9qG2rLsH7xJXziKnWcI8tlTvEC7Mu8hROlImTU9aKqcwQ1vWOihWFu+sJknmph5CvxQh87c7bNh/NXo03hrMCosyvLmMNgMF7TQL6J1dsZIUVwjKqEO+cajp5vxPN439U/gKBt8PTcYHzL/BgHCyOf4unAISj6mFC2bYC82kB5Ls460NHRUVsDeYSXpGw7UgC7sAtwShDgzdM38W7BbURXtqpqhfmB8sEQuXwoCM/6faGQuGCxyxyKWhIm+PrSD495WL3cT0hhi8Whc3NbAs9KaOyCTvrJ8qkdX19XBeTUDU00+55USFzVU2yHstcaix0mUAjJkJeuRU868Ucmk0lcguiBnMAVxjbbdHV1yeq8+u4Hgo22huSG+iQXp83ftaxW3lsPZcs6KG5T8OwaAfJiPcxlrVRVRhvF02i0F/t5VbHZ7JWDfErKTLnhE3mFPuRFepg/uxqz6TqLv6euGj3ut87t/4ylvre3t3ZehOWWO1zjSFEqMVP4GfGb/DBykJcjmaZOoLsc+hcVY/LaAgcTQAAAAABJRU5ErkJggg=='], ['OpenBSD', 'BAAAAAQCAYAAAFo9M/3AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoYAykIPu64pQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAADTklEQVQ4y32RXUxTdxjGn3N6eujoKT3SUkuk3VoBGfVjgFXAsZ7WkipyYXQbuu3CzUXZmGRbssnFEseFkWzgcGzGXky9MWL8TsC4IeFgtK4oAqOnG5vMVl1pCMVWQD7b/y5M6jLdflfvxfPked/nBQA0NDSChqnGVrLuGkES742NhJAdhAKAuk9yyUs5Gry7RQMZAARCWgivpQiPe71P5DUfH0xaqTL7m/iiLkJmphawa+e4SM2PvUyC4yUIBu8CnAQKAK53rCA5OUtQtStVpJ4Gw/FOBddZVKhCfq4MP4n6+at+DUsJm/e0G9JZzYEvI2tHwlEYjDxomkZ+3nG8WroRtHihZVOhVlorDQzh0okhcByDP4ZGcf+X9XAsvY5/RsBa7Kq5H/CqLctKyl/g08S2i6fq8W/MS3P34T9wNDVYSeDX1eTD9xhiLXbtB/Akwmmv6Kr+ICFkLpGhtNSM3qsSstS3oX8lSsmsxS6ZVn3j6PvVVqhUcvC8AtPxVPxwygVKvngN89WOjgVprggGA4eenjB4nsXsTASpC63I0wVTZYPR11FoKRB8Ax54PCFk6BhMTk5CPR3GSbHouGzknr/bYFq9EAvfc9Tu1sLjHcXNKxLuTOTgzOlOe7IHBc/beAXWpWmXlz8a84nhcLQ+ecVzsAEQrMWuMX+f9HZF2YPZ28FVSNfoPWqOzMUmqYMAJm7+/OOzXQFwHGpyEV+vi+yvtxBC9pDmpgJC4tvI3mo9GTitIxvW24nT7ug67HY/3eDs2bbyrVsrY2day70rV6kRfDAHk5lDLJqAmmeRiD9GJDKHvwb74R8G0mkTPjrQTTG122xkTTbwaV2b1H4u16JQKXGr7yG2b8/H1MQ09IsTSEmRwzf4CCwzD+dmE1re8CI7wwi5XNlFf9vaTXX4dWJg4LLl7h05fpNGwNAMWpp9CIVYNO/tRCzGwpDFQaVMQTS2CKY0BWr3GVGWNSXKACDDaA4Mh976pq9f5Sy09GgKlmeAMIBKzUKpU+BFoxJecRhUfAbMxDi4eADfHVmE79v7q575gvvYeVvjZ58LD5mwsKUyX0hnf0feslnQCWD4zxnc6reKisxsfH2oscqcmTmK/+Ow252cna7K52r+Bky6PqmoT5HBAAAAAElFTkSuQmCC'], ['Gnu', 'BAAAAAQCAYAAAFo9M/3AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoYAywUV5gQrwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAADcElEQVQ4y43Tb0jjBRzH8c9v+7nNMebcUW21Cc78g/wcuhByIScoMRwoTBmFlZCmIJ14axqkgoYIkXIqKIVBEuJNUBEUPRlpqDC3Q2Ex0nTezun2YOaPLXNIv7Vvj7zgiOj1+PPk/eADjuNEuHN6ekqMw+H4IzMz8xChUCjV1NT0JbO7uxtfXFy8NZvNr21tbd0AAEQikY6I0m1tbQbx2NjYZiqV+vn29jY+PDw8xhYWFj45PDzcb25uhlQqfSTief6X0dFRpqKigvF4PPPipaWlY7lcXhCLxXJnZmY+ZTY2NnzX19ePGxsbHw0MDLivrq5mc3Jy2pPJZLVWq/2cdbvdDSzLholoNJ1OMy6Xq0Ymk5HNZktOTU29qMgA8HYqlaKDgwNKp9M0PT09BgAM/iGuqqoimUx2yPP8U5/P9wEAMB0dHRUKheJHiUTyeGhoqAUAnE7nR0qlsjcQCLwjlsvlz+bm5mQWi0VSWlr6bXV1tU6hUMj6+/vfN5lMN0xxcfG1zWZ7SETTSqWSGhoamPHxcajV6s+8Xu9Xou7u7t9VKtW00+mkSCTC6PV6aDQa8Dw/Wl9fP8UAQCgUosvLSyovL2eWl5dRUFBw7Ha7v9vc3By5K3g1EAg8FQSBiIguLi4IgBwA2LtEjuPuJxKJ62AwKFpdXf0eQBIvYVmW/cLlchEAWK1WAADT09NzX6PR/OTz+eKVlZUzKpVqTyqVvsnzfLCkpGSrtrb2t97eXnFeXl5ZKpWyZ2RkPPP7/UUnJyefGI3GU+zt7aU4jotOTk7mAUBfX1+b1Wq9kcvlBIAcDgctLCyQxWKhoqIi6uzs/BoAVlZW3qqpqbllZmdnf1hfX//Q4/HEzWbzX+3t7fcMBgMFg0EYjUYmEolAEAREo1Hk5+fT+fk5Mzg4GD86OpJ0dXXJGQBoaWl5Ra/XP6yrq3tQVlam2N7ehslkAsuySCaTUKvVSCQS2NnZSXAcJxYEQTEyMvKeIAhLDADY7fZ7BoPhm6ysLFpbWzuan5//WKvVvsHzPEWjUSYSiSA3N5d0Oh0TjUaf+/1+S2Nj46/4FwYAr7e2tnbF4/E/iYjC4TCFw+F0LBaj/f19mpiYeID/IAagAyABYLXb7cLZ2Rml02nyer3POY6rwv8hEr34u0IkEk1mZ2cTgGMA7768/RtL5JKsGzrLIgAAAABJRU5ErkJggg=='], ['CrunchBang', 'BYAAAAQCAQAAAC45EetAAAA8ElEQVR4XnWOsUpCYQBGz1TIHYu2Qix6g0DEtSeQu/UIISJtUS8gJq61F1wcdMohcBDxKUR8hsz1xA/y44/cs3znbB+RJ0Skl3pSkeFQbUs79VAPzrwPFRmN1Ja0Ug/16I93+1oi4lKte+zMXv32WuoAm43lXMrqzbFncgWw21lORf4+/PREKpAhYqZuPXZ+T/3yXbZEajV1JavUQ104sRcq0myqc5mnHurWqc/7yhExVwuPncl+C4Bu13L60ueAwcByOtLhgAIRCzU38fRGTmSxUBvSSD3Ui1NvQkXWa7Uq1dRD9R17HiqyRUSy1NP6B7e1Yu2GtlUKAAAAAElFTkSuQmCC'], ['Yuno', 'BgAAAAPCAYAAAD+pA/bAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAABDtJREFUOE+FlHtMm1UYxrtsi8aEgCb+oTFmZur+WNS5RaPERU10C2qGaBgb6hgwLwMmHTIKlIKlQIHSQrmU24BSSmnpBVooUmihtEC5yKWDjVu5uOkcEca4lG5E93j6EQmELX7Jky/fOed9fu973vMdGu0xT3Cgz57yXMZLDdXcy821PFWLKmuA6HqLMqtLX5POl4iYb2ukWW8IOOFe/qfe3/M4n0eOjwyZD8//bldODOk37N1yDJgl+LVdjEGLFKO9KkzZm8hbje7mIrTXZ7sMtTydrJh15H8hHW11XvN/jGS7VudcD5w34ZZzeQYb67fwYO03LN4exo1+LWzNxbA05O5QuzbHqRYn+++CHDx4YK9WLfaedfQzV5em54g5Zbi8OIml+VFMDLWQ7GXoaSmFWZsDZVGCO2u0EbkhHTrhFqi9PmelSsQ8tAtSVch60dpUeGe4kxgZxegzVkBzlQ2NKBG2+iJIMqMok9r8OLRIMqApToSqmAWTmk9B2+o2YW79oshU7ABcuvAFrVGWXkVKpBYoSaBSxIS2mINpiwbjZiUMZRloVfJQyaXDKObBpimBScpHFe8KmmXpaKhK3arGrBVuVBclHN2CiPNin1OVs1tVJYlQlyZBxA6DviQVo6ZaOKd7sTplw53BVugruBBzfsRslw7rZPxaczWutSpQV/gzJPxo1JexyfaxKBBpuiEx+tw+CpKdEvGWTprGlhcwqbIzL5/DYKMYndpK3L1hxf3ZfkrzwybUZjPhnOqmvlcmutFF1jis9QSShOrcWNSXJ1MA0ou/NZWc8Ddfe4VGO3bk0JON1dyMMlK+gmxNrZCFhZF2Kng7YNO0awt4b7wLNp2EqtAsF6ImP56SG0B6siovTYpIjg15gapCVhAfJRUyIBFEo6k8AyuTtkcC/qvG/XbDexulWJvqgYH0o0nKhVHFJ40XwFQnWM5OCX+XMg86c3KvVMSMapCmPpSTIygTxGKZZOcOXhrr3Mp4uzkFuG6B3ajE3TELDDU8qEmsmvRATxquKkxAnSTFjwKEfv3JU9JC5unG6rQ1bTkbQ4Yq/DVgxOqwBWt2K9Yne3ZCZvrgHO2k5paHzOhSiVCZSkdNTgzy40JRlPgDhDHBCxUZdCs91G8fLeK87zOl6XSOICZYXMGNhDqX9fDP/mbK2DXVi/szm03eLpejl5pzOfqwOt4JBT8OeYwQt/4R/BR0OzXiLCM5LOCji/4nXt46rpywgG+zor5RxgSdupBzJdglSY+5ZZbl3XNY6mbn7W0Lcx06zBg1WBjtcC6OmG+OmRTrFrnIUZESZeVeCpwh8TpiPsQ47/tloM97T+/6m8mg55mT3tStyL54mhlwwtszNvjzD8/6HH8i7PvvPPRioZdRWuDBZUR6pEWG7I8P9Xs1Jsj36MfvvO5J/+rTw58dP7afJPfBgeef3XGz/gskFVpJc4HwGwAAAABJRU5ErkJggg==']]
|
||
};
|
||
|
||
ExpandComment = {
|
||
init: function() {
|
||
var a, _i, _len, _ref;
|
||
|
||
_ref = $$('.abbr');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
a = _ref[_i];
|
||
$.on(a.firstElementChild, 'click', ExpandComment.expand);
|
||
}
|
||
},
|
||
callbacks: [],
|
||
node: function(node) {
|
||
var callback, _i, _len, _ref;
|
||
|
||
_ref = ExpandComment.callbacks;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
callback = _ref[_i];
|
||
callback(node);
|
||
}
|
||
},
|
||
expand: function(e) {
|
||
var a, replyID, threadID, _, _ref;
|
||
|
||
e.preventDefault();
|
||
_ref = this.href.match(/(\d+)#p(\d+)/), _ = _ref[0], threadID = _ref[1], replyID = _ref[2];
|
||
this.textContent = "Loading No." + replyID + "...";
|
||
a = this;
|
||
return $.cache("//api.4chan.org" + this.pathname + ".json", function() {
|
||
return ExpandComment.parse(this, a, threadID, replyID);
|
||
});
|
||
},
|
||
parse: function(req, a, threadID, replyID) {
|
||
var bq, clone, href, post, posts, quote, quotes, spoilerRange, _conf, _i, _j, _len, _len1, _ref;
|
||
|
||
_conf = Conf;
|
||
if (req.status !== 200) {
|
||
a.textContent = "" + req.status + " " + req.statusText;
|
||
return;
|
||
}
|
||
posts = JSON.parse(req.response).posts;
|
||
if (spoilerRange = posts[0].custom_spoiler) {
|
||
Build.spoilerRange[g.BOARD] = spoilerRange;
|
||
}
|
||
replyID = +replyID;
|
||
for (_i = 0, _len = posts.length; _i < _len; _i++) {
|
||
post = posts[_i];
|
||
if (post.no === replyID) {
|
||
break;
|
||
}
|
||
}
|
||
if (post.no !== replyID) {
|
||
a.textContent = 'No.#{replyID} not found.';
|
||
return;
|
||
}
|
||
bq = $.id("m" + replyID);
|
||
clone = bq.cloneNode(false);
|
||
clone.innerHTML = post.com;
|
||
_ref = quotes = clone.getElementsByClassName('quotelink');
|
||
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
|
||
quote = _ref[_j];
|
||
href = quote.getAttribute('href');
|
||
if (href[0] === '/') {
|
||
continue;
|
||
}
|
||
quote.href = "res/" + href;
|
||
}
|
||
post = {
|
||
blockquote: clone,
|
||
threadID: threadID,
|
||
quotes: quotes,
|
||
backlinks: []
|
||
};
|
||
ExpandComment.node(post);
|
||
$.replace(bq, clone);
|
||
return Main.prettify(clone);
|
||
}
|
||
};
|
||
|
||
ExpandThread = {
|
||
init: function() {
|
||
var a, span, _i, _len, _ref;
|
||
|
||
_ref = $$('.summary');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
span = _ref[_i];
|
||
a = $.el('a', {
|
||
textContent: "+ " + span.textContent,
|
||
className: 'summary desktop',
|
||
href: 'javascript:;'
|
||
});
|
||
$.on(a, 'click', function() {
|
||
return ExpandThread.toggle(this.parentNode);
|
||
});
|
||
$.replace(span, a);
|
||
}
|
||
},
|
||
toggle: function(thread) {
|
||
var a, num, replies, reply, url, _i, _len;
|
||
|
||
url = "//api.4chan.org/" + g.BOARD + "/res/" + thread.id.slice(1) + ".json";
|
||
a = $('.summary', thread);
|
||
switch (a.textContent[0]) {
|
||
case '+':
|
||
a.textContent = a.textContent.replace('+', '× Loading...');
|
||
$.cache(url, function() {
|
||
return ExpandThread.parse(this, thread, a);
|
||
});
|
||
break;
|
||
case 'X':
|
||
a.textContent = a.textContent.replace('× Loading...', '+');
|
||
$.cache.requests[url].abort();
|
||
break;
|
||
case '-':
|
||
a.textContent = a.textContent.replace('-', '+');
|
||
num = (function() {
|
||
switch (g.BOARD) {
|
||
case 'b':
|
||
case 'vg':
|
||
case 'q':
|
||
return 3;
|
||
case 't':
|
||
return 1;
|
||
default:
|
||
return 5;
|
||
}
|
||
})();
|
||
replies = $$('.replyContainer', thread);
|
||
replies.splice(replies.length - num, num);
|
||
for (_i = 0, _len = replies.length; _i < _len; _i++) {
|
||
reply = replies[_i];
|
||
$.rm(reply);
|
||
}
|
||
}
|
||
},
|
||
parse: function(req, thread, a) {
|
||
var backlink, frag, id, link, node, nodes, post, posts, replies, reply, spoilerRange, status, threadID, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _results;
|
||
|
||
if ((status = req.status) !== 200) {
|
||
a.textContent = "" + status + " " + req.statusText;
|
||
$.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);
|
||
threadID = thread.id.slice(1);
|
||
nodes = [];
|
||
for (_i = 0, _len = replies.length; _i < _len; _i++) {
|
||
reply = replies[_i];
|
||
post = Build.postFromObject(reply, g.BOARD);
|
||
id = reply.no;
|
||
link = $('a[title="Highlight this post"]', post);
|
||
link.href = "res/" + threadID + "#p" + id;
|
||
link.nextSibling.href = "res/" + threadID + "#q" + id;
|
||
nodes.push(post);
|
||
}
|
||
_ref = $$('.summary ~ .replyContainer', a.parentNode);
|
||
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
|
||
post = _ref[_j];
|
||
$.rm(post);
|
||
}
|
||
_ref1 = $$('.backlink', a.previousElementSibling);
|
||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||
backlink = _ref1[_k];
|
||
if (!$.id(backlink.hash.slice(1))) {
|
||
$.rm(backlink);
|
||
}
|
||
}
|
||
_results = [];
|
||
for (_l = 0, _len3 = nodes.length; _l < _len3; _l++) {
|
||
node = nodes[_l];
|
||
frag = $.frag();
|
||
$.add(frag, node);
|
||
post = Main.preParse(node);
|
||
post.threadID = threadID;
|
||
Main.node(post);
|
||
_results.push($.add(thread, frag));
|
||
}
|
||
return _results;
|
||
}
|
||
};
|
||
|
||
FileInfo = {
|
||
init: function() {
|
||
if (g.BOARD === 'f') {
|
||
return;
|
||
}
|
||
this.setFormats();
|
||
QuotePreview.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var alt, filename, node, _ref;
|
||
|
||
if (!post.fileInfo) {
|
||
return;
|
||
}
|
||
node = post.fileInfo.firstElementChild;
|
||
alt = post.img.alt;
|
||
filename = ((_ref = $('span', node)) != null ? _ref.title : void 0) || node.title;
|
||
FileInfo.data = {
|
||
link: post.img.parentNode.href,
|
||
spoiler: /^Spoiler/.test(alt),
|
||
size: alt.match(/\d+\.?\d*/)[0],
|
||
unit: alt.match(/\w+$/)[0],
|
||
resolution: node.textContent.match(/\d+x\d+|PDF/)[0],
|
||
fullname: filename,
|
||
shortname: Build.shortFilename(filename, post.ID === post.threadID)
|
||
};
|
||
node.setAttribute('data-filename', filename);
|
||
return node.innerHTML = FileInfo.funk(FileInfo);
|
||
},
|
||
setFormats: function() {
|
||
var code;
|
||
|
||
code = Conf['fileInfo'].replace(/%(.)/g, function(s, c) {
|
||
if (c in FileInfo.formatters) {
|
||
return "' + f.formatters." + c + "() + '";
|
||
} else {
|
||
return s;
|
||
}
|
||
});
|
||
return this.funk = Function('f', "return '" + code + "'");
|
||
},
|
||
convertUnit: function(unitT) {
|
||
var i, size, unitF, units;
|
||
|
||
size = this.data.size;
|
||
unitF = this.data.unit;
|
||
if (unitF !== unitT) {
|
||
units = ['B', 'KB', 'MB'];
|
||
i = units.indexOf(unitF) - units.indexOf(unitT);
|
||
if (unitT === 'B') {
|
||
unitT = 'Bytes';
|
||
}
|
||
if (i > 0) {
|
||
while (i-- > 0) {
|
||
size *= 1024;
|
||
}
|
||
} else if (i < 0) {
|
||
while (i++ < 0) {
|
||
size /= 1024;
|
||
}
|
||
}
|
||
if (size < 1 && size.toString().length > size.toFixed(2).length) {
|
||
size = size.toFixed(2);
|
||
}
|
||
}
|
||
return "" + size + " " + unitT;
|
||
},
|
||
formatters: {
|
||
t: function() {
|
||
return FileInfo.data.link.match(/\d+\..+$/)[0];
|
||
},
|
||
T: function() {
|
||
return "<a href=" + FileInfo.data.link + " target=_blank>" + (this.t()) + "</a>";
|
||
},
|
||
l: function() {
|
||
return "<a href=" + FileInfo.data.link + " target=_blank>" + (this.n()) + "</a>";
|
||
},
|
||
L: function() {
|
||
return "<a href=" + FileInfo.data.link + " target=_blank>" + (this.N()) + "</a>";
|
||
},
|
||
n: function() {
|
||
if (FileInfo.data.fullname === FileInfo.data.shortname) {
|
||
return FileInfo.data.fullname;
|
||
} else {
|
||
return "<span class=fntrunc>" + FileInfo.data.shortname + "</span><span class=fnfull>" + FileInfo.data.fullname + "</span>";
|
||
}
|
||
},
|
||
N: function() {
|
||
return FileInfo.data.fullname;
|
||
},
|
||
p: function() {
|
||
if (FileInfo.data.spoiler) {
|
||
return 'Spoiler, ';
|
||
} else {
|
||
return '';
|
||
}
|
||
},
|
||
s: function() {
|
||
return "" + FileInfo.data.size + " " + FileInfo.data.unit;
|
||
},
|
||
B: function() {
|
||
return FileInfo.convertUnit('B');
|
||
},
|
||
K: function() {
|
||
return FileInfo.convertUnit('KB');
|
||
},
|
||
M: function() {
|
||
return FileInfo.convertUnit('MB');
|
||
},
|
||
r: function() {
|
||
return FileInfo.data.resolution;
|
||
}
|
||
}
|
||
};
|
||
|
||
Keybinds = {
|
||
init: function() {
|
||
var node, _i, _len, _ref;
|
||
|
||
this.bindings = this.bind();
|
||
_ref = $$('[accesskey]');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
node = _ref[_i];
|
||
node.removeAttribute('accesskey');
|
||
}
|
||
return $.on(d, 'keydown', Keybinds.keydown);
|
||
},
|
||
bind: function() {
|
||
var keys, _conf;
|
||
|
||
_conf = Conf;
|
||
keys = {};
|
||
keys[_conf.openQR] = function(thread, target, nodeName) {
|
||
return Keybinds.qr(thread, true);
|
||
};
|
||
keys[_conf.openEmptyQR] = function(thread, target, nodeName) {
|
||
return Keybinds.qr(thread);
|
||
};
|
||
keys[_conf.openOptions] = function() {
|
||
if (!$.id('overlay')) {
|
||
return Options.dialog();
|
||
}
|
||
};
|
||
keys[_conf.close] = function() {
|
||
var o;
|
||
|
||
if (o = $.id('overlay')) {
|
||
return Options.close.call(o);
|
||
} else if (QR.el) {
|
||
return QR.close();
|
||
}
|
||
};
|
||
keys[_conf.submit] = function() {
|
||
if (QR.el && !QR.status()) {
|
||
return QR.submit();
|
||
}
|
||
};
|
||
keys[_conf.hideQR] = function(thread, target, nodeName) {
|
||
if (QR.el) {
|
||
if (QR.el.hidden) {
|
||
return QR.el.hidden = false;
|
||
}
|
||
return QR.autohide.click();
|
||
} else {
|
||
return QR.open();
|
||
}
|
||
};
|
||
keys[_conf.toggleCatalog] = function() {
|
||
return CatalogLinks.toggle();
|
||
};
|
||
keys[_conf.spoiler] = function(thread, target, nodeName) {
|
||
if (!(($('[name=spoiler]')) && nodeName === 'textarea')) {
|
||
return;
|
||
}
|
||
return Keybinds.tags('spoiler', target);
|
||
};
|
||
keys[_conf.math] = function(thread, target, nodeName) {
|
||
if (!(g.BOARD === (!!$('script[src^="//boards.4chan.org/jsMath/"]', d.head)) && nodeName === 'textarea')) {
|
||
return;
|
||
}
|
||
return Keybinds.tags('math', target);
|
||
};
|
||
keys[_conf.eqn] = function(thread, target, nodeName) {
|
||
if (!(g.BOARD === (!!$('script[src^="//boards.4chan.org/jsMath/"]', d.head)) && nodeName === 'textarea')) {
|
||
return;
|
||
}
|
||
return Keybinds.tags('eqn', target);
|
||
};
|
||
keys[_conf.code] = function(thread, target, nodeName) {
|
||
if (!(g.BOARD === 'g' && nodeName === 'textarea')) {
|
||
return;
|
||
}
|
||
return Keybinds.tags('code', target);
|
||
};
|
||
keys[_conf.sageru] = function() {
|
||
$("[name=email]", QR.el).value = "sage";
|
||
return QR.selected.email = "sage";
|
||
};
|
||
keys[_conf.watch] = function(thread, target, nodeName) {
|
||
return Watcher.toggle(thread);
|
||
};
|
||
keys[_conf.update] = function() {
|
||
return Updater.update();
|
||
};
|
||
keys[_conf.unreadCountTo0] = function() {
|
||
Unread.replies = [];
|
||
return Unread.update(true);
|
||
};
|
||
keys[_conf.expandImage] = function(thread, target, nodeName) {
|
||
return Keybinds.img(thread);
|
||
};
|
||
keys[_conf.expandAllImages] = function(thread, target, nodeName) {
|
||
return Keybinds.img(thread, true);
|
||
};
|
||
keys[_conf.fappeTyme] = function(thread, target, nodeName) {
|
||
return Keybinds.ft(thread);
|
||
};
|
||
keys[_conf.zero] = function() {
|
||
return window.location = "/" + g.BOARD + "/0#delform";
|
||
};
|
||
keys[_conf.nextPage] = function() {
|
||
var form;
|
||
|
||
if (form = $('.next form')) {
|
||
return window.location = form.action;
|
||
}
|
||
};
|
||
keys[_conf.previousPage] = function() {
|
||
var form;
|
||
|
||
if (form = $('.prev form')) {
|
||
return window.location = form.action;
|
||
}
|
||
};
|
||
keys[_conf.nextThread] = function() {
|
||
if (g.REPLY) {
|
||
return;
|
||
}
|
||
return Nav.scroll(+1);
|
||
};
|
||
keys[_conf.previousThread] = function() {
|
||
if (g.REPLY) {
|
||
return;
|
||
}
|
||
return Nav.scroll(-1);
|
||
};
|
||
keys[_conf.expandThread] = function(thread, target, nodeName) {
|
||
return ExpandThread.toggle(thread);
|
||
};
|
||
keys[_conf.openThread] = function(thread, target, nodeName) {
|
||
return Keybinds.open(thread);
|
||
};
|
||
keys[_conf.openThreadTab] = function(thread, target, nodeName) {
|
||
return Keybinds.open(thread, true);
|
||
};
|
||
keys[_conf.nextReply] = function(thread, target, nodeName) {
|
||
return Keybinds.hl(+1, thread);
|
||
};
|
||
keys[_conf.previousReply] = function(thread, target, nodeName) {
|
||
return Keybinds.hl(-1, thread);
|
||
};
|
||
keys[_conf.hide] = function(thread, target, nodeName) {
|
||
if (/\bthread\b/.test(thread.className)) {
|
||
return ThreadHiding.toggle(thread);
|
||
}
|
||
};
|
||
return keys;
|
||
},
|
||
keydown: function(e) {
|
||
var bind, key, nodeName, target, thread;
|
||
|
||
if (!(key = Keybinds.keyCode(e))) {
|
||
return;
|
||
}
|
||
target = e.target;
|
||
if ((nodeName = target.nodeName.toLowerCase()) === 'textarea' || nodeName === 'input') {
|
||
if (!((key === 'Esc') || (/\+/.test(key)))) {
|
||
return;
|
||
}
|
||
}
|
||
thread = Nav.getThread();
|
||
if (!(bind = Keybinds.bindings[key])) {
|
||
return;
|
||
}
|
||
bind(thread, target, nodeName);
|
||
return e.preventDefault();
|
||
},
|
||
keyCode: function(e) {
|
||
var c, kc, key;
|
||
|
||
key = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90].contains(kc = e.keyCode) ? (c = String.fromCharCode(kc), e.shiftKey ? c : c.toLowerCase()) : ((function() {
|
||
switch (kc) {
|
||
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:
|
||
return null;
|
||
}
|
||
})());
|
||
if (key) {
|
||
if (e.altKey) {
|
||
key = 'alt+' + key;
|
||
}
|
||
if (e.ctrlKey) {
|
||
key = 'ctrl+' + key;
|
||
}
|
||
if (e.metaKey) {
|
||
key = 'meta+' + key;
|
||
}
|
||
}
|
||
return key;
|
||
},
|
||
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(ta, new Event('input'));
|
||
},
|
||
img: function(thread, all) {
|
||
var thumb;
|
||
|
||
if (all) {
|
||
return $.id('imageExpand').click();
|
||
} else {
|
||
thumb = $('img[data-md5]', $('.post.highlight', thread) || thread);
|
||
return ImageExpand.toggle(thumb.parentNode);
|
||
}
|
||
},
|
||
ft: function(thread) {
|
||
return $('#fappeTyme>input').click();
|
||
},
|
||
qr: function(thread, quote) {
|
||
if (quote) {
|
||
QR.quote.call($('a[title="Quote this post"]', $('.post.highlight', thread) || thread));
|
||
} else {
|
||
QR.open();
|
||
}
|
||
return $('textarea', QR.el).focus();
|
||
},
|
||
open: function(thread, tab) {
|
||
var id, url;
|
||
|
||
if (g.REPLY) {
|
||
return;
|
||
}
|
||
id = thread.id.slice(1);
|
||
url = "//boards.4chan.org/" + g.BOARD + "/res/" + id;
|
||
if (tab) {
|
||
return $.open(url);
|
||
} else {
|
||
return location.href = url;
|
||
}
|
||
},
|
||
hl: function(delta, thread) {
|
||
var axis, next, post, rect, replies, reply, _i, _len;
|
||
|
||
if (post = $('.reply.highlight', thread)) {
|
||
$.rmClass(post, 'highlight');
|
||
rect = post.getBoundingClientRect();
|
||
if (rect.bottom >= 0 && rect.top <= d.documentElement.clientHeight) {
|
||
axis = delta === +1 ? 'following' : 'preceding';
|
||
next = $.x(axis + '::div[contains(@class,"post reply")][1]', post);
|
||
if (!next) {
|
||
return;
|
||
}
|
||
if (!(g.REPLY || $.x('ancestor::div[parent::div[@class="board"]]', next) === thread)) {
|
||
return;
|
||
}
|
||
rect = next.getBoundingClientRect();
|
||
if (rect.top < 0 || rect.bottom > d.documentElement.clientHeight) {
|
||
next.scrollIntoView(delta === -1);
|
||
}
|
||
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 >= 0 || delta === -1 && rect.bottom <= d.documentElement.clientHeight) {
|
||
this.focus(reply);
|
||
return;
|
||
}
|
||
}
|
||
},
|
||
focus: function(post) {
|
||
$.addClass(post, 'highlight');
|
||
return post.focus();
|
||
}
|
||
};
|
||
|
||
Navigation = {
|
||
delimiter: "/",
|
||
links: [["a", "Anime & Manga", "//boards.4chan.org/a/"], ["b", "Random", "//boards.4chan.org/b/"], ["c", "Cute/Anime", "//boards.4chan.org/c/"], ["d", "Hentai/Alternative", "//boards.4chan.org/d/"], ["e", "Ecchi", "//boards.4chan.org/e/"], ["f", "Flash", "//boards.4chan.org/f/"], ["g", "Technology", "//boards.4chan.org/g/"], ["gif", "Animated Gifs", "//boards.4chan.org/gif/"], ["h", "Hentai", "//boards.4chan.org/h/"], ["hr", "High Resolution", "//boards.4chan.org/hr/"], ["k", "Weapons", "//boards.4chan.org/k/"], ["l", "Lolicon", "http://7chan.org/cake/"], ["m", "Mecha", "//boards.4chan.org/m/"], ["o", "Auto", "//boards.4chan.org/o/"], ["p", "Pictures", "//boards.4chan.org/p/"], ["r", "Requests", "//boards.4chan.org/r/"], ["s", "Sexy Beautiful Women", "//boards.4chan.org/s/"], ["t", "Torrents", "//boards.4chan.org/t/"], ["u", "Yuri", "//boards.4chan.org/u/"], ["v", "Video Games", "//boards.4chan.org/v/"], ["vg", "Video Game Generals", "//boards.4chan.org/vg/"], ["w", "Anime/Wallpapers", "//boards.4chan.org/w/"], ["wg", "Wallpapers/General", "//boards.4chan.org/wg/"], ["i", "Oekaki", "//boards.4chan.org/i/"], ["ic", "Artwork/Critique", "//boards.4chan.org/ic/"], ["r9k", "Robot 9K", "//boards.4chan.org/r9k/"], ["cm", "Cute/Male", "//boards.4chan.org/cm/"], ["hm", "Handsome Men", "//boards.4chan.org/hm/"], ["y", "Yaoi", "//boards.4chan.org/y/"], ["3", "3DCG", "//boards.4chan.org/3/"], ["adv", "Advice", "//boards.4chan.org/adv/"], ["an", "Animals", "//boards.4chan.org/an/"], ["cgl", "Cosplay & EGL", "//boards.4chan.org/cgl/"], ["ck", "Food & Cooking", "//boards.4chan.org/ck/"], ["co", "Comics & Cartoons", "//boards.4chan.org/co/"], ["diy", "Do It Yourself", "//boards.4chan.org/diy/"], ["fa", "Fashion", "//boards.4chan.org/fa/"], ["fit", "Health & Fitness", "//boards.4chan.org/fit/"], ["hc", "Hardcore", "//boards.4chan.org/hc/"], ["int", "International", "//boards.4chan.org/int/"], ["jp", "Otaku Culture", "//boards.4chan.org/jp/"], ["lit", "Literature", "//boards.4chan.org/lit/"], ["mlp", "My Little Pony", "//boards.4chan.org/mlp/"], ["mu", "Music", "//boards.4chan.org/mu/"], ["n", "Transportation", "//boards.4chan.org/n/"], ["po", "Papercraft & Origami", "//boards.4chan.org/po/"], ["pol", "Politically Incorrect", "//boards.4chan.org/pol/"], ["sci", "Science & Math", "//boards.4chan.org/sci/"], ["soc", "Social", "//boards.4chan.org/soc/"], ["sp", "Sports", "//boards.4chan.org/sp/"], ["tg", "Traditional Games", "//boards.4chan.org/tg/"], ["toy", "Toys", "//boards.4chan.org/toys/"], ["trv", "Travel", "//boards.4chan.org/trv/"], ["tv", "Television & Film", "//boards.4chan.org/tv/"], ["vp", "Pokémon", "//boards.4chan.org/vp/"], ["wsg", "Worksafe GIF", "//boards.4chan.org/wsg/"], ["x", "Paranormal", "//boards.4chan.org/x/"], ["rs", "Rapidshares", "http://rs.4chan.org/"], ["status", "4chan Status", "http://status.4chan.org/"], ["q", "4chan Discussion", "//boards.4chan.org/q/"], ["@", "4chan Twitter", "http://www.twitter.com/4chan"]]
|
||
};
|
||
|
||
Nav = {
|
||
init: function() {
|
||
var next, prev, span;
|
||
|
||
span = $.el('span', {
|
||
id: 'navlinks'
|
||
});
|
||
prev = $.el('a', {
|
||
href: 'javascript:;'
|
||
});
|
||
next = $.el('a', {
|
||
href: 'javascript:;'
|
||
});
|
||
$.on(prev, 'click', this.prev);
|
||
$.on(next, 'click', this.next);
|
||
$.add(span, [prev, next]);
|
||
return $.add(d.body, span);
|
||
},
|
||
prev: function() {
|
||
if (g.REPLY) {
|
||
return window.scrollTo(0, 0);
|
||
} else {
|
||
return Nav.scroll(-1);
|
||
}
|
||
},
|
||
next: function() {
|
||
if (g.REPLY) {
|
||
return window.scrollTo(0, d.body.scrollHeight);
|
||
} else {
|
||
return Nav.scroll(+1);
|
||
}
|
||
},
|
||
getThread: function(full) {
|
||
var bottom, i, rect, thread, _i, _len, _ref;
|
||
|
||
Nav.threads = $$('.thread:not(.hidden)');
|
||
_ref = Nav.threads;
|
||
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
|
||
thread = _ref[i];
|
||
rect = thread.getBoundingClientRect();
|
||
bottom = rect.bottom;
|
||
if (bottom > 0) {
|
||
if (full) {
|
||
return [thread, i, rect];
|
||
}
|
||
return thread;
|
||
}
|
||
}
|
||
return $('.board');
|
||
},
|
||
scroll: function(delta) {
|
||
var i, rect, thread, top, _ref, _ref1;
|
||
|
||
_ref = Nav.getThread(true), thread = _ref[0], i = _ref[1], rect = _ref[2];
|
||
top = rect.top;
|
||
if (!((delta === -1 && Math.ceil(top) < 0) || (delta === +1 && top > 1))) {
|
||
i += delta;
|
||
}
|
||
top = (_ref1 = Nav.threads[i]) != null ? _ref1.getBoundingClientRect().top : void 0;
|
||
return window.scrollBy(0, top);
|
||
}
|
||
};
|
||
|
||
Redirect = {
|
||
image: function(board, filename) {
|
||
switch (board) {
|
||
case 'a':
|
||
case 'jp':
|
||
case 'm':
|
||
case 'q':
|
||
case 'sp':
|
||
case 'tg':
|
||
case 'vg':
|
||
case 'wsg':
|
||
return "//archive.foolz.us/" + board + "/full_image/" + filename;
|
||
case 'cgl':
|
||
case 'g':
|
||
case 'mu':
|
||
case 'w':
|
||
return "//rbt.asia/" + board + "/full_image/" + filename;
|
||
case 'an':
|
||
case 'k':
|
||
case 'toy':
|
||
case 'x':
|
||
return "http://archive.heinessen.com/" + board + "/full_image/" + filename;
|
||
case 'ck':
|
||
case 'lit':
|
||
return "//fuuka.warosu.org/" + board + "/full_image/" + filename;
|
||
case 'u':
|
||
return "//nsfw.foolz.us/" + board + "/full_image/" + filename;
|
||
case 'e':
|
||
return "//www.xn--clich-fsa.net/4chan/cgi-board.pl/" + board + "/img/" + filename;
|
||
case 'c':
|
||
return "//archive.nyafuu.org/" + board + "/full_image/" + filename;
|
||
}
|
||
},
|
||
post: function(board, postID) {
|
||
var archive, name, _base1, _ref;
|
||
|
||
if (Redirect.post[board] === void 0) {
|
||
_ref = this.archiver;
|
||
for (name in _ref) {
|
||
archive = _ref[name];
|
||
if (archive.type === 'foolfuuka' && archive.boards.contains(board)) {
|
||
Redirect.post[board] = archive.base;
|
||
break;
|
||
}
|
||
}
|
||
(_base1 = Redirect.post)[board] || (_base1[board] = null);
|
||
}
|
||
if (Redirect.post[board]) {
|
||
return "" + Redirect.post[board] + "/_/api/chan/post/?board=" + board + "&num=" + postID;
|
||
}
|
||
return null;
|
||
},
|
||
archiver: {
|
||
'Foolz': {
|
||
base: '//archive.foolz.us',
|
||
boards: ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg', 'dev', 'foolz'],
|
||
type: 'foolfuuka'
|
||
},
|
||
'NSFWFoolz': {
|
||
base: '//nsfw.foolz.us',
|
||
boards: ['u', 'kuku'],
|
||
type: 'foolfuuka'
|
||
},
|
||
'TheDarkCave': {
|
||
base: 'http://archive.thedarkcave.org',
|
||
boards: ['c', 'int', 'out', 'po'],
|
||
type: 'foolfuuka'
|
||
},
|
||
'Warosu': {
|
||
base: '//fuuka.warosu.org',
|
||
boards: ['cgl', 'ck', 'fa', 'jp', 'lit', 'q', 's4s', 'tg', 'vr'],
|
||
type: 'fuuka'
|
||
},
|
||
'InstallGentoo': {
|
||
base: '//archive.installgentoo.net',
|
||
boards: ['diy', 'g', 'sci'],
|
||
type: 'fuuka'
|
||
},
|
||
'RebeccaBlackTech': {
|
||
base: '//rbt.asia',
|
||
boards: ['cgl', 'g', 'mu', 'w'],
|
||
type: 'fuuka_mail'
|
||
},
|
||
'Heinessen': {
|
||
base: 'http://archive.heinessen.com',
|
||
boards: ['an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x'],
|
||
type: 'fuuka'
|
||
},
|
||
'Cliche': {
|
||
base: '//www.xn--clich-fsa.net/4chan/cgi-board.pl',
|
||
boards: ['e'],
|
||
type: 'fuuka'
|
||
},
|
||
'NyaFuu': {
|
||
base: '//archive.nyafuu.org',
|
||
boards: ['c', 'w'],
|
||
type: 'fuuka'
|
||
}
|
||
},
|
||
select: function(board) {
|
||
var archive, name;
|
||
|
||
return (function() {
|
||
var _ref, _results;
|
||
|
||
_ref = this.archiver;
|
||
_results = [];
|
||
for (name in _ref) {
|
||
archive = _ref[name];
|
||
if (archive.boards.contains(board || g.BOARD)) {
|
||
_results.push(name);
|
||
}
|
||
}
|
||
return _results;
|
||
}).call(this);
|
||
},
|
||
to: function(data) {
|
||
var archive, board, isSearch, threadID;
|
||
|
||
board = data.board, threadID = data.threadID, isSearch = data.isSearch;
|
||
return ((archive = this.archiver[$.get("archiver/" + board + "/", this.select(board)[0])]) ? this.path(archive.base, archive.type, data) : threadID && !isSearch ? "//boards.4chan.org/" + board + "/" : null);
|
||
},
|
||
path: function(base, archiver, data) {
|
||
var board, isSearch, postID, threadID, type, url, value;
|
||
|
||
board = data.board, type = data.type, value = data.value, threadID = data.threadID, postID = data.postID, isSearch = data.isSearch;
|
||
if (isSearch) {
|
||
type = type === 'name' ? 'username' : type === 'md5' ? 'image' : type;
|
||
value = encodeURIComponent(value);
|
||
return ((url = archiver === 'foolfuuka' ? "search/" + type + "/" : type === 'image' ? "?task=search2&search_media_hash=" : type !== 'email' || archiver === 'fuuka_mail' ? "?task=search2&search_" + type + "=" : false) ? "" + base + "/" + board + "/" + url + value : url);
|
||
}
|
||
if (postID) {
|
||
postID = postID.match(/\d+/)[0];
|
||
}
|
||
return base + "/" + board + "/" + (threadID ? "thread/" + threadID : "post/" + postID) + (threadID && postID ? "#" + (archiver === 'InstallGentoo' ? 'p' : '') + postID : "");
|
||
}
|
||
};
|
||
|
||
RelativeDates = {
|
||
INTERVAL: $.MINUTE,
|
||
init: function() {
|
||
Main.callbacks.push(this.node);
|
||
return $.on(d, 'visibilitychange', this.flush);
|
||
},
|
||
node: function(post) {
|
||
var dateEl, diff, utc;
|
||
|
||
dateEl = $('.postInfo > .dateTime', post.el);
|
||
dateEl.title = dateEl.textContent;
|
||
utc = dateEl.dataset.utc * 1000;
|
||
diff = Date.now() - utc;
|
||
dateEl.textContent = RelativeDates.relative(diff);
|
||
RelativeDates.setUpdate(dateEl, utc, diff);
|
||
return RelativeDates.flush();
|
||
},
|
||
relative: function(diff) {
|
||
var number, rounded, unit;
|
||
|
||
unit = (number = diff / $.DAY) > 1 ? 'day' : (number = diff / $.HOUR) > 1 ? 'hour' : (number = diff / $.MINUTE) > 1 ? 'minute' : (number = diff / $.SECOND, 'second');
|
||
rounded = Math.round(number);
|
||
if (rounded !== 1) {
|
||
unit += 's';
|
||
}
|
||
return "" + rounded + " " + unit + " ago";
|
||
},
|
||
stale: [],
|
||
flush: $.debounce($.SECOND, function() {
|
||
var now, update, _i, _len, _ref;
|
||
|
||
if (d.hidden) {
|
||
return;
|
||
}
|
||
now = Date.now();
|
||
_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(dateEl, utc, diff) {
|
||
var markStale, setOwnTimeout, update;
|
||
|
||
setOwnTimeout = function(diff) {
|
||
var delay;
|
||
|
||
delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : $.HOUR - (diff + $.HOUR / 2) % $.HOUR;
|
||
return setTimeout(markStale, delay);
|
||
};
|
||
update = function(now) {
|
||
if (d.contains(dateEl)) {
|
||
diff = now - utc;
|
||
dateEl.textContent = RelativeDates.relative(diff);
|
||
return setOwnTimeout(diff);
|
||
}
|
||
};
|
||
markStale = function() {
|
||
return RelativeDates.stale.push(update);
|
||
};
|
||
return setOwnTimeout(diff);
|
||
}
|
||
};
|
||
|
||
Time = {
|
||
init: function() {
|
||
Time.foo();
|
||
QuotePreview.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var node;
|
||
|
||
node = $('.postInfo > .dateTime', post.el);
|
||
Time.date = new Date(node.dataset.utc * 1000);
|
||
return node.textContent = Time.funk(Time);
|
||
},
|
||
foo: function() {
|
||
var code;
|
||
|
||
code = Conf['time'].replace(/%([A-Za-z])/g, function(s, c) {
|
||
if (c in Time.formatters) {
|
||
return "' + Time.formatters." + c + "() + '";
|
||
} else {
|
||
return s;
|
||
}
|
||
});
|
||
return Time.funk = Function('Time', "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[Time.date.getDay()].slice(0, 3);
|
||
},
|
||
A: function() {
|
||
return Time.day[Time.date.getDay()];
|
||
},
|
||
b: function() {
|
||
return Time.month[Time.date.getMonth()].slice(0, 3);
|
||
},
|
||
B: function() {
|
||
return Time.month[Time.date.getMonth()];
|
||
},
|
||
d: function() {
|
||
return Time.zeroPad(Time.date.getDate());
|
||
},
|
||
e: function() {
|
||
return Time.date.getDate();
|
||
},
|
||
H: function() {
|
||
return Time.zeroPad(Time.date.getHours());
|
||
},
|
||
I: function() {
|
||
return Time.zeroPad(Time.date.getHours() % 12 || 12);
|
||
},
|
||
k: function() {
|
||
return Time.date.getHours();
|
||
},
|
||
l: function() {
|
||
return Time.date.getHours() % 12 || 12;
|
||
},
|
||
m: function() {
|
||
return Time.zeroPad(Time.date.getMonth() + 1);
|
||
},
|
||
M: function() {
|
||
return Time.zeroPad(Time.date.getMinutes());
|
||
},
|
||
p: function() {
|
||
if (Time.date.getHours() < 12) {
|
||
return 'AM';
|
||
} else {
|
||
return 'PM';
|
||
}
|
||
},
|
||
P: function() {
|
||
if (Time.date.getHours() < 12) {
|
||
return 'am';
|
||
} else {
|
||
return 'pm';
|
||
}
|
||
},
|
||
S: function() {
|
||
return Time.zeroPad(Time.date.getSeconds());
|
||
},
|
||
y: function() {
|
||
return Time.date.getFullYear() - 2000;
|
||
}
|
||
}
|
||
};
|
||
|
||
UpdateAlert = {
|
||
init: function() {
|
||
var dialog, dismiss, overlay;
|
||
|
||
if ($.get('updatealert')) {
|
||
return;
|
||
}
|
||
$.set('updatealert', true);
|
||
dialog = UpdateAlert.el = $.el('div', {
|
||
className: 'updateAlert reply dialog',
|
||
innerHTML: '\
|
||
<span class=alertMessage>The new 4chan X is out now!<br><a href="http://seaweedchan.github.io/4chan-x/" target="_blank">Get it here.</a></span>\
|
||
<br>\
|
||
<a class="dismiss redButton">Dismiss</a>'
|
||
});
|
||
overlay = $.el('div', {
|
||
id: 'overlay'
|
||
});
|
||
dismiss = $('.dismiss', dialog);
|
||
$.on(overlay, 'click', function() {
|
||
$.rm(overlay);
|
||
d.body.style.removeProperty('width');
|
||
return $.rmClass(d.body, 'unscroll');
|
||
});
|
||
$.on(dismiss, 'click', function() {
|
||
$.rm(overlay);
|
||
d.body.style.removeProperty('width');
|
||
return $.rmClass(d.body, 'unscroll');
|
||
});
|
||
$.add(overlay, dialog);
|
||
$.add(d.body, overlay);
|
||
d.body.style.setProperty('width', "" + d.body.clientWidth + "px", null);
|
||
return $.addClass(d.body, 'unscroll');
|
||
}
|
||
};
|
||
|
||
AnnouncementHiding = {
|
||
init: function() {
|
||
var gmsg, hideButton, hideState;
|
||
|
||
if (!(gmsg = $.id('globalMessage'))) {
|
||
return;
|
||
}
|
||
hideState = $.get('hidegMessage', {});
|
||
hideButton = $.el('a', {
|
||
id: 'toggleMsgButton',
|
||
className: "redButton",
|
||
textContent: "" + (hideState.hidden ? 'View Important Announcement' : 'Close Announcement'),
|
||
href: "javascript:;"
|
||
});
|
||
$.before(gmsg, hideButton);
|
||
$.on(hideButton, 'click', function() {
|
||
$.toggleClass(gmsg, 'hidden');
|
||
if (hideState.hidden) {
|
||
this.textContent = 'Close Announcement';
|
||
hideState.hidden = false;
|
||
delete hideState.gmsg;
|
||
} else {
|
||
this.textContent = 'View Important Announcement';
|
||
hideState.hidden = true;
|
||
hideState.gmsg = gmsg.textContent;
|
||
}
|
||
return $.set('hidegMessage', hideState);
|
||
});
|
||
if (hideState.hidden) {
|
||
$.toggleClass(gmsg, 'hidden');
|
||
if (gmsg.textContent !== hideState.gmsg) {
|
||
return hideButton.click();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
Anonymize = {
|
||
init: function() {
|
||
QuotePreview.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var name, parent, trip;
|
||
|
||
name = $('.postInfo .name', post.el);
|
||
name.textContent = 'Anonymous';
|
||
if ((trip = name.nextElementSibling) && trip.className === 'postertrip') {
|
||
$.rm(trip);
|
||
}
|
||
if ((parent = name.parentNode).className === 'useremail' && !/^mailto:sage$/i.test(parent.href)) {
|
||
return $.replace(parent, name);
|
||
}
|
||
}
|
||
};
|
||
|
||
Filter = {
|
||
filters: {},
|
||
init: function() {
|
||
var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4;
|
||
|
||
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' || boards.split(',').contains(g.BOARD))) {
|
||
continue;
|
||
}
|
||
if (key === 'md5') {
|
||
regexp = regexp[1];
|
||
} else {
|
||
try {
|
||
regexp = RegExp(regexp[1], regexp[2]);
|
||
} catch (_error) {
|
||
err = _error;
|
||
alert(err.message);
|
||
continue;
|
||
}
|
||
}
|
||
op = ((_ref2 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref2[1] : void 0) || 'no';
|
||
stub = (function() {
|
||
var _ref3;
|
||
|
||
switch ((_ref3 = filter.match(/stub:(yes|no)/)) != null ? _ref3[1] : void 0) {
|
||
case 'yes':
|
||
return true;
|
||
case 'no':
|
||
return false;
|
||
default:
|
||
return Conf['Show Stubs'];
|
||
}
|
||
})();
|
||
if (hl = /highlight/.test(filter)) {
|
||
hl = ((_ref3 = filter.match(/highlight:(\w+)/)) != null ? _ref3[1] : void 0) || 'filter_highlight';
|
||
top = ((_ref4 = filter.match(/top:(yes|no)/)) != null ? _ref4[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 Main.callbacks.push(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, isOP) {
|
||
if (isOP && op === 'no' || !isOP && op === 'only') {
|
||
return false;
|
||
}
|
||
if (!test(value)) {
|
||
return false;
|
||
}
|
||
return settings;
|
||
};
|
||
},
|
||
node: function(post) {
|
||
var filter, isOP, key, result, root, value, _i, _len, _ref;
|
||
|
||
if (post.isInlined) {
|
||
return;
|
||
}
|
||
isOP = post.ID === post.threadID;
|
||
root = post.root;
|
||
for (key in Filter.filters) {
|
||
value = Filter[key](post);
|
||
if (value === false) {
|
||
continue;
|
||
}
|
||
_ref = Filter.filters[key];
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
filter = _ref[_i];
|
||
if (!(result = filter(value, isOP))) {
|
||
continue;
|
||
}
|
||
if (result.hide) {
|
||
if (isOP) {
|
||
if (!g.REPLY) {
|
||
ThreadHiding.hide(root.parentNode, result.stub);
|
||
} else {
|
||
continue;
|
||
}
|
||
} else {
|
||
ReplyHiding.hide(post.root, result.stub);
|
||
}
|
||
return;
|
||
}
|
||
$.addClass(root, result["class"]);
|
||
}
|
||
}
|
||
},
|
||
name: function(post) {
|
||
return $('.name', post.el).textContent;
|
||
},
|
||
uniqueid: function(post) {
|
||
var uid;
|
||
|
||
if (uid = $('.posteruid', post.el)) {
|
||
return uid.textContent.slice(5, -1);
|
||
}
|
||
return false;
|
||
},
|
||
tripcode: function(post) {
|
||
var trip;
|
||
|
||
if (trip = $('.postertrip', post.el)) {
|
||
return trip.textContent;
|
||
}
|
||
return false;
|
||
},
|
||
mod: function(post) {
|
||
var mod;
|
||
|
||
if (mod = $('.capcode', post.el)) {
|
||
return mod.textContent;
|
||
}
|
||
return false;
|
||
},
|
||
email: function(post) {
|
||
var mail;
|
||
|
||
if (mail = $('.useremail', post.el)) {
|
||
return decodeURIComponent(mail.href.slice(7));
|
||
}
|
||
return false;
|
||
},
|
||
subject: function(post) {
|
||
var subject;
|
||
|
||
if ((subject = $('.postInfo .subject', post.el)).textContent.length !== 0) {
|
||
return subject.textContent;
|
||
}
|
||
return false;
|
||
},
|
||
comment: function(post) {
|
||
var content, data, i, nodes, text, _i, _ref;
|
||
|
||
text = [];
|
||
nodes = d.evaluate('.//br|.//text()', post.blockquote, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
||
for (i = _i = 0, _ref = nodes.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||
text.push((data = nodes.snapshotItem(i).data) ? data : '\n');
|
||
}
|
||
if ((content = text.join('')).length !== 0) {
|
||
return content;
|
||
}
|
||
return false;
|
||
},
|
||
country: function(post) {
|
||
var flag;
|
||
|
||
if (flag = $('.countryFlag', post.el)) {
|
||
return flag.title;
|
||
}
|
||
return false;
|
||
},
|
||
filename: function(post) {
|
||
var file, fileInfo;
|
||
|
||
fileInfo = post.fileInfo;
|
||
if (fileInfo) {
|
||
if (file = $('.fileText > span', fileInfo)) {
|
||
return file.title;
|
||
} else {
|
||
return fileInfo.firstElementChild.dataset.filename;
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
dimensions: function(post) {
|
||
var fileInfo, match;
|
||
|
||
fileInfo = post.fileInfo;
|
||
if (fileInfo && (match = fileInfo.textContent.match(/\d+x\d+/))) {
|
||
return match[0];
|
||
}
|
||
return false;
|
||
},
|
||
filesize: function(post) {
|
||
var img;
|
||
|
||
img = post.img;
|
||
if (img) {
|
||
return img.alt.replace('Spoiler Image, ', '');
|
||
}
|
||
return false;
|
||
},
|
||
md5: function(post) {
|
||
var img;
|
||
|
||
img = post.img;
|
||
if (img) {
|
||
return img.dataset.md5;
|
||
}
|
||
return false;
|
||
},
|
||
menuInit: function() {
|
||
var div, entry, type, _i, _len, _ref;
|
||
|
||
div = $.el('div', {
|
||
textContent: 'Filter'
|
||
});
|
||
entry = {
|
||
el: div,
|
||
open: function() {
|
||
return true;
|
||
},
|
||
children: []
|
||
};
|
||
_ref = [['Name', 'name'], ['Unique ID', 'uniqueid'], ['Tripcode', 'tripcode'], ['Admin/Mod', 'mod'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Country', 'country'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'md5']];
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
type = _ref[_i];
|
||
entry.children.push(this.createSubEntry(type[0], type[1]));
|
||
}
|
||
return Menu.addEntry(entry);
|
||
},
|
||
createSubEntry: function(text, type) {
|
||
var el, onclick, open;
|
||
|
||
el = $.el('a', {
|
||
href: 'javascript:;',
|
||
textContent: text
|
||
});
|
||
onclick = null;
|
||
open = function(post) {
|
||
var value;
|
||
|
||
value = Filter[type](post);
|
||
if (value === false) {
|
||
return false;
|
||
}
|
||
$.off(el, 'click', onclick);
|
||
onclick = function() {
|
||
var re, save, select, ta, tl;
|
||
|
||
re = type === 'md5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) {
|
||
if (c === '\n') {
|
||
return '\\n';
|
||
} else if (c === '\\') {
|
||
return '\\\\';
|
||
} else {
|
||
return "\\" + c;
|
||
}
|
||
});
|
||
re = type === 'md5' ? "/" + value + "/" : "/^" + re + "$/";
|
||
if (/\bop\b/.test(post["class"])) {
|
||
re += ';op:yes';
|
||
}
|
||
save = (save = $.get(type, '')) ? "" + save + "\n" + re : re;
|
||
$.set(type, save);
|
||
Options.dialog();
|
||
select = $('select[name=filter]', $.id('options'));
|
||
select.value = type;
|
||
$.event(select, new Event('change'));
|
||
$.id('filter_tab').checked = true;
|
||
ta = select.nextElementSibling;
|
||
tl = ta.textLength;
|
||
ta.setSelectionRange(tl, tl);
|
||
return ta.focus();
|
||
};
|
||
$.on(el, 'click', onclick);
|
||
return true;
|
||
};
|
||
return {
|
||
el: el,
|
||
open: open
|
||
};
|
||
}
|
||
};
|
||
|
||
ReplyHiding = {
|
||
init: function() {
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var side;
|
||
|
||
if (post.isInlined || post.ID === post.threadID) {
|
||
return;
|
||
}
|
||
side = $('.sideArrows', post.root);
|
||
side.innerHTML = '<a href="javascript:;"><span>[ - ]</span></a>';
|
||
$.on(side.firstChild, 'click', function() {
|
||
var button, id, root;
|
||
|
||
return ReplyHiding.toggle(button = this.parentNode, root = button.parentNode, id = root.id.slice(2));
|
||
});
|
||
if (post.ID in g.hiddenReplies) {
|
||
return ReplyHiding.hide(post.root);
|
||
}
|
||
},
|
||
toggle: function(button, root, id) {
|
||
var quote, quotes, _i, _j, _len, _len1;
|
||
|
||
quotes = $$(".quotelink[href$='#p" + id + "'], .backlink[href$='#p" + id + "']");
|
||
if (/\bstub\b/.test(button.className)) {
|
||
ReplyHiding.show(root);
|
||
for (_i = 0, _len = quotes.length; _i < _len; _i++) {
|
||
quote = quotes[_i];
|
||
$.rmClass(quote, 'filtered');
|
||
}
|
||
delete g.hiddenReplies[id];
|
||
} else {
|
||
ReplyHiding.hide(root);
|
||
for (_j = 0, _len1 = quotes.length; _j < _len1; _j++) {
|
||
quote = quotes[_j];
|
||
$.addClass(quote, 'filtered');
|
||
}
|
||
g.hiddenReplies[id] = Date.now();
|
||
}
|
||
return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
|
||
},
|
||
hide: function(root) {
|
||
var a, id, menuButton, post, stub;
|
||
|
||
post = $('.post', root);
|
||
if (post.hidden) {
|
||
return;
|
||
}
|
||
post.hidden = true;
|
||
$.addClass(root, 'hidden');
|
||
if (!Conf['Show Stubs']) {
|
||
return;
|
||
}
|
||
stub = $.el('div', {
|
||
className: 'stub',
|
||
innerHTML: "<a href='javascript:;' id='show" + (id = root.id.slice(2)) + "'><span>[ + ]</span> " + (Conf['Anonymize'] ? 'Anonymous' : $('.desktop > .nameBlock', root).textContent) + " </a>"
|
||
});
|
||
a = stub.firstChild;
|
||
$.on(a, 'click', function() {
|
||
var button;
|
||
|
||
return ReplyHiding.toggle(button = this.parentNode, root = $.id("pc" + this.id.slice(4)), id);
|
||
});
|
||
if (Conf['Menu']) {
|
||
menuButton = Menu.a.cloneNode(true);
|
||
$.on(menuButton, 'click', Menu.toggle);
|
||
$.add(stub, [$.tn(' '), menuButton]);
|
||
}
|
||
return $.prepend(root, stub);
|
||
},
|
||
show: function(root) {
|
||
var post, stub;
|
||
|
||
if ((stub = $('.stub', root))) {
|
||
$.rm(stub);
|
||
}
|
||
post = $('.post', root);
|
||
post.hidden = false;
|
||
return $.rmClass(root, 'hidden');
|
||
},
|
||
unhide: function(post) {
|
||
if (post.el.hidden) {
|
||
return ReplyHiding.show(post.root);
|
||
}
|
||
}
|
||
};
|
||
|
||
StrikethroughQuotes = {
|
||
init: function() {
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var el, quote, _i, _len, _ref;
|
||
|
||
if (post.isInlined) {
|
||
return;
|
||
}
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (!(quote.hash && (el = $.id("p" + quote.hash.slice(2))) && quote.hostname === 'boards.4chan.org' && !/catalog$/.test(quote.pathname) && el.hidden)) {
|
||
continue;
|
||
}
|
||
$.addClass(quote, 'filtered');
|
||
if (Conf['Recursive Filtering'] && post.ID !== post.threadID) {
|
||
ReplyHiding.hide(post.root);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
ThreadHiding = {
|
||
init: function() {
|
||
var a, thread, _i, _len, _ref;
|
||
|
||
this.hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
|
||
ThreadHiding.sync();
|
||
if (g.CATALOG) {
|
||
return;
|
||
}
|
||
_ref = $$('.thread');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
thread = _ref[_i];
|
||
a = $.el('a', {
|
||
className: 'hide_thread_button',
|
||
innerHTML: '<span>[ - ]</span>',
|
||
href: 'javascript:;'
|
||
});
|
||
$.on(a, 'click', function() {
|
||
return ThreadHiding.toggle($.x('ancestor::div[@class="thread"][1]', this));
|
||
});
|
||
$.add($('.op .postInfo', thread), a);
|
||
if (thread.id.slice(1) in this.hiddenThreads) {
|
||
ThreadHiding.hide(thread);
|
||
}
|
||
}
|
||
},
|
||
sync: function() {
|
||
var hiddenThreadsCatalog, id;
|
||
|
||
hiddenThreadsCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
|
||
if (g.CATALOG) {
|
||
for (id in this.hiddenThreads) {
|
||
hiddenThreadsCatalog[id] = true;
|
||
}
|
||
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsCatalog));
|
||
} else {
|
||
for (id in hiddenThreadsCatalog) {
|
||
if (!(id in this.hiddenThreads)) {
|
||
this.hiddenThreads[id] = Date.now();
|
||
}
|
||
}
|
||
return $.set("hiddenThreads/" + g.BOARD + "/", this.hiddenThreads);
|
||
}
|
||
},
|
||
toggle: function(thread) {
|
||
var id;
|
||
|
||
id = thread.id.slice(1);
|
||
if (thread.hidden || /\bhidden_thread\b/.test(thread.firstChild.className)) {
|
||
ThreadHiding.show(thread);
|
||
delete ThreadHiding.hiddenThreads[id];
|
||
} else {
|
||
ThreadHiding.hide(thread);
|
||
ThreadHiding.hiddenThreads[id] = Date.now();
|
||
}
|
||
return $.set("hiddenThreads/" + g.BOARD + "/", ThreadHiding.hiddenThreads);
|
||
},
|
||
hide: function(thread) {
|
||
var menuButton, num, opInfo, span, stub, text;
|
||
|
||
if (!Conf['Show Stubs']) {
|
||
thread.hidden = true;
|
||
thread.nextElementSibling.hidden = true;
|
||
return;
|
||
}
|
||
if (/\bhidden_thread\b/.test(thread.firstChild.className)) {
|
||
return;
|
||
}
|
||
num = 0;
|
||
if (span = $('.summary', thread)) {
|
||
num = Number(span.textContent.match(/\d+/));
|
||
}
|
||
num += $$('.opContainer ~ .replyContainer', thread).length;
|
||
text = num === 1 ? '1 reply' : "" + num + " replies";
|
||
opInfo = $('.desktop > .nameBlock', thread).textContent;
|
||
stub = $.el('a', {
|
||
className: 'hidden_thread',
|
||
innerHTML: '<span class=hide_thread_button>[ + ]</span>',
|
||
href: 'javascript:;'
|
||
});
|
||
$.on(stub, 'click', function() {
|
||
return ThreadHiding.toggle(this.parentElement);
|
||
});
|
||
$.add(stub, $.tn("" + opInfo + " (" + text + ")"));
|
||
if (Conf['Menu']) {
|
||
menuButton = Menu.a.cloneNode(true);
|
||
$.on(menuButton, 'click', Menu.toggle);
|
||
$.add(stub, [$.tn(' '), menuButton]);
|
||
}
|
||
return $.prepend(thread, stub);
|
||
},
|
||
show: function(thread) {
|
||
var stub;
|
||
|
||
if (stub = $('.hidden_thread', thread)) {
|
||
$.rm(stub);
|
||
}
|
||
thread.hidden = false;
|
||
return thread.nextElementSibling.hidden = false;
|
||
}
|
||
};
|
||
|
||
FappeTyme = {
|
||
init: function() {
|
||
var controls, el;
|
||
|
||
if (g.CATALOG || g.BOARD === 'f') {
|
||
return;
|
||
}
|
||
if (!$.id('imgControls')) {
|
||
controls = $.el('div', {
|
||
id: 'imgControls',
|
||
innerHTML: "<div id=imgContainer></div>"
|
||
});
|
||
$.prepend($.id('delform'), controls);
|
||
}
|
||
el = $.el('label', {
|
||
href: 'javascript:;',
|
||
id: 'fappeTyme',
|
||
title: 'Fappe Tyme',
|
||
innerHTML: '<input type="checkbox"><span>Fappe Tyme</span>'
|
||
});
|
||
$.add($.id('imgContainer'), el);
|
||
$.on($('input', el), 'click', FappeTyme.toggle);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
if (post.img) {
|
||
return;
|
||
}
|
||
return post.el.parentElement.classList.add("noFile");
|
||
},
|
||
toggle: function() {
|
||
return $.toggleClass(d.body, 'fappeTyme');
|
||
}
|
||
};
|
||
|
||
ImageExpand = {
|
||
init: function() {
|
||
if (g.BOARD === 'f') {
|
||
return;
|
||
}
|
||
QuoteInline.callbacks.push(this.node);
|
||
Main.callbacks.push(this.node);
|
||
return this.dialog();
|
||
},
|
||
node: function(post) {
|
||
var a;
|
||
|
||
if (!post.img || post.hasPDF) {
|
||
return;
|
||
}
|
||
a = post.img.parentNode;
|
||
$.on(a, 'click', ImageExpand.cb.toggle);
|
||
if (Conf['Don\'t Expand Spoilers'] && !Conf['Reveal Spoilers'] && /^spoiler\ image/i.test(a.firstChild.alt)) {
|
||
return;
|
||
}
|
||
if (ImageExpand.on && !post.el.hidden) {
|
||
return ImageExpand.expand(post.img);
|
||
}
|
||
},
|
||
cb: {
|
||
toggle: function(e) {
|
||
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
return ImageExpand.toggle(this);
|
||
},
|
||
all: function() {
|
||
var i, thumb, thumbs, _i, _j, _k, _len, _len1, _len2, _ref;
|
||
|
||
ImageExpand.on = this.checked;
|
||
if (ImageExpand.on) {
|
||
thumbs = $$('img[data-md5]');
|
||
if (Conf['Expand From Current']) {
|
||
for (i = _i = 0, _len = thumbs.length; _i < _len; i = ++_i) {
|
||
thumb = thumbs[i];
|
||
if (thumb.getBoundingClientRect().top > 0) {
|
||
break;
|
||
}
|
||
}
|
||
thumbs = thumbs.slice(i);
|
||
}
|
||
for (_j = 0, _len1 = thumbs.length; _j < _len1; _j++) {
|
||
thumb = thumbs[_j];
|
||
if (Conf['Don\'t Expand Spoilers'] && !Conf['Reveal Spoilers'] && /^spoiler\ image/i.test(thumb.alt)) {
|
||
continue;
|
||
}
|
||
ImageExpand.expand(thumb);
|
||
}
|
||
} else {
|
||
_ref = $$('img[data-md5][hidden]');
|
||
for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) {
|
||
thumb = _ref[_k];
|
||
ImageExpand.contract(thumb);
|
||
}
|
||
}
|
||
},
|
||
typeChange: function() {
|
||
var klass;
|
||
|
||
klass = (function() {
|
||
switch (this.value) {
|
||
case 'full':
|
||
return '';
|
||
case 'fit width':
|
||
return 'fitwidth';
|
||
case 'fit height':
|
||
return 'fitheight';
|
||
case 'fit screen':
|
||
return 'fitwidth fitheight';
|
||
}
|
||
}).call(this);
|
||
$.id('delform').className = klass;
|
||
if (/\bfitheight\b/.test(klass)) {
|
||
$.on(window, 'resize', ImageExpand.resize);
|
||
if (!ImageExpand.style) {
|
||
ImageExpand.style = $.addStyle('');
|
||
}
|
||
return ImageExpand.resize();
|
||
} else if (ImageExpand.style) {
|
||
return $.off(window, 'resize', ImageExpand.resize);
|
||
}
|
||
}
|
||
},
|
||
toggle: function(a) {
|
||
var rect, thumb;
|
||
|
||
thumb = a.firstChild;
|
||
if (thumb.hidden) {
|
||
rect = a.getBoundingClientRect();
|
||
if (rect.bottom > 0) {
|
||
if ($.engine === 'webkit') {
|
||
if (rect.top < 0) {
|
||
d.body.scrollTop += rect.top - 42;
|
||
}
|
||
if (rect.left < 0) {
|
||
d.body.scrollLeft += rect.left;
|
||
}
|
||
} else {
|
||
if (rect.top < 0) {
|
||
d.documentElement.scrollTop += rect.top - 42;
|
||
}
|
||
if (rect.left < 0) {
|
||
d.documentElement.scrollLeft += rect.left;
|
||
}
|
||
}
|
||
}
|
||
return ImageExpand.contract(thumb);
|
||
} else {
|
||
return ImageExpand.expand(thumb);
|
||
}
|
||
},
|
||
contract: function(thumb) {
|
||
thumb.hidden = false;
|
||
thumb.nextSibling.hidden = true;
|
||
return $.rmClass(thumb.parentNode.parentNode.parentNode.parentNode, 'image_expanded');
|
||
},
|
||
expand: function(thumb, src) {
|
||
var a, img;
|
||
|
||
if ($.x('ancestor-or-self::*[@hidden]', thumb)) {
|
||
return;
|
||
}
|
||
a = thumb.parentNode;
|
||
src || (src = a.href);
|
||
if (/\.pdf$/.test(src)) {
|
||
return;
|
||
}
|
||
thumb.hidden = true;
|
||
$.addClass(thumb.parentNode.parentNode.parentNode.parentNode, 'image_expanded');
|
||
if ((img = thumb.nextSibling) && img.tagName.toLowerCase() === 'img') {
|
||
img.hidden = false;
|
||
return;
|
||
}
|
||
img = $.el('img', {
|
||
src: src,
|
||
className: 'fullSize'
|
||
});
|
||
$.on(img, 'error', ImageExpand.error);
|
||
return $.after(thumb, img);
|
||
},
|
||
error: function() {
|
||
var src, thumb, timeoutID, url;
|
||
|
||
thumb = this.previousSibling;
|
||
ImageExpand.contract(thumb);
|
||
$.rm(this);
|
||
src = this.src.split('/');
|
||
if (!(src[2] === 'images.4chan.org' && (url = Redirect.image(src[3], src[5])))) {
|
||
if (g.dead) {
|
||
return;
|
||
}
|
||
url = "//images.4chan.org/" + src[3] + "/src/" + src[5];
|
||
}
|
||
if ($.engine !== 'webkit' && url.split('/')[2] === 'images.4chan.org') {
|
||
return;
|
||
}
|
||
timeoutID = setTimeout(ImageExpand.expand, 10000, thumb, url);
|
||
if ($.engine !== 'webkit' || url.split('/')[2] !== 'images.4chan.org') {
|
||
return;
|
||
}
|
||
return $.ajax(url, {
|
||
onreadystatechange: (function() {
|
||
if (this.status === 404) {
|
||
return clearTimeout(timeoutID);
|
||
}
|
||
})
|
||
}, {
|
||
type: 'head'
|
||
});
|
||
},
|
||
dialog: function() {
|
||
var controls, imageType, select;
|
||
|
||
controls = $.el('div', {
|
||
id: 'imgControls',
|
||
innerHTML: "<div id=imgContainer><select id=imageType name=imageType><option value=full>Full</option><option value='fit width'>Fit Width</option><option value='fit height'>Fit Height</option value='fit screen'><option value='fit screen'>Fit Screen</option></select><label><input type=checkbox id=imageExpand>Expand Images</label></div>"
|
||
});
|
||
imageType = $.get('imageType', 'full');
|
||
select = $('select', controls);
|
||
select.value = imageType;
|
||
ImageExpand.cb.typeChange.call(select);
|
||
$.on(select, 'change', $.cb.value);
|
||
$.on(select, 'change', ImageExpand.cb.typeChange);
|
||
$.on($('input', controls), 'click', ImageExpand.cb.all);
|
||
return $.prepend($.id('delform'), controls);
|
||
},
|
||
resize: function() {
|
||
return ImageExpand.style.textContent = ".fitheight img[data-md5] + img {max-height:" + d.documentElement.clientHeight + "px;}";
|
||
}
|
||
};
|
||
|
||
ImageHover = {
|
||
init: function() {
|
||
QuoteInline.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
if (!post.img || post.hasPDF) {
|
||
return;
|
||
}
|
||
return $.on(post.img, 'mouseover', ImageHover.mouseover);
|
||
},
|
||
mouseover: function() {
|
||
var el;
|
||
|
||
if (el = $.id('ihover')) {
|
||
if (el === UI.el) {
|
||
delete UI.el;
|
||
}
|
||
$.rm(el);
|
||
}
|
||
if (UI.el) {
|
||
return;
|
||
}
|
||
el = UI.el = $.el('img', {
|
||
id: 'ihover',
|
||
src: this.parentNode.href
|
||
});
|
||
$.add(d.body, el);
|
||
$.on(el, 'load', ImageHover.load);
|
||
$.on(el, 'error', ImageHover.error);
|
||
$.on(this, 'mousemove', UI.hover);
|
||
return $.on(this, 'mouseout', ImageHover.mouseout);
|
||
},
|
||
load: function() {
|
||
var style;
|
||
|
||
if (!this.parentNode) {
|
||
return;
|
||
}
|
||
style = this.style;
|
||
return UI.hover({
|
||
clientX: -45 + parseInt(style.left),
|
||
clientY: 120 + parseInt(style.top)
|
||
});
|
||
},
|
||
error: function() {
|
||
var src, timeoutID, url,
|
||
_this = this;
|
||
|
||
src = this.src.split('/');
|
||
if (!(src[2] === 'images.4chan.org' && (url = Redirect.image(src[3], src[5])))) {
|
||
if (g.dead) {
|
||
return;
|
||
}
|
||
url = "//images.4chan.org/" + src[3] + "/src/" + src[5];
|
||
}
|
||
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'
|
||
});
|
||
},
|
||
mouseout: function() {
|
||
UI.hoverend();
|
||
$.off(this, 'mousemove', UI.hover);
|
||
return $.off(this, 'mouseout', ImageHover.mouseout);
|
||
}
|
||
};
|
||
|
||
ImageReplace = {
|
||
init: function() {
|
||
if (g.BOARD === 'f') {
|
||
return;
|
||
}
|
||
QuoteInline.callbacks.push(this.node);
|
||
QuotePreview.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var el, href, img, type;
|
||
|
||
img = post.img;
|
||
if (post.el.hidden || !img || /spoiler/.test(img.src)) {
|
||
return;
|
||
}
|
||
if (Conf["Replace " + ((type = ((href = img.parentNode.href).match(/\w{3}$/))[0].toUpperCase()) === 'PEG' ? 'JPG' : type)]) {
|
||
el = $.el('img');
|
||
el.setAttribute('data-id', post.ID);
|
||
$.on(el, 'load', function() {
|
||
return img.src = el.src;
|
||
});
|
||
return el.src = href;
|
||
}
|
||
}
|
||
};
|
||
|
||
Prefetch = {
|
||
init: function() {
|
||
if (g.BOARD === 'f') {
|
||
return;
|
||
}
|
||
return this.dialog();
|
||
},
|
||
dialog: function() {
|
||
var controls, first, input;
|
||
|
||
controls = $.el('label', {
|
||
id: 'prefetch',
|
||
innerHTML: "<input type=checkbox>Prefetch Images"
|
||
});
|
||
input = $('input', controls);
|
||
$.on(input, 'change', Prefetch.change);
|
||
first = $.id('delform').firstElementChild;
|
||
if (first.id === 'imgControls') {
|
||
return $.after(first, controls);
|
||
} else {
|
||
return $.before(first, controls);
|
||
}
|
||
},
|
||
change: function() {
|
||
var thumb, _i, _len, _ref;
|
||
|
||
$.off(this, 'change', Prefetch.change);
|
||
_ref = $$('a.fileThumb');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
thumb = _ref[_i];
|
||
$.el('img', {
|
||
src: thumb.href
|
||
});
|
||
}
|
||
return Main.callbacks.push(Prefetch.node);
|
||
},
|
||
node: function(post) {
|
||
var img;
|
||
|
||
img = post.img;
|
||
if (post.el.hidden || !img) {
|
||
return;
|
||
}
|
||
return $.el('img', {
|
||
src: img.parentNode.href
|
||
});
|
||
}
|
||
};
|
||
|
||
RevealSpoilers = {
|
||
init: function() {
|
||
QuotePreview.callbacks.push(this.node);
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var img, s;
|
||
|
||
img = post.img;
|
||
if (!(img && /^Spoiler/.test(img.alt)) || post.isArchived) {
|
||
return;
|
||
}
|
||
img.removeAttribute('style');
|
||
s = img.style;
|
||
s.maxHeight = s.maxWidth = /\bop\b/.test(post["class"]) ? '250px' : '125px';
|
||
return img.src = "//thumbs.4chan.org" + (img.parentNode.pathname.replace(/src(\/\d+).+$/, 'thumb$1s.jpg'));
|
||
}
|
||
};
|
||
|
||
Sauce = {
|
||
init: function() {
|
||
var link, _i, _len, _ref;
|
||
|
||
if (g.BOARD === 'f') {
|
||
return;
|
||
}
|
||
this.links = [];
|
||
_ref = Conf['sauces'].split('\n');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
link = _ref[_i];
|
||
if (link[0] === '#') {
|
||
continue;
|
||
}
|
||
this.links.push(this.createSauceLink(link.trim()));
|
||
}
|
||
if (!this.links.length) {
|
||
return;
|
||
}
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
createSauceLink: function(link) {
|
||
var domain, el, href, m;
|
||
|
||
link = link.replace(/(\$\d)/g, function(parameter) {
|
||
switch (parameter) {
|
||
case '$1':
|
||
return "' + (isArchived ? img.firstChild.src : 'http://thumbs.4chan.org' + img.pathname.replace(/src(\\/\\d+).+$/, 'thumb$1s.jpg')) + '";
|
||
case '$2':
|
||
return "' + img.href + '";
|
||
case '$3':
|
||
return "' + encodeURIComponent(img.firstChild.dataset.md5) + '";
|
||
case '$4':
|
||
return g.BOARD;
|
||
default:
|
||
return parameter;
|
||
}
|
||
});
|
||
domain = (m = link.match(/;text:(.+)$/)) ? m[1] : link.match(/(\w+)\.\w+\//)[1];
|
||
href = link.replace(/;text:.+$/, '');
|
||
href = Function('img', 'isArchived', "return '" + href + "'");
|
||
el = $.el('a', {
|
||
target: '_blank',
|
||
textContent: domain
|
||
});
|
||
return function(img, isArchived) {
|
||
var a;
|
||
|
||
a = el.cloneNode(true);
|
||
a.href = href(img, isArchived);
|
||
return a;
|
||
};
|
||
},
|
||
node: function(post) {
|
||
var img, link, nodes, _i, _len, _ref;
|
||
|
||
img = post.img;
|
||
if (!img) {
|
||
return;
|
||
}
|
||
img = img.parentNode;
|
||
nodes = [];
|
||
_ref = Sauce.links;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
link = _ref[_i];
|
||
nodes.push($.tn('\u00A0'), link(img, post.isArchived));
|
||
}
|
||
return $.add(post.fileInfo, nodes);
|
||
}
|
||
};
|
||
|
||
Linkify = {
|
||
init: function() {
|
||
if (Conf['Embedding']) {
|
||
QuoteInline.callbacks.push(function(post) {
|
||
var embed, _i, _len, _ref;
|
||
|
||
_ref = $$('.embed', post.blockquote);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
embed = _ref[_i];
|
||
$.on(embed, 'click', Linkify.toggle);
|
||
}
|
||
});
|
||
}
|
||
QuotePreview.callbacks.push(this.node);
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
regString: /(\b([a-z]+:\/\/|[a-z]{3,}\.[-a-z0-9]+\.[a-z]+|[-a-z0-9]+\.[a-z]{2,4}|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-z]{3,}:[a-z0-9?]|[a-z0-9._%+-:]+@[a-z0-9.-]+\.[a-z0-9])[^\s'"]+)/gi,
|
||
cypher: $.el('div'),
|
||
node: function(post) {
|
||
var a, child, cypher, cypherText, data, i, index, len, link, links, lookahead, name, next, node, nodes, snapshot, spoiler, text, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _results;
|
||
|
||
snapshot = $.X('.//text()', post.blockquote);
|
||
cypher = Linkify.cypher;
|
||
i = -1;
|
||
len = snapshot.snapshotLength;
|
||
_results = [];
|
||
while (++i < len) {
|
||
nodes = $.frag();
|
||
node = snapshot.snapshotItem(i);
|
||
data = node.data;
|
||
if (!(node.parentNode && Linkify.regString.test(data))) {
|
||
continue;
|
||
}
|
||
Linkify.regString.lastIndex = 0;
|
||
cypherText = [];
|
||
if (next = node.nextSibling) {
|
||
cypher.textContent = node.textContent;
|
||
cypherText[0] = cypher.innerHTML;
|
||
while ((next.nodeName.toLowerCase() === 'wbr' || next.nodeName.toLowerCase() === 's') && (lookahead = next.nextSibling) && ((name = lookahead.nodeName) === "#text" || name.toLowerCase() === 'br')) {
|
||
cypher.textContent = lookahead.textContent;
|
||
cypherText.push((spoiler = next.innerHTML) ? "<s>" + (spoiler.replace(/</g, ' <')) + "</s>" : '<wbr>');
|
||
cypherText.push(cypher.innerHTML);
|
||
$.rm(next);
|
||
next = lookahead.nextSibling;
|
||
if (lookahead.nodeName === "#text") {
|
||
$.rm(lookahead);
|
||
}
|
||
if (!next) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (cypherText.length) {
|
||
data = cypherText.join('');
|
||
}
|
||
links = data.match(Linkify.regString);
|
||
for (_i = 0, _len = links.length; _i < _len; _i++) {
|
||
link = links[_i];
|
||
index = data.indexOf(link);
|
||
if (text = data.slice(0, index)) {
|
||
cypher.innerHTML = text;
|
||
_ref = __slice.call(cypher.childNodes);
|
||
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
|
||
child = _ref[_j];
|
||
$.add(nodes, child);
|
||
}
|
||
}
|
||
cypher.innerHTML = (link.indexOf(':') < 0 ? (link.indexOf('@') > 0 ? 'mailto:' + link : 'http://' + link) : link).replace(/<(wbr|s|\/s)>/g, '');
|
||
a = $.el('a', {
|
||
innerHTML: link,
|
||
className: 'linkify',
|
||
rel: 'nofollow noreferrer',
|
||
target: '_blank',
|
||
href: cypher.textContent
|
||
});
|
||
$.add(nodes, Linkify.embedder(a));
|
||
data = data.slice(index + link.length);
|
||
}
|
||
if (data) {
|
||
cypher.innerHTML = data;
|
||
_ref1 = __slice.call(cypher.childNodes);
|
||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||
child = _ref1[_k];
|
||
$.add(nodes, child);
|
||
}
|
||
}
|
||
_results.push($.replace(node, nodes));
|
||
}
|
||
return _results;
|
||
},
|
||
toggle: function() {
|
||
var el, embed, style, type, url;
|
||
|
||
embed = this.previousElementSibling;
|
||
if (this.className.contains("embedded")) {
|
||
el = $.el('a', {
|
||
rel: 'nofollow noreferrer',
|
||
target: 'blank',
|
||
className: 'linkify',
|
||
href: url = this.getAttribute("data-originalURL"),
|
||
textContent: this.getAttribute("data-title") || url
|
||
});
|
||
this.textContent = '(embed)';
|
||
} else {
|
||
el = (type = Linkify.types[this.getAttribute("data-service")]).el.call(this);
|
||
el.style.cssText = (style = type.style) ? style : "border: 0; width: " + ($.get('embedWidth', Config['embedWidth'])) + "px; height: " + ($.get('embedHeight', Config['embedHeight'])) + "px";
|
||
this.textContent = '(unembed)';
|
||
}
|
||
$.replace(embed, el);
|
||
return $.toggleClass(this, 'embedded');
|
||
},
|
||
types: {
|
||
YouTube: {
|
||
regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*).*/,
|
||
el: function() {
|
||
return $.el('iframe', {
|
||
src: "//www.youtube.com/embed/" + this.name
|
||
});
|
||
},
|
||
title: {
|
||
api: function() {
|
||
return "https://gdata.youtube.com/feeds/api/videos/" + this.name + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode";
|
||
},
|
||
text: function() {
|
||
return JSON.parse(this.responseText).entry.title.$t;
|
||
}
|
||
}
|
||
},
|
||
Vocaroo: {
|
||
regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/,
|
||
style: 'border: 0; width: 150px; height: 45px;',
|
||
el: function() {
|
||
return $.el('object', {
|
||
innerHTML: "<embed src='http://vocaroo.com/player.swf?playMediaID=" + (this.name.replace(/^i\//, '')) + "&autoplay=0' width='150' height='45' pluginspage='http://get.adobe.com/flashplayer/' type='application/x-shockwave-flash'></embed>"
|
||
});
|
||
}
|
||
},
|
||
Vimeo: {
|
||
regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/,
|
||
el: function() {
|
||
return $.el('iframe', {
|
||
src: "//player.vimeo.com/video/" + this.name
|
||
});
|
||
},
|
||
title: {
|
||
api: function() {
|
||
return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + this.name;
|
||
},
|
||
text: function() {
|
||
return JSON.parse(this.responseText).title;
|
||
}
|
||
}
|
||
},
|
||
LiveLeak: {
|
||
regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/,
|
||
el: function() {
|
||
return $.el('iframe', {
|
||
src: "http://www.liveleak.com/e/" + this.name + "?autostart=true"
|
||
});
|
||
}
|
||
},
|
||
audio: {
|
||
regExp: /(.*\.(mp3|ogg|wav))$/,
|
||
el: function() {
|
||
return $.el('audio', {
|
||
controls: 'controls',
|
||
preload: 'auto',
|
||
src: this.name
|
||
});
|
||
}
|
||
},
|
||
SoundCloud: {
|
||
regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/,
|
||
style: 'height: auto; width: 500px; display: inline-block;',
|
||
el: function() {
|
||
var div;
|
||
|
||
div = $.el('div', {
|
||
className: "soundcloud",
|
||
name: "soundcloud"
|
||
});
|
||
$.ajax("//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=" + (this.getAttribute('data-originalURL')) + "&color=" + (Style.colorToHex(Themes[Conf['theme']]['Background Color'])), {
|
||
div: div,
|
||
onloadend: function() {
|
||
return this.div.innerHTML = JSON.parse(this.responseText).html;
|
||
}
|
||
}, false);
|
||
return div;
|
||
}
|
||
},
|
||
pastebin: {
|
||
regExp: /.*(?:pastebin.com\/)([^#\&\?]*).*/,
|
||
el: function() {
|
||
var div;
|
||
|
||
return div = $.el('iframe', {
|
||
src: "http://pastebin.com/embed_iframe.php?i=" + this.name
|
||
});
|
||
}
|
||
}
|
||
},
|
||
embedder: function(a) {
|
||
var callbacks, embed, err, key, match, service, title, titles, type, _ref;
|
||
|
||
if (!Conf['Embedding']) {
|
||
return [a];
|
||
}
|
||
callbacks = function() {
|
||
var title;
|
||
|
||
return a.textContent = (function() {
|
||
switch (this.status) {
|
||
case 200:
|
||
case 304:
|
||
title = "[" + (embed.getAttribute('data-service')) + "] " + (service.text.call(this));
|
||
embed.setAttribute('data-title', title);
|
||
titles[embed.name] = [title, Date.now()];
|
||
$.set('CachedTitles', titles);
|
||
return title;
|
||
case 404:
|
||
return "[" + key + "] Not Found";
|
||
case 403:
|
||
return "[" + key + "] Forbidden or Private";
|
||
default:
|
||
return "[" + key + "] " + this.status + "'d";
|
||
}
|
||
}).call(this);
|
||
};
|
||
_ref = Linkify.types;
|
||
for (key in _ref) {
|
||
type = _ref[key];
|
||
if (!(match = a.href.match(type.regExp))) {
|
||
continue;
|
||
}
|
||
embed = $.el('a', {
|
||
name: (a.name = match[1]),
|
||
className: 'embedlink',
|
||
href: 'javascript:;',
|
||
textContent: '(embed)'
|
||
});
|
||
embed.setAttribute('data-service', key);
|
||
embed.setAttribute('data-originalURL', a.href);
|
||
$.on(embed, 'click', Linkify.toggle);
|
||
if (Conf['Link Title'] && (service = type.title)) {
|
||
titles = $.get('CachedTitles', {});
|
||
if (title = titles[match[1]]) {
|
||
a.textContent = title[0];
|
||
embed.setAttribute('data-title', title[0]);
|
||
} else {
|
||
try {
|
||
$.cache(service.api.call(a), callbacks);
|
||
} catch (_error) {
|
||
err = _error;
|
||
a.innerHTML = "[" + key + "] <span class=warning>Title Link Blocked</span> (are you using NoScript?)</a>";
|
||
}
|
||
}
|
||
}
|
||
return [a, $.tn(' '), embed];
|
||
}
|
||
return [a];
|
||
}
|
||
};
|
||
|
||
ArchiveLink = {
|
||
init: function() {
|
||
var div, entry, key, type, _ref;
|
||
|
||
div = $.el('div', {
|
||
textContent: 'Archive'
|
||
});
|
||
entry = {
|
||
el: div,
|
||
open: function(post) {
|
||
var path;
|
||
|
||
path = $('a[title="Highlight this post"]', post.el).pathname.split('/');
|
||
if ((Redirect.to({
|
||
board: path[1],
|
||
threadID: path[3],
|
||
postID: post.ID
|
||
})) === ("//boards.4chan.org/" + path[1] + "/")) {
|
||
return false;
|
||
}
|
||
post.info = [path[1], path[3]];
|
||
return true;
|
||
},
|
||
children: []
|
||
};
|
||
_ref = {
|
||
Post: 'apost',
|
||
Name: 'name',
|
||
Tripcode: 'tripcode',
|
||
'E-mail': 'email',
|
||
Subject: 'subject',
|
||
Filename: 'filename',
|
||
'Image MD5': 'md5'
|
||
};
|
||
for (key in _ref) {
|
||
type = _ref[key];
|
||
entry.children.push(this.createSubEntry(key, type));
|
||
}
|
||
return Menu.addEntry(entry);
|
||
},
|
||
createSubEntry: function(text, type) {
|
||
var el, open;
|
||
|
||
el = $.el('a', {
|
||
textContent: text,
|
||
target: '_blank'
|
||
});
|
||
open = function(post) {
|
||
var value;
|
||
|
||
if (type === 'apost') {
|
||
el.href = Redirect.to({
|
||
board: post.info[0],
|
||
threadID: post.info[1],
|
||
postID: post.ID
|
||
});
|
||
return true;
|
||
}
|
||
value = Filter[type](post);
|
||
if (!value) {
|
||
return false;
|
||
}
|
||
return el.href = Redirect.to({
|
||
board: post.info[0],
|
||
type: type,
|
||
value: value,
|
||
isSearch: true
|
||
});
|
||
};
|
||
return {
|
||
el: el,
|
||
open: open
|
||
};
|
||
}
|
||
};
|
||
|
||
DeleteLink = {
|
||
init: function() {
|
||
var aImage, aPost, children, div;
|
||
|
||
div = $.el('div', {
|
||
className: 'delete_link',
|
||
textContent: 'Delete'
|
||
});
|
||
aPost = $.el('a', {
|
||
className: 'delete_post',
|
||
href: 'javascript:;'
|
||
});
|
||
aImage = $.el('a', {
|
||
className: 'delete_image',
|
||
href: 'javascript:;'
|
||
});
|
||
children = [];
|
||
children.push({
|
||
el: aPost,
|
||
open: function() {
|
||
aPost.textContent = 'Post';
|
||
$.on(aPost, 'click', DeleteLink["delete"]);
|
||
return true;
|
||
}
|
||
});
|
||
children.push({
|
||
el: aImage,
|
||
open: function(post) {
|
||
if (!post.img) {
|
||
return false;
|
||
}
|
||
aImage.textContent = 'Image';
|
||
$.on(aImage, 'click', DeleteLink["delete"]);
|
||
return true;
|
||
}
|
||
});
|
||
Menu.addEntry({
|
||
el: div,
|
||
open: function(post) {
|
||
var node, seconds;
|
||
|
||
if (post.isArchived) {
|
||
return false;
|
||
}
|
||
node = div.firstChild;
|
||
if (seconds = DeleteLink.cooldown[post.ID]) {
|
||
node.textContent = "Delete (" + seconds + ")";
|
||
DeleteLink.cooldown.el = node;
|
||
} else {
|
||
node.textContent = 'Delete';
|
||
delete DeleteLink.cooldown.el;
|
||
}
|
||
return true;
|
||
},
|
||
children: children
|
||
});
|
||
return $.on(d, 'QRPostSuccessful', this.cooldown.start);
|
||
},
|
||
"delete": function() {
|
||
var board, form, id, m, menu, pwd, self;
|
||
|
||
menu = $.id('menu');
|
||
id = menu.dataset.id;
|
||
if (DeleteLink.cooldown[id]) {
|
||
return;
|
||
}
|
||
$.off(this, 'click', DeleteLink["delete"]);
|
||
this.textContent = 'Deleting...';
|
||
pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $.id('delPassword').value;
|
||
board = $('a[title="Highlight this post"]', $.id(menu.dataset.rootid)).pathname.split('/')[1];
|
||
self = this;
|
||
form = {
|
||
mode: 'usrdel',
|
||
onlyimgdel: /\bdelete_image\b/.test(this.className),
|
||
pwd: pwd
|
||
};
|
||
form[id] = 'delete';
|
||
return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + board + "/"), {
|
||
onload: function() {
|
||
return DeleteLink.load(self, this.response);
|
||
},
|
||
onerror: function() {
|
||
return DeleteLink.error(self);
|
||
}
|
||
}, {
|
||
form: $.formData(form)
|
||
});
|
||
},
|
||
load: function(self, html) {
|
||
var doc, msg, s;
|
||
|
||
doc = d.implementation.createHTMLDocument('');
|
||
doc.documentElement.innerHTML = html;
|
||
if (doc.title === '4chan - Banned') {
|
||
s = 'Banned!';
|
||
} else if (msg = doc.getElementById('errmsg')) {
|
||
s = msg.textContent;
|
||
$.on(self, 'click', DeleteLink["delete"]);
|
||
} else {
|
||
s = 'Deleted';
|
||
}
|
||
return self.textContent = s;
|
||
},
|
||
error: function(self) {
|
||
self.textContent = 'Connection error, please retry.';
|
||
return $.on(self, 'click', DeleteLink["delete"]);
|
||
},
|
||
cooldown: {
|
||
start: function(e) {
|
||
var seconds;
|
||
|
||
seconds = g.BOARD === 'q' ? 600 : 30;
|
||
return DeleteLink.cooldown.count(e.detail.postID, seconds, seconds);
|
||
},
|
||
count: function(postID, seconds, length) {
|
||
var el;
|
||
|
||
if (!((0 <= seconds && seconds <= length))) {
|
||
return;
|
||
}
|
||
setTimeout(DeleteLink.cooldown.count, 1000, postID, seconds - 1, length);
|
||
el = DeleteLink.cooldown.el;
|
||
if (seconds === 0) {
|
||
if (el != null) {
|
||
el.textContent = 'Delete';
|
||
}
|
||
delete DeleteLink.cooldown[postID];
|
||
delete DeleteLink.cooldown.el;
|
||
return;
|
||
}
|
||
if (el != null) {
|
||
el.textContent = "Delete (" + seconds + ")";
|
||
}
|
||
return DeleteLink.cooldown[postID] = seconds;
|
||
}
|
||
}
|
||
};
|
||
|
||
DownloadLink = {
|
||
init: function() {
|
||
var a;
|
||
|
||
if ($.el('a').download == null) {
|
||
return;
|
||
}
|
||
a = $.el('a', {
|
||
className: 'download_link',
|
||
textContent: 'Download file'
|
||
});
|
||
return Menu.addEntry({
|
||
el: a,
|
||
open: function(post) {
|
||
var fileText;
|
||
|
||
if (!post.img) {
|
||
return false;
|
||
}
|
||
a.href = post.img.parentNode.href;
|
||
fileText = post.fileInfo.firstElementChild;
|
||
a.download = Conf['File Info Formatting'] ? fileText.dataset.filename : $('span', fileText).title;
|
||
return true;
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
EmbedLink = {
|
||
init: function() {
|
||
var a;
|
||
|
||
a = $.el('a', {
|
||
className: 'embed_link',
|
||
textContent: 'Embed all in post'
|
||
});
|
||
$.on(a, 'click', EmbedLink.toggle);
|
||
return Menu.addEntry({
|
||
el: a,
|
||
open: function(post) {
|
||
var quote;
|
||
|
||
if ($('.embed', (quote = post.blockquote))) {
|
||
if ($('.embedded', quote)) {
|
||
this.el.textContent = 'Unembed all in post';
|
||
EmbedLink[post.id] = true;
|
||
}
|
||
$.on(this.el, 'click', this.toggle);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
});
|
||
},
|
||
toggle: function() {
|
||
var embed, id, menu, root, _i, _len, _ref;
|
||
|
||
menu = $.id('menu');
|
||
id = menu.dataset.id;
|
||
root = $.id("m" + id);
|
||
_ref = $$('.embed', root);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
embed = _ref[_i];
|
||
if ((!EmbedLink[id] && embed.className.contains('embedded')) || (EmbedLink[id] && !embed.className.contains('embedded'))) {
|
||
continue;
|
||
}
|
||
embed.click();
|
||
}
|
||
return EmbedLink[id] = !EmbedLink[id];
|
||
}
|
||
};
|
||
|
||
Menu = {
|
||
entries: [],
|
||
init: function() {
|
||
this.a = $.el('a', {
|
||
className: 'menu_button',
|
||
href: 'javascript:;',
|
||
innerHTML: '[<span class=dropmarker></span>]'
|
||
});
|
||
this.el = $.el('div', {
|
||
className: 'reply dialog',
|
||
id: 'menu',
|
||
tabIndex: 0
|
||
});
|
||
$.on(this.el, 'click', function(e) {
|
||
return e.stopPropagation();
|
||
});
|
||
$.on(this.el, 'keydown', this.keybinds);
|
||
$.on(d, 'AddMenuEntry', function(e) {
|
||
return Menu.addEntry(e.detail);
|
||
});
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var a;
|
||
|
||
if (post.isInlined && !post.isCrosspost) {
|
||
a = $('.menu_button', post.el);
|
||
} else {
|
||
a = Menu.a.cloneNode(true);
|
||
$.add($('.postInfo', post.el), [$.tn('\u00A0'), a]);
|
||
}
|
||
return $.on(a, 'click', Menu.toggle);
|
||
},
|
||
toggle: function(e) {
|
||
var lastOpener, post;
|
||
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
if (Menu.el.parentNode) {
|
||
lastOpener = Menu.lastOpener;
|
||
Menu.close();
|
||
if (lastOpener === this) {
|
||
return;
|
||
}
|
||
}
|
||
Menu.lastOpener = this;
|
||
post = /\bhidden_thread\b/.test(this.parentNode.className) ? $.x('ancestor::div[parent::div[@class="board"]]/child::div[contains(@class,"opContainer")]', this) : $.x('ancestor::div[contains(@class,"postContainer")][1]', this);
|
||
return Menu.open(this, Main.preParse(post));
|
||
},
|
||
open: function(button, post) {
|
||
var bLeft, bRect, bTop, el, entry, funk, mRect, _i, _len, _ref;
|
||
|
||
el = Menu.el;
|
||
el.setAttribute('data-id', post.ID);
|
||
el.setAttribute('data-rootid', post.root.id);
|
||
funk = function(entry, parent) {
|
||
var child, children, subMenu, _i, _len;
|
||
|
||
children = entry.children;
|
||
if (!entry.open(post)) {
|
||
return;
|
||
}
|
||
$.add(parent, entry.el);
|
||
if (!children) {
|
||
return;
|
||
}
|
||
if (subMenu = $('.subMenu', entry.el)) {
|
||
$.rm(subMenu);
|
||
}
|
||
subMenu = $.el('div', {
|
||
className: 'reply dialog subMenu'
|
||
});
|
||
$.add(entry.el, subMenu);
|
||
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
||
child = children[_i];
|
||
funk(child, subMenu);
|
||
}
|
||
};
|
||
_ref = Menu.entries;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
entry = _ref[_i];
|
||
funk(entry, el);
|
||
}
|
||
Menu.focus($('.entry', Menu.el));
|
||
$.on(d, 'click', Menu.close);
|
||
$.add(d.body, el);
|
||
mRect = el.getBoundingClientRect();
|
||
bRect = button.getBoundingClientRect();
|
||
bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top;
|
||
bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left;
|
||
el.style.top = bRect.top + bRect.height + mRect.height < d.documentElement.clientHeight ? bTop + bRect.height + 2 + 'px' : bTop - mRect.height - 2 + 'px';
|
||
el.style.left = bRect.left + mRect.width < d.documentElement.clientWidth ? bLeft + 'px' : bLeft + bRect.width - mRect.width + 'px';
|
||
return el.focus();
|
||
},
|
||
close: function() {
|
||
var el, focused, _i, _len, _ref;
|
||
|
||
el = Menu.el;
|
||
$.rm(el);
|
||
_ref = $$('.focused.entry', el);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
focused = _ref[_i];
|
||
$.rmClass(focused, 'focused');
|
||
}
|
||
el.innerHTML = null;
|
||
el.removeAttribute('style');
|
||
delete Menu.lastOpener;
|
||
delete Menu.focusedEntry;
|
||
return $.off(d, 'click', Menu.close);
|
||
},
|
||
keybinds: function(e) {
|
||
var el, next, subMenu;
|
||
|
||
el = Menu.focusedEntry;
|
||
switch (Keybinds.keyCode(e) || e.keyCode) {
|
||
case 'Esc':
|
||
Menu.lastOpener.focus();
|
||
Menu.close();
|
||
break;
|
||
case 13:
|
||
case 32:
|
||
el.click();
|
||
break;
|
||
case 'Up':
|
||
if (next = el.previousElementSibling) {
|
||
Menu.focus(next);
|
||
}
|
||
break;
|
||
case 'Down':
|
||
if (next = el.nextElementSibling) {
|
||
Menu.focus(next);
|
||
}
|
||
break;
|
||
case 'Right':
|
||
if ((subMenu = $('.subMenu', el)) && (next = subMenu.firstElementChild)) {
|
||
Menu.focus(next);
|
||
}
|
||
break;
|
||
case 'Left':
|
||
if (next = $.x('parent::*[contains(@class,"subMenu")]/parent::*', el)) {
|
||
Menu.focus(next);
|
||
}
|
||
break;
|
||
default:
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
return e.stopPropagation();
|
||
},
|
||
focus: function(el) {
|
||
var focused, _i, _len, _ref;
|
||
|
||
if (focused = $.x('parent::*/child::*[contains(@class,"focused")]', el)) {
|
||
$.rmClass(focused, 'focused');
|
||
}
|
||
_ref = $$('.focused', el);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
focused = _ref[_i];
|
||
$.rmClass(focused, 'focused');
|
||
}
|
||
Menu.focusedEntry = el;
|
||
return $.addClass(el, 'focused');
|
||
},
|
||
addEntry: function(entry) {
|
||
var funk;
|
||
|
||
funk = function(entry) {
|
||
var child, children, el, _i, _len;
|
||
|
||
el = entry.el, children = entry.children;
|
||
$.addClass(el, 'entry');
|
||
$.on(el, 'focus mouseover', function(e) {
|
||
e.stopPropagation();
|
||
return Menu.focus(this);
|
||
});
|
||
if (!children) {
|
||
return;
|
||
}
|
||
$.addClass(el, 'hasSubMenu');
|
||
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
||
child = children[_i];
|
||
funk(child);
|
||
}
|
||
};
|
||
funk(entry);
|
||
return Menu.entries.push(entry);
|
||
}
|
||
};
|
||
|
||
ReplyHideLink = {
|
||
init: function() {
|
||
var a;
|
||
|
||
if (!Conf['Reply Hiding']) {
|
||
Main.callbacks.push(this.node);
|
||
}
|
||
a = $.el('a', {
|
||
className: 'reply_hide_link',
|
||
href: 'javascript:;',
|
||
textContent: 'Hide / Restore Post'
|
||
});
|
||
$.on(a, 'click', function() {
|
||
var button, id, menu, root;
|
||
|
||
menu = Menu.el;
|
||
id = menu.dataset.id;
|
||
root = $.id("pc" + id);
|
||
button = root.firstChild;
|
||
ReplyHiding.toggle(button, root, id);
|
||
return Menu.close();
|
||
});
|
||
return Menu.addEntry({
|
||
el: a,
|
||
open: function(post) {
|
||
if (post.isInlined || post.el.classList.contains('op')) {
|
||
return false;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
});
|
||
},
|
||
node: function(post) {
|
||
if (post.isInlined || post.ID === post.threadID) {
|
||
return;
|
||
}
|
||
if (post.ID in g.hiddenReplies) {
|
||
return ReplyHiding.hide(post.root);
|
||
}
|
||
}
|
||
};
|
||
|
||
ReportLink = {
|
||
init: function() {
|
||
var a;
|
||
|
||
a = $.el('a', {
|
||
className: 'report_link',
|
||
href: 'javascript:;',
|
||
textContent: 'Report this post'
|
||
});
|
||
$.on(a, 'click', this.report);
|
||
return Menu.addEntry({
|
||
el: a,
|
||
open: function(post) {
|
||
return post.isArchived === false;
|
||
}
|
||
});
|
||
},
|
||
report: function() {
|
||
var a, id, set, url;
|
||
|
||
a = $('a[title="Highlight this post"]', $.id(this.parentNode.dataset.rootid));
|
||
url = "//sys.4chan.org/" + (a.pathname.split('/')[1]) + "/imgboard.php?mode=report&no=" + this.parentNode.dataset.id;
|
||
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);
|
||
}
|
||
};
|
||
|
||
ThreadHideLink = {
|
||
init: function() {
|
||
var a;
|
||
|
||
if (!Conf['Thread Hiding']) {
|
||
$.ready(this.iterate);
|
||
}
|
||
a = $.el('a', {
|
||
className: 'thread_hide_link',
|
||
href: 'javascript:;',
|
||
textContent: 'Hide / Restore Thread'
|
||
});
|
||
$.on(a, 'click', function() {
|
||
var menu, thread;
|
||
|
||
menu = Menu.el;
|
||
thread = $.id("t" + menu.dataset.id);
|
||
ThreadHiding.toggle(thread);
|
||
return Menu.close();
|
||
});
|
||
return Menu.addEntry({
|
||
el: a,
|
||
open: function(post) {
|
||
if (post.el.classList.contains('op')) {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
});
|
||
},
|
||
iterate: function() {
|
||
var thread, _i, _len, _ref;
|
||
|
||
ThreadHiding.hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
|
||
_ref = $$('.thread');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
thread = _ref[_i];
|
||
if (thread.id.slice(1) in ThreadHiding.hiddenThreads) {
|
||
ThreadHiding.hide(thread);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
Favicon = {
|
||
init: function() {
|
||
var href;
|
||
|
||
if (this.el) {
|
||
return;
|
||
}
|
||
this.el = $('link[rel="shortcut icon"]', d.head);
|
||
this.el.type = 'image/x-icon';
|
||
href = this.el.href;
|
||
this.SFW = /ws.ico$/.test(href);
|
||
this["default"] = href;
|
||
return this["switch"]();
|
||
},
|
||
"switch": function() {
|
||
this.unreadDead = this.unreadSFW = this.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA';
|
||
switch (Conf['favicon']) {
|
||
case 'ferongr':
|
||
this.unreadDead += 'BAAAAAQBAMAAADt3eJSAAAAD1BMVEWrVlbpCwJzBQD/jIzlCgLerRyUAAAAAXRSTlMAQObYZgAAAFhJREFUeF5Fi8ENw0AMw6gNZHcCXbJAkw2C7D9Tz68KJKAP+a8MKtAK9DJ9X9ZxB+WT/rbpt9L1Bq3lEapGgBqY3hvYfTagY6rLKHPa6DzTz2PothJAApsfXPUIxXEAtJ4AAAAASUVORK5CYII=';
|
||
this.unreadNSFW += 'BAAAAAQCAMAAAAoLQ9TAAAAFVBMVEWJq3ho/gooegBJ3ABU/QBW/wHV/8Hz/s/JAAAAAnRSTlMAscr1TiIAAABVSURBVBjTZY5LDgAxCEKNovc/8mgozq9d+CQRMPs/AC+Auz8BXlUfyGzoPZN7xNDoEVR0u2Zy3ziTsEV0oj5eTCn1KaVQGTpCHiH64wzegKZYV8M9Lia0Aj8l3NBcAAAAAElFTkSuQmCC';
|
||
this.unreadSFW += 'BAAAAAQCAMAAAAoLQ9TAAAAFVBMVEUAS1QAnbAAsseF5vMA2fMA1/EAb37J/JegAAAAA3RSTlMAmPz35Xr7AAAAUUlEQVQY02WOCQ4AIQgDSUr5/5Pl9NjVhE6bYBX5H5IP0MxuoAH4gKqDe9XyZFDkPlirt+bjjyae2X2cWR7VgvkPpqWSoA60g7wtMsmWTIRHFpbuAyerdnAvAAAAAElFTkSuQmCC';
|
||
break;
|
||
case 'xat-':
|
||
this.unreadDead += 'BAAAAAQBAMAAADt3eJSAAAAG1BMVEXzZmTzZGLzZGLzZGIAAAD/AAD/lJX4bWz/0tMaHcyBAAAABHRSTlMAm8l+71ABtwAAAFpJREFUeF5ty9EJgDAQA9B8dIGKC1gcoQNUm+ICvRWKAwjdwLklCAXBfD2SO/yE2ftIwFkNoVgCih2XVTWCGrI1EsDUz7svH2gSoo4zxruwry/KNlfBOSAljDwk8xZR3HxWZAAAAABJRU5ErkJggg==';
|
||
this.unreadNSFW += 'BAAAAAQBAMAAADt3eJSAAAAIVBMVEVirGJdqF9dqF9dqF9dqF9082JmzDOq/5oAAACR/33Z/9JztnAYAAAABXRSTlMAyZ2Ses9C/CQAAABjSURBVHhebcsxDkBAFATQKbddGq1otJxij8AFJnsFqiVr8x1AuIFr8iMRhaleZv7HTyS2lRPA0FubGIDEpaPXhutBbUT2QQRA2Y/nln3R6JQDcHoc8b4rpuJBmmuvMAYIAW8utWkfv3LWVYEAAAAASUVORK5CYII=';
|
||
this.unreadSFW += 'BAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAABde6Zde6Zde6Zde6aQz/8umMNquPcAAADQ6/+nHRY3AAAABXRSTlMAyZ16kvc074oAAABfSURBVHhebcuxCYAwFIThv0yrWNgKFo6QVnewcIFHNohlNBDfAu4rDyFYeNXHHcdPNC+jV3ASmqZIgiLXLsEagzWq66oKDHG7Y/vFbFMHeHtl6t1w9C/KOQWDc5ASNQ9glx6N+XFPbgAAAABJRU5ErkJggg==';
|
||
break;
|
||
case 'Mayhem':
|
||
this.unreadDead += 'BAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAAAAAAAAAAAAAAAAAAATExMBAQEAAAD/AAD///+gujywAAAACHRSTlMPJRcbLzEcM37+zgIAAAB9SURBVHheRcu9DoJAEATgcX0B+Wns7uAFRGgoCVhQ0phca8K77JXEI+6+rUujU32ZzOAXanLAFw5e91cdNEfPcVmF3+iEt8BxtOaANV51WdU2VE5FMw0O1B0YDaUOD30aZk6Bd4eT8Mfulz/OIinEeANd5yxLmwPqtqraO75dUSZT40SwmAAAAABJRU5ErkJggg==';
|
||
this.unreadNSFW += 'BAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///9mzDPTSEsdAAAACHRSTlMaDCUeLg4zKeknoAsAAACHSURBVHheJcqxCsIwEMbxLw2CY27oLiSCYwioeyjS0Sp9Ah26d+koUtrkDXJv6xXhhj+/70B9R1T3BBN8V2urUKXV6ykdsOcSXeYPLpnXictLZAuRKqXokvzc3duGW9zBXBsbmlHBuG2KEi3PcgrPzMvA5YzHP44ieW6LiDkNNixfBYIHNOgHHmcn+8KfmKQAAAAASUVORK5CYII=';
|
||
this.unreadSFW += 'BAAAAAQBAMAAADt3eJSAAAAG1BMVEUAAAAAAAABAwMAAAAAAAAAAAAAAAAumMP///+/4sWwAAAAB3RSTlMVJxQdMSkcONaevAAAAIJJREFUeF4lirEKgzAURa9PcBai4PjI0NlA6y61kFXawVHq4h+8rEI0+ewmdLqHcw80SGtOw2Yg3hShiGdfLrHGLm5ug1y4Bzk6cc9kMiRTxDi3MTVVMykzjSv48VLm8yZwk6+RcFvEWzm/KEMG16P4Q51M8NYlw51Vxh8EXQ3AtuofzNIkEO8Bb0kAAAAASUVORK5CYII=';
|
||
break;
|
||
case '4chanJS':
|
||
this.unreadDead += 'BAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC';
|
||
this.unreadNSFW += 'BAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC';
|
||
this.unreadSFW += 'BAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC';
|
||
break;
|
||
case 'Original':
|
||
this.unreadDead += 'BAAAAAQCAMAAAAoLQ9TAAAAD1BMVEWYmJiYmJj///8AAAD/AACKRYF4AAAAAnRSTlMAvLREMp8AAABFSURBVBjTbY7BDgAgCEIZ+P/f3MGgXHkR3wYCvENyCEq6BVVVPzFvg03sTZjT8w4GKWKL+8ih7jPffoEaKB52KJMKnrUA5kwBxesBDg0AAAAASUVORK5CYII=';
|
||
this.unreadNSFW += 'BAAAAAQCAMAAAAoLQ9TAAAADFBMVEWYmJj///9mzDMAAAADduU3AAAAAXRSTlMAQObYZgAAAERJREFUGNNtjkESACAIAkH+/+cOBuWUF3FnQIB3SA5BSbegquon5m2wib0Jc3rewSBFbHEfOdR95tsvUAPFww5lUsGzFpsgATH7KrmBAAAAAElFTkSuQmCC';
|
||
this.unreadSFW += 'BAAAAAQCAMAAAAoLQ9TAAAADFBMVEWYmJj///8umMMAAACriBKaAAAAAXRSTlMAQObYZgAAAERJREFUGNNtjkESACAIAkH+/+cOBuWUF3FnQIB3SA5BSbegquon5m2wib0Jc3rewSBFbHEfOdR95tsvUAPFww5lUsGzFpsgATH7KrmBAAAAAElFTkSuQmCC';
|
||
}
|
||
this.unread = this.SFW ? this.unreadSFW : this.unreadNSFW;
|
||
},
|
||
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
|
||
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
|
||
};
|
||
|
||
IDColor = {
|
||
init: function() {
|
||
QuotePreview.callbacks.push(this.node);
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var str, uid;
|
||
|
||
if (!(uid = $('.postInfo .hand', post.el))) {
|
||
return;
|
||
}
|
||
str = uid.textContent;
|
||
if (uid.nodeName === 'SPAN') {
|
||
uid.style.cssText = IDColor.apply.call(str);
|
||
}
|
||
if (!IDColor.highlight[str]) {
|
||
IDColor.highlight[str] = [];
|
||
}
|
||
if (str === $.get("highlightedID/" + g.BOARD + "/")) {
|
||
IDColor.highlight.current.push(post);
|
||
$.addClass(post.el, 'highlight');
|
||
}
|
||
IDColor.highlight[str].push(post);
|
||
return $.on(uid, 'click', function() {
|
||
return IDColor.idClick(str);
|
||
});
|
||
},
|
||
ids: {},
|
||
compute: function(str) {
|
||
var hash, rgb;
|
||
|
||
hash = this.hash(str);
|
||
rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF];
|
||
rgb[3] = ((rgb[0] * 0.299) + (rgb[1] * 0.587) + (rgb[2] * 0.114)) > 125;
|
||
this.ids[str] = rgb;
|
||
return rgb;
|
||
},
|
||
apply: function() {
|
||
var rgb;
|
||
|
||
rgb = IDColor.ids[this] || IDColor.compute(this);
|
||
return ("background-color: rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "); color: ") + (rgb[3] ? "black;" : "white;");
|
||
},
|
||
hash: function(str) {
|
||
var i, j, msg;
|
||
|
||
msg = 0;
|
||
i = 0;
|
||
j = str.length;
|
||
while (i < j) {
|
||
msg = ((msg << 5) - msg) + str.charCodeAt(i);
|
||
++i;
|
||
}
|
||
return msg;
|
||
},
|
||
highlight: {
|
||
current: []
|
||
},
|
||
idClick: function(str) {
|
||
var last, post, value, _i, _j, _len, _len1, _ref, _ref1;
|
||
|
||
_ref = this.highlight.current;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
post = _ref[_i];
|
||
$.rmClass(post.el, 'highlight');
|
||
}
|
||
last = $.get(value = "highlightedID/" + g.BOARD + "/", false);
|
||
if (str === last) {
|
||
this.highlight.current = [];
|
||
return $["delete"](value);
|
||
}
|
||
_ref1 = this.highlight[str];
|
||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||
post = _ref1[_j];
|
||
if (post.isInlined) {
|
||
continue;
|
||
}
|
||
$.addClass(post.el, 'highlight');
|
||
this.highlight.current.push(post);
|
||
}
|
||
return $.set(value, str);
|
||
}
|
||
};
|
||
|
||
MarkOwn = {
|
||
init: function() {
|
||
Main.callbacks.push(this.node);
|
||
return this.posts = $.get('ownedPosts', {});
|
||
},
|
||
node: function(post) {
|
||
var owned, posts, quote, _i, _len, _ref;
|
||
|
||
posts = MarkOwn.posts;
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (!(quote.hash && posts[quote.hash.slice(2)])) {
|
||
continue;
|
||
}
|
||
owned = true;
|
||
$.addClass(quote, 'ownreply');
|
||
quote.textContent += " (You)";
|
||
}
|
||
if (owned) {
|
||
$.addClass(post.el, 'quotedYou');
|
||
}
|
||
if (posts[post.ID]) {
|
||
$.addClass(post.el, 'yourPost');
|
||
}
|
||
}
|
||
};
|
||
|
||
RemoveSpoilers = {
|
||
init: function() {
|
||
if (Conf['Indicate Spoilers']) {
|
||
this.wrapper = function(text) {
|
||
return "[spoiler]" + text + "[/spoiler]";
|
||
};
|
||
}
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
wrapper: function(text) {
|
||
return text;
|
||
},
|
||
node: function(post) {
|
||
var spoiler, spoilers, _i, _len;
|
||
|
||
spoilers = $$('s', post.el);
|
||
for (_i = 0, _len = spoilers.length; _i < _len; _i++) {
|
||
spoiler = spoilers[_i];
|
||
$.replace(spoiler, $.tn(RemoveSpoilers.wrapper(spoiler.textContent)));
|
||
}
|
||
}
|
||
};
|
||
|
||
ThreadStats = {
|
||
init: function() {
|
||
var container, dialog, move;
|
||
|
||
ThreadStats.postcount = $.el('span', {
|
||
id: 'postcount',
|
||
textContent: '0'
|
||
});
|
||
ThreadStats.imagecount = $.el('span', {
|
||
id: 'imagecount',
|
||
textContent: '0'
|
||
});
|
||
if (Conf['Thread Updater'] && Conf['Merged Updater and Stats'] && (move = Updater.count.parentElement)) {
|
||
container = $.el('span');
|
||
$.add(container, [$.tn('['), ThreadStats.postcount, $.tn(' / '), ThreadStats.imagecount, $.tn('] ')]);
|
||
$.prepend(move, container);
|
||
} else {
|
||
dialog = UI.dialog('stats', 'bottom: 0; left: 0;', '<div class=move></div>');
|
||
dialog.className = 'dialog';
|
||
$.add($(".move", dialog), ThreadStats.postcount);
|
||
$.add($(".move", dialog), $.tn(" / "));
|
||
$.add($(".move", dialog), ThreadStats.imagecount);
|
||
$.add(d.body, dialog);
|
||
}
|
||
this.posts = this.images = 0;
|
||
this.imgLimit = (function() {
|
||
switch (g.BOARD) {
|
||
case 'a':
|
||
case 'b':
|
||
case 'v':
|
||
case 'co':
|
||
case 'mlp':
|
||
return 251;
|
||
case 'vg':
|
||
return 376;
|
||
default:
|
||
return 151;
|
||
}
|
||
})();
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
if (post.isInlined) {
|
||
return;
|
||
}
|
||
ThreadStats.postcount.textContent = ++ThreadStats.posts;
|
||
if (!post.img) {
|
||
return;
|
||
}
|
||
ThreadStats.imagecount.textContent = ++ThreadStats.images;
|
||
if (ThreadStats.images > ThreadStats.imgLimit) {
|
||
return $.addClass(ThreadStats.imagecount, 'warning');
|
||
}
|
||
}
|
||
};
|
||
|
||
TitlePost = {
|
||
init: function() {
|
||
return d.title = Get.title();
|
||
}
|
||
};
|
||
|
||
Unread = {
|
||
init: function() {
|
||
this.title = d.title;
|
||
$.on(d, 'QRPostSuccessful', this.post);
|
||
this.update();
|
||
$.on(window, 'scroll', Unread.scroll);
|
||
$.on(window, 'focus', Unread.focus);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
replies: [],
|
||
foresee: [],
|
||
post: function(e) {
|
||
return Unread.foresee.push(e.detail.postID);
|
||
},
|
||
node: function(post) {
|
||
var count, el, index, root;
|
||
|
||
if ((index = Unread.foresee.indexOf(post.ID)) !== -1) {
|
||
Unread.foresee.splice(index, 1);
|
||
return;
|
||
}
|
||
el = post.el, root = post.root;
|
||
if (el.hidden || /\bop\b/.test(post["class"]) || post.isInlined) {
|
||
return;
|
||
}
|
||
count = Unread.replies.push(el);
|
||
return Unread.update(count === 1);
|
||
},
|
||
focus: function() {
|
||
if (Unread.replies !== 0) {
|
||
return Unread.count();
|
||
}
|
||
},
|
||
scroll: function() {
|
||
if (!(d.hidden || Unread.replies === 0)) {
|
||
return Unread.count();
|
||
}
|
||
},
|
||
count: function() {
|
||
var bottom, height, i, reply, _i, _len, _ref;
|
||
|
||
height = d.documentElement.clientHeight;
|
||
_ref = Unread.replies;
|
||
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
|
||
reply = _ref[i];
|
||
bottom = reply.getBoundingClientRect().bottom;
|
||
if (bottom > height) {
|
||
break;
|
||
}
|
||
}
|
||
if (i === 0) {
|
||
return;
|
||
}
|
||
Unread.replies = Unread.replies.slice(i);
|
||
return Unread.update(Unread.replies.length === 0);
|
||
},
|
||
setTitle: function(count) {
|
||
if (this.scheduled) {
|
||
clearTimeout(this.scheduled);
|
||
delete Unread.scheduled;
|
||
this.setTitle(count);
|
||
return;
|
||
}
|
||
return this.scheduled = setTimeout((function() {
|
||
return d.title = "(" + count + ") " + Unread.title;
|
||
}), 5);
|
||
},
|
||
update: function(updateFavicon) {
|
||
var count;
|
||
|
||
if (!g.REPLY) {
|
||
return;
|
||
}
|
||
count = this.replies.length;
|
||
if (Conf['Unread Count']) {
|
||
this.setTitle(count);
|
||
}
|
||
if (!(Conf['Unread Favicon'] && updateFavicon)) {
|
||
return;
|
||
}
|
||
if ($.engine === 'presto') {
|
||
$.rm(Favicon.el);
|
||
}
|
||
Favicon.el.href = g.dead ? count ? Favicon.unreadDead : Favicon.dead : count ? Favicon.unread : Favicon["default"];
|
||
if (g.dead) {
|
||
$.addClass(Favicon.el, 'dead');
|
||
} else {
|
||
$.rmClass(Favicon.el, 'dead');
|
||
}
|
||
if (count) {
|
||
$.addClass(Favicon.el, 'unread');
|
||
} else {
|
||
$.rmClass(Favicon.el, 'unread');
|
||
}
|
||
if ($.engine !== 'webkit') {
|
||
return $.add(d.head, Favicon.el);
|
||
}
|
||
}
|
||
};
|
||
|
||
Updater = {
|
||
init: function() {
|
||
var checkbox, checked, dialog, html, input, name, title, _i, _len, _ref;
|
||
|
||
html = '<div class=move><span id=count></span> <span id=timer></span></div>';
|
||
checkbox = Config.updater.checkbox;
|
||
for (name in checkbox) {
|
||
title = checkbox[name][1];
|
||
checked = Conf[name] ? 'checked' : '';
|
||
html += "<div><label title='" + title + "'>" + name + "<input name='" + name + "' type=checkbox " + checked + "></label></div>";
|
||
}
|
||
checked = Conf['Auto Update'] ? 'checked' : '';
|
||
html += " <div><label title='Controls whether *this* thread automatically updates or not'>Auto Update This<input name='Auto Update This' type=checkbox " + checked + "></label></div> <div><label>Interval (s)<input type=number name=Interval" + (Conf['Interval per board'] ? "_" + g.BOARD : '') + " class=field min=1></label></div> <div><label>BGInterval<input type=number name=BGInterval" + (Conf['Interval per board'] ? "_" + g.BOARD : '') + " class=field min=1></label></div> <div><input value='Update Now' type=button name='Update Now'></div>";
|
||
dialog = UI.dialog('updater', 'bottom: 0; right: 0;', html);
|
||
this.count = $('#count', dialog);
|
||
this.timer = $('#timer', dialog);
|
||
this.thread = $.id("t" + g.THREAD_ID);
|
||
this.save = [];
|
||
this.checkPostCount = 0;
|
||
this.unsuccessfulFetchCount = 0;
|
||
this.lastModified = '0';
|
||
_ref = $$('input', dialog);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
input = _ref[_i];
|
||
if (input.type === 'checkbox') {
|
||
$.on(input, 'click', $.cb.checked);
|
||
}
|
||
switch (input.name) {
|
||
case 'Scroll BG':
|
||
$.on(input, 'click', this.cb.scrollBG);
|
||
this.cb.scrollBG.call(input);
|
||
break;
|
||
case 'Verbose':
|
||
$.on(input, 'click', this.cb.verbose);
|
||
this.cb.verbose.call(input);
|
||
break;
|
||
case 'Auto Update This':
|
||
$.on(input, 'click', this.cb.autoUpdate);
|
||
this.cb.autoUpdate.call(input);
|
||
break;
|
||
case 'Interval':
|
||
case 'BGInterval':
|
||
case "Interval_" + g.BOARD:
|
||
case "BGInterval_" + g.BOARD:
|
||
input.value = Conf[input.name];
|
||
$.on(input, 'change', this.cb.interval);
|
||
this.cb.interval.call(input);
|
||
break;
|
||
case 'Update Now':
|
||
$.on(input, 'click', this.update);
|
||
}
|
||
}
|
||
$.add(d.body, dialog);
|
||
$.on(d, 'QRPostSuccessful', this.cb.post);
|
||
return $.on(d, 'visibilitychange', this.cb.visibility);
|
||
},
|
||
/*
|
||
beep1.wav
|
||
http://freesound.org/people/pierrecartoons1979/sounds/90112
|
||
|
||
This work is licensed under the Attribution Noncommercial License.
|
||
http://creativecommons.org/licenses/by-nc/3.0/
|
||
*/
|
||
|
||
audio: $.el('audio', {
|
||
src: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA'
|
||
}),
|
||
cb: {
|
||
post: function() {
|
||
if (!Conf['Auto Update This']) {
|
||
return;
|
||
}
|
||
Updater.unsuccessfulFetchCount = 0;
|
||
return setTimeout(Updater.update, 500);
|
||
},
|
||
checkpost: function(status) {
|
||
if (!(status === 404 || Updater.foundPost || Updater.checkPostCount >= 10)) {
|
||
return setTimeout(Updater.update, ++Updater.checkPostCount * 500);
|
||
}
|
||
Updater.checkPostCount = 0;
|
||
delete Updater.foundPost;
|
||
return delete Updater.postID;
|
||
},
|
||
visibility: function() {
|
||
if (d.hidden) {
|
||
return;
|
||
}
|
||
Updater.unsuccessfulFetchCount = 0;
|
||
if (Updater.timer.textContent < (Conf['Interval per board'] ? -Conf['Interval_' + g.BOARD] : -Conf['Interval'])) {
|
||
return Updater.set('timer', -Updater.getInterval());
|
||
}
|
||
},
|
||
interval: function() {
|
||
var val;
|
||
|
||
val = parseInt(this.value, 10);
|
||
this.value = val > 0 ? val : 30;
|
||
$.cb.value.call(this);
|
||
return Updater.set('timer', -Updater.getInterval());
|
||
},
|
||
verbose: function() {
|
||
if (Conf['Verbose']) {
|
||
Updater.set('count', '+0');
|
||
return Updater.timer.hidden = false;
|
||
} else {
|
||
Updater.set('count', '+0');
|
||
Updater.count.className = '';
|
||
return Updater.timer.hidden = true;
|
||
}
|
||
},
|
||
autoUpdate: function() {
|
||
if (Conf['Auto Update This'] = this.checked) {
|
||
return Updater.timeoutID = setTimeout(Updater.timeout, 1000);
|
||
} else {
|
||
return clearTimeout(Updater.timeoutID);
|
||
}
|
||
},
|
||
scrollBG: function() {
|
||
return Updater.scrollBG = this.checked ? function() {
|
||
return true;
|
||
} : function() {
|
||
return !d.hidden;
|
||
};
|
||
},
|
||
load: function() {
|
||
switch (this.status) {
|
||
case 404:
|
||
Updater.set('timer', '');
|
||
Updater.set('count', 404);
|
||
Updater.count.className = 'warning';
|
||
clearTimeout(Updater.timeoutID);
|
||
g.dead = true;
|
||
if (Conf['Unread Count']) {
|
||
Unread.title = Unread.title.match(/^.+-/)[0] + ' 404';
|
||
} else {
|
||
d.title = d.title.match(/^.+-/)[0] + ' 404';
|
||
}
|
||
Unread.update(true);
|
||
QR.abort();
|
||
break;
|
||
case 0:
|
||
case 304:
|
||
/*
|
||
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.
|
||
*/
|
||
|
||
Updater.unsuccessfulFetchCount++;
|
||
Updater.set('timer', -Updater.getInterval());
|
||
if (Conf['Verbose']) {
|
||
Updater.set('count', '+0');
|
||
Updater.count.className = null;
|
||
}
|
||
break;
|
||
case 200:
|
||
Updater.lastModified = this.getResponseHeader('Last-Modified');
|
||
Updater.cb.update(JSON.parse(this.response).posts);
|
||
Updater.set('timer', -Updater.getInterval());
|
||
break;
|
||
default:
|
||
Updater.unsuccessfulFetchCount++;
|
||
Updater.set('timer', -Updater.getInterval());
|
||
if (Conf['Verbose']) {
|
||
Updater.set('count', this.statusText);
|
||
Updater.count.className = 'warning';
|
||
}
|
||
}
|
||
if (Updater.postID) {
|
||
Updater.cb.checkpost(this.status);
|
||
}
|
||
return delete Updater.request;
|
||
},
|
||
update: function(posts) {
|
||
var count, frag, id, lastPost, node, nodes, post, scroll, spoilerRange, _i, _len, _ref;
|
||
|
||
if (spoilerRange = posts[0].custom_spoiler) {
|
||
Build.spoilerRange[g.BOARD] = spoilerRange;
|
||
}
|
||
lastPost = Updater.thread.lastElementChild;
|
||
id = +lastPost.id.slice(2);
|
||
nodes = (function() {
|
||
var _i, _len, _ref, _results;
|
||
|
||
_ref = posts.reverse();
|
||
_results = [];
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
post = _ref[_i];
|
||
if (post.no <= id) {
|
||
break;
|
||
}
|
||
if (Updater.postID) {
|
||
if (("" + post.no) === Updater.postID) {
|
||
Updater.foundPost = true;
|
||
}
|
||
}
|
||
_results.push(Build.postFromObject(post, g.BOARD));
|
||
}
|
||
return _results;
|
||
})();
|
||
count = nodes.length;
|
||
if (Conf['Verbose']) {
|
||
Updater.set('count', "+" + count);
|
||
Updater.count.className = count ? 'new' : null;
|
||
}
|
||
if (count) {
|
||
if (Conf['Beep'] && d.hidden && (Unread.replies.length === 0)) {
|
||
Updater.audio.play();
|
||
}
|
||
Updater.unsuccessfulFetchCount = 0;
|
||
} else {
|
||
Updater.unsuccessfulFetchCount++;
|
||
return;
|
||
}
|
||
scroll = Conf['Scrolling'] && Updater.scrollBG() && lastPost.getBoundingClientRect().bottom - d.documentElement.clientHeight < 25;
|
||
_ref = nodes.reverse();
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
node = _ref[_i];
|
||
frag = $.frag();
|
||
$.add(frag, node);
|
||
Main.node(Main.preParse(node));
|
||
$.add(Updater.thread, frag);
|
||
}
|
||
if (scroll && (nodes != null)) {
|
||
return nodes[0].scrollIntoView();
|
||
}
|
||
}
|
||
},
|
||
set: function(name, text) {
|
||
var el, node;
|
||
|
||
el = Updater[name];
|
||
if (node = el.firstChild) {
|
||
return node.data = text;
|
||
} else {
|
||
return el.textContent = text;
|
||
}
|
||
},
|
||
getInput: function(input) {
|
||
var i, number, _i, _len, _results;
|
||
|
||
while ((i = input.length) < 10) {
|
||
input[i] = input[i - 1];
|
||
}
|
||
_results = [];
|
||
for (_i = 0, _len = input.length; _i < _len; _i++) {
|
||
number = input[_i];
|
||
_results.push(parseInt(number, 10));
|
||
}
|
||
return _results;
|
||
},
|
||
getInterval: function() {
|
||
var count, i, increase, increaseString, j, string;
|
||
|
||
string = "Interval" + (Conf['Interval per board'] ? "_" + g.BOARD : "");
|
||
increaseString = "updateIncrease";
|
||
if (d.hidden) {
|
||
string = "BG" + string;
|
||
increaseString += "B";
|
||
}
|
||
i = +Conf[string];
|
||
j = (count = this.unsuccessfulFetchCount) > 9 ? 9 : count;
|
||
return (Conf['Optional Increase'] ? (i > (increase = Updater.getInput(Conf[increaseString].split(','))[j]) ? i : increase) : i);
|
||
},
|
||
timeout: function() {
|
||
var n;
|
||
|
||
Updater.timeoutID = setTimeout(Updater.timeout, 1000);
|
||
n = 1 + parseInt(Updater.timer.firstChild.data, 10);
|
||
if (n === 0) {
|
||
return Updater.update();
|
||
} else if (n >= Updater.getInterval()) {
|
||
Updater.unsuccessfulFetchCount++;
|
||
Updater.set('count', 'Retry');
|
||
Updater.count.className = null;
|
||
return Updater.update();
|
||
} else {
|
||
return Updater.set('timer', n);
|
||
}
|
||
},
|
||
update: function() {
|
||
var request, url;
|
||
|
||
Updater.set('timer', 0);
|
||
request = Updater.request;
|
||
if (request) {
|
||
request.onloadend = null;
|
||
request.abort();
|
||
}
|
||
url = "//api.4chan.org/" + g.BOARD + "/res/" + g.THREAD_ID + ".json";
|
||
return Updater.request = $.ajax(url, {
|
||
onloadend: Updater.cb.load
|
||
}, {
|
||
headers: {
|
||
'If-Modified-Since': Updater.lastModified
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
Watcher = {
|
||
init: function() {
|
||
var favicon, html, input, _i, _len, _ref;
|
||
|
||
html = '<div class=move>Thread Watcher</div>';
|
||
this.dialog = UI.dialog('watcher', 'top: 50px; left: 0px;', html);
|
||
$.add(d.body, this.dialog);
|
||
_ref = $$('.op input');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
input = _ref[_i];
|
||
favicon = $.el('img', {
|
||
className: 'favicon'
|
||
});
|
||
$.on(favicon, 'click', this.cb.toggle);
|
||
$.before(input, favicon);
|
||
}
|
||
if (g.THREAD_ID === $.get('autoWatch', 0)) {
|
||
this.watch(g.THREAD_ID);
|
||
$["delete"]('autoWatch');
|
||
} else {
|
||
this.refresh();
|
||
}
|
||
$.on(d, 'QRPostSuccessful', this.cb.post);
|
||
return $.sync('watched', this.refresh);
|
||
},
|
||
refresh: function(watched) {
|
||
var board, div, favicon, id, link, nodes, props, watchedBoard, x, _i, _j, _len, _len1, _ref, _ref1, _ref2;
|
||
|
||
watched || (watched = $.get('watched', {}));
|
||
nodes = [];
|
||
for (board in watched) {
|
||
_ref = watched[board];
|
||
for (id in _ref) {
|
||
props = _ref[id];
|
||
x = $.el('a', {
|
||
textContent: '×',
|
||
href: 'javascript:;'
|
||
});
|
||
$.on(x, 'click', Watcher.cb.x);
|
||
link = $.el('a', props);
|
||
link.title = link.textContent;
|
||
div = $.el('div');
|
||
$.add(div, [x, $.tn(' '), link]);
|
||
nodes.push(div);
|
||
}
|
||
}
|
||
_ref1 = $$('div:not(.move)', Watcher.dialog);
|
||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
||
div = _ref1[_i];
|
||
$.rm(div);
|
||
}
|
||
$.add(Watcher.dialog, nodes);
|
||
watchedBoard = watched[g.BOARD] || {};
|
||
_ref2 = $$('.favicon');
|
||
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
|
||
favicon = _ref2[_j];
|
||
id = favicon.nextSibling.name;
|
||
if (id in watchedBoard) {
|
||
favicon.src = Favicon["default"];
|
||
} else {
|
||
favicon.src = Favicon.empty;
|
||
}
|
||
}
|
||
},
|
||
cb: {
|
||
toggle: function() {
|
||
return Watcher.toggle(this.parentNode);
|
||
},
|
||
x: function() {
|
||
var thread;
|
||
|
||
thread = this.nextElementSibling.pathname.split('/');
|
||
return Watcher.unwatch(thread[3], thread[1]);
|
||
},
|
||
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 Watcher.watch(threadID);
|
||
}
|
||
}
|
||
},
|
||
toggle: function(thread) {
|
||
var id;
|
||
|
||
id = $('.favicon + input', thread).name;
|
||
return Watcher.watch(id) || Watcher.unwatch(id, g.BOARD);
|
||
},
|
||
unwatch: function(id, board) {
|
||
var watched;
|
||
|
||
watched = $.get('watched', {});
|
||
delete watched[board][id];
|
||
$.set('watched', watched);
|
||
return Watcher.refresh();
|
||
},
|
||
watch: function(id) {
|
||
var thread, watched, _name;
|
||
|
||
thread = $.id("t" + id);
|
||
if ($('.favicon', thread).src === Favicon["default"]) {
|
||
return false;
|
||
}
|
||
watched = $.get('watched', {});
|
||
watched[_name = g.BOARD] || (watched[_name] = {});
|
||
watched[g.BOARD][id] = {
|
||
href: "/" + g.BOARD + "/res/" + id,
|
||
textContent: Get.title(thread)
|
||
};
|
||
$.set('watched', watched);
|
||
Watcher.refresh();
|
||
return true;
|
||
}
|
||
};
|
||
|
||
Markdown = {
|
||
format: function(text) {
|
||
var pattern, tag, tag_patterns;
|
||
|
||
tag_patterns = {
|
||
bi: /(\*\*\*|___)(?=\S)([^\r\n]*?\S)\1/g,
|
||
b: /(\*\*|__)(?=\S)([^\r\n]*?\S)\1/g,
|
||
i: /(\*|_)(?=\S)([^\r\n]*?\S)\1/g,
|
||
code: /(`)(?=\S)([^\r\n]*?\S)\1/g,
|
||
ds: /(\|\||__)(?=\S)([^\r\n]*?\S)\1/g
|
||
};
|
||
for (tag in tag_patterns) {
|
||
pattern = tag_patterns[tag];
|
||
text = text ? text.replace(pattern, Markdown.unicode_convert) : '\u0020';
|
||
}
|
||
return text;
|
||
},
|
||
unicode_convert: function(str, tag, inner) {
|
||
var c, charcode, charcodes, codepoints, codes, fmt, i, unicode_text;
|
||
|
||
fmt = (function() {
|
||
switch (tag) {
|
||
case '_':
|
||
case '*':
|
||
return 'i';
|
||
case '__':
|
||
case '**':
|
||
return 'b';
|
||
case '___':
|
||
case '***':
|
||
return 'bi';
|
||
case '||':
|
||
return 'ds';
|
||
case '`':
|
||
case '```':
|
||
return 'code';
|
||
}
|
||
})();
|
||
codepoints = {
|
||
b: [0x1D7CE, 0x1D400, 0x1D41A],
|
||
i: [0x1D7F6, 0x1D434, 0x1D44E],
|
||
bi: [0x1D7CE, 0x1D468, 0x1D482],
|
||
code: [0x1D7F6, 0x1D670, 0x1D68A],
|
||
ds: [0x1D7D8, 0x1D538, 0x1D552]
|
||
};
|
||
charcodes = (function() {
|
||
var _i, _len, _results;
|
||
|
||
_results = [];
|
||
for (i = _i = 0, _len = inner.length; _i < _len; i = ++_i) {
|
||
c = inner[i];
|
||
_results.push(inner.charCodeAt(i));
|
||
}
|
||
return _results;
|
||
})();
|
||
codes = (function() {
|
||
var _i, _len, _results;
|
||
|
||
_results = [];
|
||
for (_i = 0, _len = charcodes.length; _i < _len; _i++) {
|
||
charcode = charcodes[_i];
|
||
if (charcode >= 48 && charcode <= 57) {
|
||
_results.push(charcode - 48 + codepoints[fmt][0]);
|
||
} else if (charcode >= 65 && charcode <= 90) {
|
||
_results.push(charcode - 65 + codepoints[fmt][1]);
|
||
} else if (charcode >= 97 && charcode <= 122) {
|
||
if (charcode === 104 && tag === 'i') {
|
||
_results.push(0x210E);
|
||
} else {
|
||
_results.push(charcode - 97 + codepoints[fmt][2]);
|
||
}
|
||
} else {
|
||
_results.push(charcode);
|
||
}
|
||
}
|
||
return _results;
|
||
})();
|
||
unicode_text = codes.map(Markdown.ucs2_encode).join('');
|
||
if (tag === 'code') {
|
||
unicode_text = unicode_text.replace(/\x20/g, '\xA0');
|
||
}
|
||
return unicode_text;
|
||
},
|
||
ucs2_encode: function(value) {
|
||
/*
|
||
From Punycode.js: https://github.com/bestiejs/punycode.js
|
||
|
||
Copyright Mathias Bynens <http://mathiasbynens.be/>
|
||
|
||
Permission is hereby granted, free of charge, to any person obtaining
|
||
a copy of this software and associated documentation files (the
|
||
"Software"), to deal in the Software without restriction, including
|
||
without limitation the rights to use, copy, modify, merge, publish,
|
||
distribute, sublicense, and/or sell copies of the Software, and to
|
||
permit persons to whom the Software is furnished to do so, subject to
|
||
the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be
|
||
included in all copies or substantial portions of the Software.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF`
|
||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
*/
|
||
|
||
var output;
|
||
|
||
output = '';
|
||
if (value > 0xFFFF) {
|
||
value -= 0x10000;
|
||
output += String.fromCharCode(value >>> 10 & 0x3FF | 0xD800);
|
||
value = 0xDC00 | value & 0x3FF;
|
||
}
|
||
return output += String.fromCharCode(value);
|
||
}
|
||
};
|
||
|
||
QR = {
|
||
init: function() {
|
||
if (!$.id('postForm')) {
|
||
return;
|
||
}
|
||
QuoteInline.callbacks.push(this.node);
|
||
Main.callbacks.push(this.node);
|
||
return setTimeout(this.asyncInit);
|
||
},
|
||
asyncInit: function() {
|
||
var link, title;
|
||
|
||
if (Conf['Hide Original Post Form']) {
|
||
link = $.el('h1', {
|
||
innerHTML: "<a href=javascript:;>" + (title = g.REPLY ? 'Reply to Thread' : 'Start a Thread') + "</a>",
|
||
title: title
|
||
});
|
||
$.on(link.firstChild, 'click', function() {
|
||
QR.open();
|
||
if (!g.REPLY) {
|
||
QR.threadSelector.value = g.BOARD === 'f' ? '9999' : 'new';
|
||
}
|
||
return $('textarea', QR.el).focus();
|
||
});
|
||
$.before($.id('postForm'), link);
|
||
if (Conf['Check for Bans']) {
|
||
BanChecker.init();
|
||
}
|
||
}
|
||
if (Conf['Persistent QR']) {
|
||
QR.dialog();
|
||
if (Conf['Auto Hide QR']) {
|
||
QR.hide();
|
||
}
|
||
}
|
||
$.on(d, 'dragover', QR.dragOver);
|
||
$.on(d, 'drop', QR.dropFile);
|
||
return $.on(d, 'dragstart dragend', QR.drag);
|
||
},
|
||
node: function(post) {
|
||
return $.on($('a[title="Quote this post"]', $('.postInfo', post.el)), 'click', QR.quote);
|
||
},
|
||
open: function() {
|
||
if (QR.el) {
|
||
QR.el.hidden = false;
|
||
return QR.unhide();
|
||
} else {
|
||
return QR.dialog();
|
||
}
|
||
},
|
||
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.cleanError();
|
||
},
|
||
hide: function() {
|
||
d.activeElement.blur();
|
||
$.addClass(QR.el, 'autohide');
|
||
return QR.autohide.checked = true;
|
||
},
|
||
unhide: function() {
|
||
$.rmClass(QR.el, 'autohide');
|
||
return QR.autohide.checked = false;
|
||
},
|
||
toggleHide: function() {
|
||
return this.checked && QR.hide() || QR.unhide();
|
||
},
|
||
error: function(err) {
|
||
if (typeof err === 'string') {
|
||
QR.warning.textContent = err;
|
||
} else {
|
||
QR.warning.innerHTML = null;
|
||
$.add(QR.warning, err);
|
||
}
|
||
QR.open();
|
||
if (QR.captcha.isEnabled && /captcha|verification/i.test(QR.warning.textContent)) {
|
||
$('[autocomplete]', QR.el).focus();
|
||
}
|
||
if (d.hidden) {
|
||
return alert(QR.warning.textContent);
|
||
}
|
||
},
|
||
cleanError: function() {
|
||
return QR.warning.textContent = null;
|
||
},
|
||
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 && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit';
|
||
return input.disabled = disabled || false;
|
||
},
|
||
cooldown: {
|
||
init: function() {
|
||
if (!Conf['Cooldown']) {
|
||
return;
|
||
}
|
||
QR.cooldown.types = {
|
||
thread: (function() {
|
||
switch (g.BOARD) {
|
||
case 'q':
|
||
return 86400;
|
||
case 'b':
|
||
case 'soc':
|
||
case 'r9k':
|
||
return 600;
|
||
default:
|
||
return 300;
|
||
}
|
||
})(),
|
||
sage: 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;
|
||
|
||
if (!Conf['Cooldown']) {
|
||
return;
|
||
}
|
||
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;
|
||
}
|
||
if ((isReply = g.REPLY ? true : QR.threadSelector.value !== 'new')) {
|
||
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, id, range, s, sel, ta, text, _ref;
|
||
|
||
if (e != null) {
|
||
e.preventDefault();
|
||
}
|
||
QR.open();
|
||
if (!g.REPLY) {
|
||
QR.threadSelector.value = $.x('ancestor::div[parent::div[@class="board"]]', this).id.slice(1);
|
||
}
|
||
id = this.previousSibling.hash.slice(2);
|
||
text = ">>" + id + "\n";
|
||
sel = d.getSelection();
|
||
if ((s = sel.toString().trim()) && id === ((_ref = $.x('ancestor-or-self::blockquote', sel.anchorNode)) != null ? _ref.id.match(/\d+$/)[0] : void 0)) {
|
||
s = s.replace(/\n/g, '\n>');
|
||
text += ">" + s + "\n";
|
||
}
|
||
ta = $('textarea', QR.el);
|
||
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 $.event(ta, 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.cleanError();
|
||
if (this.files.length === 1) {
|
||
file = this.files[0];
|
||
if (file.size > this.max) {
|
||
QR.error('File too large.');
|
||
QR.resetFileInput();
|
||
} else if (!QR.mimeTypes.contains(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.");
|
||
break;
|
||
} else if (!QR.mimeTypes.contains(file.type)) {
|
||
QR.error("" + file.name + ": Unsupported file type.");
|
||
break;
|
||
}
|
||
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 QR.fileEl.value = null;
|
||
},
|
||
replies: [],
|
||
reply: (function() {
|
||
function _Class() {
|
||
var key, persona, prev,
|
||
_this = this;
|
||
|
||
prev = QR.replies[QR.replies.length - 1];
|
||
persona = $.get('persona', {
|
||
global: {}
|
||
});
|
||
if (!persona[key = Conf['Per Board Persona'] ? g.BOARD : 'global']) {
|
||
persona[key] = JSON.parse(JSON.stringify(persona.global));
|
||
}
|
||
this.name = prev ? prev.name : persona[key].name || null;
|
||
this.email = prev && (Conf["Remember Sage"] || !/^sage$/.test(prev.email)) ? prev.email : persona[key].email || null;
|
||
this.sub = prev && Conf['Remember Subject'] ? prev.sub : Conf['Remember Subject'] ? persona[key].sub : null;
|
||
this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
|
||
this.com = null;
|
||
this.el = $.el('a', {
|
||
className: 'thumbnail',
|
||
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, url,
|
||
_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 (!(url = window.URL || window.webkitURL)) {
|
||
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', {
|
||
height: img.height,
|
||
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 typeof url.revokeObjectURL === "function" ? url.revokeObjectURL(fileUrl) : void 0;
|
||
});
|
||
return img.src = fileUrl;
|
||
};
|
||
|
||
_Class.prototype.rmFile = function() {
|
||
var _base1;
|
||
|
||
QR.resetFileInput();
|
||
delete this.file;
|
||
this.el.title = null;
|
||
this.el.style.backgroundImage = null;
|
||
if (QR.spoiler) {
|
||
$('label', this.el).hidden = true;
|
||
}
|
||
return typeof (_base1 = window.URL || window.webkitURL).revokeObjectURL === "function" ? _base1.revokeObjectURL(this.url) : void 0;
|
||
};
|
||
|
||
_Class.prototype.select = function() {
|
||
var check, data, field, rectEl, rectList, _i, _len, _ref, _ref1;
|
||
|
||
if (QR.selected === this) {
|
||
return;
|
||
}
|
||
if ((_ref = QR.selected) != null) {
|
||
_ref.el.removeAttribute('id');
|
||
}
|
||
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];
|
||
field = $("[name=" + data + "]", QR.el);
|
||
field.value = this[data];
|
||
if (Conf['Tripcode Hider']) {
|
||
if (data === 'name') {
|
||
check = /^.*##?.+/.test(this[data]);
|
||
if (check) {
|
||
$.addClass(field, 'tripped');
|
||
}
|
||
$.on(field, 'blur', function() {
|
||
check = /^.*##?.+/.test(this.value);
|
||
if (check && !this.className.match("\\btripped\\b")) {
|
||
return $.addClass(this, 'tripped');
|
||
} else if (!check && this.className.match("\\btripped\\b")) {
|
||
return $.rmClass(this, 'tripped');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
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 __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, _base1;
|
||
|
||
QR.resetFileInput();
|
||
$.rm(this.el);
|
||
index = QR.replies.indexOf(this);
|
||
if (QR.replies.length === 1) {
|
||
new QR.reply().select();
|
||
$.rmClass(QR.el, 'dump');
|
||
} else if (this.el.id === 'selected') {
|
||
(QR.replies[index - 1] || QR.replies[index + 1]).select();
|
||
}
|
||
QR.replies.splice(index, 1);
|
||
return typeof (_base1 = window.URL || window.webkitURL).revokeObjectURL === "function" ? _base1.revokeObjectURL(this.url) : void 0;
|
||
};
|
||
|
||
return _Class;
|
||
|
||
})(),
|
||
captcha: {
|
||
init: function() {
|
||
var observer, onMutationObserver,
|
||
_this = this;
|
||
|
||
if (d.cookie.contains('pass_enabled=') || !(this.isEnabled = !!$.id('captchaFormPart'))) {
|
||
return;
|
||
}
|
||
if ($.id('recaptcha_challenge_field_holder')) {
|
||
return this.ready();
|
||
} else {
|
||
if (MutationObserver) {
|
||
observer = new MutationObserver(onMutationObserver = function() {
|
||
if ($.id('recaptcha_challenge_field_holder')) {
|
||
_this.ready();
|
||
return observer.disconnect();
|
||
}
|
||
});
|
||
return observer.observe($.id('recaptcha_widget_div'), {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
} else {
|
||
return $.on($.id('recaptcha_widget_div'), 'DOMNodeInserted', this.ready);
|
||
}
|
||
}
|
||
},
|
||
ready: function() {
|
||
var observer,
|
||
_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.input, 'focus', function() {
|
||
return QR.el.classList.add('focus');
|
||
});
|
||
$.on(this.input, 'blur', function() {
|
||
return QR.el.classList.remove('focus');
|
||
});
|
||
if (MutationObserver) {
|
||
observer = new MutationObserver(function() {
|
||
return _this.load();
|
||
});
|
||
observer.observe(this.challenge, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
} else {
|
||
$.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() + 4 * $.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) {
|
||
$.globalEval('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 i, id, mimeTypes, name, size, spoiler, ta, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
|
||
|
||
QR.el = UI.dialog('qr', 'bottom: 0; right: 0;', '\
|
||
<div id=qrtab class=move>\
|
||
<label><input type=checkbox id=autohide title=Auto-hide> Quick Reply</label>\
|
||
<span> <a class=close title=Close>×</a> </span>\
|
||
<div id=threadselect></div>\
|
||
</div>\
|
||
<form>\
|
||
<div class=userInfo><input id=dump type=button title="Dump list" value=+ class=field><input name=name title=Name placeholder=Name class=field><input name=email title=E-mail placeholder=E-mail class=field><input name=sub title=Subject placeholder=Subject class=field></div>\
|
||
<div id=replies><div><span id=addReply href=javascript:; title="Add a reply">+</a></div></div>\
|
||
<div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span><div style=clear:both></div></div>\
|
||
<div class=inputs><input type=file multiple size=16><input type=submit></div>\
|
||
<label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image?</label>\
|
||
<div class=warning></div>\
|
||
</form>');
|
||
if (Conf['Remember QR size']) {
|
||
$.on(ta = $('textarea', QR.el), 'mouseup', function() {
|
||
return $.set('QR.size', this.style.cssText);
|
||
});
|
||
ta.style.cssText = $.get('QR.size', '');
|
||
}
|
||
QR.autohide = $('#autohide', QR.el);
|
||
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('');
|
||
QR.fileEl = $('input[type=file]', QR.el);
|
||
QR.fileEl.max = $('input[name=MAX_FILE_SIZE]').value;
|
||
if ($.engine !== 'presto') {
|
||
QR.fileEl.accept = mimeTypes;
|
||
}
|
||
QR.warning = $('.warning', QR.el);
|
||
QR.spoiler = !!$('input[name=spoiler]');
|
||
spoiler = $('#spoilerLabel', QR.el);
|
||
spoiler.hidden = !QR.spoiler;
|
||
QR.charaCounter = $('#charCount', QR.el);
|
||
ta = $('textarea', QR.el);
|
||
if (!g.REPLY) {
|
||
threads = '<option value=new>New thread</option>';
|
||
_ref = $$('.thread');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
thread = _ref[_i];
|
||
id = thread.id.slice(1);
|
||
threads += "<option value=" + id + ">Thread " + id + "</option>";
|
||
}
|
||
QR.threadSelector = g.BOARD === 'f' ? $('select[name=filetag]').cloneNode(true) : $.el('select', {
|
||
innerHTML: threads,
|
||
title: 'Create a new thread / Reply to a thread'
|
||
});
|
||
QR.threadSelector.className = null;
|
||
$.prepend($('#threadselect', QR.el), QR.threadSelector);
|
||
$.on(QR.threadSelector, 'mousedown', function(e) {
|
||
return e.stopPropagation();
|
||
});
|
||
}
|
||
i = 0;
|
||
size = QR.fileEl.max;
|
||
while (i++ < 2) {
|
||
size /= 1024;
|
||
}
|
||
$.on(QR.fileEl, 'change', $.on(QR.fileEl, 'change', QR.fileInput));
|
||
$.on(QR.fileEl, 'click', function(e) {
|
||
if (e.shiftKey) {
|
||
return QR.selected.rmFile() || e.preventDefault();
|
||
}
|
||
});
|
||
$.on(QR.autohide, 'change', QR.toggleHide);
|
||
$.on($('.close', QR.el), 'click', QR.close);
|
||
$.on($('#dump', QR.el), 'click', function() {
|
||
return $.toggleClass(QR.el, '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(spoiler.firstChild, 'change', function() {
|
||
return $('input', QR.selected.el).click();
|
||
});
|
||
$.on(QR.warning, 'click', QR.cleanError);
|
||
new QR.reply().select();
|
||
_ref1 = ['name', 'email', 'sub', 'com'];
|
||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||
name = _ref1[_j];
|
||
$.on($("[name=" + name + "]", QR.el), 'focus', function() {
|
||
return QR.el.classList.add('focus');
|
||
});
|
||
$.on($("[name=" + name + "]", QR.el), 'blur', function() {
|
||
return QR.el.classList.remove('focus');
|
||
});
|
||
$.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;
|
||
}
|
||
});
|
||
$.on(QR.fileEl, 'focus', function() {
|
||
return QR.el.classList.add('focus');
|
||
});
|
||
$.on(QR.fileEl, 'blur', function() {
|
||
return QR.el.classList.remove('focus');
|
||
});
|
||
}
|
||
QR.status.input = $('input[type=submit]', QR.el);
|
||
QR.status();
|
||
QR.cooldown.init();
|
||
QR.captcha.init();
|
||
$.add(d.body, QR.el);
|
||
return $.event(QR.el, new CustomEvent('QRDialogCreation', {
|
||
bubbles: true
|
||
}));
|
||
},
|
||
submit: function(e) {
|
||
var callbacks, captcha, captchas, challenge, err, filetag, m, opts, post, reply, response, textOnly, threadID;
|
||
|
||
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.REPLY && g.BOARD === 'f') {
|
||
filetag = QR.threadSelector.value;
|
||
threadID = 'new';
|
||
} else {
|
||
threadID = g.THREAD_ID || QR.threadSelector.value;
|
||
}
|
||
if (threadID === 'new') {
|
||
threadID = null;
|
||
if (['vg', 'q'].contains(g.BOARD) && !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 === '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.cleanError();
|
||
QR.cooldown.auto = QR.replies.length > 1;
|
||
if (Conf['Auto Hide QR'] && !QR.cooldown.auto && Conf['Post Form Style'] === "float") {
|
||
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: Conf['Markdown'] ? Markdown.format(reply.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() {
|
||
QR.cooldown.auto = false;
|
||
QR.status();
|
||
QR.error($.el('a', {
|
||
href: '//www.4chan.org/banned',
|
||
target: '_blank',
|
||
textContent: 'Connection error, or you are banned.'
|
||
}));
|
||
if (Conf['Check for Bans']) {
|
||
$["delete"]('lastBanCheck');
|
||
return BanChecker.init();
|
||
}
|
||
}
|
||
};
|
||
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, doc, el, err, key, persona, postID, reply, threadID, _, _ref;
|
||
|
||
doc = d.implementation.createHTMLDocument('');
|
||
doc.documentElement.innerHTML = html;
|
||
if (ban = $('.banType', doc)) {
|
||
board = $('.board', doc).innerHTML;
|
||
err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? (Conf['Check for Bans'] ? ($["delete"]('lastBanCheck'), BanChecker.init()) : void 0, err.innerHTML = "You are banned on " + board + "! ;_;<br>\nClick <a href=//www.4chan.org/banned target=_blank>here</a> to see the reason.") : err.innerHTML = "You were issued a warning on " + board + " as " + ($('.nameBlock', doc).innerHTML) + ".<br>\nReason: " + ($('.reason', doc).innerHTML));
|
||
} else if (err = doc.getElementById('errmsg')) {
|
||
if (el = $('a', err)) {
|
||
el.target = '_blank';
|
||
}
|
||
} else if (doc.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.textContent = '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;
|
||
}
|
||
reply = QR.replies[0];
|
||
persona = $.get('persona', {
|
||
global: {}
|
||
});
|
||
if (!persona[key = Conf['Per Board Persona'] ? g.BOARD : 'global']) {
|
||
persona[key] = JSON.parse(JSON.stringify(persona.global));
|
||
}
|
||
persona[key] = {
|
||
name: reply.name,
|
||
email: !Conf["Remember Sage"] && /^sage$/.test(reply.email) ? /^sage$/.test(persona[key].email) ? null : persona[key].email : reply.email,
|
||
sub: Conf['Remember Subject'] ? reply.sub : null
|
||
};
|
||
$.set('persona', persona);
|
||
_ref = doc.body.lastChild.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref[0], threadID = _ref[1], postID = _ref[2];
|
||
Updater.postID = postID;
|
||
if (!MarkOwn.posts) {
|
||
MarkOwn.posts = $.get('ownedPosts', {});
|
||
}
|
||
MarkOwn.posts[postID] = Date.now();
|
||
$.set('ownedPosts', MarkOwn.posts);
|
||
$.event(QR.el, new CustomEvent('QRPostSuccessful', {
|
||
bubbles: true,
|
||
detail: {
|
||
threadID: threadID,
|
||
postID: postID
|
||
}
|
||
}));
|
||
if ($.get('isBanned')) {
|
||
if (BanChecker.el) {
|
||
$.rm(BanChecker.el);
|
||
delete BanChecker.el;
|
||
}
|
||
$["delete"]('isBanned');
|
||
}
|
||
QR.cooldown.set({
|
||
post: reply,
|
||
isReply: threadID !== '0'
|
||
});
|
||
if (threadID === '0') {
|
||
location.pathname = "/" + g.BOARD + "/res/" + postID;
|
||
} else {
|
||
QR.cooldown.auto = QR.replies.length > 1;
|
||
if (Conf['Open Reply in New Tab'] && !g.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() {
|
||
var _ref;
|
||
|
||
if ((_ref = QR.ajax) != null) {
|
||
_ref.abort();
|
||
}
|
||
delete QR.ajax;
|
||
return QR.status();
|
||
}
|
||
};
|
||
|
||
QuoteBacklink = {
|
||
init: function() {
|
||
var format;
|
||
|
||
format = Conf['backlink'].replace(/%id/g, "' + id + '");
|
||
this.funk = Function('id', "return '" + format + "'");
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var a, container, el, link, nodes, owned, qid, quote, quotes, _i, _len, _ref, _ref1;
|
||
|
||
if (post.isInlined) {
|
||
return;
|
||
}
|
||
quotes = {};
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (quote.parentNode.parentNode.className === 'capcodeReplies') {
|
||
break;
|
||
}
|
||
if (quote.hostname === 'boards.4chan.org' && !/catalog$/.test(quote.pathname) && (qid = (_ref1 = quote.hash) != null ? _ref1.slice(2) : void 0)) {
|
||
quotes[qid] = true;
|
||
}
|
||
}
|
||
a = $.el('a', {
|
||
href: "/" + g.BOARD + "/res/" + post.threadID + "#p" + post.ID,
|
||
className: post.el.hidden ? 'filtered backlink' : 'backlink',
|
||
textContent: QuoteBacklink.funk(post.ID)
|
||
});
|
||
if (Conf['Mark Owned Posts']) {
|
||
if (MarkOwn.posts[post.ID]) {
|
||
$.addClass(a, 'ownreply');
|
||
a.textContent += " (You)";
|
||
owned = true;
|
||
}
|
||
}
|
||
for (qid in quotes) {
|
||
if (!(el = $.id("pi" + qid)) || !Conf['OP Backlinks'] && /\bop\b/.test(el.parentNode.className)) {
|
||
continue;
|
||
}
|
||
link = a.cloneNode(true);
|
||
nodes = $.nodes([$.tn(' '), link]);
|
||
if (Conf['Quote Preview']) {
|
||
$.on(link, 'mouseover', QuotePreview.mouseover);
|
||
}
|
||
if (Conf['Quote Inline']) {
|
||
$.on(link, 'click', QuoteInline.toggle);
|
||
if (Conf['Quote Hash Navigation']) {
|
||
QuoteInline.qiQuote(link);
|
||
}
|
||
}
|
||
if (!(container = $.id("blc" + qid))) {
|
||
$.addClass(el.parentNode, 'quoted');
|
||
if (owned) {
|
||
$.addClass(el.parentNode, 'youQuoted');
|
||
}
|
||
container = $.el('span', {
|
||
className: 'container',
|
||
id: "blc" + qid
|
||
});
|
||
$.add(el, container);
|
||
}
|
||
$.add(container, nodes);
|
||
}
|
||
}
|
||
};
|
||
|
||
QuoteCT = {
|
||
init: function() {
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var path, quote, _i, _len, _ref;
|
||
|
||
if (post.isInlined && !post.isCrosspost) {
|
||
return;
|
||
}
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (!(quote.hash && quote.hostname === 'boards.4chan.org' && !/catalog$/.test(quote.pathname))) {
|
||
continue;
|
||
}
|
||
path = quote.pathname.split('/');
|
||
if (path[1] === g.BOARD && path[3] !== post.threadID) {
|
||
$.add(quote, $.tn('\u00A0(Cross-thread)'));
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
QuoteInline = {
|
||
init: function() {
|
||
this.callbacks.push(this.node);
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
callbacks: [],
|
||
cb: function(post, root) {
|
||
var callback, _i, _len, _ref;
|
||
|
||
post.isCrosspost = post.isInlined = true;
|
||
post.threadID = $.x('ancestor::div[parent::div[@class="board"]]', root).id.match(/\d+$/)[0];
|
||
_ref = Main.callbacks;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
callback = _ref[_i];
|
||
callback(post);
|
||
}
|
||
},
|
||
cb2: function(post) {
|
||
var callback, _i, _len, _ref;
|
||
|
||
post.isInlined = true;
|
||
_ref = QuoteInline.callbacks;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
callback = _ref[_i];
|
||
callback(post);
|
||
}
|
||
},
|
||
node: function(post) {
|
||
var quote, _i, _j, _len, _len1, _ref, _ref1;
|
||
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (!(quote.hash && quote.hostname === 'boards.4chan.org' && !/catalog$/.test(quote.pathname) || /\bdeadlink\b/.test(quote.className))) {
|
||
continue;
|
||
}
|
||
$.on(quote, 'click', QuoteInline.toggle);
|
||
if (Conf['Quote Hash Navigation'] && !post.isInlined) {
|
||
QuoteInline.qiQuote(quote);
|
||
}
|
||
}
|
||
_ref1 = post.backlinks;
|
||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||
quote = _ref1[_j];
|
||
$.on(quote, 'click', QuoteInline.toggle);
|
||
}
|
||
},
|
||
qiQuote: function(quote) {
|
||
return $.after(quote, [
|
||
$.tn(' '), $.el('a', {
|
||
className: 'qiQuote',
|
||
textContent: '#',
|
||
href: quote.href
|
||
})
|
||
]);
|
||
},
|
||
toggle: function(e) {
|
||
var id;
|
||
|
||
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
id = this.dataset.id || this.hash.slice(2);
|
||
if (/\binlined\b/.test(this.className)) {
|
||
QuoteInline.rm(this, id);
|
||
} else {
|
||
if ($.x("ancestor::div[contains(@id,'p" + id + "')]", this)) {
|
||
return;
|
||
}
|
||
QuoteInline.add(this, id);
|
||
}
|
||
return $.toggleClass(this, 'inlined');
|
||
},
|
||
add: function(q, id) {
|
||
var board, el, i, inline, isBacklink, path, postID, root, threadID;
|
||
|
||
if (q.host === 'boards.4chan.org') {
|
||
path = q.pathname.split('/');
|
||
board = path[1];
|
||
threadID = path[3];
|
||
postID = id;
|
||
} else {
|
||
board = q.dataset.board;
|
||
threadID = 0;
|
||
postID = q.dataset.id;
|
||
}
|
||
el = board === g.BOARD ? $.id("p" + postID) : false;
|
||
inline = $.el('div', {
|
||
id: "i" + postID,
|
||
className: el ? 'inline' : 'inline crosspost'
|
||
});
|
||
root = (isBacklink = /\bbacklink\b/.test(q.className)) ? q.parentNode : $.x('ancestor-or-self::*[parent::blockquote][1]', q);
|
||
if (Conf['Quote Hash Navigation'] && !isBacklink && root === q) {
|
||
$.after(root.nextElementSibling, inline);
|
||
} else {
|
||
$.after(root, inline);
|
||
}
|
||
Get.post(board, threadID, postID, inline, QuoteInline.cb, QuoteInline.cb2);
|
||
if (!el) {
|
||
return;
|
||
}
|
||
if (isBacklink && Conf['Forward Hiding']) {
|
||
$.addClass(el.parentNode, 'forwarded');
|
||
++el.dataset.forwarded || (el.dataset.forwarded = 1);
|
||
}
|
||
if ((i = Unread.replies.indexOf(el)) !== -1) {
|
||
Unread.replies.splice(i, 1);
|
||
Unread.update(true);
|
||
}
|
||
if (Conf['Color user IDs'] && ['b', 'q', 'soc'].contains(board)) {
|
||
return setTimeout(function() {
|
||
return $.rmClass($('.reply.highlight', inline), 'highlight');
|
||
});
|
||
}
|
||
},
|
||
rm: function(q, id) {
|
||
var div, inlined, _i, _len, _ref;
|
||
|
||
div = $.x("following::div[@id='i" + id + "']", q);
|
||
$.rm(div);
|
||
if (!Conf['Forward Hiding']) {
|
||
return;
|
||
}
|
||
_ref = $$('.backlink.inlined', div);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
inlined = _ref[_i];
|
||
div = $.id(inlined.hash.slice(1));
|
||
if (!--div.dataset.forwarded) {
|
||
$.rmClass(div.parentNode, 'forwarded');
|
||
}
|
||
}
|
||
if (/\bbacklink\b/.test(q.className)) {
|
||
div = $.id("p" + id);
|
||
if (!--div.dataset.forwarded) {
|
||
return $.rmClass(div.parentNode, 'forwarded');
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
QuoteOP = {
|
||
init: function() {
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var quote, _i, _len, _ref;
|
||
|
||
if (post.isInlined && !post.isCrosspost) {
|
||
return;
|
||
}
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (quote.hash.slice(2) === post.threadID) {
|
||
$.add(quote, $.tn('\u00A0(OP)'));
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
QuotePreview = {
|
||
init: function() {
|
||
QuoteInline.callbacks.push(this.node);
|
||
ExpandComment.callbacks.push(this.node);
|
||
Main.callbacks.push(this.node);
|
||
return $.ready(function() {
|
||
return $.add(d.body, QuotePreview.el = $.el('div', {
|
||
id: 'qp',
|
||
className: 'reply dialog'
|
||
}));
|
||
});
|
||
},
|
||
callbacks: [],
|
||
callback: function(node) {
|
||
var callback, _i, _len, _ref;
|
||
|
||
_ref = QuotePreview.callbacks;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
callback = _ref[_i];
|
||
callback(node);
|
||
}
|
||
},
|
||
node: function(post) {
|
||
var quote, _i, _j, _len, _len1, _ref, _ref1;
|
||
|
||
_ref = post.quotes;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
quote = _ref[_i];
|
||
if (!(quote.hostname === 'boards.4chan.org' && quote.hash && !/catalog$/.test(quote.pathname) || /\bdeadlink\b/.test(quote.className))) {
|
||
continue;
|
||
}
|
||
$.on(quote, 'mouseover', QuotePreview.mouseover);
|
||
}
|
||
_ref1 = post.backlinks;
|
||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||
quote = _ref1[_j];
|
||
$.on(quote, 'mouseover', QuotePreview.mouseover);
|
||
}
|
||
},
|
||
mouseover: function(e) {
|
||
var board, child, children, el, path, postID, qp, quote, quoterID, threadID, _conf, _i, _j, _len, _len1, _ref;
|
||
|
||
if (UI.el || /\binlined\b/.test(this.className)) {
|
||
return;
|
||
}
|
||
qp = QuotePreview.el;
|
||
if (children = qp.children) {
|
||
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
||
child = children[_i];
|
||
$.rm(child);
|
||
}
|
||
}
|
||
if (this.host === 'boards.4chan.org') {
|
||
path = this.pathname.split('/');
|
||
board = path[1];
|
||
threadID = path[3];
|
||
postID = this.hash.slice(2);
|
||
} else {
|
||
board = this.dataset.board;
|
||
threadID = 0;
|
||
postID = this.dataset.id;
|
||
}
|
||
UI.el = qp;
|
||
UI.hover(e);
|
||
Get.post(board, threadID, postID, qp, function(post) {
|
||
Main.prettify(post.blockquote);
|
||
post.isArchived = qp.className.contains('archivedPost');
|
||
return QuotePreview.callback(post);
|
||
});
|
||
$.on(this, 'mousemove', UI.hover);
|
||
$.on(this, 'mouseout click', QuotePreview.mouseout);
|
||
_conf = Conf;
|
||
if (el = $.id("p" + postID)) {
|
||
_conf = Conf;
|
||
if (_conf['Quote Highlighting']) {
|
||
if (/\bop\b/.test(el.className)) {
|
||
$.addClass(el.parentNode, 'qphl');
|
||
} else {
|
||
$.addClass(el, 'qphl');
|
||
}
|
||
}
|
||
quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0];
|
||
_ref = $$('.quotelink, .backlink', qp);
|
||
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
|
||
quote = _ref[_j];
|
||
if (quote.hash.slice(2) === quoterID) {
|
||
$.addClass(quote, 'forwardlink');
|
||
}
|
||
}
|
||
}
|
||
},
|
||
mouseout: function(e) {
|
||
var el, hash;
|
||
|
||
delete UI.el;
|
||
$.rm(QuotePreview.el.firstChild);
|
||
$.rmClass(QuotePreview.el, 'warning');
|
||
if ((hash = this.hash) && (el = $.id(hash.slice(1)))) {
|
||
$.rmClass(el.parentNode, 'qphl');
|
||
$.rmClass(el, 'qphl');
|
||
}
|
||
$.off(this, 'mousemove', UI.hover);
|
||
return $.off(this, 'mouseout click', QuotePreview.mouseout);
|
||
}
|
||
};
|
||
|
||
Quotify = {
|
||
init: function() {
|
||
QuotePreview.callbacks.push(this.node);
|
||
ExpandComment.callbacks.push(this.node);
|
||
return Main.callbacks.push(this.node);
|
||
},
|
||
node: function(post) {
|
||
var a, board, deadlink, id, m, postBoard, quote, _i, _len, _ref;
|
||
|
||
if (post.isInlined && !post.isCrosspost) {
|
||
return;
|
||
}
|
||
_ref = $$('.deadlink', post.blockquote);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
deadlink = _ref[_i];
|
||
quote = deadlink.textContent;
|
||
a = $.el('a', {
|
||
textContent: "" + quote + "\u00A0(Dead)"
|
||
});
|
||
if (!(id = quote.match(/\d+$/))) {
|
||
continue;
|
||
}
|
||
id = id[0];
|
||
if (m = quote.match(/^>>>\/([a-z\d]+)/)) {
|
||
board = m[1];
|
||
} else if (postBoard) {
|
||
board = postBoard;
|
||
} else {
|
||
board = postBoard = $('a[title="Highlight this post"]', post.el).pathname.split('/')[1];
|
||
}
|
||
if (board === g.BOARD && $.id("p" + id)) {
|
||
a.href = "#p" + id;
|
||
a.className = 'quotelink';
|
||
} else {
|
||
a.href = Redirect.to({
|
||
board: board,
|
||
threadID: 0,
|
||
postID: id
|
||
});
|
||
a.className = 'deadlink';
|
||
a.target = '_blank';
|
||
if (Redirect.post(board, id)) {
|
||
$.addClass(a, 'quotelink');
|
||
a.setAttribute('data-board', board);
|
||
a.setAttribute('data-id', id);
|
||
}
|
||
}
|
||
$.replace(deadlink, a);
|
||
}
|
||
}
|
||
};
|
||
|
||
Style = {
|
||
init: function() {
|
||
this.agent = {
|
||
'gecko': '-moz-',
|
||
'webkit': '-webkit-',
|
||
'presto': '-o-'
|
||
}[$.engine];
|
||
if (d.head) {
|
||
return this.wrapper();
|
||
}
|
||
return this.observe();
|
||
},
|
||
cleanup: function() {
|
||
delete Style.init;
|
||
delete Style.observe;
|
||
delete Style.wrapper;
|
||
return delete Style.cleanup;
|
||
},
|
||
observe: function() {
|
||
var onMutationObserver;
|
||
|
||
if (MutationObserver) {
|
||
Style.observer = new MutationObserver(onMutationObserver = this.wrapper);
|
||
return Style.observer.observe(d, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
} else {
|
||
return $.on(d, 'DOMNodeInserted', this.wrapper);
|
||
}
|
||
},
|
||
wrapper: function() {
|
||
if (d.head) {
|
||
Style.addStyle();
|
||
if (Style.observer) {
|
||
Style.observer.disconnect();
|
||
} else {
|
||
$.off(d, 'DOMNodeInserted', Style.wrapper);
|
||
}
|
||
return Style.cleanup();
|
||
}
|
||
},
|
||
emoji: function(position) {
|
||
var css, icon, margin, name, _conf, _i, _len, _ref;
|
||
|
||
_conf = Conf;
|
||
css = [];
|
||
margin = "margin-" + (position === "before" ? "right" : "left") + ": 5px;";
|
||
_ref = Emoji.icons;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
icon = _ref[_i];
|
||
name = icon[0];
|
||
css[css.length] = "a.useremail[href*='" + name + "']:last-of-type::" + position + ",\na.useremail[href*='" + (name.toLowerCase()) + "']:last-of-type::" + position + ",\na.useremail[href*='" + (name.toUpperCase()) + "']:last-of-type::" + position + " {\ncontent: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA" + icon[1] + "');\nvertical-align: top;\n" + margin + "\n}\n";
|
||
}
|
||
return css.join("");
|
||
},
|
||
addStyle: function() {
|
||
var agent;
|
||
|
||
agent = Style.agent;
|
||
return Style.css = $.addStyle("/* dialog styling */\nhr.abovePostForm {\n width: 100% !important;\n}\n.dialog.reply {\n display: block;\n border: 1px solid rgba(0,0,0,.25);\n padding: 0;\n}\n.move {\n cursor: move;\n}\nlabel, .favicon, .export, .import {\n cursor: pointer;\n}\na[href=\"javascript:;\"] {\n text-decoration: none;\n}\n.warning:not(:empty) {\n color: rgb(185, 74, 72);\n background: rgb(242, 222, 222);\n border: 1px solid rgba(238, 50, 50,0.3);\n padding: 3px;\n text-align: center;\n}\n\n#options .warning {\n width: 98%;\n border-radius: 4px;\n}\n\n.warning>code {\n border: 1px solid #E5D3D3;\n}\n\n.hide_thread_button:not(.hidden_thread) {\n float: left;\n}\n\n.thread > .hidden_thread ~ *,\n.hidden .sideArrows,\n[hidden],\n#globalMessage.hidden,\n#content > [name=tab]:not(:checked) + div,\n#updater:not(:hover) > :not(.move),\n.autohide:not(:hover) > form,\n#qp input, .forwarded,\n.fappeTyme > #delform .noFile {\n display: none !important;\n}\n\n.menu_button {\n display: inline-block;\n}\n.menu_button > span {\n border-top: .5em solid;\n border-right: .3em solid transparent;\n border-left: .3em solid transparent;\n display: inline-block;\n margin: 2px;\n vertical-align: middle;\n}\n#menu {\n position: absolute;\n outline: none;\n}\n.entry {\n border-bottom: 1px solid rgba(0, 0, 0, .25);\n cursor: pointer;\n display: block;\n outline: none;\n padding: 3px 7px;\n position: relative;\n text-decoration: none;\n white-space: nowrap;\n}\n.entry:last-child {\n border: none;\n}\n.focused.entry {\n background: rgba(255, 255, 255, .33);\n}\n.entry.hasSubMenu {\n padding-right: 1.5em;\n}\n.hasSubMenu::after {\n content: \"\";\n border-left: .5em solid;\n border-top: .3em solid transparent;\n border-bottom: .3em solid transparent;\n display: inline-block;\n margin: .3em;\n position: absolute;\n right: 3px;\n}\n.hasSubMenu:not(.focused) > .subMenu {\n display: none;\n}\n.subMenu {\n position: absolute;\n left: 100%;\n top: 0;\n margin-top: -1px;\n}\nh1,\nh2 {\n text-align: center;\n}\n#qr > .move {\n min-width: 300px;\n overflow: hidden;\n box-sizing: border-box;\n " + agent + "box-sizing: border-box;\n padding: 0 2px;\n}\n#threadselect,\n#qr > .move > span {\n float: right;\n padding: 0 2px;\n}\n#autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning {\n cursor: pointer;\n}\n#qr select,\n#qr > form {\n margin: 0;\n}\n#dump {\n background: " + agent + "linear-gradient(#EEE, #CCC);\n background: linear-gradient(#EEE, #CCC);\n width: 10%;\n}\n.gecko #dump {\n padding: 1px 0 2px;\n}\n#dump:hover, #dump:focus {\n background: " + agent + "linear-gradient(#FFF, #DDD);\n background: linear-gradient(#FFF, #DDD);\n}\n#dump:active, .dump #dump:not(:hover):not(:focus) {\n background: " + agent + "linear-gradient(#CCC, #DDD);\n background: linear-gradient(#CCC, #DDD);\n}\n#qp:empty,\n#qr:not(.dump) #replies, .dump > form > label {\n display: none;\n}\n#replies {\n display: block;\n height: 100px;\n position: relative;\n " + agent + "user-select: none;\n user-select: none;\n}\n#replies > div {\n counter-reset: thumbnails;\n top: 0; right: 0; bottom: 0; left: 0;\n margin: 0; padding: 0;\n overflow: hidden;\n position: absolute;\n white-space: pre;\n}\n#replies > div:hover {\n bottom: -10px;\n overflow-x: auto;\n z-index: 1;\n}\n.thumbnail {\n background-color: rgba(0,0,0,.2) !important;\n background-position: 50% 20% !important;\n background-size: cover !important;\n border: 1px solid #666;\n box-sizing: border-box;\n " + agent + "box-sizing: border-box;\n cursor: move;\n display: inline-block;\n height: 90px; width: 90px;\n margin: 5px; padding: 2px;\n opacity: .5;\n outline: none;\n overflow: hidden;\n position: relative;\n text-shadow: 0 1px 1px #000;\n " + agent + "transition: opacity .25s ease-in-out;\n transition: opacity .25s ease-in-out;\n vertical-align: top;\n}\n.thumbnail:hover, .thumbnail:focus {\n opacity: .9;\n}\n.thumbnail#selected {\n opacity: 1;\n}\n.thumbnail::before {\n counter-increment: thumbnails;\n content: counter(thumbnails);\n color: #FFF;\n font-weight: 700;\n padding: 3px;\n position: absolute;\n top: 0;\n right: 0;\n text-shadow: 0 0 3px #000, 0 0 8px #000;\n}\n.thumbnail.drag {\n box-shadow: 0 0 10px rgba(0,0,0,.5);\n}\n.thumbnail.over {\n border-color: #FFF;\n}\n.thumbnail > span {\n color: #FFF;\n}\n.remove {\n background: none;\n color: #E00;\n font-weight: 700;\n padding: 3px;\n}\n.remove:hover::after {\n content: \" Remove\";\n}\n.thumbnail > label {\n background: rgba(0,0,0,.5);\n color: #FFF;\n right: 0; bottom: 0; left: 0;\n position: absolute;\n text-align: center;\n}\n.thumbnail > label > input {\n margin: 0;\n}\n#addReply {\n color: #333;\n font-size: 3.5em;\n line-height: 100px;\n}\n#addReply:hover, #addReply:focus {\n color: #000;\n}\n.field {\n border: 1px solid #CCC;\n box-sizing: border-box;\n " + agent + "box-sizing: border-box;\n color: #333;\n font: 13px sans-serif;\n margin: 0;\n padding: 2px 4px 3px;\n " + agent + "transition: color .25s, border .25s;\n transition: color .25s, border .25s;\n}\n.field:" + agent + "placeholder,\n.field:hover:" + agent + "placeholder {\n color: #AAA;\n}\n.field:hover, .field:focus {\n border-color: #999;\n color: #000;\n outline: none;\n}\n.userInfo > .field:not(#dump) {\n width: 95px;\n min-width: 30%;\n max-width: 30%;\n}\n#qr textarea.field {\n display: " + agent + "box;\n min-height: 160px;\n min-width: 100%;\n}\n#qr.captcha textarea.field {\n min-height: 120px;\n}\n.textarea {\n position: relative;\n}\n#charCount {\n color: #000;\n background: hsla(0, 0%, 100%, .5);\n font-size: 8pt;\n margin: 1px;\n position: absolute;\n bottom: 0;\n right: 0;\n pointer-events: none;\n}\n#charCount.warning {\n color: red;\n}\n.captchainput > .field {\n min-width: 100%;\n}\n.captchaimg {\n background: #FFF;\n outline: 1px solid #CCC;\n outline-offset: -1px;\n text-align: center;\n}\n.captchaimg > img {\n display: block;\n height: 57px;\n width: 300px;\n}\n#qr [type=file] {\n margin: 1px 0;\n width: 70%;\n}\n#qr [type=submit] {\n margin: 1px 0;\n padding: 1px; /* not Gecko */\n width: 30%;\n}\n.gecko #qr [type=submit] {\n padding: 0 1px; /* Gecko does not respect box-sizing: border-box */\n}\n#spoilerLabel:not([hidden]) {\n display: block;\n}\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\n display: none;\n}\n.fitwidth img[data-md5] + img {\n max-width: 100%;\n}\n.gecko .fitwidth img[data-md5] + img,\n.presto .fitwidth img[data-md5] + img {\n width: 100%;\n}\n#qr, #qp, #updater, #stats, #ihover, #overlay, #navlinks, #mouseover {\n position: fixed;\n}\n#ihover {\n max-height: 97%;\n max-width: 75%;\n padding-bottom: 18px;\n}\n#navlinks {\n font-size: 16px;\n top: 25px;\n right: 5px;\n}\nbody {\n box-sizing: border-box;\n " + agent + "box-sizing: border-box;\n}\nbody.unscroll {\n overflow: hidden;\n}\n#mouseover {\n z-index: 2;\n}\n#overlay {\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n text-align: center;\n background: none repeat scroll 0% 0% rgba(25, 25, 25, 0.6);\n z-index: 1;\n}\n#overlay::after {\n content: \"\";\n display: inline-block;\n height: 100%;\n vertical-align: middle;\n}\n#options {\n box-sizing: border-box;\n " + agent + "box-sizing: border-box;\n display: inline-block;\n padding: 5px;\n position: relative;\n text-align: left;\n vertical-align: middle;\n width: 900px;\n max-width: 100%;\n height: 600px;\n max-height: 100%;\n box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.4); \n border-radius: 4px;\n}\n\n#credits {\n float: right;\n}\n#options ul {\n padding: 0;\n}\n#options fieldset {\n border: 1px solid rgb(150,150,150);\n border-radius: 3px;\n}\n#options legend {\n font-weight: 700;\n}\n#options article li {\n margin: 10px 0 10px 2em;\n}\n#options code {\n background: hsla(0, 0%, 100%, .5);\n color: #000;\n padding: 0 2px;\n}\n#selected_tab {\n font-weight: 700;\n}\n.rice_tab, .main_tab {\n margin-right: 5px;\n}\n.rice_tab {\n margin-top: -15px;\n}\n#content {\n overflow: auto;\n position: absolute;\n top: 2.5em;\n right: 5px;\n bottom: 5px;\n left: 5px;\n}\n#content textarea {\n font-family: monospace;\n min-height: 350px;\n resize: vertical;\n width: 100%;\n}\n.imp-exp .placeholder:not(:empty) {\n position: absolute;\n top:5px;\n right:0px;\n left:0px;\n text-align:center;\n width: 200px;\n margin: auto;\n}\n.imp-exp-result:empty {\n display: none;\n}\n.imp-exp-result {\n position: absolute;\n top:5px;\n right:0px;\n left:0px;\n width:200px;\n margin:auto;\n text-align: center;\n color:red;\n}\n\n#updater {\n text-align: right;\n}\n#updater:not(:hover) {\n border: none;\n background: transparent;\n}\n#updater input[type=number] {\n width: 4em;\n}\n.new {\n background: lime;\n}\n\n#watcher {\n padding-bottom: 5px;\n position: absolute;\n overflow: hidden;\n white-space: nowrap;\n}\n#watcher:not(:hover) {\n max-height: 220px;\n}\n#watcher > div {\n max-width: 200px;\n overflow: hidden;\n padding-left: 5px;\n padding-right: 5px;\n text-overflow: ellipsis;\n}\n#watcher > .move {\n padding-top: 5px;\n text-decoration: underline;\n}\n\n#qp {\n padding: 2px 2px 5px;\n}\n#qp .post {\n border: none;\n margin: 0;\n padding: 0;\n}\n#qp img {\n max-height: 300px;\n max-width: 500px;\n}\n.qphl {\n box-shadow: 0 0 0 2px rgba(216, 94, 49, .7);\n}\n.quotelink.deadlink {\n text-decoration: underline !important;\n}\n.deadlink:not(.quotelink) {\n text-decoration: none !important;\n}\n.inlined {\n opacity: .5;\n}\n.inline {\n background-color: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(128, 128, 128, 0.5);\n display: table;\n margin: 2px;\n padding: 2px;\n}\n.inline .post {\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n}\ndiv.opContainer {\n display: block !important;\n}\n.opContainer.filter_highlight {\n box-shadow: inset 5px 0 rgba(255, 0, 0, .5);\n}\n.opContainer.filter_highlight.qphl {\n box-shadow: inset 5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n.filter_highlight > .reply {\n box-shadow: -5px 0 rgba(255, 0, 0, .5);\n}\n.filter_highlight > .reply.qphl {\n box-shadow: -5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7)\n}\n.filtered,\n.quotelink.filtered {\n text-decoration: underline;\n text-decoration: line-through !important;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\n text-decoration: none;\n border-bottom: 1px dashed;\n}\n.threadContainer {\n margin-left: 20px;\n border-left: 1px solid black;\n}\n.postContainer iframe {\n display: block !important;\n}\n#toggleMsgButton {\n width: 200px;\n display: block;\n text-align: center;\n margin: 0 auto 0;\n}\n.redButton {\n background-color: rgb(255, 173, 173);\n background-image: url(\"http://static.4chan.org/image/buttonfade-red.png\");\n border: 1px solid rgb(196, 88, 88);\n color: rgb(136, 0, 0) !important;\n border-radius: 3px 3px 3px 3px;\n padding: 6px 10px 5px;\n font-weight: bold;\n background-repeat: repeat-x;\n text-decoration: none;\n}\n.updateAlert {\n width: 400px;\n height: 150px;\n text-align: center;\n margin: auto;\n position: absolute;\n left: 0px;\n right: 0px;\n top: 0px;\n bottom: 0px;\n border-radius: 3px;\n padding: 10px;\n box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.4); \n height: 100px !important;\n width: 300px !important;\n padding: 25px !important;\n}\n.updateAlert>span {\n display: block;\n}\n.updateAlert>.dismiss {\n font-size: 14px;\n position: absolute;\n bottom: 0px;\n right: 0px;\n left: 0px;\n}\n.updateAlert>.dismiss:hover {\n cursor:pointer;\n}\n.authors {\n font-size: 12px;\n text-align: right;\n}\n.dear {\n text-align: left;\n font-size: 12px;\n}\n.dismissMessage {\n font-size: 12px;\n opacity: 0.4;\n}\n.alertMessage {\n font-size: 15px;\n text-align: center;\n}\n" + (Conf["Announcement Hiding"] ? '#globalMessage.hidden { display: none; }' : '') + "\n" + (Conf["Custom CSS"] ? Conf["customCSS"] : "") + "\n" + (Conf['Emoji'] ? Style.emoji(Conf['emojiPos']) : '') + " \n" + (Conf['Quick Reply'] && Conf['Hide Original Post Form'] ? '#postForm { display: none; }' : ''));
|
||
}
|
||
};
|
||
|
||
Get = {
|
||
post: function(board, threadID, postID, root, cb, cb2) {
|
||
var post, url;
|
||
|
||
if (board === g.BOARD && (post = $.id("pc" + postID))) {
|
||
post = Get.cleanPost(post.cloneNode(true));
|
||
if (cb2) {
|
||
cb2(Main.preParse(post));
|
||
}
|
||
$.add(root, post);
|
||
return;
|
||
}
|
||
root.innerHTML = "<div class=post>Loading post No." + postID + "...</div>";
|
||
if (threadID) {
|
||
return $.cache("//api.4chan.org/" + board + "/res/" + threadID + ".json", function() {
|
||
return Get.parsePost(this, board, threadID, postID, root, cb);
|
||
});
|
||
} else if (url = Redirect.post(board, postID)) {
|
||
return $.cache(url, function() {
|
||
return Get.parseArchivedPost(this, board, postID, root, cb);
|
||
});
|
||
}
|
||
},
|
||
parsePost: function(req, board, threadID, postID, root, cb) {
|
||
var post, postNode, posts, spoilerRange, status, url, _i, _len;
|
||
|
||
status = req.status;
|
||
if (status !== 200) {
|
||
if (url = Redirect.post(board, postID)) {
|
||
$.cache(url, function() {
|
||
return Get.parseArchivedPost(this, board, postID, root, cb);
|
||
});
|
||
} else {
|
||
$.addClass(root, 'warning');
|
||
root.innerHTML = status === 404 ? "<div class=post>Thread No." + threadID + " 404'd.</div>" : "<div class=post>Error " + req.status + ": " + req.statusText + ".</div>";
|
||
}
|
||
return;
|
||
}
|
||
posts = JSON.parse(req.response).posts;
|
||
if (spoilerRange = posts[0].custom_spoiler) {
|
||
Build.spoilerRange[board] = spoilerRange;
|
||
}
|
||
postID = +postID;
|
||
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.parseArchivedPost(this, board, postID, root, cb);
|
||
});
|
||
} else {
|
||
$.addClass(root, 'warning');
|
||
root.textContent = "Post No." + postID + " was not found.";
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
post = Main.preParse(postNode = Get.cleanPost(Build.postFromObject(post, board)));
|
||
if (cb) {
|
||
cb(post, root);
|
||
}
|
||
return $.replace(root.firstChild, postNode);
|
||
},
|
||
parseArchivedPost: function(req, board, postID, root, cb) {
|
||
var bq, comment, data, o, post, postNode, _ref;
|
||
|
||
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 '<s>';
|
||
case '[/spoiler]':
|
||
return '</s>';
|
||
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(/(^|>)(>[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3');
|
||
o = {
|
||
postID: postID,
|
||
threadID: data.thread_num,
|
||
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.replace(/"/g, '"')) : '',
|
||
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'
|
||
};
|
||
}
|
||
post = Main.preParse(postNode = Get.cleanPost(Build.post(o, true)));
|
||
if (cb) {
|
||
cb(post, root);
|
||
}
|
||
return $.replace(root.firstChild, postNode);
|
||
},
|
||
cleanPost: function(root) {
|
||
var child, el, els, inline, inlined, now, post, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2;
|
||
|
||
post = $('.post', root);
|
||
_ref = __slice.call(root.childNodes);
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
child = _ref[_i];
|
||
if (child !== post) {
|
||
$.rm(child);
|
||
}
|
||
}
|
||
_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');
|
||
}
|
||
now = Date.now();
|
||
els = $$('[id]', root);
|
||
els.push(root);
|
||
for (_l = 0, _len3 = els.length; _l < _len3; _l++) {
|
||
el = els[_l];
|
||
el.id = "" + now + "_" + el.id;
|
||
}
|
||
$.rmClass(root, 'forwarded');
|
||
$.rmClass(root, 'qphl');
|
||
$.rmClass(post, 'highlight');
|
||
$.rmClass(post, 'qphl');
|
||
root.hidden = post.hidden = false;
|
||
return root;
|
||
},
|
||
title: function(thread) {
|
||
var el, op, span;
|
||
|
||
op = $('.op', thread);
|
||
el = $('.postInfo .subject', op);
|
||
if (!el.textContent) {
|
||
el = $('blockquote', op);
|
||
if (!el.textContent) {
|
||
el = $('.nameBlock', op);
|
||
}
|
||
}
|
||
span = $.el('span', {
|
||
innerHTML: el.innerHTML.replace(/<br>/g, ' ')
|
||
});
|
||
return "/" + g.BOARD + "/ - " + (span.textContent.trim());
|
||
}
|
||
};
|
||
|
||
Build = {
|
||
spoilerRange: {},
|
||
shortFilename: function(filename, isOP) {
|
||
var threshold;
|
||
|
||
threshold = isOP ? 40 : 30;
|
||
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(/"/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 class=file id=f" + postID + "><div class=fileInfo></div><span class=fileThumb>") + ("<img src='" + staticPath + "/image/filedeleted.gif' alt='File deleted.' class='fileDeleted retina'>") + "</span></div>" : ("<div id=f" + postID + " class=file><span class=fileThumb>") + ("<img src='" + staticPath + "/image/filedeleted-res.gif' alt='File deleted.' class='fileDeletedRes retina'>") + "</span></div>";
|
||
} 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;
|
||
}
|
||
}
|
||
imgSrc = ("<a class='fileThumb" + (file.isSpoiler ? ' imgspoiler' : '') + "' href='" + file.url + "' target=_blank>") + ("<img src='" + fileThumb + "' alt='" + fileSize + "' data-md5=" + file.MD5 + " style='width:" + file.twidth + "px;height:" + file.theight + "px'></a>");
|
||
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, ''');
|
||
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 + ">>></div>") + ("<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'>") + (isOP ? fileHTML : '') + ("<div class='postInfo desktop' id=pi" + postID + ">") + ("<input type=checkbox name=" + postID + " value=delete> ") + ("" + subject + " ") + ("<span class='nameBlock" + capcodeClass + "'>") + emailStart + ("<span class=name>" + (name || '') + "</span>") + tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + ' </span> ' + ("<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span> ") + "<span class='postNum desktop'>" + ("<a href=" + ("/" + board + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a>") + ("<a href='" + (g.REPLY && +g.THREAD_ID === threadID ? "javascript:quote(" + postID + ")" : "/" + board + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>") + '</span>' + '</div>' + (isOP ? '' : fileHTML) + ("<blockquote class=postMessage id=m" + postID + ">" + (comment || '') + "</blockquote> ") + '</div>'
|
||
});
|
||
_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;
|
||
}
|
||
};
|
||
|
||
Main = {
|
||
init: function() {
|
||
var key, now, path, pathname, settings, temp, val, _conf;
|
||
|
||
Main.flatten(null, Config);
|
||
for (key in Conf) {
|
||
val = Conf[key];
|
||
Conf[key] = $.get(key, val);
|
||
}
|
||
path = location.pathname;
|
||
pathname = path.slice(1).split('/');
|
||
g.BOARD = pathname[0], temp = pathname[1];
|
||
switch (temp) {
|
||
case 'res':
|
||
g.REPLY = true;
|
||
g.THREAD_ID = pathname[2];
|
||
break;
|
||
case 'catalog':
|
||
g.CATALOG = true;
|
||
}
|
||
if (['b', 'd', 'e', 'gif', 'h', 'hc', 'hm', 'hr', 'pol', 'r', 'r9k', 'rs', 's', 's4s', 'soc', 't', 'u', 'y'].contains(g.BOARD)) {
|
||
g.TYPE = 'nsfw';
|
||
}
|
||
_conf = Conf;
|
||
if (_conf["Interval per board"]) {
|
||
Conf["Interval_" + g.BOARD] = $.get("Interval_" + g.BOARD, Conf["Interval"]);
|
||
Conf["BGInterval_" + g.BOARD] = $.get("BGInterval_" + g.BOARD, Conf["BGInteval"]);
|
||
}
|
||
switch (location.hostname) {
|
||
case 'sys.4chan.org':
|
||
if (/report/.test(location.search)) {
|
||
$.ready(function() {
|
||
var field, form;
|
||
|
||
form = $('form');
|
||
field = $.id('recaptcha_response_field');
|
||
$.on(field, 'keydown', function(e) {
|
||
if (e.keyCode === 8 && !e.target.value) {
|
||
return $.globalEval('Recaptcha.reload()');
|
||
}
|
||
});
|
||
return $.on(form, 'submit', function(e) {
|
||
var response;
|
||
|
||
e.preventDefault();
|
||
response = field.value.trim();
|
||
if (!/\s/.test(response)) {
|
||
field.value = "" + response + " " + response;
|
||
}
|
||
return form.submit();
|
||
});
|
||
});
|
||
}
|
||
return;
|
||
case 'images.4chan.org':
|
||
$.ready(function() {
|
||
var url;
|
||
|
||
if (/^4chan - 404/.test(d.title) && _conf['404 Redirect']) {
|
||
path = location.pathname.split('/');
|
||
url = Redirect.image(path[1], path[3]);
|
||
if (url) {
|
||
return location.href = url;
|
||
}
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
userNavigation = $.get("userNavigation", Navigation);
|
||
Main.prune();
|
||
now = Date.now();
|
||
if (_conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 18 * $.HOUR) {
|
||
$.ready(function() {
|
||
$.on(window, 'message', Main.message);
|
||
$.set('lastUpdate', now);
|
||
return $.add(d.head, $.el('script', {
|
||
src: 'https://github.com/zixaphir/appchan-x/raw/4chanX/latest.js'
|
||
}));
|
||
});
|
||
}
|
||
settings = JSON.parse(localStorage.getItem('4chan-settings')) || {};
|
||
settings.disableAll = true;
|
||
localStorage.setItem('4chan-settings', JSON.stringify(settings));
|
||
Main.polyfill();
|
||
if (g.CATALOG) {
|
||
return $.ready(Main.catalog);
|
||
} else {
|
||
return Main.features();
|
||
}
|
||
},
|
||
polyfill: function() {
|
||
var event, prefix, property;
|
||
|
||
if (!('visibilityState' in document)) {
|
||
prefix = 'mozVisibilityState' in document ? 'moz' : 'webkitVisibilityState' in document ? 'webkit' : 'o';
|
||
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(d, new CustomEvent('visibilitychange'));
|
||
});
|
||
}
|
||
},
|
||
catalog: function() {
|
||
var _conf;
|
||
|
||
_conf = Conf;
|
||
if (_conf['Catalog Links']) {
|
||
CatalogLinks.init();
|
||
}
|
||
if (_conf['Thread Hiding']) {
|
||
ThreadHiding.init();
|
||
}
|
||
return $.ready(function() {
|
||
var a, nav, _i, _len, _ref;
|
||
|
||
if (_conf['Custom Navigation']) {
|
||
CustomNavigation.init();
|
||
}
|
||
_ref = ['boardNavDesktop', 'boardNavDesktopFoot'];
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
nav = _ref[_i];
|
||
if (a = $("a[href*='/" + g.BOARD + "/']", $.id(nav))) {
|
||
$.addClass(a, 'current');
|
||
}
|
||
}
|
||
});
|
||
},
|
||
features: function() {
|
||
var _conf;
|
||
|
||
_conf = Conf;
|
||
Emoji.init();
|
||
Style.init();
|
||
if (_conf['Filter']) {
|
||
Filter.init();
|
||
}
|
||
if (_conf['Reply Hiding']) {
|
||
ReplyHiding.init();
|
||
}
|
||
if (_conf['Reply Hiding'] || _conf['Reply Hiding Link'] || _conf['Filter']) {
|
||
StrikethroughQuotes.init();
|
||
QuotePreview.callbacks.push(ReplyHiding.unhide);
|
||
QuoteInline.callbacks.push(ReplyHiding.unhide);
|
||
}
|
||
if (_conf['Anonymize']) {
|
||
Anonymize.init();
|
||
}
|
||
if (_conf['Time Formatting']) {
|
||
Time.init();
|
||
}
|
||
if (Conf['Relative Post Dates']) {
|
||
RelativeDates.init();
|
||
}
|
||
if (_conf['File Info Formatting']) {
|
||
FileInfo.init();
|
||
}
|
||
if (_conf['Sauce']) {
|
||
Sauce.init();
|
||
}
|
||
if (_conf['Reveal Spoilers']) {
|
||
RevealSpoilers.init();
|
||
}
|
||
if (_conf['Image Auto-Gif']) {
|
||
AutoGif.init();
|
||
}
|
||
if (_conf['Png Thumbnail Fix']) {
|
||
PngFix.init();
|
||
}
|
||
if (_conf['Image Hover']) {
|
||
ImageHover.init();
|
||
}
|
||
if (_conf['Menu']) {
|
||
Menu.init();
|
||
if (_conf['Report Link']) {
|
||
ReportLink.init();
|
||
}
|
||
if (_conf['Delete Link']) {
|
||
DeleteLink.init();
|
||
}
|
||
if (_conf['Filter']) {
|
||
Filter.menuInit();
|
||
}
|
||
if (_conf['Archive Link']) {
|
||
ArchiveLink.init();
|
||
}
|
||
if (_conf['Download Link']) {
|
||
DownloadLink.init();
|
||
}
|
||
if (_conf['Embed Link']) {
|
||
EmbedLink.init();
|
||
}
|
||
if (_conf['Thread Hiding Link']) {
|
||
ThreadHideLink.init();
|
||
}
|
||
if (_conf['Reply Hiding Link']) {
|
||
ReplyHideLink.init();
|
||
}
|
||
}
|
||
if (_conf['Linkify']) {
|
||
Linkify.init();
|
||
}
|
||
if (_conf['Resurrect Quotes']) {
|
||
Quotify.init();
|
||
}
|
||
if (_conf['Remove Spoilers']) {
|
||
RemoveSpoilers.init();
|
||
}
|
||
if (_conf['Quote Inline']) {
|
||
QuoteInline.init();
|
||
}
|
||
if (_conf['Quote Preview']) {
|
||
QuotePreview.init();
|
||
}
|
||
if (_conf['Quote Backlinks']) {
|
||
QuoteBacklink.init();
|
||
}
|
||
if (_conf['Indicate Own Posts']) {
|
||
MarkOwn.init();
|
||
}
|
||
if (_conf['Indicate OP quote']) {
|
||
QuoteOP.init();
|
||
}
|
||
if (_conf['Indicate Cross-thread Quotes']) {
|
||
QuoteCT.init();
|
||
}
|
||
if (_conf['Color user IDs']) {
|
||
IDColor.init();
|
||
}
|
||
if (_conf['Replace GIF'] || _conf['Replace PNG'] || _conf['Replace JPG']) {
|
||
ImageReplace.init();
|
||
}
|
||
return $.ready(Main.featuresReady);
|
||
},
|
||
featuresReady: function() {
|
||
var a, board, nav, node, nodes, now, ready, _conf, _i, _j, _len, _len1, _ref, _ref1;
|
||
|
||
_conf = Conf;
|
||
if (/^4chan - 404/.test(d.title)) {
|
||
if (_conf['404 Redirect'] && /^\d+$/.test(g.THREAD_ID)) {
|
||
location.href = Redirect.to({
|
||
board: g.BOARD,
|
||
threadID: g.THREAD_ID,
|
||
postID: location.hash
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
if (!$.id('navtopright')) {
|
||
return;
|
||
}
|
||
$.addClass(d.body, $.engine);
|
||
$.addClass(d.body, 'fourchan_x');
|
||
if (_conf['Custom Navigation']) {
|
||
CustomNavigation.init();
|
||
}
|
||
_ref = ['boardNavDesktop', 'boardNavDesktopFoot'];
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
nav = _ref[_i];
|
||
if (a = $("a[href*='/" + g.BOARD + "/']", $.id(nav))) {
|
||
$.addClass(a, 'current');
|
||
}
|
||
}
|
||
now = Date.now();
|
||
UpdateAlert.init();
|
||
Favicon.init();
|
||
Options.init();
|
||
if (_conf['Quick Reply']) {
|
||
QR.init();
|
||
}
|
||
if (_conf['Image Expansion']) {
|
||
ImageExpand.init();
|
||
}
|
||
if (_conf['Catalog Links']) {
|
||
CatalogLinks.init();
|
||
}
|
||
if (_conf['Thread Watcher']) {
|
||
Watcher.init();
|
||
}
|
||
if (_conf['Keybinds']) {
|
||
Keybinds.init();
|
||
}
|
||
if (_conf['Fappe Tyme']) {
|
||
FappeTyme.init();
|
||
}
|
||
if (_conf['Announcement Hiding']) {
|
||
AnnouncementHiding.init();
|
||
}
|
||
if (g.REPLY) {
|
||
if (_conf['Prefetch']) {
|
||
Prefetch.init();
|
||
}
|
||
if (_conf['Thread Updater']) {
|
||
Updater.init();
|
||
}
|
||
if (_conf['Thread Stats']) {
|
||
ThreadStats.init();
|
||
}
|
||
if (_conf['Reply Navigation']) {
|
||
Nav.init();
|
||
}
|
||
if (_conf['Post in Title']) {
|
||
TitlePost.init();
|
||
}
|
||
if (_conf['Unread Count'] || _conf['Unread Favicon']) {
|
||
Unread.init();
|
||
}
|
||
} else {
|
||
if (_conf['Thread Hiding']) {
|
||
ThreadHiding.init();
|
||
}
|
||
if (_conf['Thread Expansion']) {
|
||
ExpandThread.init();
|
||
}
|
||
if (_conf['Comment Expansion']) {
|
||
ExpandComment.init();
|
||
}
|
||
if (_conf['Index Navigation']) {
|
||
Nav.init();
|
||
}
|
||
}
|
||
board = $('.board');
|
||
nodes = [];
|
||
ready = function() {
|
||
if (d.readyState === "complete") {
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
_ref1 = $$('.postContainer', board);
|
||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||
node = _ref1[_j];
|
||
Main.node(Main.preParse(node), ready);
|
||
}
|
||
if (MutationObserver) {
|
||
Main.observer = new MutationObserver(Main.observe);
|
||
Main.observer.observe(board, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
$.ready(function() {
|
||
return Main.observer.disconnect();
|
||
});
|
||
} else {
|
||
$.on(board, 'DOMNodeInserted', Main.listener);
|
||
$.ready(function() {
|
||
return $.off(board, 'DOMNodeInserted', Main.listener);
|
||
});
|
||
}
|
||
},
|
||
prune: function() {
|
||
var cutoff, hiddenThreads, id, now, ownedPosts, timestamp, titles, _ref;
|
||
|
||
now = Date.now();
|
||
g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {});
|
||
if ($.get('lastChecked', 0) < now - 1 * $.DAY) {
|
||
$.set('lastChecked', now);
|
||
cutoff = now - 7 * $.DAY;
|
||
hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
|
||
ownedPosts = $.get('ownedPosts', {});
|
||
titles = $.get('CachedTitles', {});
|
||
for (id in hiddenThreads) {
|
||
timestamp = hiddenThreads[id];
|
||
if (timestamp < cutoff) {
|
||
delete hiddenThreads[id];
|
||
}
|
||
}
|
||
_ref = g.hiddenReplies;
|
||
for (id in _ref) {
|
||
timestamp = _ref[id];
|
||
if (timestamp < cutoff) {
|
||
delete g.hiddenReplies[id];
|
||
}
|
||
}
|
||
for (id in ownedPosts) {
|
||
timestamp = ownedPosts[id];
|
||
if (timestamp < cutoff) {
|
||
delete ownedPosts[id];
|
||
}
|
||
}
|
||
for (id in titles) {
|
||
if (titles[id][1] < cutoff) {
|
||
delete titles[id];
|
||
}
|
||
}
|
||
$.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
|
||
$.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
|
||
$.set('CachedTitles', titles);
|
||
return $.set('ownedPosts', ownedPosts);
|
||
}
|
||
},
|
||
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];
|
||
Main.flatten(key, val);
|
||
}
|
||
} else {
|
||
Conf[parent] = obj;
|
||
}
|
||
},
|
||
message: function(e) {
|
||
var version, xupdate;
|
||
|
||
version = e.data.version;
|
||
if (version && version !== Main.version) {
|
||
xupdate = $.el('div', {
|
||
id: 'xupdater',
|
||
className: 'reply',
|
||
innerHTML: "<a href=https://raw.github.com/zixaphir/appchan-x/" + version + "/appchan_x.user.js>An updated version of Appchan X (v" + version + ") is available.</a> <a href=javascript:; id=dismiss_xupdate>dismiss</a>"
|
||
});
|
||
$.on($('#dismiss_xupdate', xupdate), 'click', function() {
|
||
return $.rm(xupdate);
|
||
});
|
||
return $.prepend($.id('delform'), xupdate);
|
||
}
|
||
},
|
||
preParse: function(node) {
|
||
var el, img, imgParent, parent, parentClass, post;
|
||
|
||
parentClass = (parent = node.parentElement) ? parent.className : "";
|
||
el = $('.post', node);
|
||
post = {
|
||
root: node,
|
||
el: el,
|
||
"class": el.className,
|
||
ID: el.id.match(/\d+$/)[0],
|
||
threadID: g.THREAD_ID || (parent ? $.x('ancestor::div[parent::div[@class="board"]]', node).id.match(/\d+$/)[0] : void 0),
|
||
isArchived: parentClass.contains('archivedPost'),
|
||
isInlined: /\binline\b/.test(parentClass),
|
||
isCrosspost: parentClass.contains('crosspost'),
|
||
blockquote: el.lastElementChild,
|
||
quotes: el.getElementsByClassName('quotelink'),
|
||
backlinks: el.getElementsByClassName('backlink'),
|
||
fileInfo: false,
|
||
img: false
|
||
};
|
||
if (img = $('img[data-md5]', el)) {
|
||
imgParent = img.parentNode;
|
||
post.img = img;
|
||
post.fileInfo = imgParent.previousElementSibling;
|
||
post.hasPdf = /\.pdf$/.test(imgParent.href);
|
||
}
|
||
Main.prettify(post.blockquote);
|
||
return post;
|
||
},
|
||
node: function(node, notify) {
|
||
var callback, err, _i, _len, _ref;
|
||
|
||
_ref = Main.callbacks;
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
callback = _ref[_i];
|
||
try {
|
||
callback(node);
|
||
} catch (_error) {
|
||
err = _error;
|
||
if (notify) {
|
||
alert("4chan X has experienced an error. You can help by sending this snippet to:\nhttps://github.com/zixaphir/appchan-x/issues\n\n" + Main.version + "\n" + window.location + "\n" + navigator.userAgent + "\n\n" + err + "\n" + err.stack);
|
||
}
|
||
}
|
||
}
|
||
},
|
||
observe: function(mutations) {
|
||
var addedNode, mutation, nodes, _i, _len, _results;
|
||
|
||
nodes = [];
|
||
_results = [];
|
||
for (_i = 0, _len = mutations.length; _i < _len; _i++) {
|
||
mutation = mutations[_i];
|
||
_results.push((function() {
|
||
var _j, _len1, _ref, _results1;
|
||
|
||
_ref = mutation.addedNodes;
|
||
_results1 = [];
|
||
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
|
||
addedNode = _ref[_j];
|
||
if (/\bpostContainer\b/.test(addedNode.className)) {
|
||
_results1.push(Main.node(Main.preParse(addedNode)));
|
||
}
|
||
}
|
||
return _results1;
|
||
})());
|
||
}
|
||
return _results;
|
||
},
|
||
listener: function(e) {
|
||
var target;
|
||
|
||
target = e.target;
|
||
if (/\bpostContainer\b/.test(target.className)) {
|
||
return Main.node(Main.preParse(target));
|
||
}
|
||
},
|
||
prettify: function(bq) {
|
||
var code;
|
||
|
||
if (!Main.hasCodeTags) {
|
||
return;
|
||
}
|
||
switch (g.BOARD) {
|
||
case 'g':
|
||
code = function() {
|
||
var pre, _i, _len, _ref;
|
||
|
||
_ref = document.getElementById('_id_').getElementsByClassName('prettyprint');
|
||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||
pre = _ref[_i];
|
||
pre.innerHTML = prettyPrintOne(pre.innerHTML.replace(/\s/g, ' '));
|
||
}
|
||
};
|
||
break;
|
||
case 'sci':
|
||
code = function() {
|
||
jsMath.Process(document.getElementById('_id_'));
|
||
};
|
||
break;
|
||
default:
|
||
return;
|
||
}
|
||
return $.globalEval(("" + code).replace('_id_', bq.id));
|
||
},
|
||
namespace: '4chan_x.',
|
||
version: '1.0.11',
|
||
callbacks: []
|
||
};
|
||
|
||
Main.init();
|
||
|
||
}).call(this);
|