Created
February 21, 2014 23:11
-
-
Save Spittie/9145564 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Generated by CoffeeScript | |
| // ==UserScript== | |
| // @name 4chan X | |
| // @version 1.3.10 | |
| // @minGMVer 1.13 | |
| // @minFFVer 26 | |
| // @namespace 4chan-X | |
| // @description Cross-browser userscript for maximum lurking on 4chan. | |
| // @license MIT; https://github.com/Spittie/4chan-x/blob/master/LICENSE | |
| // @match *://boards.4chan.org/* | |
| // @match *://sys.4chan.org/* | |
| // @match *://a.4cdn.org/* | |
| // @match *://i.4cdn.org/* | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // @grant GM_deleteValue | |
| // @grant GM_openInTab | |
| // @grant GM_xmlhttpRequest | |
| // @run-at document-start | |
| // @updateURL https://github.com/Spittie/4chan-x/raw/stable/builds/4chan-X.meta.js | |
| // @downloadURL https://github.com/Spittie/4chan-x/raw/stable/builds/4chan-X.user.js | |
| // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAF5JREFUeNrtkTESABAQxPD/R6tsE2dUGYUtFJvLDKf93KevHJAjpBorAQWSBIKqFASC4G0pCAkm4GfaEvgYXl0T6HBaE97f0vmnfYHbZOMLZCx9ISdKWwjOWZSC8GYm4SUGwfYgqI4AAAAASUVORK5CYII= | |
| // ==/UserScript== | |
| /* | |
| * 4chan X - Version 1.3.10 - 2014-02-22 | |
| * | |
| * Licensed under the MIT license. | |
| * https://github.com/Spittie/4chan-x/blob/master/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-2014 Nicolas Stepien <stepien.nicolas@gmail.com> | |
| * https://4chan-x.just-believe.in/ | |
| * 4chan x Copyright © 2013-2014 Jordan Bates <saudrapsmann@gmail.com> | |
| * http://seaweedchan.github.io/4chan-x/ | |
| * 4chan x Copyright © 2012-2014 ihavenoface | |
| * http://ihavenoface.github.io/4chan-x/ | |
| * 4chan SS Copyright © 2011-2013 Ahodesuka | |
| * https://github.com/ahodesuka/4chan-Style-Script/ | |
| * | |
| * Permission is hereby granted, free of charge, to any person | |
| * obtaining a copy of this software and associated documentation | |
| * files (the "Software"), to deal in the Software without | |
| * restriction, including without limitation the rights to use, | |
| * copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the | |
| * Software is furnished to do so, subject to the following | |
| * conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be | |
| * included in all copies or substantial portions of the Software. | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
| * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
| * OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| * Contributors: | |
| * aeosynth | |
| * mayhemydg | |
| * noface | |
| * !K.WeEabo0o | |
| * blaise | |
| * that4chanwolf | |
| * desuwa | |
| * seaweed | |
| * e000 | |
| * ahodesuka | |
| * Shou | |
| * ferongr | |
| * xat | |
| * Ongpot | |
| * thisisanon | |
| * Anonymous | |
| * Seiba | |
| * herpaderpderp | |
| * WakiMiko | |
| * btmcsweeney | |
| * AppleBloom | |
| * detharonil | |
| * | |
| * All the people who've taken the time to write bug reports. | |
| * | |
| * Thank you. | |
| */ | |
| /* | |
| * Contains data from external sources: | |
| * | |
| * audio/beep.wav from http://freesound.org/people/pierrecartoons1979/sounds/90112/ | |
| * cc-by-nc-3.0 | |
| * | |
| * 4chan/4chan-JS (https://github.com/4chan/4chan-JS) | |
| * Copyright (c) 2012-2013, 4chan LLC | |
| * All rights reserved. | |
| * | |
| * license: https://github.com/4chan/4chan-JS/blob/master/LICENSE | |
| */ | |
| 'use strict'; | |
| (function() { | |
| var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, g, | |
| __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, | |
| __slice = [].slice, | |
| __hasProp = {}.hasOwnProperty, | |
| __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, | |
| __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; | |
| Array.prototype.indexOf = function(val) { | |
| var i; | |
| i = this.length; | |
| while (i--) { | |
| if (this[i] === val) { | |
| return i; | |
| } | |
| } | |
| return i; | |
| }; | |
| __indexOf = [].indexOf; | |
| Config = { | |
| main: { | |
| 'Miscellaneous': { | |
| 'Catalog Links': [true, 'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'], | |
| 'External Catalog': [false, 'Link to external catalog instead of the internal one.'], | |
| 'QR Shortcut': [false, 'Adds a small [QR] link in the header.'], | |
| 'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'], | |
| 'Desktop Notifications': [false, 'Enables desktop notifications across various 4chan X features.'], | |
| '404 Redirect': [true, 'Redirect dead threads and images.'], | |
| 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'], | |
| 'Time Formatting': [true, 'Localize and format timestamps.'], | |
| 'Relative Post Dates': [true, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'], | |
| 'File Info Formatting': [true, 'Reformat the file information.'], | |
| 'Thread Expansion': [true, 'Add buttons to expand threads.'], | |
| 'Index Navigation': [false, 'Add buttons to navigate between threads.'], | |
| 'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'], | |
| 'Show Dice Roll': [true, 'Show dice that were entered into the email field.'], | |
| 'Custom Board Titles': [true, 'Allow editing of the board title and subtitle by ctrl+clicking them'], | |
| 'Persistent Custom Board Titles': [false, 'Force custom board titles to be persistent, even if moot updates the board titles.'], | |
| 'Show Updated Notifications': [true, 'Show notifications when 4chan X is successfully updated.'], | |
| 'Emoji': [false, 'Adds icons next to names for different emails'], | |
| 'Color User IDs': [false, 'Assign unique colors to user IDs on boards that use them'], | |
| 'Remove Spoilers': [false, 'Remove all spoilers in text.'], | |
| 'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'] | |
| }, | |
| 'Linkification': { | |
| 'Linkify': [true, 'Convert text into links where applicable.'], | |
| 'Embedding': [true, 'Embed supported services.'], | |
| 'Auto-embed': [false, 'Auto-embed Linkify Embeds.'], | |
| 'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists'] | |
| }, | |
| 'Filtering': { | |
| 'Anonymize': [false, 'Make everyone Anonymous.'], | |
| 'Filter': [true, 'Self-moderation placebo.'], | |
| 'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'], | |
| 'Thread Hiding Buttons': [false, 'Add buttons to hide entire threads.'], | |
| 'Reply Hiding Buttons': [false, 'Add buttons to hide single replies.'], | |
| 'Filtered Backlinks': [true, 'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.'], | |
| 'Stubs': [true, 'Show stubs of hidden threads / replies.'] | |
| }, | |
| 'Images': { | |
| 'Image Expansion': [true, 'Expand images.'], | |
| 'Image Hover': [true, 'Show full image on mouseover.'], | |
| 'Gallery': [true, 'Adds a simple and cute image gallery.'], | |
| 'Sauce': [true, 'Add sauce links to images.'], | |
| 'Reveal Spoiler Thumbnails': [false, 'Replace spoiler thumbnails with the original image.'], | |
| 'Replace GIF': [false, 'Replace thumbnail of gifs with its actual image.'], | |
| 'Replace PNG': [false, 'Replace pngs.'], | |
| 'Replace JPG': [false, 'Replace jpgs.'], | |
| 'Image Prefetching': [false, 'Preload images'], | |
| 'Fappe Tyme': [false, 'Hide posts without images. *hint* *hint*'], | |
| 'Werk Tyme': [false, 'Hide all post images.'] | |
| }, | |
| 'Menu': { | |
| 'Menu': [true, 'Add a drop-down menu to posts.'], | |
| 'Report Link': [true, 'Add a report link to the menu.'], | |
| 'Thread Hiding Link': [true, 'Add a link to hide entire threads.'], | |
| 'Reply Hiding Link': [true, 'Add a link to hide single replies.'], | |
| 'Delete Link': [true, 'Add post and image deletion links to the menu.'], | |
| 'Archive Link': [true, 'Add an archive link to the menu.'] | |
| }, | |
| 'Monitoring': { | |
| 'Thread Updater': [true, 'Fetch and insert new replies. Has more options in its own dialog.'], | |
| 'Unread Count': [true, 'Show the unread posts count in the tab title.'], | |
| 'Hide Unread Count at (0)': [false, 'Hide the unread posts count in the tab title when it reaches 0.'], | |
| 'Unread Favicon': [true, 'Show a different favicon when there are unread posts.'], | |
| 'Unread Line': [true, 'Show a line to distinguish read posts from unread ones.'], | |
| 'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.'], | |
| 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'], | |
| 'Thread Stats': [true, 'Display reply and image count.'], | |
| 'Page Count in Stats': [false, 'Display the page count in the thread stats as well.'], | |
| 'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'], | |
| 'Thread Watcher': [true, 'Bookmark threads.'], | |
| 'Toggleable Thread Watcher': [true, 'Adds a shortcut for the thread watcher, hides the watcher by default, and makes it scroll with the page.'] | |
| }, | |
| 'Posting': { | |
| 'Quick Reply': [true, 'All-in-one form to reply, create threads, automate dumping and more.'], | |
| 'Persistent QR': [true, 'The Quick reply won\'t disappear after posting.'], | |
| 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'], | |
| 'Open Post in New Tab': [true, 'Open new threads or replies to a thread from the index in a new tab.'], | |
| 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'], | |
| 'Remember QR Size': [false, 'Remember the size of the Quick reply.'], | |
| 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'], | |
| 'Hide Original Post Form': [true, 'Hide the normal post form.'], | |
| 'Cooldown': [true, 'Indicate the remaining time before posting again.'], | |
| 'Cooldown Prediction': [true, 'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'], | |
| 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'], | |
| 'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'], | |
| 'Auto-load captcha': [false, 'Automatically load the captcha when you open a thread'] | |
| }, | |
| 'Quote Links': { | |
| 'Quote Backlinks': [true, 'Add quote backlinks.'], | |
| 'OP Backlinks': [true, 'Add backlinks to the OP.'], | |
| 'Quote Inlining': [true, 'Inline quoted post on click.'], | |
| 'Quote Hash Navigation': [false, 'Include an extra link after quotes for autoscrolling to quoted posts.'], | |
| 'Forward Hiding': [true, 'Hide original posts of inlined backlinks.'], | |
| 'Quote Previewing': [true, 'Show quoted post on hover.'], | |
| 'Quote Highlighting': [true, 'Highlight the previewed post.'], | |
| 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], | |
| 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], | |
| 'Quoted Title': [false, 'Change the page title to reflect you\'ve been quoted.'], | |
| 'Highlight Posts Quoting You': [false, 'Highlights any posts that contain a quote to your post.'], | |
| 'Highlight Own Posts': [false, 'Highlights own posts if Mark Quotes of You is enabled.'], | |
| 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], | |
| 'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'], | |
| 'Quote Threading': [true, 'Thread conversations'] | |
| } | |
| }, | |
| imageExpansion: { | |
| 'Fit width': [false, ''], | |
| 'Fit height': [false, ''], | |
| 'Expand spoilers': [true, 'Expand all images along with spoilers.'], | |
| 'Expand from here': [false, 'Expand all images only from current position to thread end.'], | |
| 'Advance on contract': [false, 'Advance to next post when contracting an expanded image.'] | |
| }, | |
| gallery: { | |
| 'Hide Thumbnails': [false], | |
| 'Fit Width': [true], | |
| 'Fit Height': [true] | |
| }, | |
| threadWatcher: { | |
| 'Current Board': [false, 'Only show watched threads from the current board.'], | |
| 'Auto Watch': [true, 'Automatically watch threads you start.'], | |
| 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'], | |
| 'Auto Prune': [false, 'Automatically prune 404\'d threads.'] | |
| }, | |
| filter: { | |
| name: "# Filter any namefags:\n#/^(?!Anonymous$)/", | |
| uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/", | |
| tripcode: "# Filter any tripfag\n#/^!/", | |
| capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for moot:\n#/Admin$/;highlight:moot;op:yes", | |
| email: "", | |
| subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only", | |
| comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g", | |
| flag: '', | |
| filename: '', | |
| dimensions: "# Highlight potential wallpapers:\n#/1920x1080/;op:yes;highlight;top:no;boards:w,wg", | |
| filesize: '', | |
| MD5: '' | |
| }, | |
| sauces: "https://www.google.com/searchbyimage?image_url=%TURL\nhttp://iqdb.org/?url=%TURL\n#//tineye.com/search?url=%TURL\n#http://saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#http://imgur.com/upload?url=%URL;text:Upload to imgur\n#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/", | |
| FappeT: { | |
| fappe: false, | |
| werk: false | |
| }, | |
| 'sageEmoji': '4chan SS', | |
| 'emojiPos': 'before', | |
| 'Custom CSS': false, | |
| Index: { | |
| 'Index Mode': 'paged', | |
| 'Index Sort': 'bump', | |
| 'Show Replies': true, | |
| 'Anchor Hidden Threads': true, | |
| 'Refreshed Navigation': false | |
| }, | |
| Header: { | |
| 'Fixed Header': true, | |
| 'Header auto-hide': false, | |
| 'Header auto-hide on scroll': false, | |
| 'Bottom Header': false, | |
| 'Centered links': false, | |
| 'Header catalog links': false, | |
| 'Bottom Board List': true, | |
| 'Shortcut Icons': true, | |
| 'Custom Board Navigation': true | |
| }, | |
| boardnav: "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/seaweedchan/4chan-x/wiki/Frequently-Asked-Questions\"]", | |
| QR: { | |
| 'QR.personas': "#email:\"sage\";boards:jp;always" | |
| }, | |
| time: '%m/%d/%y(%a)%H:%M:%S', | |
| backlink: '>>%id', | |
| fileInfo: '%L (%p%s, %r)', | |
| favicon: 'ferongr', | |
| usercss: '', | |
| hotkeys: { | |
| 'Toggle board list': ['Ctrl+b', 'Toggle the full board list.'], | |
| 'Toggle header': ['Shift+h', 'Toggle the auto-hide option of the header.'], | |
| 'Open empty QR': ['i', 'Open QR without post number inserted.'], | |
| 'Open QR': ['Shift+i', 'Open QR with post number inserted.'], | |
| 'Open settings': ['Alt+o', 'Open Settings.'], | |
| 'Close': ['Esc', 'Close Settings, Notifications or QR.'], | |
| 'Spoiler tags': ['Ctrl+s', 'Insert spoiler tags.'], | |
| 'Code tags': ['Alt+c', 'Insert code tags.'], | |
| 'Eqn tags': ['Alt+e', 'Insert eqn tags.'], | |
| 'Math tags': ['Alt+m', 'Insert math tags.'], | |
| 'Toggle sage': ['Alt+s', 'Toggle sage in email field'], | |
| 'Submit QR': ['Ctrl+Enter', 'Submit post.'], | |
| 'Watch': ['w', 'Watch thread.'], | |
| 'Update': ['r', 'Update the thread now.'], | |
| 'Expand image': ['Shift+e', 'Expand selected image.'], | |
| 'Expand images': ['e', 'Expand all images.'], | |
| 'Open Gallery': ['g', 'Opens the gallery.'], | |
| 'fappeTyme': ['f', 'Fappe Tyme.'], | |
| 'werkTyme': ['Shift+w', 'Werk Tyme'], | |
| 'Front page': ['0', 'Jump to page 0.'], | |
| 'Open front page': ['Shift+0', 'Open page 0 in a new tab.'], | |
| 'Next page': ['Shift+Right', 'Jump to the next page.'], | |
| 'Previous page': ['Shift+Left', 'Jump to the previous page.'], | |
| 'Open catalog': ['Shift+c', 'Open the catalog of the current board'], | |
| 'Search form': ['Ctrl+Alt+s', 'Focus the search field on the board index.'], | |
| 'Next thread': ['Shift+Down', 'See next thread.'], | |
| 'Previous thread': ['Shift+Up', 'See previous thread.'], | |
| 'Expand thread': ['Ctrl+e', 'Expand thread.'], | |
| 'Open thread': ['o', 'Open thread in current tab.'], | |
| 'Open thread tab': ['Shift+o', 'Open thread in new tab.'], | |
| 'Next reply': ['j', 'Select next reply.'], | |
| 'Previous reply': ['k', 'Select previous reply.'], | |
| 'Deselect reply': ['Shift+d', 'Deselect reply.'], | |
| 'Hide': ['x', 'Hide thread.'], | |
| 'Previous Post Quoting You': ['Alt+Up', 'Scroll to the previous post that quotes you.'], | |
| 'Next Post Quoting You': ['Alt+Down', 'Scroll to the next post that quotes you.'] | |
| }, | |
| updater: { | |
| checkbox: { | |
| 'Beep': [false, 'Beep on new post to completely read thread.'], | |
| 'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'], | |
| 'Bottom Scroll': [false, 'Always scroll to the bottom, not the first new post. Useful for event threads.'], | |
| 'Scroll BG': [false, 'Auto-scroll background tabs.'], | |
| 'Auto Update': [true, 'Automatically fetch new posts.'], | |
| 'Optional Increase': [false, 'Increase the intervals between updates on threads without new posts.'] | |
| }, | |
| 'Interval': 30 | |
| } | |
| }; | |
| Conf = {}; | |
| c = console; | |
| d = document; | |
| doc = d.documentElement; | |
| g = { | |
| VERSION: '1.3.10', | |
| NAMESPACE: '4chan X.', | |
| boards: {}, | |
| threads: {}, | |
| posts: {} | |
| }; | |
| $ = function(selector, root) { | |
| if (root == null) { | |
| root = d.body; | |
| } | |
| return root.querySelector(selector); | |
| }; | |
| $.extend = function(obj, prop) { | |
| var key, val; | |
| for (key in prop) { | |
| val = prop[key]; | |
| if (prop.hasOwnProperty(key)) { | |
| obj[key] = val; | |
| } | |
| } | |
| }; | |
| $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000))); | |
| $.id = function(id) { | |
| return d.getElementById(id); | |
| }; | |
| $.ready = function(fc) { | |
| var cb; | |
| if (d.readyState !== 'loading') { | |
| $.queueTask(fc); | |
| return; | |
| } | |
| cb = function() { | |
| $.off(d, 'DOMContentLoaded', cb); | |
| return fc(); | |
| }; | |
| return $.on(d, 'DOMContentLoaded', cb); | |
| }; | |
| $.formData = function(form) { | |
| var fd, key, val; | |
| if (form instanceof HTMLFormElement) { | |
| return new FormData(form); | |
| } | |
| fd = new FormData(); | |
| for (key in form) { | |
| val = form[key]; | |
| if (val) { | |
| if (typeof val === 'object' && 'newName' in val) { | |
| fd.append(key, val, val.newName); | |
| } else { | |
| fd.append(key, val); | |
| } | |
| } | |
| } | |
| return fd; | |
| }; | |
| $.extend = function(object, properties) { | |
| var key, val; | |
| for (key in properties) { | |
| val = properties[key]; | |
| object[key] = val; | |
| } | |
| }; | |
| $.ajax = (function() { | |
| var lastModified; | |
| lastModified = {}; | |
| return function(url, options, extra) { | |
| var form, r, sync, type, upCallbacks, whenModified; | |
| if (extra == null) { | |
| extra = {}; | |
| } | |
| type = extra.type, whenModified = extra.whenModified, upCallbacks = extra.upCallbacks, form = extra.form, sync = extra.sync; | |
| r = new XMLHttpRequest(); | |
| type || (type = form && 'post' || 'get'); | |
| r.open(type, url, !sync); | |
| if (whenModified) { | |
| if (url in lastModified) { | |
| r.setRequestHeader('If-Modified-Since', lastModified[url]); | |
| } | |
| $.on(r, 'load', function() { | |
| return lastModified[url] = r.getResponseHeader('Last-Modified'); | |
| }); | |
| } | |
| $.extend(r, options); | |
| $.extend(r.upload, upCallbacks); | |
| r.send(form); | |
| return r; | |
| }; | |
| })(); | |
| $.cache = (function() { | |
| var reqs; | |
| reqs = {}; | |
| return function(url, cb, options) { | |
| var err, req, rm; | |
| if (req = reqs[url]) { | |
| if (req.readyState === 4) { | |
| cb.call(req, req.evt); | |
| } else { | |
| req.callbacks.push(cb); | |
| } | |
| return; | |
| } | |
| rm = function() { | |
| return delete reqs[url]; | |
| }; | |
| try { | |
| req = $.ajax(url, options); | |
| } catch (_error) { | |
| err = _error; | |
| return; | |
| } | |
| $.on(req, 'load', function(e) { | |
| var _i, _len, _ref; | |
| _ref = this.callbacks; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| cb = _ref[_i]; | |
| cb.call(this, e); | |
| } | |
| this.evt = e; | |
| return delete this.callbacks; | |
| }); | |
| $.on(req, 'abort error', rm); | |
| req.callbacks = [cb]; | |
| return reqs[url] = req; | |
| }; | |
| })(); | |
| $.cb = { | |
| checked: function() { | |
| $.set(this.name, this.checked); | |
| return Conf[this.name] = this.checked; | |
| }, | |
| value: function() { | |
| $.set(this.name, this.value.trim()); | |
| return Conf[this.name] = this.value; | |
| } | |
| }; | |
| $.asap = function(test, cb) { | |
| if (test()) { | |
| return cb(); | |
| } else { | |
| return setTimeout($.asap, 25, test, cb); | |
| } | |
| }; | |
| $.addStyle = function(css, id) { | |
| var style; | |
| style = $.el('style', { | |
| id: id, | |
| textContent: css | |
| }); | |
| $.asap((function() { | |
| return d.head; | |
| }), function() { | |
| return $.add(d.head, style); | |
| }); | |
| return style; | |
| }; | |
| $.x = function(path, root) { | |
| root || (root = d.body); | |
| return d.evaluate(path, root, null, 8, null).singleNodeValue; | |
| }; | |
| $.X = function(path, root) { | |
| root || (root = d.body); | |
| return d.evaluate(path, root, null, 7, null); | |
| }; | |
| $.addClass = function(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 __indexOf.call(el.classList, className) >= 0; | |
| }; | |
| $.rm = (function() { | |
| if ('remove' in Element.prototype) { | |
| return function(el) { | |
| return el.remove(); | |
| }; | |
| } else { | |
| return function(el) { | |
| var _ref; | |
| return (_ref = el.parentNode) != null ? _ref.removeChild(el) : void 0; | |
| }; | |
| } | |
| })(); | |
| $.rmAll = function(root) { | |
| var node, _i, _len, _ref; | |
| _ref = __slice.call(root.childNodes); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| node = _ref[_i]; | |
| root.removeChild(node); | |
| } | |
| }; | |
| $.tn = function(s) { | |
| return d.createTextNode(s); | |
| }; | |
| $.frag = function() { | |
| return d.createDocumentFragment(); | |
| }; | |
| $.nodes = function(nodes) { | |
| var frag, node, _i, _len; | |
| if (!(nodes instanceof Array)) { | |
| return nodes; | |
| } | |
| frag = $.frag(); | |
| for (_i = 0, _len = nodes.length; _i < _len; _i++) { | |
| node = nodes[_i]; | |
| frag.appendChild(node); | |
| } | |
| return frag; | |
| }; | |
| $.add = function(parent, el) { | |
| return parent.appendChild($.nodes(el)); | |
| }; | |
| $.prepend = function(parent, el) { | |
| return parent.insertBefore($.nodes(el), parent.firstChild); | |
| }; | |
| $.after = function(root, el) { | |
| return root.parentNode.insertBefore($.nodes(el), root.nextSibling); | |
| }; | |
| $.before = function(root, el) { | |
| return root.parentNode.insertBefore($.nodes(el), root); | |
| }; | |
| $.replace = function(root, el) { | |
| return root.parentNode.replaceChild($.nodes(el), root); | |
| }; | |
| $.el = function(tag, properties) { | |
| var el; | |
| el = d.createElement(tag); | |
| if (properties) { | |
| $.extend(el, properties); | |
| } | |
| return el; | |
| }; | |
| $.on = function(el, events, handler) { | |
| var event, _i, _len, _ref; | |
| _ref = events.split(' '); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| event = _ref[_i]; | |
| el.addEventListener(event, handler, false); | |
| } | |
| }; | |
| $.off = function(el, events, handler) { | |
| var event, _i, _len, _ref; | |
| _ref = events.split(' '); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| event = _ref[_i]; | |
| el.removeEventListener(event, handler, false); | |
| } | |
| }; | |
| $.event = function(event, detail, root) { | |
| if (root == null) { | |
| root = d; | |
| } | |
| return root.dispatchEvent(new CustomEvent(event, { | |
| bubbles: true, | |
| detail: detail | |
| })); | |
| }; | |
| $.open = GM_openInTab; | |
| $.debounce = function(wait, fn) { | |
| var args, exec, lastCall, that, timeout; | |
| lastCall = 0; | |
| timeout = null; | |
| that = null; | |
| args = null; | |
| exec = function() { | |
| lastCall = Date.now(); | |
| return fn.apply(that, args); | |
| }; | |
| return function() { | |
| args = arguments; | |
| that = this; | |
| if (lastCall < Date.now() - wait) { | |
| return exec(); | |
| } | |
| clearTimeout(timeout); | |
| return timeout = setTimeout(exec, wait); | |
| }; | |
| }; | |
| $.queueTask = (function() { | |
| var execTask, taskChannel, taskQueue; | |
| taskQueue = []; | |
| execTask = function() { | |
| var args, func, task; | |
| task = taskQueue.shift(); | |
| func = task[0]; | |
| args = Array.prototype.slice.call(task, 1); | |
| return func.apply(func, args); | |
| }; | |
| if (window.MessageChannel) { | |
| taskChannel = new MessageChannel(); | |
| taskChannel.port1.onmessage = execTask; | |
| return function() { | |
| taskQueue.push(arguments); | |
| return taskChannel.port2.postMessage(null); | |
| }; | |
| } else { | |
| return function() { | |
| taskQueue.push(arguments); | |
| return setTimeout(execTask, 0); | |
| }; | |
| } | |
| })(); | |
| $.globalEval = function(code) { | |
| var script; | |
| script = $.el('script', { | |
| textContent: code | |
| }); | |
| $.add(d.head || doc, script); | |
| return $.rm(script); | |
| }; | |
| $.bytesToString = function(size) { | |
| var unit; | |
| unit = 0; | |
| while (size >= 1024) { | |
| size /= 1024; | |
| unit++; | |
| } | |
| size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size); | |
| return "" + size + " " + ['B', 'KB', 'MB', 'GB'][unit]; | |
| }; | |
| $.minmax = function(value, min, max) { | |
| return (value < min ? min : value > max ? max : value); | |
| }; | |
| $.item = function(key, val) { | |
| var item; | |
| item = {}; | |
| item[key] = val; | |
| return item; | |
| }; | |
| $.syncing = {}; | |
| $.sync = (function() { | |
| $.on(window, 'storage', function(_arg) { | |
| var cb, key, newValue; | |
| key = _arg.key, newValue = _arg.newValue; | |
| if (cb = $.syncing[key]) { | |
| return cb(JSON.parse(newValue), key); | |
| } | |
| }); | |
| return function(key, cb) { | |
| return $.syncing[g.NAMESPACE + key] = cb; | |
| }; | |
| })(); | |
| $["delete"] = function(keys) { | |
| var key, _i, _len; | |
| if (!(keys instanceof Array)) { | |
| keys = [keys]; | |
| } | |
| for (_i = 0, _len = keys.length; _i < _len; _i++) { | |
| key = keys[_i]; | |
| key = g.NAMESPACE + key; | |
| localStorage.removeItem(key); | |
| GM_deleteValue(key); | |
| } | |
| }; | |
| $.get = function(key, val, cb) { | |
| var items; | |
| if (typeof cb === 'function') { | |
| items = $.item(key, val); | |
| } else { | |
| items = key; | |
| cb = val; | |
| } | |
| return $.queueTask(function() { | |
| for (key in items) { | |
| if (val = GM_getValue(g.NAMESPACE + key)) { | |
| items[key] = JSON.parse(val); | |
| } | |
| } | |
| return cb(items); | |
| }); | |
| }; | |
| $.set = (function() { | |
| var set; | |
| set = function(key, val) { | |
| key = g.NAMESPACE + key; | |
| val = JSON.stringify(val); | |
| if (key in $.syncing) { | |
| localStorage.setItem(key, val); | |
| } | |
| return GM_setValue(key, val); | |
| }; | |
| return function(keys, val) { | |
| var key; | |
| if (typeof keys === 'string') { | |
| set(keys, val); | |
| return; | |
| } | |
| for (key in keys) { | |
| val = keys[key]; | |
| set(key, val); | |
| } | |
| }; | |
| })(); | |
| $$ = function(selector, root) { | |
| if (root == null) { | |
| root = d.body; | |
| } | |
| return __slice.call(root.querySelectorAll(selector)); | |
| }; | |
| Callbacks = (function() { | |
| function Callbacks() {} | |
| Callbacks.prototype.push = function(_arg) { | |
| var cb, name; | |
| name = _arg.name, cb = _arg.cb; | |
| return this[name] = cb; | |
| }; | |
| Callbacks.prototype.clean = function() { | |
| var name; | |
| for (name in this) { | |
| if (this.hasOwnProperty(name)) { | |
| this.rm(name); | |
| } | |
| } | |
| }; | |
| Callbacks.prototype.rm = function(name) { | |
| return delete this[name]; | |
| }; | |
| Callbacks.prototype.execute = function(node) { | |
| var err, errors, name; | |
| for (name in this) { | |
| if (this.hasOwnProperty(name)) { | |
| try { | |
| this[name].call(node); | |
| } catch (_error) { | |
| err = _error; | |
| if (!errors) { | |
| errors = []; | |
| } | |
| errors.push({ | |
| message: ['"', name, '" crashed on node No.', node, ' (', node.board, ').'].join(''), | |
| error: err | |
| }); | |
| } | |
| } | |
| } | |
| if (errors) { | |
| return Main.handleErrors(errors); | |
| } | |
| }; | |
| return Callbacks; | |
| })(); | |
| Board = (function() { | |
| Board.prototype.toString = function() { | |
| return this.ID; | |
| }; | |
| function Board(ID) { | |
| this.ID = ID; | |
| this.threads = {}; | |
| this.posts = {}; | |
| g.boards[this] = this; | |
| } | |
| return Board; | |
| })(); | |
| Thread = (function() { | |
| Thread.callbacks = new Callbacks(); | |
| Thread.prototype.toString = function() { | |
| return this.ID; | |
| }; | |
| function Thread(ID, board) { | |
| this.ID = ID; | |
| this.board = board; | |
| this.fullID = "" + this.board + "." + this.ID; | |
| this.posts = {}; | |
| this.isSticky = false; | |
| this.isClosed = false; | |
| this.postLimit = false; | |
| this.fileLimit = false; | |
| g.threads[this.fullID] = board.threads[this] = this; | |
| } | |
| Thread.prototype.setPage = function(pageNum) { | |
| var icon, key, _i, _len, _ref; | |
| icon = $('.page-num', this.OP.nodes.post); | |
| _ref = ['title', 'textContent']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| key = _ref[_i]; | |
| icon[key] = icon[key].replace(/\d+/, pageNum); | |
| } | |
| }; | |
| Thread.prototype.setStatus = function(type, status) { | |
| var icon, name, root, typeLC; | |
| name = "is" + type; | |
| if (this[name] === status) { | |
| return; | |
| } | |
| this[name] = status; | |
| if (!this.OP) { | |
| return; | |
| } | |
| typeLC = type.toLowerCase(); | |
| if (!status) { | |
| $.rm($("." + typeLC + "Icon", this.OP.nodes.info)); | |
| return; | |
| } | |
| icon = $.el('img', { | |
| src: "//s.4cdn.org/image/" + typeLC + (window.devicePixelRatio >= 2 ? '@2x' : '') + ".gif", | |
| alt: type, | |
| title: type, | |
| className: "" + typeLC + "Icon" | |
| }); | |
| root = type === 'Closed' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : g.VIEW === 'index' ? $('.page-num', this.OP.nodes.info) : $('[title="Quote this post"]', this.OP.nodes.info); | |
| return $.after(root, [$.tn(' '), icon]); | |
| }; | |
| Thread.prototype.kill = function() { | |
| this.isDead = true; | |
| return this.timeOfDeath = Date.now(); | |
| }; | |
| Thread.prototype.collect = function() { | |
| var post, postID, _i, _len, _ref; | |
| _ref = this.posts; | |
| for (post = _i = 0, _len = _ref.length; _i < _len; post = ++_i) { | |
| postID = _ref[post]; | |
| post.collect(); | |
| } | |
| delete g.threads[this.fullID]; | |
| return delete this.board.threads[this]; | |
| }; | |
| return Thread; | |
| })(); | |
| Post = (function() { | |
| Post.callbacks = new Callbacks(); | |
| Post.prototype.toString = function() { | |
| return this.ID; | |
| }; | |
| function Post(root, thread, board, that) { | |
| var capcode, date, email, flag, info, name, post, subject, tripcode, uniqueID; | |
| this.thread = thread; | |
| this.board = board; | |
| if (that == null) { | |
| that = {}; | |
| } | |
| this.ID = +root.id.slice(2); | |
| this.fullID = "" + this.board + "." + this.ID; | |
| if (that.isOriginalMarkup) { | |
| this.cleanup(root); | |
| } | |
| post = $('.post', root); | |
| info = $('.postInfo', post); | |
| this.nodes = { | |
| root: root, | |
| post: post, | |
| info: info, | |
| comment: $('.postMessage', post), | |
| links: [], | |
| quotelinks: [], | |
| backlinks: info.getElementsByClassName('backlink') | |
| }; | |
| if (!(this.isReply = $.hasClass(post, 'reply'))) { | |
| this.thread.OP = this; | |
| this.thread.isSticky = !!$('.stickyIcon', info); | |
| this.thread.isClosed = !!$('.closedIcon', info); | |
| } | |
| this.info = {}; | |
| if (subject = $('.subject', info)) { | |
| this.nodes.subject = subject; | |
| this.info.subject = subject.textContent; | |
| } | |
| if (name = $('.name', info)) { | |
| this.nodes.name = name; | |
| this.info.name = name.textContent; | |
| } | |
| if (email = $('.useremail', info)) { | |
| this.nodes.email = email; | |
| this.info.email = decodeURIComponent(email.href.slice(7)); | |
| } | |
| if (tripcode = $('.postertrip', info)) { | |
| this.nodes.tripcode = tripcode; | |
| this.info.tripcode = tripcode.textContent; | |
| } | |
| if (uniqueID = $('.posteruid', info)) { | |
| this.nodes.uniqueID = uniqueID; | |
| this.info.uniqueID = uniqueID.firstElementChild.textContent; | |
| } | |
| if (capcode = $('.capcode.hand', info)) { | |
| this.nodes.capcode = capcode; | |
| this.info.capcode = capcode.textContent.replace('## ', ''); | |
| } | |
| if (flag = $('.flag, .countryFlag', info)) { | |
| this.nodes.flag = flag; | |
| this.info.flag = flag.title; | |
| } | |
| if (date = $('.dateTime', info)) { | |
| this.nodes.date = date; | |
| this.info.date = new Date(date.dataset.utc * 1000); | |
| } | |
| if (Conf['Quick Reply']) { | |
| this.info.yours = QR.db.get({ | |
| boardID: this.board, | |
| threadID: this.thread, | |
| postID: this.ID | |
| }); | |
| } | |
| this.parseComment(); | |
| this.parseQuotes(); | |
| this.parseFile(that); | |
| this.clones = []; | |
| g.posts[this.fullID] = thread.posts[this] = board.posts[this] = this; | |
| if (that.isArchived) { | |
| this.kill(); | |
| } | |
| } | |
| Post.prototype.parseComment = function() { | |
| var bq, i, node, nodes, text; | |
| this.nodes.comment.normalize(); | |
| bq = this.nodes.comment.cloneNode(true); | |
| nodes = $$('.abbr, .exif, b', bq); | |
| i = 0; | |
| while (node = nodes[i++]) { | |
| $.rm(node); | |
| } | |
| text = ""; | |
| nodes = $.X('.//br|.//text()', bq); | |
| i = 0; | |
| while (node = nodes.snapshotItem(i++)) { | |
| text += node.data || '\n'; | |
| } | |
| return this.info.comment = text.trim().replace(/\s+$/gm, ''); | |
| }; | |
| Post.prototype.parseQuotes = function() { | |
| var quotelink, _i, _len, _ref; | |
| this.quotes = []; | |
| _ref = $$('.quotelink', this.nodes.comment); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quotelink = _ref[_i]; | |
| this.parseQuote(quotelink); | |
| } | |
| }; | |
| Post.prototype.parseQuote = function(quotelink) { | |
| var fullID, match; | |
| if (!(match = quotelink.href.match(/boards\.4chan\.org\/([^\/]+)\/res\/\d+#p(\d+)$/))) { | |
| return; | |
| } | |
| this.nodes.quotelinks.push(quotelink); | |
| if (this.isClone) { | |
| return; | |
| } | |
| fullID = "" + match[1] + "." + match[2]; | |
| if (__indexOf.call(this.quotes, fullID) < 0) { | |
| return this.quotes.push(fullID); | |
| } | |
| }; | |
| Post.prototype.parseFile = function(that) { | |
| var anchor, fileEl, fileText, nameNode, size, thumb, unit; | |
| if (!((fileEl = $('.file', this.nodes.post)) && (thumb = $('img[data-md5]', fileEl)))) { | |
| return; | |
| } | |
| anchor = thumb.parentNode; | |
| fileText = fileEl.firstElementChild; | |
| this.file = { | |
| text: fileText, | |
| thumb: thumb, | |
| URL: anchor.href, | |
| size: thumb.alt.match(/[\d.]+\s\w+/)[0], | |
| MD5: thumb.dataset.md5, | |
| isSpoiler: $.hasClass(anchor, 'imgspoiler') | |
| }; | |
| size = +this.file.size.match(/[\d.]+/)[0]; | |
| unit = ['B', 'KB', 'MB', 'GB'].indexOf(this.file.size.match(/\w+$/)[0]); | |
| while (unit-- > 0) { | |
| size *= 1024; | |
| } | |
| this.file.sizeInBytes = size; | |
| this.file.thumbURL = that.isArchived ? thumb.src : "" + location.protocol + "//t.4cdn.org/" + this.board + "/thumb/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg"; | |
| this.file.name = (nameNode = $('span', fileText)) ? nameNode.title || nameNode.textContent : fileText.title; | |
| if (this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name)) { | |
| return this.file.dimensions = fileText.textContent.match(/\d+x\d+/)[0]; | |
| } | |
| }; | |
| Post.prototype.cleanup = function(root) { | |
| var node, _i, _j, _len, _len1, _ref, _ref1; | |
| _ref = $$('.mobile', root); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| node = _ref[_i]; | |
| $.rm(node); | |
| } | |
| _ref1 = $$('.desktop', root); | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| node = _ref1[_j]; | |
| $.rmClass(node, 'desktop'); | |
| } | |
| }; | |
| Post.prototype.kill = function(file, now) { | |
| var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1; | |
| now || (now = new Date()); | |
| if (file) { | |
| if (this.file.isDead) { | |
| return; | |
| } | |
| this.file.isDead = true; | |
| this.file.timeOfDeath = now; | |
| $.addClass(this.nodes.root, 'deleted-file'); | |
| } else { | |
| if (this.isDead) { | |
| return; | |
| } | |
| this.isDead = true; | |
| this.timeOfDeath = now; | |
| $.addClass(this.nodes.root, 'deleted-post'); | |
| } | |
| if (!(strong = $('strong.warning', this.nodes.info))) { | |
| strong = $.el('strong', { | |
| className: 'warning', | |
| textContent: this.isReply ? '[Deleted]' : '[Dead]' | |
| }); | |
| $.after($('input', this.nodes.info), strong); | |
| } | |
| strong.textContent = file ? '[File deleted]' : '[Deleted]'; | |
| if (this.isClone) { | |
| return; | |
| } | |
| _ref = this.clones; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| clone = _ref[_i]; | |
| clone.kill(file, now); | |
| } | |
| if (file) { | |
| return; | |
| } | |
| _ref1 = Get.allQuotelinksLinkingTo(this); | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| quotelink = _ref1[_j]; | |
| if (!(!$.hasClass(quotelink, 'deadlink'))) { | |
| continue; | |
| } | |
| $.add(quotelink, $.tn('\u00A0(Dead)')); | |
| $.addClass(quotelink, 'deadlink'); | |
| } | |
| }; | |
| Post.prototype.resurrect = function() { | |
| var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1; | |
| delete this.isDead; | |
| delete this.timeOfDeath; | |
| $.rmClass(this.nodes.root, 'deleted-post'); | |
| strong = $('strong.warning', this.nodes.info); | |
| if (this.file && this.file.isDead) { | |
| strong.textContent = '[File deleted]'; | |
| } else { | |
| $.rm(strong); | |
| } | |
| if (this.isClone) { | |
| return; | |
| } | |
| _ref = this.clones; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| clone = _ref[_i]; | |
| clone.resurrect(); | |
| } | |
| _ref1 = Get.allQuotelinksLinkingTo(this); | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| quotelink = _ref1[_j]; | |
| if ($.hasClass(quotelink, 'deadlink')) { | |
| quotelink.textContent = quotelink.textContent.replace('\u00A0(Dead)', ''); | |
| $.rmClass(quotelink, 'deadlink'); | |
| } | |
| } | |
| }; | |
| Post.prototype.collect = function() { | |
| this.kill(); | |
| delete g.posts[this.fullID]; | |
| delete this.thread.posts[this]; | |
| return delete this.board.posts[this]; | |
| }; | |
| Post.prototype.addClone = function(context) { | |
| return new Clone(this, context); | |
| }; | |
| Post.prototype.rmClone = function(index) { | |
| var clone, _i, _len, _ref; | |
| this.clones.splice(index, 1); | |
| _ref = this.clones.slice(index); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| clone = _ref[_i]; | |
| clone.nodes.root.dataset.clone = index++; | |
| } | |
| }; | |
| return Post; | |
| })(); | |
| Clone = (function(_super) { | |
| __extends(Clone, _super); | |
| function Clone(origin, context) { | |
| var file, info, inline, inlined, key, nodes, post, root, val, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3; | |
| this.origin = origin; | |
| this.context = context; | |
| _ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| key = _ref[_i]; | |
| this[key] = origin[key]; | |
| } | |
| nodes = origin.nodes; | |
| root = nodes.root.cloneNode(true); | |
| post = $('.post', root); | |
| info = $('.postInfo', post); | |
| this.nodes = { | |
| root: root, | |
| post: post, | |
| info: info, | |
| comment: $('.postMessage', post), | |
| quotelinks: [], | |
| backlinks: info.getElementsByClassName('backlink') | |
| }; | |
| _ref1 = $$('.inline', post); | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| inline = _ref1[_j]; | |
| $.rm(inline); | |
| } | |
| _ref2 = $$('.inlined', post); | |
| for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { | |
| inlined = _ref2[_k]; | |
| $.rmClass(inlined, 'inlined'); | |
| } | |
| root.hidden = false; | |
| $.rmClass(root, 'forwarded'); | |
| $.rmClass(post, 'highlight'); | |
| if (nodes.subject) { | |
| this.nodes.subject = $('.subject', info); | |
| } | |
| if (nodes.name) { | |
| this.nodes.name = $('.name', info); | |
| } | |
| if (nodes.email) { | |
| this.nodes.email = $('.useremail', info); | |
| } | |
| if (nodes.tripcode) { | |
| this.nodes.tripcode = $('.postertrip', info); | |
| } | |
| if (nodes.uniqueID) { | |
| this.nodes.uniqueID = $('.posteruid', info); | |
| } | |
| if (nodes.capcode) { | |
| this.nodes.capcode = $('.capcode', info); | |
| } | |
| if (nodes.flag) { | |
| this.nodes.flag = $('.countryFlag', info); | |
| } | |
| if (nodes.date) { | |
| this.nodes.date = $('.dateTime', info); | |
| } | |
| this.parseQuotes(); | |
| if (origin.file) { | |
| this.file = {}; | |
| _ref3 = origin.file; | |
| for (key in _ref3) { | |
| val = _ref3[key]; | |
| this.file[key] = val; | |
| } | |
| file = $('.file', post); | |
| this.file.text = file.firstElementChild; | |
| this.file.thumb = $('img[data-md5]', file); | |
| this.file.fullImage = $('.full-image', file); | |
| } | |
| if (origin.isDead) { | |
| this.isDead = true; | |
| } | |
| this.isClone = true; | |
| root.dataset.clone = origin.clones.push(this) - 1; | |
| } | |
| return Clone; | |
| })(Post); | |
| DataBoard = (function() { | |
| DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads']; | |
| function DataBoard(key, sync, dontClean) { | |
| var init, | |
| _this = this; | |
| this.key = key; | |
| this.onSync = __bind(this.onSync, this); | |
| this.data = Conf[key]; | |
| $.sync(key, this.onSync); | |
| if (!dontClean) { | |
| this.clean(); | |
| } | |
| if (!sync) { | |
| return; | |
| } | |
| init = function() { | |
| $.off(d, '4chanXInitFinished', init); | |
| return _this.sync = sync; | |
| }; | |
| $.on(d, '4chanXInitFinished', init); | |
| } | |
| DataBoard.prototype.save = function() { | |
| return $.set(this.key, this.data); | |
| }; | |
| DataBoard.prototype["delete"] = function(_arg) { | |
| var boardID, postID, threadID; | |
| boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID; | |
| if (postID) { | |
| delete this.data.boards[boardID][threadID][postID]; | |
| this.deleteIfEmpty({ | |
| boardID: boardID, | |
| threadID: threadID | |
| }); | |
| } else if (threadID) { | |
| delete this.data.boards[boardID][threadID]; | |
| this.deleteIfEmpty({ | |
| boardID: boardID | |
| }); | |
| } else { | |
| delete this.data.boards[boardID]; | |
| } | |
| return this.save(); | |
| }; | |
| DataBoard.prototype.deleteIfEmpty = function(_arg) { | |
| var boardID, threadID; | |
| boardID = _arg.boardID, threadID = _arg.threadID; | |
| if (threadID) { | |
| if (!Object.keys(this.data.boards[boardID][threadID]).length) { | |
| delete this.data.boards[boardID][threadID]; | |
| return this.deleteIfEmpty({ | |
| boardID: boardID | |
| }); | |
| } | |
| } else if (!Object.keys(this.data.boards[boardID]).length) { | |
| return delete this.data.boards[boardID]; | |
| } | |
| }; | |
| DataBoard.prototype.set = function(_arg) { | |
| var boardID, postID, threadID, val, _base, _base1, _base2; | |
| boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, val = _arg.val; | |
| if (postID !== void 0) { | |
| ((_base = ((_base1 = this.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[postID] = val; | |
| } else if (threadID !== void 0) { | |
| ((_base2 = this.data.boards)[boardID] || (_base2[boardID] = {}))[threadID] = val; | |
| } else { | |
| this.data.boards[boardID] = val; | |
| } | |
| return this.save(); | |
| }; | |
| DataBoard.prototype.get = function(_arg) { | |
| var ID, board, boardID, defaultValue, postID, thread, threadID, val, _i, _len; | |
| boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, defaultValue = _arg.defaultValue; | |
| if (board = this.data.boards[boardID]) { | |
| if (!threadID) { | |
| if (postID) { | |
| for (thread = _i = 0, _len = board.length; _i < _len; thread = ++_i) { | |
| ID = board[thread]; | |
| if (postID in thread) { | |
| val = thread[postID]; | |
| break; | |
| } | |
| } | |
| } else { | |
| val = board; | |
| } | |
| } else if (thread = board[threadID]) { | |
| val = postID ? thread[postID] : thread; | |
| } | |
| } | |
| return val || defaultValue; | |
| }; | |
| DataBoard.prototype.clean = function() { | |
| var boardID, now, val, _ref; | |
| _ref = this.data.boards; | |
| for (boardID in _ref) { | |
| val = _ref[boardID]; | |
| this.deleteIfEmpty({ | |
| boardID: boardID | |
| }); | |
| } | |
| now = Date.now(); | |
| if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { | |
| this.data.lastChecked = now; | |
| for (boardID in this.data.boards) { | |
| this.ajaxClean(boardID); | |
| } | |
| } | |
| return this.save(); | |
| }; | |
| DataBoard.prototype.ajaxClean = function(boardID) { | |
| var _this = this; | |
| return $.cache("//a.4cdn.org/" + boardID + "/threads.json", function(e) { | |
| var board, page, thread, threads, _i, _j, _len, _len1, _ref, _ref1; | |
| if (e.target.status !== 200) { | |
| if (e.target.status === 404) { | |
| _this["delete"](boardID); | |
| } | |
| return; | |
| } | |
| board = _this.data.boards[boardID]; | |
| threads = {}; | |
| _ref = JSON.parse(e.target.response); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| page = _ref[_i]; | |
| _ref1 = page.threads; | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| thread = _ref1[_j]; | |
| if (thread.no in board) { | |
| threads[thread.no] = board[thread.no]; | |
| } | |
| } | |
| } | |
| _this.data.boards[boardID] = threads; | |
| _this.deleteIfEmpty({ | |
| boardID: boardID | |
| }); | |
| return _this.save(); | |
| }); | |
| }; | |
| DataBoard.prototype.onSync = function(data) { | |
| this.data = data || { | |
| boards: {} | |
| }; | |
| return typeof this.sync === "function" ? this.sync() : void 0; | |
| }; | |
| return DataBoard; | |
| })(); | |
| Notice = (function() { | |
| function Notice(type, content, timeout) { | |
| this.timeout = timeout; | |
| this.close = __bind(this.close, this); | |
| this.add = __bind(this.add, this); | |
| this.el = $.el('div', { | |
| innerHTML: '<a href=javascript:; class="close fa fa-times" title=Close></a><div class=message></div>' | |
| }); | |
| this.el.style.opacity = 0; | |
| this.setType(type); | |
| $.on(this.el.firstElementChild, 'click', this.close); | |
| if (typeof content === 'string') { | |
| content = $.tn(content); | |
| } | |
| $.add(this.el.lastElementChild, content); | |
| $.ready(this.add); | |
| } | |
| Notice.prototype.setType = function(type) { | |
| return this.el.className = "notification " + type; | |
| }; | |
| Notice.prototype.add = function() { | |
| if (d.hidden) { | |
| $.on(d, 'visibilitychange', this.add); | |
| return; | |
| } | |
| $.off(d, 'visibilitychange', this.add); | |
| $.add(Header.noticesRoot, this.el); | |
| this.el.clientHeight; | |
| this.el.style.opacity = 1; | |
| if (this.timeout) { | |
| return setTimeout(this.close, this.timeout * $.SECOND); | |
| } | |
| }; | |
| Notice.prototype.close = function() { | |
| $.off(d, 'visibilitychange', this.add); | |
| return $.rm(this.el); | |
| }; | |
| return Notice; | |
| })(); | |
| RandomAccessList = (function() { | |
| function RandomAccessList() { | |
| this.length = 0; | |
| } | |
| RandomAccessList.prototype.push = function(item) { | |
| var ID, last; | |
| ID = item.ID; | |
| if (this[ID]) { | |
| return; | |
| } | |
| last = this.last; | |
| item.prev = last; | |
| this[ID] = item; | |
| this.last = last ? last.next = item : this.first = item; | |
| return this.length++; | |
| }; | |
| RandomAccessList.prototype.after = function(root, item) { | |
| var next; | |
| if (item.prev === root) { | |
| return; | |
| } | |
| this.rmi(item); | |
| next = root.next; | |
| root.next = item; | |
| item.prev = root; | |
| item.next = next; | |
| return next.prev = item; | |
| }; | |
| RandomAccessList.prototype.prepend = function(item) { | |
| var first; | |
| first = this.first; | |
| if (item === first || !this[item.ID]) { | |
| return; | |
| } | |
| this.rmi(item); | |
| item.next = first; | |
| first.prev = item; | |
| this.first = item; | |
| return delete item.prev; | |
| }; | |
| RandomAccessList.prototype.shift = function() { | |
| return this.rm(this.first.ID); | |
| }; | |
| RandomAccessList.prototype.rm = function(ID) { | |
| var item; | |
| item = this[ID]; | |
| if (!item) { | |
| return; | |
| } | |
| delete this[ID]; | |
| this.length--; | |
| this.rmi(item); | |
| delete item.next; | |
| return delete item.prev; | |
| }; | |
| RandomAccessList.prototype.rmi = function(item) { | |
| var next, prev; | |
| prev = item.prev, next = item.next; | |
| if (prev) { | |
| prev.next = next; | |
| } else { | |
| this.first = next; | |
| } | |
| if (next) { | |
| return next.prev = prev; | |
| } else { | |
| return this.last = prev; | |
| } | |
| }; | |
| return RandomAccessList; | |
| })(); | |
| Polyfill = { | |
| init: function() {}, | |
| notificationPermission: function() { | |
| if (!window.Notification || 'permission' in Notification || !window.webkitNotifications) { | |
| return; | |
| } | |
| return Object.defineProperty(Notification, 'permission', { | |
| get: function() { | |
| switch (webkitNotifications.checkPermission()) { | |
| case 0: | |
| return 'granted'; | |
| case 1: | |
| return 'default'; | |
| case 2: | |
| return 'denied'; | |
| } | |
| } | |
| }); | |
| }, | |
| toBlob: function() { | |
| var _base; | |
| return (_base = HTMLCanvasElement.prototype).toBlob || (_base.toBlob = function(cb) { | |
| var data, i, l, ui8a, _i; | |
| data = atob(this.toDataURL().slice(22)); | |
| l = data.length; | |
| ui8a = new Uint8Array(l); | |
| for (i = _i = 0; _i < l; i = _i += 1) { | |
| ui8a[i] = data.charCodeAt(i); | |
| } | |
| return cb(new Blob([ui8a], { | |
| type: 'image/png' | |
| })); | |
| }); | |
| }, | |
| visibility: function() { | |
| if ('visibilityState' in d) { | |
| return; | |
| } | |
| Object.defineProperties(HTMLDocument.prototype, { | |
| visibilityState: { | |
| get: function() { | |
| return this.webkitVisibilityState; | |
| } | |
| }, | |
| hidden: { | |
| get: function() { | |
| return this.webkitHidden; | |
| } | |
| } | |
| }); | |
| return $.on(d, 'webkitvisibilitychange', function() { | |
| return $.event('visibilitychange'); | |
| }); | |
| } | |
| }; | |
| Header = { | |
| init: function() { | |
| var barFixedToggler, barPositionToggler, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler, | |
| _this = this; | |
| this.menu = new UI.Menu('header'); | |
| menuButton = $.el('span', { | |
| className: 'menu-button', | |
| innerHTML: '<i></i>' | |
| }); | |
| barFixedToggler = $.el('label', { | |
| innerHTML: '<input type=checkbox name="Fixed Header"> Fixed Header' | |
| }); | |
| headerToggler = $.el('label', { | |
| innerHTML: '<input type=checkbox name="Header auto-hide"> Auto-hide header' | |
| }); | |
| scrollHeaderToggler = $.el('label', { | |
| innerHTML: '<input type=checkbox name="Header auto-hide on scroll"> Auto-hide header on scroll' | |
| }); | |
| barPositionToggler = $.el('label', { | |
| innerHTML: '<input type=checkbox name="Bottom Header"> Bottom header' | |
| }); | |
| linkJustifyToggler = $.el('label', { | |
| innerHTML: "<input type=checkbox " + (Conf['Centered links'] ? 'checked' : '') + "> Centered links" | |
| }); | |
| customNavToggler = $.el('label', { | |
| innerHTML: '<input type=checkbox name="Custom Board Navigation"> Custom board navigation' | |
| }); | |
| footerToggler = $.el('label', { | |
| innerHTML: "<input type=checkbox " + (!Conf['Bottom Board List'] ? 'checked' : '') + "> Hide bottom board list" | |
| }); | |
| shortcutToggler = $.el('label', { | |
| innerHTML: "<input type=checkbox " + (!Conf['Shortcut Icons'] ? 'checked' : '') + "> Shortcut Icons" | |
| }); | |
| editCustomNav = $.el('a', { | |
| textContent: 'Edit custom board navigation', | |
| href: 'javascript:;' | |
| }); | |
| this.barFixedToggler = barFixedToggler.firstElementChild; | |
| this.scrollHeaderToggler = scrollHeaderToggler.firstElementChild; | |
| this.barPositionToggler = barPositionToggler.firstElementChild; | |
| this.linkJustifyToggler = linkJustifyToggler.firstElementChild; | |
| this.headerToggler = headerToggler.firstElementChild; | |
| this.footerToggler = footerToggler.firstElementChild; | |
| this.shortcutToggler = shortcutToggler.firstElementChild; | |
| this.customNavToggler = customNavToggler.firstElementChild; | |
| $.on(menuButton, 'click', this.menuToggle); | |
| $.on(this.headerToggler, 'change', this.toggleBarVisibility); | |
| $.on(this.barFixedToggler, 'change', this.toggleBarFixed); | |
| $.on(this.barPositionToggler, 'change', this.toggleBarPosition); | |
| $.on(this.scrollHeaderToggler, 'change', this.toggleHideBarOnScroll); | |
| $.on(this.linkJustifyToggler, 'change', this.toggleLinkJustify); | |
| $.on(this.headerToggler, 'change', this.toggleBarVisibility); | |
| $.on(this.footerToggler, 'change', this.toggleFooterVisibility); | |
| $.on(this.shortcutToggler, 'change', this.toggleShortcutIcons); | |
| $.on(this.customNavToggler, 'change', this.toggleCustomNav); | |
| $.on(editCustomNav, 'click', this.editCustomNav); | |
| this.setBarFixed(Conf['Fixed Header']); | |
| this.setHideBarOnScroll(Conf['Header auto-hide on scroll']); | |
| this.setBarVisibility(Conf['Header auto-hide']); | |
| this.setLinkJustify(Conf['Centered links']); | |
| this.setShortcutIcons(Conf['Shortcut Icons']); | |
| $.sync('Fixed Header', this.setBarFixed); | |
| $.sync('Header auto-hide on scroll', this.setHideBarOnScroll); | |
| $.sync('Bottom Header', this.setBarPosition); | |
| $.sync('Shortcut Icons', this.setShortcutIcons); | |
| $.sync('Header auto-hide', this.setBarVisibility); | |
| $.sync('Centered links', this.setLinkJustify); | |
| this.addShortcut(menuButton); | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: $.el('span', { | |
| textContent: 'Header' | |
| }), | |
| order: 107, | |
| subEntries: [ | |
| { | |
| el: barFixedToggler | |
| }, { | |
| el: headerToggler | |
| }, { | |
| el: scrollHeaderToggler | |
| }, { | |
| el: barPositionToggler | |
| }, { | |
| el: linkJustifyToggler | |
| }, { | |
| el: footerToggler | |
| }, { | |
| el: shortcutToggler | |
| }, { | |
| el: customNavToggler | |
| }, { | |
| el: editCustomNav | |
| } | |
| ] | |
| }); | |
| $.on(window, 'load hashchange', Header.hashScroll); | |
| $.on(d, 'CreateNotification', this.createNotification); | |
| $.asap((function() { | |
| return d.body; | |
| }), function() { | |
| if (!Main.isThisPageLegit()) { | |
| return; | |
| } | |
| $.asap((function() { | |
| return $.id('boardNavMobile') || d.readyState !== 'loading'; | |
| }), Header.setBoardList); | |
| $.prepend(d.body, _this.bar); | |
| $.add(d.body, Header.hover); | |
| _this.setBarPosition(Conf['Bottom Header']); | |
| return _this; | |
| }); | |
| $.ready(function() { | |
| var a, cs, footer; | |
| _this.footer = footer = $.id('boardNavDesktopFoot'); | |
| if (a = $("a[href*='/" + g.BOARD + "/']", footer)) { | |
| a.className = 'current'; | |
| $.on(a, 'click', Index.cb.link); | |
| } | |
| cs = $.el('a', { | |
| id: 'settingsWindowLink', | |
| href: 'javascript:;', | |
| textContent: 'Catalog Settings' | |
| }); | |
| if (g.VIEW === 'catalog') { | |
| _this.addShortcut(cs); | |
| } | |
| Header.setFooterVisibility(Conf['Bottom Board List']); | |
| return $.sync('Bottom Board List', Header.setFooterVisibility); | |
| }); | |
| return this.enableDesktopNotifications(); | |
| }, | |
| bar: $.el('div', { | |
| id: 'header-bar' | |
| }), | |
| noticesRoot: $.el('div', { | |
| id: 'notifications' | |
| }), | |
| shortcuts: $.el('span', { | |
| id: 'shortcuts' | |
| }), | |
| hover: $.el('div', { | |
| id: 'hoverUI' | |
| }), | |
| toggle: $.el('div', { | |
| id: 'scroll-marker' | |
| }), | |
| setBoardList: function() { | |
| var a, boardList, btn, fourchannav, fullBoardList; | |
| fourchannav = $.id('boardNavDesktop'); | |
| boardList = $.el('span', { | |
| id: 'board-list', | |
| innerHTML: "<span id=custom-board-list></span><span id=full-board-list hidden><span class='hide-board-list-container brackets-wrap'><a href=javascript:; class='hide-board-list-button'> - </a></span> " + fourchannav.innerHTML + "</span>" | |
| }); | |
| if (a = $("a[href*='/" + g.BOARD + "/']", boardList)) { | |
| a.className = 'current'; | |
| $.on(a, 'click', Index.cb.link); | |
| } | |
| fullBoardList = $('#full-board-list', boardList); | |
| btn = $('.hide-board-list-button', fullBoardList); | |
| $.on(btn, 'click', Header.toggleBoardList); | |
| $.rm($('#navtopright', fullBoardList)); | |
| $.add(boardList, fullBoardList); | |
| $.add(Header.bar, [boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]); | |
| Header.setCustomNav(Conf['Custom Board Navigation']); | |
| Header.generateBoardList(Conf['boardnav'].replace(/(\r\n|\n|\r)/g, ' ')); | |
| $.sync('Custom Board Navigation', Header.setCustomNav); | |
| return $.sync('boardnav', Header.generateBoardList); | |
| }, | |
| generateBoardList: function(text) { | |
| var as, list, nodes; | |
| list = $('#custom-board-list', Header.bar); | |
| $.rmAll(list); | |
| if (!text) { | |
| return; | |
| } | |
| as = $$('#full-board-list a[title]', Header.bar); | |
| nodes = text.match(/[\w@]+((-(all|title|replace|full|index|catalog|url:"[^"]+[^"]"|text:"[^"]+")|\,"[^"]+[^"]"))*|[^\w@]+/g).map(function(t) { | |
| var a, board, current, m, _i, _len; | |
| if (/^[^\w@]/.test(t)) { | |
| return $.tn(t); | |
| } | |
| if (/^toggle-all/.test(t)) { | |
| a = $.el('a', { | |
| className: 'show-board-list-button', | |
| textContent: (t.match(/-text:"(.+)"/) || [null, '+'])[1], | |
| href: 'javascript:;' | |
| }); | |
| $.on(a, 'click', Header.toggleBoardList); | |
| return a; | |
| } | |
| if (/^external/.test(t)) { | |
| a = $.el('a', { | |
| href: (t.match(/\,"(.+)"/) || [null, '+'])[1], | |
| textContent: (t.match(/-text:"(.+)"\,/) || [null, '+'])[1], | |
| className: 'external' | |
| }); | |
| return a; | |
| } | |
| board = /^current/.test(t) ? g.BOARD.ID : t.match(/^[^-]+/)[0]; | |
| for (_i = 0, _len = as.length; _i < _len; _i++) { | |
| a = as[_i]; | |
| if (a.textContent === board) { | |
| a = a.cloneNode(true); | |
| current = $.hasClass(a, 'current'); | |
| if (current) { | |
| $.on(a, 'click', Index.cb.link); | |
| } | |
| a.textContent = /-title/.test(t) || /-replace/.test(t) && current ? a.title : /-full/.test(t) ? "/" + board + "/ - " + a.title : (m = t.match(/-text:"(.+)"/)) ? m[1] : a.textContent; | |
| if (m = t.match(/-(index|catalog)/)) { | |
| a.dataset.only = m[1]; | |
| a.href = "//boards.4chan.org/" + board + "/"; | |
| if (m[1] === 'catalog') { | |
| if (Conf['External Catalog']) { | |
| a.href = CatalogLinks.external(board); | |
| } else { | |
| a.href += 'catalog'; | |
| } | |
| $.addClass(a, 'catalog'); | |
| } | |
| } | |
| if (board === '@') { | |
| $.addClass(a, 'navSmall'); | |
| } | |
| return a; | |
| } | |
| } | |
| return $.tn(t); | |
| }); | |
| return $.add(list, nodes); | |
| }, | |
| toggleBoardList: function() { | |
| var bar, custom, full, showBoardList; | |
| bar = Header.bar; | |
| custom = $('#custom-board-list', bar); | |
| full = $('#full-board-list', bar); | |
| showBoardList = !full.hidden; | |
| custom.hidden = !showBoardList; | |
| return full.hidden = showBoardList; | |
| }, | |
| setLinkJustify: function(centered) { | |
| Header.linkJustifyToggler.checked = centered; | |
| if (centered) { | |
| return $.addClass(doc, 'centered-links'); | |
| } else { | |
| return $.rmClass(doc, 'centered-links'); | |
| } | |
| }, | |
| toggleLinkJustify: function() { | |
| var centered; | |
| $.event('CloseMenu'); | |
| centered = this.nodeName === 'INPUT' ? this.checked : void 0; | |
| Header.setLinkJustify(centered); | |
| return $.set('Centered links', centered); | |
| }, | |
| setBarFixed: function(fixed) { | |
| Header.barFixedToggler.checked = fixed; | |
| if (fixed) { | |
| $.addClass(doc, 'fixed'); | |
| return $.addClass(Header.bar, 'dialog'); | |
| } else { | |
| $.rmClass(doc, 'fixed'); | |
| return $.rmClass(Header.bar, 'dialog'); | |
| } | |
| }, | |
| toggleBarFixed: function() { | |
| $.event('CloseMenu'); | |
| Header.setBarFixed(this.checked); | |
| Conf['Fixed Header'] = this.checked; | |
| return $.set('Fixed Header', this.checked); | |
| }, | |
| setShortcutIcons: function(show) { | |
| Header.shortcutToggler.checked = show; | |
| if (show) { | |
| return $.addClass(doc, 'shortcut-icons'); | |
| } else { | |
| return $.rmClass(doc, 'shortcut-icons'); | |
| } | |
| }, | |
| toggleShortcutIcons: function() { | |
| $.event('CloseMenu'); | |
| Header.setShortcutIcons(this.checked); | |
| Conf['Shortcut Icons'] = this.checked; | |
| return $.set('Shortcut Icons', this.checked); | |
| }, | |
| setBarVisibility: function(hide) { | |
| Header.headerToggler.checked = hide; | |
| $.event('CloseMenu'); | |
| (hide ? $.addClass : $.rmClass)(Header.bar, 'autohide'); | |
| return (hide ? $.addClass : $.rmClass)(doc, 'autohide'); | |
| }, | |
| toggleBarVisibility: function() { | |
| var hide, message; | |
| hide = this.nodeName === 'INPUT' ? this.checked : !$.hasClass(Header.bar, 'autohide'); | |
| this.checked = hide; | |
| $.set('Header auto-hide', Conf['Header auto-hide'] = hide); | |
| Header.setBarVisibility(hide); | |
| message = "The header bar will " + (hide ? 'automatically hide itself.' : 'remain visible.'); | |
| return new Notice('info', message, 2); | |
| }, | |
| setHideBarOnScroll: function(hide) { | |
| Header.scrollHeaderToggler.checked = hide; | |
| if (hide) { | |
| $.on(window, 'scroll', Header.hideBarOnScroll); | |
| return; | |
| } | |
| $.off(window, 'scroll', Header.hideBarOnScroll); | |
| $.rmClass(Header.bar, 'scroll'); | |
| if (!Conf['Header auto-hide']) { | |
| return $.rmClass(Header.bar, 'autohide'); | |
| } | |
| }, | |
| toggleHideBarOnScroll: function(e) { | |
| var hide; | |
| hide = this.checked; | |
| $.set('Header auto-hide on scroll', hide); | |
| return Header.setHideBarOnScroll(hide); | |
| }, | |
| hideBarOnScroll: function() { | |
| var offsetY; | |
| offsetY = window.pageYOffset; | |
| if (offsetY > (Header.previousOffset || 0)) { | |
| $.addClass(Header.bar, 'autohide'); | |
| $.addClass(Header.bar, 'scroll'); | |
| } else { | |
| $.rmClass(Header.bar, 'autohide'); | |
| $.rmClass(Header.bar, 'scroll'); | |
| } | |
| return Header.previousOffset = offsetY; | |
| }, | |
| setBarPosition: function(bottom) { | |
| var args; | |
| Header.barPositionToggler.checked = bottom; | |
| $.event('CloseMenu'); | |
| args = bottom ? ['bottom-header', 'top-header', 'bottom', 'after'] : ['top-header', 'bottom-header', 'top', 'add']; | |
| $.addClass(doc, args[0]); | |
| $.rmClass(doc, args[1]); | |
| Header.bar.parentNode.className = args[2]; | |
| return $[args[3]](Header.bar, Header.noticesRoot); | |
| }, | |
| toggleBarPosition: function() { | |
| $.cb.checked.call(this); | |
| return Header.setBarPosition(this.checked); | |
| }, | |
| setFooterVisibility: function(hide) { | |
| Header.footerToggler.checked = hide; | |
| return Header.footer.hidden = hide; | |
| }, | |
| toggleFooterVisibility: function() { | |
| var hide, message; | |
| $.event('CloseMenu'); | |
| hide = this.nodeName === 'INPUT' ? this.checked : !!Header.footer.hidden; | |
| Header.setFooterVisibility(hide); | |
| $.set('Bottom Board List', hide); | |
| message = hide ? 'The bottom navigation will now be hidden.' : 'The bottom navigation will remain visible.'; | |
| return new Notice('info', message, 2); | |
| }, | |
| setCustomNav: function(show) { | |
| var btn, cust, full, _ref; | |
| Header.customNavToggler.checked = show; | |
| cust = $('#custom-board-list', Header.bar); | |
| full = $('#full-board-list', Header.bar); | |
| btn = $('.hide-board-list-button', full); | |
| return _ref = show ? [false, true] : [true, false], cust.hidden = _ref[0], full.hidden = _ref[1], _ref; | |
| }, | |
| toggleCustomNav: function() { | |
| $.cb.checked.call(this); | |
| return Header.setCustomNav(this.checked); | |
| }, | |
| editCustomNav: function() { | |
| var settings; | |
| Settings.open('Advanced'); | |
| settings = $.id('fourchanx-settings'); | |
| return $('input[name=boardnav]', settings).focus(); | |
| }, | |
| hashScroll: function() { | |
| var hash, post; | |
| hash = this.location.hash.slice(1); | |
| if (!(/^p\d+$/.test(hash) && (post = $.id(hash)))) { | |
| return; | |
| } | |
| if ((Get.postFromRoot(post)).isHidden) { | |
| return; | |
| } | |
| return Header.scrollTo(post); | |
| }, | |
| scrollTo: function(root, down, needed) { | |
| var x; | |
| if (down) { | |
| x = Header.getBottomOf(root); | |
| if (!(needed && x >= 0)) { | |
| return window.scrollBy(0, -x); | |
| } | |
| } else { | |
| x = Header.getTopOf(root); | |
| if (!(needed && x >= 0)) { | |
| return window.scrollBy(0, x); | |
| } | |
| } | |
| }, | |
| scrollToIfNeeded: function(root, down) { | |
| return Header.scrollTo(root, down, true); | |
| }, | |
| getTopOf: function(root) { | |
| var headRect, top; | |
| top = root.getBoundingClientRect().top; | |
| if (Conf['Fixed Header'] && !Conf['Bottom Header']) { | |
| headRect = Header.toggle.getBoundingClientRect(); | |
| top -= headRect.top + headRect.height; | |
| } | |
| return top; | |
| }, | |
| getBottomOf: function(root) { | |
| var bottom, clientHeight, headRect; | |
| clientHeight = doc.clientHeight; | |
| bottom = clientHeight - root.getBoundingClientRect().bottom; | |
| if (Conf['Bottom Header']) { | |
| headRect = Header.toggle.getBoundingClientRect(); | |
| bottom -= clientHeight - headRect.bottom + headRect.height; | |
| } | |
| return bottom; | |
| }, | |
| addShortcut: function(el) { | |
| var shortcut; | |
| shortcut = $.el('span', { | |
| className: 'shortcut brackets-wrap' | |
| }); | |
| $.add(shortcut, el); | |
| return $.prepend(Header.shortcuts, shortcut); | |
| }, | |
| menuToggle: function(e) { | |
| return Header.menu.toggle(e, this, g); | |
| }, | |
| createNotification: function(e) { | |
| var cb, content, lifetime, notice, type, _ref; | |
| _ref = e.detail, type = _ref.type, content = _ref.content, lifetime = _ref.lifetime, cb = _ref.cb; | |
| notice = new Notice(type, content, lifetime); | |
| if (cb) { | |
| return cb(notice); | |
| } | |
| }, | |
| areNotificationsEnabled: false, | |
| enableDesktopNotifications: function() { | |
| var authorize, disable, el, notice, _ref; | |
| if (!(window.Notification && Conf['Desktop Notifications'])) { | |
| return; | |
| } | |
| switch (Notification.permission) { | |
| case 'granted': | |
| Header.areNotificationsEnabled = true; | |
| return; | |
| case 'denied': | |
| return; | |
| } | |
| el = $.el('span', { | |
| innerHTML: "Desktop notification permissions are not granted.\n[<a href='https://github.com/MayhemYDG/4chan-x/wiki/FAQ#desktop-notifications' target=_blank>FAQ</a>]<br>\n<button>Authorize</button> or <button>Disable</button>" | |
| }); | |
| _ref = $$('button', el), authorize = _ref[0], disable = _ref[1]; | |
| $.on(authorize, 'click', function() { | |
| return Notification.requestPermission(function(status) { | |
| Header.areNotificationsEnabled = status === 'granted'; | |
| if (status === 'default') { | |
| return; | |
| } | |
| return notice.close(); | |
| }); | |
| }); | |
| $.on(disable, 'click', function() { | |
| $.set('Desktop Notifications', false); | |
| return notice.close(); | |
| }); | |
| return notice = new Notice('info', el); | |
| } | |
| }; | |
| Index = { | |
| init: function() { | |
| var anchorEntry, input, label, modeEntry, name, refNavEntry, repliesEntry, sortEntry, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; | |
| if (g.VIEW !== 'index' || g.BOARD.ID === 'f') { | |
| return; | |
| } | |
| this.button = $.el('a', { | |
| className: 'index-refresh-shortcut fa fa-refresh', | |
| title: 'Refresh Index', | |
| href: 'javascript:;', | |
| textContent: 'Refresh Index' | |
| }); | |
| $.on(this.button, 'click', this.update); | |
| Header.addShortcut(this.button, 1); | |
| modeEntry = { | |
| el: $.el('span', { | |
| textContent: 'Index mode' | |
| }), | |
| subEntries: [ | |
| { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Mode" value="paged"> Paged' | |
| }) | |
| }, { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Mode" value="infinite"> Infinite scrolling' | |
| }) | |
| }, { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Mode" value="all pages"> All threads' | |
| }) | |
| } | |
| ] | |
| }; | |
| _ref = modeEntry.subEntries; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| label = _ref[_i]; | |
| input = label.el.firstChild; | |
| input.checked = Conf['Index Mode'] === input.value; | |
| $.on(input, 'change', $.cb.value); | |
| $.on(input, 'change', this.cb.mode); | |
| } | |
| sortEntry = { | |
| el: $.el('span', { | |
| textContent: 'Sort by' | |
| }), | |
| subEntries: [ | |
| { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Sort" value="bump"> Bump order' | |
| }) | |
| }, { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Sort" value="lastreply"> Last reply' | |
| }) | |
| }, { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Sort" value="birth"> Creation date' | |
| }) | |
| }, { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Sort" value="replycount"> Reply count' | |
| }) | |
| }, { | |
| el: $.el('label', { | |
| innerHTML: '<input type=radio name="Index Sort" value="filecount"> File count' | |
| }) | |
| } | |
| ] | |
| }; | |
| _ref1 = sortEntry.subEntries; | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| label = _ref1[_j]; | |
| input = label.el.firstChild; | |
| input.checked = Conf['Index Sort'] === input.value; | |
| $.on(input, 'change', $.cb.value); | |
| $.on(input, 'change', this.cb.sort); | |
| } | |
| repliesEntry = { | |
| el: $.el('label', { | |
| innerHTML: '<input type=checkbox name="Show Replies"> Show replies' | |
| }) | |
| }; | |
| anchorEntry = { | |
| el: $.el('label', { | |
| innerHTML: '<input type=checkbox name="Anchor Hidden Threads"> Anchor hidden threads', | |
| title: 'Move hidden threads at the end of the index.' | |
| }) | |
| }; | |
| refNavEntry = { | |
| el: $.el('label', { | |
| innerHTML: '<input type=checkbox name="Refreshed Navigation"> Refreshed navigation', | |
| title: 'Refresh index when navigating through pages.' | |
| }) | |
| }; | |
| _ref2 = [repliesEntry, anchorEntry, refNavEntry]; | |
| for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { | |
| label = _ref2[_k]; | |
| input = label.el.firstChild; | |
| name = input.name; | |
| input.checked = Conf[name]; | |
| $.on(input, 'change', $.cb.checked); | |
| switch (name) { | |
| case 'Show Replies': | |
| $.on(input, 'change', this.cb.replies); | |
| break; | |
| case 'Anchor Hidden Threads': | |
| $.on(input, 'change', this.cb.sort); | |
| } | |
| } | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: $.el('span', { | |
| textContent: 'Index Navigation' | |
| }), | |
| order: 98, | |
| subEntries: [repliesEntry, anchorEntry, refNavEntry, modeEntry, sortEntry] | |
| }); | |
| $.addClass(doc, 'index-loading'); | |
| this.update(); | |
| this.root = $.el('div', { | |
| className: 'board' | |
| }); | |
| this.pagelist = $.el('div', { | |
| className: 'pagelist', | |
| hidden: true, | |
| innerHTML: "<div class=\"prev\"><a><button disabled>Previous</button></a></div><div class=\"pages\"></div><div class=\"next\"><a><button disabled>Next</button></a></div><div class=\"pages cataloglink\"><a href=\"./catalog\">Catalog</a></div>" | |
| }); | |
| this.navLinks = $.el('div', { | |
| className: 'navLinks', | |
| innerHTML: "[<a href=\"./catalog\">Catalog</a>] [<time id=\"index-last-refresh\" title=\"Last index refresh\">...</time>] <input type=\"search\" id=\"index-search\" class=\"field\" placeholder=\"Search\"><a id=\"index-search-clear\" href=\"javascript:;\" title=\"Clear search\">×</a>" | |
| }); | |
| this.searchInput = $('#index-search', this.navLinks); | |
| this.currentPage = this.getCurrentPage(); | |
| $.on(window, 'popstate', this.cb.popstate); | |
| $.on(d, 'scroll', Index.scroll); | |
| $.on(this.pagelist, 'click', this.cb.pageNav); | |
| $.on(this.searchInput, 'input', this.onSearchInput); | |
| $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch); | |
| return $.asap((function() { | |
| return $('.board', doc) || d.readyState !== 'loading'; | |
| }), function() { | |
| var board, navLink, _l, _len3, _ref3; | |
| board = $('.board'); | |
| $.replace(board, Index.root); | |
| d.implementation.createDocument(null, null, null).appendChild(board); | |
| _ref3 = $$('.navLinks'); | |
| for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { | |
| navLink = _ref3[_l]; | |
| $.rm(navLink); | |
| } | |
| $.after($.x('child::form/preceding-sibling::hr[1]'), Index.navLinks); | |
| $.rmClass(doc, 'index-loading'); | |
| return $.asap((function() { | |
| return $('.pagelist') || d.readyState !== 'loading'; | |
| }), function() { | |
| return $.replace($('.pagelist'), Index.pagelist); | |
| }); | |
| }); | |
| }, | |
| scroll: $.debounce(100, function() { | |
| var nodes, nodesPerPage, pageNum; | |
| if (Index.req || Conf['Index Mode'] !== 'infinite' || ((d.body.scrollTop || doc.scrollTop) <= doc.scrollHeight - (300 + window.innerHeight))) { | |
| return; | |
| } | |
| pageNum = Index.getCurrentPage() + 1; | |
| if (pageNum >= Index.pagesNum) { | |
| return Index.endNotice(); | |
| } | |
| nodesPerPage = Index.threadsNumPerPage * 2; | |
| history.pushState(null, '', "/" + g.BOARD + "/" + pageNum); | |
| nodes = Index.sortedNodes.slice(nodesPerPage * pageNum, nodesPerPage * (pageNum + 1)); | |
| if (Conf['Show Replies']) { | |
| Index.buildReplies(nodes); | |
| } | |
| $.add(Index.root, nodes); | |
| return Index.setPage(); | |
| }), | |
| endNotice: (function() { | |
| var notify, reset; | |
| notify = false; | |
| reset = function() { | |
| return notify = false; | |
| }; | |
| return function() { | |
| if (notify) { | |
| return; | |
| } | |
| notify = true; | |
| new Notice('info', "Last page reached.", 2); | |
| return setTimeout(reset, 3 * $.SECOND); | |
| }; | |
| })(), | |
| cb: { | |
| mode: function() { | |
| Index.togglePagelist(); | |
| return Index.buildIndex(); | |
| }, | |
| sort: function() { | |
| Index.sort(); | |
| return Index.buildIndex(); | |
| }, | |
| replies: function() { | |
| Index.buildThreads(); | |
| Index.sort(); | |
| return Index.buildIndex(); | |
| }, | |
| popstate: function(e) { | |
| var pageNum; | |
| pageNum = Index.getCurrentPage(); | |
| if (Index.currentPage !== pageNum) { | |
| return Index.pageLoad(pageNum); | |
| } | |
| }, | |
| pageNav: function(e) { | |
| var a; | |
| if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { | |
| return; | |
| } | |
| switch (e.target.nodeName) { | |
| case 'BUTTON': | |
| a = e.target.parentNode; | |
| break; | |
| case 'A': | |
| a = e.target; | |
| break; | |
| default: | |
| return; | |
| } | |
| if (a.textContent === 'Catalog') { | |
| return; | |
| } | |
| e.preventDefault(); | |
| return Index.userPageNav(+a.pathname.split('/')[2]); | |
| }, | |
| link: function(e) { | |
| if (g.VIEW !== 'index' || /catalog/.test(this.href)) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| history.pushState(null, '', this.pathname); | |
| return Index.update(); | |
| } | |
| }, | |
| scrollToIndex: function() { | |
| return Header.scrollToIfNeeded(Index.root); | |
| }, | |
| getCurrentPage: function() { | |
| return +window.location.pathname.split('/')[2]; | |
| }, | |
| userPageNav: function(pageNum) { | |
| if (Conf['Refreshed Navigation'] && Conf['Index Mode'] !== 'all pages') { | |
| return Index.update(pageNum); | |
| } else { | |
| return Index.pageNav(pageNum); | |
| } | |
| }, | |
| pageNav: function(pageNum) { | |
| if (Index.currentPage === pageNum) { | |
| return; | |
| } | |
| history.pushState(null, '', pageNum === 0 ? './' : pageNum); | |
| return Index.pageLoad(pageNum); | |
| }, | |
| pageLoad: function(pageNum) { | |
| Index.currentPage = pageNum; | |
| if (Conf['Index Mode'] === 'all pages') { | |
| return; | |
| } | |
| Index.buildIndex(); | |
| Index.setPage(); | |
| return Index.scrollToIndex(); | |
| }, | |
| getPagesNum: function() { | |
| if (Index.isSearching) { | |
| return Math.ceil((Index.sortedNodes.length / 2) / Index.threadsNumPerPage); | |
| } else { | |
| return Index.pagesNum; | |
| } | |
| }, | |
| getMaxPageNum: function() { | |
| return Math.max(0, Index.getPagesNum() - 1); | |
| }, | |
| togglePagelist: function() { | |
| return Index.pagelist.hidden = Conf['Index Mode'] === 'all pages'; | |
| }, | |
| buildPagelist: function() { | |
| var a, i, maxPageNum, nodes, pagesRoot, _i; | |
| pagesRoot = $('.pages', Index.pagelist); | |
| maxPageNum = Index.getMaxPageNum(); | |
| if (pagesRoot.childElementCount !== maxPageNum + 1) { | |
| nodes = []; | |
| for (i = _i = 0; _i <= maxPageNum; i = _i += 1) { | |
| a = $.el('a', { | |
| textContent: i, | |
| href: i ? i : './' | |
| }); | |
| nodes.push($.tn('['), a, $.tn('] ')); | |
| } | |
| $.rmAll(pagesRoot); | |
| $.add(pagesRoot, nodes); | |
| } | |
| return Index.togglePagelist(); | |
| }, | |
| setPage: function() { | |
| var a, href, maxPageNum, next, pageNum, pagesRoot, prev, strong; | |
| pageNum = Index.getCurrentPage(); | |
| maxPageNum = Index.getMaxPageNum(); | |
| pagesRoot = $('.pages', Index.pagelist); | |
| prev = pagesRoot.previousSibling.firstChild; | |
| next = pagesRoot.nextSibling.firstChild; | |
| href = Math.max(pageNum - 1, 0); | |
| prev.href = href === 0 ? './' : href; | |
| prev.firstChild.disabled = href === pageNum; | |
| href = Math.min(pageNum + 1, maxPageNum); | |
| next.href = href === 0 ? './' : href; | |
| next.firstChild.disabled = href === pageNum; | |
| if (strong = $('strong', pagesRoot)) { | |
| if (+strong.textContent === pageNum) { | |
| return; | |
| } | |
| $.replace(strong, strong.firstChild); | |
| } else { | |
| strong = $.el('strong'); | |
| } | |
| a = pagesRoot.children[pageNum]; | |
| $.before(a, strong); | |
| return $.add(strong, a); | |
| }, | |
| update: function(pageNum) { | |
| var onload, _ref, _ref1; | |
| if (!navigator.onLine) { | |
| return; | |
| } | |
| if ((_ref = Index.req) != null) { | |
| _ref.abort(); | |
| } | |
| if ((_ref1 = Index.notice) != null) { | |
| _ref1.close(); | |
| } | |
| if (typeof pageNum !== 'number') { | |
| pageNum = null; | |
| } | |
| onload = function(e) { | |
| return Index.load(e, pageNum); | |
| }; | |
| Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { | |
| onabort: onload, | |
| onloadend: onload | |
| }, { | |
| whenModified: true | |
| }); | |
| return $.addClass(Index.button, 'fa-spin'); | |
| }, | |
| load: function(e, pageNum) { | |
| var err, notice, req, timeEl; | |
| $.rmClass(Index.button, 'fa-spin'); | |
| req = Index.req, notice = Index.notice; | |
| delete Index.req; | |
| delete Index.notice; | |
| if (e.type === 'abort') { | |
| req.onloadend = null; | |
| notice.close(); | |
| return; | |
| } | |
| try { | |
| if (req.status === 200) { | |
| Index.parse(JSON.parse(req.response), pageNum); | |
| } else if (req.status === 304 && (pageNum != null)) { | |
| Index.pageNav(pageNum); | |
| } | |
| } catch (_error) { | |
| err = _error; | |
| c.error('Index failure:', err.stack); | |
| if (notice) { | |
| notice.setType('error'); | |
| notice.el.lastElementChild.textContent = 'Index refresh failed.'; | |
| setTimeout(notice.close, 2 * $.SECOND); | |
| } else { | |
| new Notice('error', 'Index refresh failed.', 2); | |
| } | |
| return; | |
| } | |
| timeEl = $('#index-last-refresh', Index.navLinks); | |
| timeEl.dataset.utc = Date.parse(req.getResponseHeader('Last-Modified')); | |
| RelativeDates.update(timeEl); | |
| return Index.scrollToIndex(); | |
| }, | |
| parse: function(pages, pageNum) { | |
| Index.parseThreadList(pages); | |
| Index.buildThreads(); | |
| Index.sort(); | |
| Index.buildPagelist(); | |
| if (pageNum != null) { | |
| Index.pageNav(pageNum); | |
| return; | |
| } | |
| Index.buildIndex(); | |
| return Index.setPage(); | |
| }, | |
| parseThreadList: function(pages) { | |
| var thread, threadID, _ref, _ref1; | |
| Index.pagesNum = pages.length; | |
| Index.threadsNumPerPage = pages[0].threads.length; | |
| Index.liveThreadData = pages.reduce((function(arr, next) { | |
| return arr.concat(next.threads); | |
| }), []); | |
| Index.liveThreadIDs = Index.liveThreadData.map(function(data) { | |
| return data.no; | |
| }); | |
| _ref = g.BOARD.threads; | |
| for (threadID in _ref) { | |
| thread = _ref[threadID]; | |
| if (_ref1 = thread.ID, __indexOf.call(Index.liveThreadIDs, _ref1) < 0) { | |
| thread.collect(); | |
| } | |
| } | |
| }, | |
| buildThreads: function() { | |
| var err, errors, i, posts, thread, threadData, threadRoot, threads, _i, _len, _ref; | |
| Index.nodes = []; | |
| threads = []; | |
| posts = []; | |
| _ref = Index.liveThreadData; | |
| for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { | |
| threadData = _ref[i]; | |
| threadRoot = Build.thread(g.BOARD, threadData); | |
| Index.nodes.push(threadRoot, $.el('hr')); | |
| if (thread = g.BOARD.threads[threadData.no]) { | |
| thread.setPage(Math.floor(i / Index.threadsNumPerPage)); | |
| thread.setStatus('Sticky', !!threadData.sticky); | |
| thread.setStatus('Closed', !!threadData.closed); | |
| } else { | |
| thread = new Thread(threadData.no, g.BOARD); | |
| threads.push(thread); | |
| } | |
| if (thread.ID in thread.posts) { | |
| continue; | |
| } | |
| try { | |
| posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD)); | |
| } catch (_error) { | |
| err = _error; | |
| if (!errors) { | |
| errors = []; | |
| } | |
| errors.push({ | |
| message: "Parsing of Post No." + thread + " failed. Post will be skipped.", | |
| error: err | |
| }); | |
| } | |
| } | |
| if (errors) { | |
| Main.handleErrors(errors); | |
| } | |
| $.nodes(Index.nodes); | |
| Main.callbackNodes(Thread, threads); | |
| Main.callbackNodes(Post, posts); | |
| return $.event('IndexRefresh'); | |
| }, | |
| buildReplies: function(threadRoots) { | |
| var data, err, errors, i, lastReplies, node, nodes, post, posts, thread, threadRoot, _i, _j, _len, _len1; | |
| posts = []; | |
| for (_i = 0, _len = threadRoots.length; _i < _len; _i += 2) { | |
| threadRoot = threadRoots[_i]; | |
| thread = Get.threadFromRoot(threadRoot); | |
| i = Index.liveThreadIDs.indexOf(thread.ID); | |
| if (!(lastReplies = Index.liveThreadData[i].last_replies)) { | |
| continue; | |
| } | |
| nodes = []; | |
| for (_j = 0, _len1 = lastReplies.length; _j < _len1; _j++) { | |
| data = lastReplies[_j]; | |
| if (post = thread.posts[data.no]) { | |
| nodes.push(post.nodes.root); | |
| continue; | |
| } | |
| nodes.push(node = Build.postFromObject(data, thread.board.ID)); | |
| try { | |
| posts.push(new Post(node, thread, thread.board)); | |
| } catch (_error) { | |
| err = _error; | |
| if (!errors) { | |
| errors = []; | |
| } | |
| errors.push({ | |
| message: "Parsing of Post No." + data.no + " failed. Post will be skipped.", | |
| error: err | |
| }); | |
| } | |
| } | |
| $.add(threadRoot, nodes); | |
| } | |
| if (errors) { | |
| Main.handleErrors(errors); | |
| } | |
| return Main.callbackNodes(Post, posts); | |
| }, | |
| sort: function() { | |
| var i, sortedThreadIDs, threadID, _i, _len; | |
| switch (Conf['Index Sort']) { | |
| case 'bump': | |
| sortedThreadIDs = Index.liveThreadIDs; | |
| break; | |
| case 'lastreply': | |
| sortedThreadIDs = __slice.call(Index.liveThreadData).sort(function(a, b) { | |
| if ('last_replies' in a) { | |
| a = a.last_replies[a.last_replies.length - 1]; | |
| } | |
| if ('last_replies' in b) { | |
| b = b.last_replies[b.last_replies.length - 1]; | |
| } | |
| return b.no - a.no; | |
| }).map(function(data) { | |
| return data.no; | |
| }); | |
| break; | |
| case 'birth': | |
| sortedThreadIDs = __slice.call(Index.liveThreadIDs).sort(function(a, b) { | |
| return b - a; | |
| }); | |
| break; | |
| case 'replycount': | |
| sortedThreadIDs = __slice.call(Index.liveThreadData).sort(function(a, b) { | |
| return b.replies - a.replies; | |
| }).map(function(data) { | |
| return data.no; | |
| }); | |
| break; | |
| case 'filecount': | |
| sortedThreadIDs = __slice.call(Index.liveThreadData).sort(function(a, b) { | |
| return b.images - a.images; | |
| }).map(function(data) { | |
| return data.no; | |
| }); | |
| } | |
| Index.sortedNodes = []; | |
| for (_i = 0, _len = sortedThreadIDs.length; _i < _len; _i++) { | |
| threadID = sortedThreadIDs[_i]; | |
| i = Index.liveThreadIDs.indexOf(threadID) * 2; | |
| Index.sortedNodes.push(Index.nodes[i], Index.nodes[i + 1]); | |
| } | |
| if (Index.isSearching) { | |
| Index.sortedNodes = Index.querySearch(Index.searchInput.value) || Index.sortedNodes; | |
| } | |
| Index.sortOnTop(function(thread) { | |
| return thread.isSticky; | |
| }); | |
| if (Conf['Filter']) { | |
| Index.sortOnTop(function(thread) { | |
| return thread.isOnTop; | |
| }); | |
| } | |
| if (Conf['Anchor Hidden Threads']) { | |
| return Index.sortOnTop(function(thread) { | |
| return !thread.isHidden; | |
| }); | |
| } | |
| }, | |
| sortOnTop: function(match) { | |
| var i, offset, threadRoot, _i, _len, _ref, _ref1; | |
| offset = 0; | |
| _ref = Index.sortedNodes; | |
| for (i = _i = 0, _len = _ref.length; _i < _len; i = _i += 2) { | |
| threadRoot = _ref[i]; | |
| if (match(Get.threadFromRoot(threadRoot))) { | |
| (_ref1 = Index.sortedNodes).splice.apply(_ref1, [offset++ * 2, 0].concat(__slice.call(Index.sortedNodes.splice(i, 2)))); | |
| } | |
| } | |
| }, | |
| buildIndex: function() { | |
| var nodes, nodesPerPage, pageNum; | |
| if (Conf['Index Mode'] !== 'all pages') { | |
| pageNum = Index.getCurrentPage(); | |
| nodesPerPage = Index.threadsNumPerPage * 2; | |
| nodes = Index.sortedNodes.slice(nodesPerPage * pageNum, nodesPerPage * (pageNum + 1)); | |
| } else { | |
| nodes = Index.sortedNodes; | |
| } | |
| $.rmAll(Index.root); | |
| $.rmAll(Header.hover); | |
| if (Conf['Show Replies']) { | |
| Index.buildReplies(nodes); | |
| } | |
| $.event('IndexBuild', nodes); | |
| return $.add(Index.root, nodes); | |
| }, | |
| isSearching: false, | |
| clearSearch: function() { | |
| Index.searchInput.value = null; | |
| Index.onSearchInput(); | |
| return Index.searchInput.focus(); | |
| }, | |
| onSearchInput: function() { | |
| var pageNum; | |
| if (Index.isSearching = !!Index.searchInput.value.trim()) { | |
| if (!Index.searchInput.dataset.searching) { | |
| Index.searchInput.dataset.searching = 1; | |
| Index.pageBeforeSearch = Index.getCurrentPage(); | |
| pageNum = 0; | |
| } else { | |
| pageNum = Index.getCurrentPage(); | |
| } | |
| } else { | |
| pageNum = Index.pageBeforeSearch; | |
| delete Index.pageBeforeSearch; | |
| Index.searchInput.removeAttribute('data-searching'); | |
| } | |
| Index.sort(); | |
| if (Conf['Index Mode'] !== 'all pages') { | |
| pageNum = Math.min(pageNum, Index.getMaxPageNum()); | |
| } | |
| Index.buildPagelist(); | |
| if (Index.currentPage === pageNum) { | |
| Index.buildIndex(); | |
| return Index.setPage(); | |
| } else { | |
| return Index.pageNav(pageNum); | |
| } | |
| }, | |
| querySearch: function(query) { | |
| var keywords; | |
| if (!(keywords = query.toLowerCase().match(/\S+/g))) { | |
| return; | |
| } | |
| return Index.search(keywords); | |
| }, | |
| search: function(keywords) { | |
| var found, i, threadRoot, _i, _len, _ref; | |
| found = []; | |
| _ref = Index.sortedNodes; | |
| for (i = _i = 0, _len = _ref.length; _i < _len; i = _i += 2) { | |
| threadRoot = _ref[i]; | |
| if (Index.searchMatch(Get.threadFromRoot(threadRoot), keywords)) { | |
| found.push(Index.sortedNodes[i], Index.sortedNodes[i + 1]); | |
| } | |
| } | |
| return found; | |
| }, | |
| searchMatch: function(thread, keywords) { | |
| var file, info, key, keyword, text, _i, _j, _len, _len1, _ref, _ref1; | |
| _ref = thread.OP, info = _ref.info, file = _ref.file; | |
| text = []; | |
| _ref1 = ['comment', 'subject', 'name', 'tripcode', 'email']; | |
| for (_i = 0, _len = _ref1.length; _i < _len; _i++) { | |
| key = _ref1[_i]; | |
| if (key in info) { | |
| text.push(info[key]); | |
| } | |
| } | |
| if (file) { | |
| text.push(file.name); | |
| } | |
| text = text.join(' ').toLowerCase(); | |
| for (_j = 0, _len1 = keywords.length; _j < _len1; _j++) { | |
| keyword = keywords[_j]; | |
| if (-1 === text.indexOf(keyword)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| }; | |
| Build = { | |
| staticPath: '//s.4cdn.org/image/', | |
| gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', | |
| spoilerRange: {}, | |
| shortFilename: function(filename, isReply) { | |
| var threshold; | |
| threshold = isReply ? 30 : 40; | |
| if (filename.length - 4 > threshold) { | |
| return "" + filename.slice(0, threshold - 5) + "(...)." + filename.slice(-3); | |
| } else { | |
| return filename; | |
| } | |
| }, | |
| thumbRotate: (function() { | |
| var n; | |
| n = 0; | |
| return function() { | |
| return n = (n + 1) % 3; | |
| }; | |
| })(), | |
| postFromObject: function(data, boardID) { | |
| var o; | |
| o = { | |
| postID: data.no, | |
| threadID: data.resto || data.no, | |
| boardID: boardID, | |
| 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: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/src/" + data.filename + data.ext : "//i.4cdn.org/" + boardID + "/src/" + data.tim + data.ext, | |
| height: data.h, | |
| width: data.w, | |
| MD5: data.md5, | |
| size: data.fsize, | |
| turl: "//" + (Build.thumbRotate()) + ".t.4cdn.org/" + boardID + "/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, boardID, capcode, capcodeClass, capcodeIcon, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, gifIcon, href, imgSrc, isClosed, isOP, isSticky, name, pageIcon, pageNum, postID, quote, replyLink, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref; | |
| postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file; | |
| isOP = postID === threadID; | |
| staticPath = Build.staticPath, gifIcon = Build.gifIcon; | |
| tripcode = tripcode ? " <span class=postertrip>" + tripcode + "</span>" : ''; | |
| if (email) { | |
| emailStart = '<a href="mailto:' + email + '" class="useremail">'; | |
| emailEnd = '</a>'; | |
| } else { | |
| emailStart = ''; | |
| emailEnd = ''; | |
| } | |
| switch (capcode) { | |
| case 'admin': | |
| case 'admin_highlight': | |
| capcodeClass = " capcodeAdmin"; | |
| capcodeStart = " <strong class='capcode hand id_admin'" + "title='Highlight posts by the Administrator'>## Admin</strong>"; | |
| capcodeIcon = (" <img src='" + staticPath + "adminicon" + gifIcon + "' ") + "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>"; | |
| capcodeIcon = (" <img src='" + staticPath + "modicon" + gifIcon + "' ") + "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>"; | |
| capcodeIcon = (" <img src='" + staticPath + "developericon" + gifIcon + "' ") + "title='This user is a 4chan Developer.' class=identityIcon>"; | |
| break; | |
| default: | |
| capcodeClass = ''; | |
| capcodeStart = ''; | |
| capcodeIcon = ''; | |
| } | |
| userID = !capcode && uniqueID ? (" <span class='posteruid id_" + uniqueID + "'>(ID: ") + ("<span class=hand title='Highlight posts by this ID'>" + uniqueID + "</span>)</span> ") : ''; | |
| flag = !flagCode ? '' : boardID === 'pol' ? " <img src='" + staticPath + "country/troll/" + (flagCode.toLowerCase()) + ".gif' alt=" + flagCode + " title='" + flagName + "' class=countryFlag>" : " <span title='" + flagName + "' class='flag flag-" + (flagCode.toLowerCase()) + "'></span>"; | |
| if (file != null ? file.isDeleted : void 0) { | |
| fileHTML = isOP ? ("<div class=file id=f" + postID + "><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted" + gifIcon + "' alt='File deleted.' class=fileDeleted>") + "</span></div>" : ("<div class=file id=f" + postID + "><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted-res" + gifIcon + "' alt='File deleted.' class=fileDeletedRes>") + "</span></div>"; | |
| } else if (file) { | |
| fileSize = $.bytesToString(file.size); | |
| fileThumb = file.turl; | |
| if (file.isSpoiler) { | |
| fileSize = "Spoiler Image, " + fileSize; | |
| if (!isArchived) { | |
| fileThumb = "" + staticPath + "spoiler"; | |
| if (spoilerRange = Build.spoilerRange[boardID]) { | |
| fileThumb += ("-" + boardID) + Math.floor(1 + spoilerRange * Math.random()); | |
| } | |
| fileThumb += '.png'; | |
| file.twidth = file.theight = 100; | |
| } | |
| } | |
| imgSrc = boardID === 'f' ? '' : ("<a class='fileThumb" + (file.isSpoiler ? ' imgspoiler' : '') + "' href='" + file.url + "' target=_blank>") + ("<img src='" + fileThumb + "' alt='" + fileSize + "' data-md5=" + file.MD5 + " style='height: " + file.theight + "px; width: " + file.twidth + "px;'>") + "</a>"; | |
| a = $.el('a', { | |
| innerHTML: file.name | |
| }); | |
| filename = a.textContent.replace(/%22/g, '"'); | |
| a.textContent = Build.shortFilename(filename); | |
| shortFilename = a.innerHTML; | |
| a.textContent = filename; | |
| filename = a.innerHTML.replace(/'/g, '''); | |
| fileDims = file.name.slice(-3) === 'pdf' ? 'PDF' : "" + file.width + "x" + file.height; | |
| fileInfo = ("<div class=fileText id=fT" + postID + (file.isSpoiler ? " title='" + filename + "'" : '') + ">File: <a href='" + file.url + "' target=_blank>" + file.timestamp + "</a>") + ("-(" + fileSize + ", " + fileDims + (file.isSpoiler ? '' : ", <span" + (filename !== shortFilename ? " title='" + filename + "'" : '') + ">" + shortFilename + "</span>")) + ")</div>"; | |
| fileHTML = "<div class=file id=f" + postID + ">" + fileInfo + imgSrc + "</div>"; | |
| } else { | |
| fileHTML = ''; | |
| } | |
| sticky = isSticky ? " <img src=" + staticPath + "sticky" + gifIcon + " alt=Sticky title=Sticky class=stickyIcon>" : ''; | |
| closed = isClosed ? " <img src=" + staticPath + "closed" + gifIcon + " alt=Closed title=Closed class=closedIcon>" : ''; | |
| if (isOP && g.VIEW === 'index') { | |
| pageNum = Math.floor(Index.liveThreadIDs.indexOf(postID) / Index.threadsNumPerPage); | |
| pageIcon = " <span class=page-num title='This thread is on page " + pageNum + " in the original index.'>[" + pageNum + "]</span>"; | |
| replyLink = " <span>[<a href='/" + boardID + "/res/" + threadID + "' class=replylink>Reply</a>]</span>"; | |
| } else { | |
| pageIcon = replyLink = ''; | |
| } | |
| 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') + (capcodeIcon === 'admin_highlight' ? ' highlightPost' : '') + "'>" + (isOP ? fileHTML : '') + "<div class='postInfo' id=pi" + postID + "><input type=checkbox name=" + postID + " value=delete>" + ' ' + "<span class=subject>" + (subject || '') + "</span>" + ' ' + "<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + ' ' + "<span class='postNum'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>" + (pageIcon + sticky + closed + replyLink) + "</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 = "/" + boardID + "/res/" + href; | |
| } | |
| return container; | |
| }, | |
| summary: function(boardID, threadID, posts, files) { | |
| var text; | |
| text = []; | |
| text.push("" + posts + " post" + (posts > 1 ? 's' : '')); | |
| if (files) { | |
| text.push("and " + files + " image repl" + (files > 1 ? 'ies' : 'y')); | |
| } | |
| text.push('omitted.'); | |
| return $.el('a', { | |
| className: 'summary', | |
| textContent: text.join(' '), | |
| href: "/" + boardID + "/res/" + threadID | |
| }); | |
| }, | |
| thread: function(board, data) { | |
| var OP, files, nodes, posts, root, _ref; | |
| Build.spoilerRange[board] = data.custom_spoiler; | |
| if ((OP = board.posts[data.no]) && (root = OP.nodes.root.parentNode)) { | |
| $.rmAll(root); | |
| } else { | |
| root = $.el('div', { | |
| className: 'thread', | |
| id: "t" + data.no | |
| }); | |
| } | |
| nodes = [OP ? OP.nodes.root : Build.postFromObject(data, board.ID)]; | |
| if (data.omitted_posts || !Conf['Show Replies'] && data.replies) { | |
| _ref = Conf['Show Replies'] ? [data.omitted_posts, data.omitted_images] : [ | |
| data.replies, data.omitted_images + data.last_replies.filter(function(data) { | |
| return !!data.ext; | |
| }).length | |
| ], posts = _ref[0], files = _ref[1]; | |
| nodes.push(Build.summary(board.ID, data.no, posts, files)); | |
| } | |
| $.add(root, nodes); | |
| return root; | |
| } | |
| }; | |
| Get = { | |
| threadExcerpt: function(thread) { | |
| var OP, excerpt, _ref; | |
| OP = thread.OP; | |
| excerpt = ((_ref = OP.info.subject) != null ? _ref.trim() : void 0) || OP.info.comment.replace(/\n+/g, ' // ') || Conf['Anonymize'] && 'Anonymous' || $('.nameBlock', OP.nodes.info).textContent.trim(); | |
| if (excerpt.length > 70) { | |
| excerpt = "" + excerpt.slice(0, 67) + "..."; | |
| } | |
| return "/" + thread.board + "/ - " + excerpt; | |
| }, | |
| threadFromRoot: function(root) { | |
| return g.threads["" + g.BOARD + "." + root.id.slice(1)]; | |
| }, | |
| threadFromNode: function(node) { | |
| return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node)); | |
| }, | |
| postFromRoot: function(root) { | |
| var boardID, index, link, post, postID; | |
| link = $('a[title="Highlight this post"]', root); | |
| boardID = link.pathname.split('/')[1]; | |
| postID = link.hash.slice(2); | |
| index = root.dataset.clone; | |
| post = g.posts["" + boardID + "." + postID]; | |
| if (index) { | |
| return post.clones[index]; | |
| } else { | |
| return post; | |
| } | |
| }, | |
| postFromNode: function(root) { | |
| return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root)); | |
| }, | |
| contextFromNode: function(node) { | |
| return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', node)); | |
| }, | |
| postDataFromLink: function(link) { | |
| var boardID, path, postID, threadID, _ref; | |
| if (link.hostname === 'boards.4chan.org') { | |
| path = link.pathname.split('/'); | |
| boardID = path[1]; | |
| threadID = path[3]; | |
| postID = link.hash.slice(2); | |
| } else { | |
| _ref = link.dataset, boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID; | |
| threadID || (threadID = 0); | |
| } | |
| return { | |
| boardID: boardID, | |
| threadID: +threadID, | |
| postID: +postID | |
| }; | |
| }, | |
| allQuotelinksLinkingTo: function(post) { | |
| var ID, quote, quotedPost, quotelinks, quoterPost, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3, _ref4; | |
| quotelinks = []; | |
| _ref = g.posts; | |
| for (ID in _ref) { | |
| quoterPost = _ref[ID]; | |
| if (_ref1 = post.fullID, __indexOf.call(quoterPost.quotes, _ref1) >= 0) { | |
| _ref2 = [quoterPost].concat(quoterPost.clones); | |
| for (_i = 0, _len = _ref2.length; _i < _len; _i++) { | |
| quoterPost = _ref2[_i]; | |
| quotelinks.push.apply(quotelinks, quoterPost.nodes.quotelinks); | |
| } | |
| } | |
| } | |
| if (Conf['Quote Backlinks']) { | |
| _ref3 = post.quotes; | |
| for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { | |
| quote = _ref3[_j]; | |
| if (!(quotedPost = g.posts[quote])) { | |
| continue; | |
| } | |
| _ref4 = [quotedPost].concat(quotedPost.clones); | |
| for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) { | |
| quotedPost = _ref4[_k]; | |
| quotelinks.push.apply(quotelinks, __slice.call(quotedPost.nodes.backlinks)); | |
| } | |
| } | |
| } | |
| return quotelinks.filter(function(quotelink) { | |
| var boardID, postID, _ref5; | |
| _ref5 = Get.postDataFromLink(quotelink), boardID = _ref5.boardID, postID = _ref5.postID; | |
| return boardID === post.board.ID && postID === post.ID; | |
| }); | |
| }, | |
| postClone: function(boardID, threadID, postID, root, context) { | |
| var post, url; | |
| if (post = g.posts["" + boardID + "." + postID]) { | |
| Get.insert(post, root, context); | |
| return; | |
| } | |
| root.textContent = "Loading post No." + postID + "..."; | |
| if (threadID) { | |
| return $.cache("//a.4cdn.org/" + boardID + "/res/" + threadID + ".json", function() { | |
| return Get.fetchedPost(this, boardID, threadID, postID, root, context); | |
| }); | |
| } else if (url = Redirect.to('post', { | |
| boardID: boardID, | |
| postID: postID | |
| })) { | |
| return $.cache(url, function() { | |
| return Get.archivedPost(this, boardID, postID, root, context); | |
| }, { | |
| withCredentials: url.archive.withCredentials | |
| }); | |
| } | |
| }, | |
| insert: function(post, root, context) { | |
| var clone, nodes; | |
| if (!root.parentNode) { | |
| return; | |
| } | |
| clone = post.addClone(context); | |
| Main.callbackNodes(Clone, [clone]); | |
| nodes = clone.nodes; | |
| $.rmAll(nodes.root); | |
| $.add(nodes.root, nodes.post); | |
| $.rmAll(root); | |
| return $.add(root, nodes.root); | |
| }, | |
| fetchedPost: function(req, boardID, threadID, postID, root, context) { | |
| var board, post, posts, status, thread, url, _i, _len; | |
| if (post = g.posts["" + boardID + "." + postID]) { | |
| Get.insert(post, root, context); | |
| return; | |
| } | |
| status = req.status; | |
| if (status !== 200 && status !== 304) { | |
| if (url = Redirect.to('post', { | |
| boardID: boardID, | |
| postID: postID | |
| })) { | |
| $.cache(url, function() { | |
| return Get.archivedPost(this, boardID, postID, root, context); | |
| }, { | |
| withCredentials: url.archive.withCredentials | |
| }); | |
| } else { | |
| $.addClass(root, 'warning'); | |
| root.textContent = status === 404 ? "Thread No." + threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ")."; | |
| } | |
| return; | |
| } | |
| posts = JSON.parse(req.response).posts; | |
| Build.spoilerRange[boardID] = posts[0].custom_spoiler; | |
| for (_i = 0, _len = posts.length; _i < _len; _i++) { | |
| post = posts[_i]; | |
| if (post.no === postID) { | |
| break; | |
| } | |
| } | |
| if (post.no !== postID) { | |
| if (url = Redirect.to('post', { | |
| boardID: boardID, | |
| postID: postID | |
| })) { | |
| $.cache(url, function() { | |
| return Get.archivedPost(this, boardID, postID, root, context); | |
| }, { | |
| withCredentials: url.archive.withCredentials | |
| }); | |
| } else { | |
| $.addClass(root, 'warning'); | |
| root.textContent = "Post No." + postID + " was not found."; | |
| } | |
| return; | |
| } | |
| board = g.boards[boardID] || new Board(boardID); | |
| thread = g.threads["" + boardID + "." + threadID] || new Thread(threadID, board); | |
| post = new Post(Build.postFromObject(post, boardID), thread, board); | |
| Main.callbackNodes(Post, [post]); | |
| return Get.insert(post, root, context); | |
| }, | |
| archivedPost: function(req, boardID, postID, root, context) { | |
| var board, bq, comment, data, o, post, thread, threadID, _ref; | |
| if (post = g.posts["" + boardID + "." + postID]) { | |
| Get.insert(post, root, context); | |
| return; | |
| } | |
| data = JSON.parse(req.response); | |
| if (data.error) { | |
| $.addClass(root, 'warning'); | |
| root.textContent = data.error; | |
| return; | |
| } | |
| bq = $.el('blockquote', { | |
| textContent: data.comment | |
| }); | |
| bq.innerHTML = bq.innerHTML.replace(/\n|\[\/?[a-z]+(:lit)?\]/g, Get.parseMarkup); | |
| comment = bq.innerHTML.replace(/(^|>)(>[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3').replace(/((>){2}(>\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>'); | |
| threadID = +data.thread_num; | |
| o = { | |
| postID: postID, | |
| threadID: threadID, | |
| boardID: boardID, | |
| name: data.name_processed, | |
| capcode: (function() { | |
| switch (data.capcode) { | |
| case 'M': | |
| return 'mod'; | |
| case 'A': | |
| return 'admin'; | |
| case 'D': | |
| return 'developer'; | |
| } | |
| })(), | |
| tripcode: data.trip, | |
| uniqueID: data.poster_hash, | |
| email: data.email ? encodeURI(data.email) : '', | |
| subject: data.title_processed, | |
| flagCode: data.poster_country, | |
| flagName: data.poster_country_name_processed, | |
| date: data.fourchan_date, | |
| dateUTC: data.timestamp, | |
| comment: comment | |
| }; | |
| if ((_ref = data.media) != null ? _ref.media_filename : void 0) { | |
| o.file = { | |
| name: data.media.media_filename_processed, | |
| timestamp: data.media.media_orig, | |
| url: data.media.media_link || data.media.remote_media_link, | |
| height: data.media.media_h, | |
| width: data.media.media_w, | |
| MD5: data.media.media_hash, | |
| size: data.media.media_size, | |
| turl: data.media.thumb_link || ("//t.4cdn.org/" + boardID + "/thumb/" + data.media.preview_orig), | |
| theight: data.media.preview_h, | |
| twidth: data.media.preview_w, | |
| isSpoiler: data.media.spoiler === '1' | |
| }; | |
| } | |
| board = g.boards[boardID] || new Board(boardID); | |
| thread = g.threads["" + boardID + "." + threadID] || new Thread(threadID, board); | |
| post = new Post(Build.post(o, true), thread, board, { | |
| isArchived: true | |
| }); | |
| Main.callbackNodes(Post, [post]); | |
| return Get.insert(post, root, context); | |
| }, | |
| parseMarkup: 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 '<strong style="color: red;">'; | |
| case '[/banned]': | |
| return '</strong>'; | |
| default: | |
| return text.replace(':lit', ''); | |
| } | |
| } | |
| }; | |
| UI = (function() { | |
| var Menu, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove; | |
| dialog = function(id, position, html) { | |
| var child, el, move, _i, _len, _ref; | |
| el = $.el('div', { | |
| className: 'dialog', | |
| innerHTML: html, | |
| id: id | |
| }); | |
| el.style.cssText = position; | |
| $.get("" + id + ".position", position, function(item) { | |
| return el.style.cssText = item["" + id + ".position"]; | |
| }); | |
| move = $('.move', el); | |
| $.on(move, 'touchstart mousedown', dragstart); | |
| _ref = move.children; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| child = _ref[_i]; | |
| if (!child.tagName) { | |
| continue; | |
| } | |
| $.on(child, 'touchstart mousedown', function(e) { | |
| return e.stopPropagation(); | |
| }); | |
| } | |
| return el; | |
| }; | |
| Menu = (function() { | |
| var currentMenu, lastToggledButton; | |
| currentMenu = null; | |
| lastToggledButton = null; | |
| function Menu(type) { | |
| this.type = type; | |
| this.addEntry = __bind(this.addEntry, this); | |
| this.keybinds = __bind(this.keybinds, this); | |
| this.close = __bind(this.close, this); | |
| $.on(d, 'AddMenuEntry', this.addEntry); | |
| this.entries = []; | |
| } | |
| Menu.prototype.makeMenu = function() { | |
| var menu; | |
| menu = $.el('div', { | |
| className: 'dialog', | |
| id: 'menu', | |
| tabIndex: 0 | |
| }); | |
| $.on(menu, 'click', function(e) { | |
| return e.stopPropagation(); | |
| }); | |
| $.on(menu, 'keydown', this.keybinds); | |
| return menu; | |
| }; | |
| Menu.prototype.toggle = function(e, button, data) { | |
| var previousButton; | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (currentMenu) { | |
| previousButton = lastToggledButton; | |
| this.close(); | |
| if (previousButton === button) { | |
| return; | |
| } | |
| } | |
| if (!this.entries.length) { | |
| return; | |
| } | |
| return this.open(button, data); | |
| }; | |
| Menu.prototype.open = function(button, data) { | |
| var bLeft, bRect, bTop, bottom, cHeight, cWidth, entry, left, mRect, menu, right, style, top, _i, _len, _ref, _ref1, _ref2; | |
| menu = this.makeMenu(); | |
| currentMenu = menu; | |
| lastToggledButton = button; | |
| this.entries.sort(function(first, second) { | |
| return first.order - second.order; | |
| }); | |
| _ref = this.entries; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| entry = _ref[_i]; | |
| this.insertEntry(entry, menu, data); | |
| } | |
| $.addClass(lastToggledButton, 'active'); | |
| $.on(d, 'click', this.close); | |
| $.on(d, 'CloseMenu', this.close); | |
| $.add(Header.hover, menu); | |
| mRect = menu.getBoundingClientRect(); | |
| bRect = button.getBoundingClientRect(); | |
| bTop = window.scrollY + bRect.top; | |
| bLeft = window.scrollX + bRect.left; | |
| cHeight = doc.clientHeight; | |
| cWidth = doc.clientWidth; | |
| _ref1 = bRect.top + bRect.height + mRect.height < cHeight ? [bRect.bottom, null] : [null, cHeight - bRect.top], top = _ref1[0], bottom = _ref1[1]; | |
| _ref2 = bRect.left + mRect.width < cWidth ? [bRect.left, null] : [null, cWidth - bRect.right], left = _ref2[0], right = _ref2[1]; | |
| style = menu.style; | |
| style.top = "" + top + "px"; | |
| style.right = "" + right + "px"; | |
| style.bottom = "" + bottom + "px"; | |
| style.left = "" + left + "px"; | |
| if (right) { | |
| $.addClass(menu, 'left'); | |
| } | |
| entry = $('.entry', menu); | |
| this.focus(entry); | |
| return menu.focus(); | |
| }; | |
| Menu.prototype.insertEntry = function(entry, parent, data) { | |
| var subEntry, submenu, _i, _len, _ref; | |
| if (typeof entry.open === 'function') { | |
| if (!entry.open(data)) { | |
| return; | |
| } | |
| } | |
| $.add(parent, entry.el); | |
| if (!entry.subEntries) { | |
| return; | |
| } | |
| if (submenu = $('.submenu', entry.el)) { | |
| $.rm(submenu); | |
| } | |
| submenu = $.el('div', { | |
| className: 'dialog submenu' | |
| }); | |
| _ref = entry.subEntries; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| subEntry = _ref[_i]; | |
| this.insertEntry(subEntry, submenu, data); | |
| } | |
| $.add(entry.el, submenu); | |
| }; | |
| Menu.prototype.close = function() { | |
| $.rm(currentMenu); | |
| $.rmClass(lastToggledButton, 'active'); | |
| currentMenu = null; | |
| lastToggledButton = null; | |
| return $.off(d, 'click CloseMenu', this.close); | |
| }; | |
| Menu.prototype.findNextEntry = function(entry, direction) { | |
| var entries; | |
| entries = __slice.call(entry.parentNode.children); | |
| entries.sort(function(first, second) { | |
| return first.style.order - second.style.order; | |
| }); | |
| return entries[entries.indexOf(entry) + direction]; | |
| }; | |
| Menu.prototype.keybinds = function(e) { | |
| var entry, next, nextPrev, subEntry, submenu; | |
| entry = $('.focused', currentMenu); | |
| while (subEntry = $('.focused', entry)) { | |
| entry = subEntry; | |
| } | |
| switch (e.keyCode) { | |
| case 27: | |
| lastToggledButton.focus(); | |
| this.close(); | |
| break; | |
| case 13: | |
| case 32: | |
| entry.click(); | |
| break; | |
| case 38: | |
| if (next = this.findNextEntry(entry, -1)) { | |
| this.focus(next); | |
| } | |
| break; | |
| case 40: | |
| if (next = this.findNextEntry(entry, +1)) { | |
| this.focus(next); | |
| } | |
| break; | |
| case 39: | |
| if ((submenu = $('.submenu', entry)) && (next = submenu.firstElementChild)) { | |
| while (nextPrev = this.findNextEntry(next, -1)) { | |
| next = nextPrev; | |
| } | |
| this.focus(next); | |
| } | |
| break; | |
| case 37: | |
| if (next = $.x('parent::*[contains(@class,"submenu")]/parent::*', entry)) { | |
| this.focus(next); | |
| } | |
| break; | |
| default: | |
| return; | |
| } | |
| e.preventDefault(); | |
| return e.stopPropagation(); | |
| }; | |
| Menu.prototype.focus = function(entry) { | |
| var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; | |
| while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { | |
| $.rmClass(focused, 'focused'); | |
| } | |
| _ref = $$('.focused', entry); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| focused = _ref[_i]; | |
| $.rmClass(focused, 'focused'); | |
| } | |
| $.addClass(entry, 'focused'); | |
| if (!(submenu = $('.submenu', entry))) { | |
| return; | |
| } | |
| sRect = submenu.getBoundingClientRect(); | |
| eRect = entry.getBoundingClientRect(); | |
| cHeight = doc.clientHeight; | |
| cWidth = doc.clientWidth; | |
| _ref1 = eRect.top + sRect.height < cHeight ? ['0px', 'auto'] : ['auto', '0px'], top = _ref1[0], bottom = _ref1[1]; | |
| _ref2 = eRect.right + sRect.width < cWidth - 150 ? ['100%', 'auto'] : ['auto', '100%'], left = _ref2[0], right = _ref2[1]; | |
| style = submenu.style; | |
| style.top = top; | |
| style.bottom = bottom; | |
| style.left = left; | |
| return style.right = right; | |
| }; | |
| Menu.prototype.addEntry = function(e) { | |
| var entry; | |
| entry = e.detail; | |
| if (entry.type !== this.type) { | |
| return; | |
| } | |
| this.parseEntry(entry); | |
| return this.entries.push(entry); | |
| }; | |
| Menu.prototype.parseEntry = function(entry) { | |
| var el, subEntries, subEntry, _i, _len; | |
| el = entry.el, subEntries = entry.subEntries; | |
| $.addClass(el, 'entry'); | |
| $.on(el, 'focus mouseover', (function(e) { | |
| e.stopPropagation(); | |
| return this.focus(el); | |
| }).bind(this)); | |
| el.style.order = entry.order || 100; | |
| if (!subEntries) { | |
| return; | |
| } | |
| $.addClass(el, 'has-submenu'); | |
| for (_i = 0, _len = subEntries.length; _i < _len; _i++) { | |
| subEntry = subEntries[_i]; | |
| this.parseEntry(subEntry); | |
| } | |
| }; | |
| return Menu; | |
| })(); | |
| dragstart = function(e) { | |
| var el, isTouching, o, rect, screenHeight, screenWidth, _ref; | |
| if (e.type === 'mousedown' && e.button !== 0) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| if (isTouching = e.type === 'touchstart') { | |
| e = e.changedTouches[e.changedTouches.length - 1]; | |
| } | |
| el = $.x('ancestor::div[contains(@class,"dialog")][1]', this); | |
| rect = el.getBoundingClientRect(); | |
| screenHeight = doc.clientHeight; | |
| screenWidth = doc.clientWidth; | |
| o = { | |
| id: el.id, | |
| style: el.style, | |
| dx: e.clientX - rect.left, | |
| dy: e.clientY - rect.top, | |
| height: screenHeight - rect.height, | |
| width: screenWidth - rect.width, | |
| screenHeight: screenHeight, | |
| screenWidth: screenWidth, | |
| isTouching: isTouching | |
| }; | |
| _ref = Conf['Header auto-hide'] || !Conf['Fixed Header'] ? [0, 0] : Conf['Bottom Header'] ? [0, Header.bar.getBoundingClientRect().height] : [Header.bar.getBoundingClientRect().height, 0], o.topBorder = _ref[0], o.bottomBorder = _ref[1]; | |
| if (isTouching) { | |
| o.identifier = e.identifier; | |
| o.move = touchmove.bind(o); | |
| o.up = touchend.bind(o); | |
| $.on(d, 'touchmove', o.move); | |
| return $.on(d, 'touchend touchcancel', o.up); | |
| } else { | |
| o.move = drag.bind(o); | |
| o.up = dragend.bind(o); | |
| $.on(d, 'mousemove', o.move); | |
| return $.on(d, 'mouseup', o.up); | |
| } | |
| }; | |
| touchmove = function(e) { | |
| var touch, _i, _len, _ref; | |
| _ref = e.changedTouches; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| touch = _ref[_i]; | |
| if (touch.identifier === this.identifier) { | |
| drag.call(this, touch); | |
| return; | |
| } | |
| } | |
| }; | |
| drag = function(e) { | |
| var bottom, clientX, clientY, left, right, style, top; | |
| clientX = e.clientX, clientY = e.clientY; | |
| left = clientX - this.dx; | |
| left = left < 10 ? 0 : this.width - left < 10 ? null : left / this.screenWidth * 100 + '%'; | |
| top = clientY - this.dy; | |
| top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? null : top / this.screenHeight * 100 + '%'; | |
| right = left === null ? 0 : null; | |
| bottom = top === null ? this.bottomBorder + 'px' : null; | |
| style = this.style; | |
| style.left = left; | |
| style.right = right; | |
| style.top = top; | |
| return style.bottom = bottom; | |
| }; | |
| touchend = function(e) { | |
| var touch, _i, _len, _ref; | |
| _ref = e.changedTouches; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| touch = _ref[_i]; | |
| if (touch.identifier === this.identifier) { | |
| dragend.call(this); | |
| return; | |
| } | |
| } | |
| }; | |
| dragend = function() { | |
| if (this.isTouching) { | |
| $.off(d, 'touchmove', this.move); | |
| $.off(d, 'touchend touchcancel', this.up); | |
| } else { | |
| $.off(d, 'mousemove', this.move); | |
| $.off(d, 'mouseup', this.up); | |
| } | |
| return $.set("" + this.id + ".position", this.style.cssText); | |
| }; | |
| hoverstart = function(_arg) { | |
| var asapTest, cb, el, endEvents, latestEvent, o, root; | |
| root = _arg.root, el = _arg.el, latestEvent = _arg.latestEvent, endEvents = _arg.endEvents, asapTest = _arg.asapTest, cb = _arg.cb; | |
| o = { | |
| root: root, | |
| el: el, | |
| style: el.style, | |
| cb: cb, | |
| endEvents: endEvents, | |
| latestEvent: latestEvent, | |
| clientHeight: doc.clientHeight, | |
| clientWidth: doc.clientWidth | |
| }; | |
| o.hover = hover.bind(o); | |
| o.hoverend = hoverend.bind(o); | |
| $.asap(function() { | |
| return !el.parentNode || asapTest(); | |
| }, function() { | |
| if (el.parentNode) { | |
| return o.hover(o.latestEvent); | |
| } | |
| }); | |
| $.on(root, endEvents, o.hoverend); | |
| if ($.x('ancestor::div[contains(@class,"inline")][1]', root)) { | |
| $.on(d, 'keydown', o.hoverend); | |
| } | |
| $.on(root, 'mousemove', o.hover); | |
| o.workaround = function(e) { | |
| if (!root.contains(e.target)) { | |
| return o.hoverend(); | |
| } | |
| }; | |
| return $.on(doc, 'mousemove', o.workaround); | |
| }; | |
| hover = function(e) { | |
| var clientX, clientY, height, left, right, style, top, _ref; | |
| this.latestEvent = e; | |
| height = this.el.offsetHeight; | |
| clientX = e.clientX, clientY = e.clientY; | |
| top = clientY - 120; | |
| top = this.clientHeight <= height || top <= 0 ? 0 : top + height >= this.clientHeight ? this.clientHeight - height : top; | |
| _ref = clientX <= this.clientWidth - 400 ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = _ref[0], right = _ref[1]; | |
| style = this.style; | |
| style.top = top + 'px'; | |
| style.left = left; | |
| return style.right = right; | |
| }; | |
| hoverend = function(e) { | |
| if (e.type === 'keydown' && e.keyCode !== 13 || e.target.nodeName === "TEXTAREA") { | |
| return; | |
| } | |
| $.rm(this.el); | |
| $.off(this.root, this.endEvents, this.hoverend); | |
| $.off(d, 'keydown', this.hoverend); | |
| $.off(this.root, 'mousemove', this.hover); | |
| $.off(doc, 'mousemove', this.workaround); | |
| if (this.cb) { | |
| return this.cb.call(this); | |
| } | |
| }; | |
| return { | |
| dialog: dialog, | |
| Menu: Menu, | |
| hover: hoverstart | |
| }; | |
| })(); | |
| Anonymize = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Anonymize']) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Anonymize', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var email, name, tripcode, _ref; | |
| if (this.info.capcode || this.isClone) { | |
| return; | |
| } | |
| _ref = this.nodes, name = _ref.name, tripcode = _ref.tripcode, email = _ref.email; | |
| if (this.info.name !== 'Anonymous') { | |
| name.textContent = 'Anonymous'; | |
| } | |
| if (tripcode) { | |
| $.rm(tripcode); | |
| delete this.nodes.tripcode; | |
| } | |
| if (this.info.email) { | |
| $.replace(email, name); | |
| return delete this.nodes.email; | |
| } | |
| } | |
| }; | |
| Filter = { | |
| filters: {}, | |
| init: function() { | |
| var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; | |
| if (g.VIEW === 'catalog' || !Conf['Filter']) { | |
| return; | |
| } | |
| if (!Conf['Filtered Backlinks']) { | |
| $.addClass(doc, 'hide-backlinks'); | |
| } | |
| for (key in Config.filter) { | |
| this.filters[key] = []; | |
| _ref = Conf[key].split('\n'); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| filter = _ref[_i]; | |
| if (filter[0] === '#') { | |
| continue; | |
| } | |
| if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { | |
| continue; | |
| } | |
| filter = filter.replace(regexp[0], ''); | |
| boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; | |
| if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { | |
| continue; | |
| } | |
| if (key === 'uniqueID' || key === 'MD5') { | |
| regexp = regexp[1]; | |
| } else { | |
| try { | |
| regexp = RegExp(regexp[1], regexp[2]); | |
| } catch (_error) { | |
| err = _error; | |
| new Notice('warning', err.message, 60); | |
| continue; | |
| } | |
| } | |
| op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1] : void 0) || 'yes'; | |
| stub = (function() { | |
| var _ref4; | |
| switch ((_ref4 = filter.match(/stub:(yes|no)/)) != null ? _ref4[1] : void 0) { | |
| case 'yes': | |
| return true; | |
| case 'no': | |
| return false; | |
| default: | |
| return Conf['Stubs']; | |
| } | |
| })(); | |
| if (hl = /highlight/.test(filter)) { | |
| hl = ((_ref4 = filter.match(/highlight:(\w+)/)) != null ? _ref4[1] : void 0) || 'filter-highlight'; | |
| top = ((_ref5 = filter.match(/top:(yes|no)/)) != null ? _ref5[1] : void 0) || 'yes'; | |
| top = top === 'yes'; | |
| } | |
| this.filters[key].push(this.createFilter(regexp, op, stub, hl, top)); | |
| } | |
| if (!this.filters[key].length) { | |
| delete this.filters[key]; | |
| } | |
| } | |
| if (!Object.keys(this.filters).length) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Filter', | |
| cb: this.node | |
| }); | |
| }, | |
| createFilter: function(regexp, op, stub, hl, top) { | |
| var settings, test; | |
| test = typeof regexp === 'string' ? function(value) { | |
| return regexp === value; | |
| } : function(value) { | |
| return regexp.test(value); | |
| }; | |
| settings = { | |
| hide: !hl, | |
| stub: stub, | |
| "class": hl, | |
| top: top | |
| }; | |
| return function(value, isReply) { | |
| if (isReply && op === 'only' || !isReply && op === 'no') { | |
| return false; | |
| } | |
| if (!test(value)) { | |
| return false; | |
| } | |
| return settings; | |
| }; | |
| }, | |
| node: function() { | |
| var filter, key, result, value, _i, _len, _ref; | |
| if (this.isClone) { | |
| return; | |
| } | |
| for (key in Filter.filters) { | |
| value = Filter[key](this); | |
| if (value === false) { | |
| continue; | |
| } | |
| _ref = Filter.filters[key]; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| filter = _ref[_i]; | |
| if (!(result = filter(value, this.isReply))) { | |
| continue; | |
| } | |
| if (result.hide) { | |
| if (this.isReply) { | |
| PostHiding.hide(this, result.stub); | |
| } else if (g.VIEW === 'index') { | |
| ThreadHiding.hide(this.thread, result.stub); | |
| } else { | |
| continue; | |
| } | |
| return; | |
| } | |
| $.addClass(this.nodes.root, result["class"]); | |
| if (!this.isReply && result.top) { | |
| this.thread.isOnTop = true; | |
| } | |
| } | |
| } | |
| }, | |
| name: function(post) { | |
| if ('name' in post.info) { | |
| return post.info.name; | |
| } | |
| return false; | |
| }, | |
| uniqueID: function(post) { | |
| if ('uniqueID' in post.info) { | |
| return post.info.uniqueID; | |
| } | |
| return false; | |
| }, | |
| tripcode: function(post) { | |
| if ('tripcode' in post.info) { | |
| return post.info.tripcode; | |
| } | |
| return false; | |
| }, | |
| capcode: function(post) { | |
| if ('capcode' in post.info) { | |
| return post.info.capcode; | |
| } | |
| return false; | |
| }, | |
| email: function(post) { | |
| if ('email' in post.info) { | |
| return post.info.email; | |
| } | |
| return false; | |
| }, | |
| subject: function(post) { | |
| if ('subject' in post.info) { | |
| return post.info.subject || false; | |
| } | |
| return false; | |
| }, | |
| comment: function(post) { | |
| if ('comment' in post.info) { | |
| return post.info.comment; | |
| } | |
| return false; | |
| }, | |
| flag: function(post) { | |
| if ('flag' in post.info) { | |
| return post.info.flag; | |
| } | |
| return false; | |
| }, | |
| filename: function(post) { | |
| if (post.file) { | |
| return post.file.name; | |
| } | |
| return false; | |
| }, | |
| dimensions: function(post) { | |
| if (post.file && post.file.isImage) { | |
| return post.file.dimensions; | |
| } | |
| return false; | |
| }, | |
| filesize: function(post) { | |
| if (post.file) { | |
| return post.file.size; | |
| } | |
| return false; | |
| }, | |
| MD5: function(post) { | |
| if (post.file) { | |
| return post.file.MD5; | |
| } | |
| return false; | |
| }, | |
| menu: { | |
| init: function() { | |
| var div, entry, type, _i, _len, _ref; | |
| if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Filter']) { | |
| return; | |
| } | |
| div = $.el('div', { | |
| textContent: 'Filter' | |
| }); | |
| entry = { | |
| type: 'post', | |
| el: div, | |
| order: 50, | |
| open: function(post) { | |
| Filter.menu.post = post; | |
| return true; | |
| }, | |
| subEntries: [] | |
| }; | |
| _ref = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| type = _ref[_i]; | |
| entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1])); | |
| } | |
| return $.event('AddMenuEntry', entry); | |
| }, | |
| createSubEntry: function(text, type) { | |
| var el; | |
| el = $.el('a', { | |
| href: 'javascript:;', | |
| textContent: text | |
| }); | |
| el.dataset.type = type; | |
| $.on(el, 'click', Filter.menu.makeFilter); | |
| return { | |
| el: el, | |
| open: function(post) { | |
| var value; | |
| value = Filter[type](post); | |
| return value !== false; | |
| } | |
| }; | |
| }, | |
| makeFilter: function() { | |
| var re, type, value; | |
| type = this.dataset.type; | |
| value = Filter[type](Filter.menu.post); | |
| re = type === 'uniqueID' || type === 'MD5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { | |
| if (c === '\n') { | |
| return '\\n'; | |
| } else if (c === '\\') { | |
| return '\\\\'; | |
| } else { | |
| return "\\" + c; | |
| } | |
| }); | |
| re = type === 'uniqueID' || type === 'MD5' ? "/" + re + "/" : "/^" + re + "$/"; | |
| return $.get(type, Conf[type], function(item) { | |
| var save, section, select, ta, tl; | |
| save = item[type]; | |
| save = save ? "" + save + "\n" + re : re; | |
| $.set(type, save); | |
| Settings.open('Filter'); | |
| section = $('.section-container'); | |
| select = $('select[name=filter]', section); | |
| select.value = type; | |
| Settings.selectFilter.call(select); | |
| ta = $('textarea', section); | |
| tl = ta.textLength; | |
| ta.setSelectionRange(tl, tl); | |
| return ta.focus(); | |
| }); | |
| } | |
| } | |
| }; | |
| PostHiding = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Reply Hiding Buttons'] && !Conf['Reply Hiding Link']) { | |
| return; | |
| } | |
| if (Conf['Reply Hiding Buttons']) { | |
| $.addClass(doc, "reply-hide"); | |
| } | |
| this.db = new DataBoard('hiddenPosts'); | |
| return Post.callbacks.push({ | |
| name: 'Reply Hiding', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var data; | |
| if (!this.isReply || this.isClone) { | |
| return; | |
| } | |
| if (data = PostHiding.db.get({ | |
| boardID: this.board.ID, | |
| threadID: this.thread.ID, | |
| postID: this.ID | |
| })) { | |
| if (data.thisPost) { | |
| PostHiding.hide(this, data.makeStub, data.hideRecursively); | |
| } else { | |
| Recursive.apply(PostHiding.hide, this, data.makeStub, true); | |
| Recursive.add(PostHiding.hide, this, data.makeStub, true); | |
| } | |
| } | |
| if (!Conf['Reply Hiding Buttons']) { | |
| return; | |
| } | |
| return $.replace($('.sideArrows', this.nodes.root), PostHiding.makeButton(this, 'hide')); | |
| }, | |
| menu: { | |
| init: function() { | |
| var apply, div, hideStubLink, makeStub, replies, thisPost; | |
| if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding Link']) { | |
| return; | |
| } | |
| div = $.el('div', { | |
| className: 'hide-reply-link', | |
| textContent: 'Hide reply' | |
| }); | |
| apply = $.el('a', { | |
| textContent: 'Apply', | |
| href: 'javascript:;' | |
| }); | |
| $.on(apply, 'click', PostHiding.menu.hide); | |
| thisPost = $.el('label', { | |
| innerHTML: '<input type=checkbox name=thisPost checked> This post' | |
| }); | |
| replies = $.el('label', { | |
| innerHTML: "<input type=checkbox name=replies " + (Conf['Recursive Hiding'] ? 'checked' : '') + "> Hide replies" | |
| }); | |
| makeStub = $.el('label', { | |
| innerHTML: "<input type=checkbox name=makeStub " + (Conf['Stubs'] ? 'checked' : '') + "> Make stub" | |
| }); | |
| $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: div, | |
| order: 20, | |
| open: function(post) { | |
| if (!post.isReply || post.isClone || post.isHidden) { | |
| return false; | |
| } | |
| PostHiding.menu.post = post; | |
| return true; | |
| }, | |
| subEntries: [ | |
| { | |
| el: apply | |
| }, { | |
| el: thisPost | |
| }, { | |
| el: replies | |
| }, { | |
| el: makeStub | |
| } | |
| ] | |
| }); | |
| div = $.el('div', { | |
| className: 'show-reply-link', | |
| textContent: 'Show reply' | |
| }); | |
| apply = $.el('a', { | |
| textContent: 'Apply', | |
| href: 'javascript:;' | |
| }); | |
| $.on(apply, 'click', PostHiding.menu.show); | |
| thisPost = $.el('label', { | |
| innerHTML: '<input type=checkbox name=thisPost> This post' | |
| }); | |
| replies = $.el('label', { | |
| innerHTML: "<input type=checkbox name=replies> Show replies" | |
| }); | |
| hideStubLink = $.el('a', { | |
| textContent: 'Hide stub', | |
| href: 'javascript:;' | |
| }); | |
| $.on(hideStubLink, 'click', PostHiding.menu.hideStub); | |
| $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: div, | |
| order: 20, | |
| open: function(post) { | |
| var data; | |
| if (!post.isReply || post.isClone || !post.isHidden) { | |
| return false; | |
| } | |
| if (!(data = PostHiding.db.get({ | |
| boardID: post.board.ID, | |
| threadID: post.thread.ID, | |
| postID: post.ID | |
| }))) { | |
| return false; | |
| } | |
| PostHiding.menu.post = post; | |
| thisPost.firstChild.checked = post.isHidden; | |
| replies.firstChild.checked = (data != null ? data.hideRecursively : void 0) != null ? data.hideRecursively : Conf['Recursive Hiding']; | |
| return true; | |
| }, | |
| subEntries: [ | |
| { | |
| el: apply | |
| }, { | |
| el: thisPost | |
| }, { | |
| el: replies | |
| } | |
| ] | |
| }); | |
| return $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: hideStubLink, | |
| order: 15, | |
| open: function(post) { | |
| var data; | |
| if (!post.isReply || post.isClone || !post.isHidden) { | |
| return false; | |
| } | |
| if (!(data = PostHiding.db.get({ | |
| boardID: post.board.ID, | |
| threadID: post.thread.ID, | |
| postID: post.ID | |
| }))) { | |
| return false; | |
| } | |
| return PostHiding.menu.post = post; | |
| } | |
| }); | |
| }, | |
| hide: function() { | |
| var makeStub, parent, post, replies, thisPost; | |
| parent = this.parentNode; | |
| thisPost = $('input[name=thisPost]', parent).checked; | |
| replies = $('input[name=replies]', parent).checked; | |
| makeStub = $('input[name=makeStub]', parent).checked; | |
| post = PostHiding.menu.post; | |
| if (thisPost) { | |
| PostHiding.hide(post, makeStub, replies); | |
| } else if (replies) { | |
| Recursive.apply(PostHiding.hide, post, makeStub, true); | |
| Recursive.add(PostHiding.hide, post, makeStub, true); | |
| } else { | |
| return; | |
| } | |
| PostHiding.saveHiddenState(post, true, thisPost, makeStub, replies); | |
| return $.event('CloseMenu'); | |
| }, | |
| show: function() { | |
| var data, parent, post, replies, thisPost; | |
| parent = this.parentNode; | |
| thisPost = $('input[name=thisPost]', parent).checked; | |
| replies = $('input[name=replies]', parent).checked; | |
| post = PostHiding.menu.post; | |
| if (thisPost) { | |
| PostHiding.show(post, replies); | |
| } else if (replies) { | |
| Recursive.apply(PostHiding.show, post, true); | |
| Recursive.rm(PostHiding.hide, post, true); | |
| } else { | |
| return; | |
| } | |
| if (data = PostHiding.db.get({ | |
| boardID: post.board.ID, | |
| threadID: post.thread.ID, | |
| postID: post.ID | |
| })) { | |
| PostHiding.saveHiddenState(post, !(thisPost && replies), !thisPost, data.makeStub, !replies); | |
| } | |
| return $.event('CloseMenu'); | |
| }, | |
| hideStub: function() { | |
| var post; | |
| post = PostHiding.menu.post; | |
| post.nodes.root.hidden = true; | |
| $.event('CloseMenu'); | |
| } | |
| }, | |
| makeButton: function(post, type) { | |
| var a, span; | |
| span = $.el('span', { | |
| className: "fa fa-" + (type === 'hide' ? 'minus' : 'plus') + "-square-o", | |
| textContent: "" | |
| }); | |
| a = $.el('a', { | |
| className: "" + type + "-reply-button", | |
| href: 'javascript:;' | |
| }); | |
| $.add(a, span); | |
| $.on(a, 'click', PostHiding.toggle); | |
| return a; | |
| }, | |
| saveHiddenState: function(post, isHiding, thisPost, makeStub, hideRecursively) { | |
| var data; | |
| data = { | |
| boardID: post.board.ID, | |
| threadID: post.thread.ID, | |
| postID: post.ID | |
| }; | |
| if (isHiding) { | |
| data.val = { | |
| thisPost: thisPost !== false, | |
| makeStub: makeStub, | |
| hideRecursively: hideRecursively | |
| }; | |
| return PostHiding.db.set(data); | |
| } else { | |
| return PostHiding.db["delete"](data); | |
| } | |
| }, | |
| toggle: function() { | |
| var post; | |
| post = Get.postFromNode(this); | |
| PostHiding[(post.isHidden ? 'show' : 'hide')](post); | |
| return PostHiding.saveHiddenState(post, post.isHidden); | |
| }, | |
| hide: function(post, makeStub, hideRecursively) { | |
| var a, postInfo, quotelink, _i, _len, _ref; | |
| if (makeStub == null) { | |
| makeStub = Conf['Stubs']; | |
| } | |
| if (hideRecursively == null) { | |
| hideRecursively = Conf['Recursive Hiding']; | |
| } | |
| if (post.isHidden) { | |
| return; | |
| } | |
| post.isHidden = true; | |
| if (hideRecursively) { | |
| Recursive.apply(PostHiding.hide, post, makeStub, true); | |
| Recursive.add(PostHiding.hide, post, makeStub, true); | |
| } | |
| _ref = Get.allQuotelinksLinkingTo(post); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quotelink = _ref[_i]; | |
| $.addClass(quotelink, 'filtered'); | |
| } | |
| if (!makeStub) { | |
| post.nodes.root.hidden = true; | |
| return; | |
| } | |
| a = PostHiding.makeButton(post, 'show'); | |
| postInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent; | |
| $.add(a, $.tn(" " + postInfo)); | |
| post.nodes.stub = $.el('div', { | |
| className: 'stub' | |
| }); | |
| $.add(post.nodes.stub, a); | |
| if (Conf['Menu']) { | |
| $.add(post.nodes.stub, Menu.makeButton()); | |
| } | |
| return $.prepend(post.nodes.root, post.nodes.stub); | |
| }, | |
| show: function(post, showRecursively) { | |
| var quotelink, _i, _len, _ref; | |
| if (showRecursively == null) { | |
| showRecursively = Conf['Recursive Hiding']; | |
| } | |
| if (post.nodes.stub) { | |
| $.rm(post.nodes.stub); | |
| delete post.nodes.stub; | |
| } else { | |
| post.nodes.root.hidden = false; | |
| } | |
| post.isHidden = false; | |
| if (showRecursively) { | |
| Recursive.apply(PostHiding.show, post, true); | |
| Recursive.rm(PostHiding.hide, post); | |
| } | |
| _ref = Get.allQuotelinksLinkingTo(post); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quotelink = _ref[_i]; | |
| $.rmClass(quotelink, 'filtered'); | |
| } | |
| } | |
| }; | |
| Recursive = { | |
| recursives: {}, | |
| init: function() { | |
| if (g.VIEW === 'catalog') { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Recursive', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var i, obj, quote, recursive, _i, _j, _len, _len1, _ref, _ref1; | |
| if (this.isClone) { | |
| return; | |
| } | |
| _ref = this.quotes; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quote = _ref[_i]; | |
| if (obj = Recursive.recursives[quote]) { | |
| _ref1 = obj.recursives; | |
| for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) { | |
| recursive = _ref1[i]; | |
| recursive.apply(null, [this].concat(__slice.call(obj.args[i]))); | |
| } | |
| } | |
| } | |
| }, | |
| add: function() { | |
| var args, obj, post, recursive, _base, _name; | |
| recursive = arguments[0], post = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : []; | |
| obj = (_base = Recursive.recursives)[_name = post.fullID] || (_base[_name] = { | |
| recursives: [], | |
| args: [] | |
| }); | |
| obj.recursives.push(recursive); | |
| return obj.args.push(args); | |
| }, | |
| rm: function(recursive, post) { | |
| var i, obj, rec, _i, _len, _ref; | |
| if (!(obj = Recursive.recursives[post.fullID])) { | |
| return; | |
| } | |
| _ref = obj.recursives; | |
| for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { | |
| rec = _ref[i]; | |
| if (rec === recursive) { | |
| obj.recursives.splice(i, 1); | |
| obj.args.splice(i, 1); | |
| } | |
| } | |
| }, | |
| apply: function() { | |
| var ID, args, fullID, post, recursive, _ref; | |
| recursive = arguments[0], post = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : []; | |
| fullID = post.fullID; | |
| _ref = g.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| if (__indexOf.call(post.quotes, fullID) >= 0) { | |
| recursive.apply(null, [post].concat(__slice.call(args))); | |
| } | |
| } | |
| } | |
| }; | |
| ThreadHiding = { | |
| init: function() { | |
| if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link']) { | |
| return; | |
| } | |
| this.db = new DataBoard('hiddenThreads'); | |
| this.syncCatalog(); | |
| $.on(d, 'IndexBuild', this.onIndexBuild); | |
| return Thread.callbacks.push({ | |
| name: 'Thread Hiding', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var data; | |
| if (data = ThreadHiding.db.get({ | |
| boardID: this.board.ID, | |
| threadID: this.ID | |
| })) { | |
| ThreadHiding.hide(this, data.makeStub); | |
| } | |
| if (!Conf['Thread Hiding Buttons']) { | |
| return; | |
| } | |
| return $.prepend(this.OP.nodes.root, ThreadHiding.makeButton(this, 'hide')); | |
| }, | |
| onIndexBuild: function(_arg) { | |
| var i, nodes, root, thread, _i, _len; | |
| nodes = _arg.detail; | |
| for (i = _i = 0, _len = nodes.length; _i < _len; i = _i += 2) { | |
| root = nodes[i]; | |
| thread = Get.threadFromRoot(root); | |
| if (!thread.isHidden) { | |
| continue; | |
| } | |
| if (!thread.stub) { | |
| nodes[i + 1].hidden = true; | |
| } else if (!root.contains(thread.stub)) { | |
| ThreadHiding.makeStub(thread, root); | |
| } | |
| } | |
| }, | |
| syncCatalog: function() { | |
| var hiddenThreads, hiddenThreadsOnCatalog, threadID; | |
| hiddenThreads = ThreadHiding.db.get({ | |
| boardID: g.BOARD.ID, | |
| defaultValue: {} | |
| }); | |
| hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; | |
| for (threadID in hiddenThreadsOnCatalog) { | |
| if (!(threadID in hiddenThreads)) { | |
| hiddenThreads[threadID] = {}; | |
| } | |
| } | |
| for (threadID in hiddenThreads) { | |
| if (!(threadID in hiddenThreadsOnCatalog)) { | |
| delete hiddenThreads[threadID]; | |
| } | |
| } | |
| if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) { | |
| ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog); | |
| } | |
| return ThreadHiding.db.set({ | |
| boardID: g.BOARD.ID, | |
| val: hiddenThreads | |
| }); | |
| }, | |
| cleanCatalog: function(hiddenThreadsOnCatalog) { | |
| return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() { | |
| var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1; | |
| if (this.status !== 200) { | |
| return; | |
| } | |
| threads = {}; | |
| _ref = JSON.parse(this.response); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| page = _ref[_i]; | |
| _ref1 = page.threads; | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| thread = _ref1[_j]; | |
| if (thread.no in hiddenThreadsOnCatalog) { | |
| threads[thread.no] = hiddenThreadsOnCatalog[thread.no]; | |
| } | |
| } | |
| } | |
| if (Object.keys(threads).length) { | |
| return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads)); | |
| } else { | |
| return localStorage.removeItem("4chan-hide-t-" + g.BOARD); | |
| } | |
| }); | |
| }, | |
| menu: { | |
| init: function() { | |
| var apply, div, hideStubLink, makeStub; | |
| if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding Link']) { | |
| return; | |
| } | |
| div = $.el('div', { | |
| className: 'hide-thread-link', | |
| textContent: 'Hide thread' | |
| }); | |
| apply = $.el('a', { | |
| textContent: 'Apply', | |
| href: 'javascript:;' | |
| }); | |
| $.on(apply, 'click', ThreadHiding.menu.hide); | |
| makeStub = $.el('label', { | |
| innerHTML: "<input type=checkbox " + (Conf['Stubs'] ? 'checked' : '') + "> Make stub" | |
| }); | |
| $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: div, | |
| order: 20, | |
| open: function(_arg) { | |
| var isReply, thread; | |
| thread = _arg.thread, isReply = _arg.isReply; | |
| if (isReply || thread.isHidden) { | |
| return false; | |
| } | |
| ThreadHiding.menu.thread = thread; | |
| return true; | |
| }, | |
| subEntries: [ | |
| { | |
| el: apply | |
| }, { | |
| el: makeStub | |
| } | |
| ] | |
| }); | |
| div = $.el('a', { | |
| className: 'show-thread-link', | |
| textContent: 'Show thread', | |
| href: 'javascript:;' | |
| }); | |
| $.on(div, 'click', ThreadHiding.menu.show); | |
| $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: div, | |
| order: 20, | |
| open: function(_arg) { | |
| var isReply, thread; | |
| thread = _arg.thread, isReply = _arg.isReply; | |
| if (isReply || !thread.isHidden) { | |
| return false; | |
| } | |
| ThreadHiding.menu.thread = thread; | |
| return true; | |
| } | |
| }); | |
| hideStubLink = $.el('a', { | |
| textContent: 'Hide stub', | |
| href: 'javascript:;' | |
| }); | |
| $.on(hideStubLink, 'click', ThreadHiding.menu.hideStub); | |
| return $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: hideStubLink, | |
| order: 15, | |
| open: function(_arg) { | |
| var isReply, thread; | |
| thread = _arg.thread, isReply = _arg.isReply; | |
| if (isReply || !thread.isHidden) { | |
| return false; | |
| } | |
| return ThreadHiding.menu.thread = thread; | |
| } | |
| }); | |
| }, | |
| hide: function() { | |
| var makeStub, thread; | |
| makeStub = $('input', this.parentNode).checked; | |
| thread = ThreadHiding.menu.thread; | |
| ThreadHiding.hide(thread, makeStub); | |
| ThreadHiding.saveHiddenState(thread, makeStub); | |
| return $.event('CloseMenu'); | |
| }, | |
| show: function() { | |
| var thread; | |
| thread = ThreadHiding.menu.thread; | |
| ThreadHiding.show(thread); | |
| ThreadHiding.saveHiddenState(thread); | |
| return $.event('CloseMenu'); | |
| }, | |
| hideStub: function() { | |
| var thread; | |
| thread = ThreadHiding.menu.thread; | |
| ThreadHiding.hide(thread, false); | |
| $.event('CloseMenu'); | |
| } | |
| }, | |
| makeButton: function(thread, type) { | |
| var a; | |
| a = $.el('a', { | |
| className: "" + type + "-thread-button", | |
| innerHTML: "<span class='fa fa-" + (type === 'hide' ? 'minus' : 'plus') + "-square'></span>", | |
| href: 'javascript:;' | |
| }); | |
| a.dataset.fullID = thread.fullID; | |
| $.on(a, 'click', ThreadHiding.toggle); | |
| return a; | |
| }, | |
| makeStub: function(thread, root) { | |
| var a, numReplies, opInfo, summary; | |
| numReplies = $$('.thread > .replyContainer', root).length; | |
| if (summary = $('.summary', root)) { | |
| numReplies += +summary.textContent.match(/\d+/); | |
| } | |
| opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', thread.OP.nodes.info).textContent; | |
| a = ThreadHiding.makeButton(thread, 'show'); | |
| $.add(a, $.tn(" " + opInfo + " (" + (numReplies === 1 ? '1 reply' : "" + numReplies + " replies") + ")")); | |
| thread.stub = $.el('div', { | |
| className: 'stub' | |
| }); | |
| if (Conf['Menu']) { | |
| $.add(thread.stub, [a, Menu.makeButton()]); | |
| } else { | |
| $.add(thread.stub, a); | |
| } | |
| return $.prepend(root, thread.stub); | |
| }, | |
| saveHiddenState: function(thread, makeStub) { | |
| var hiddenThreadsOnCatalog; | |
| hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; | |
| if (thread.isHidden) { | |
| ThreadHiding.db.set({ | |
| boardID: thread.board.ID, | |
| threadID: thread.ID, | |
| val: { | |
| makeStub: makeStub | |
| } | |
| }); | |
| hiddenThreadsOnCatalog[thread] = true; | |
| } else { | |
| ThreadHiding.db["delete"]({ | |
| boardID: thread.board.ID, | |
| threadID: thread.ID | |
| }); | |
| delete hiddenThreadsOnCatalog[thread]; | |
| } | |
| return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog)); | |
| }, | |
| toggle: function(thread) { | |
| if (!(thread instanceof Thread)) { | |
| thread = g.threads[this.dataset.fullID]; | |
| } | |
| if (thread.isHidden) { | |
| ThreadHiding.show(thread); | |
| } else { | |
| ThreadHiding.hide(thread); | |
| } | |
| return ThreadHiding.saveHiddenState(thread); | |
| }, | |
| hide: function(thread, makeStub) { | |
| var threadRoot; | |
| if (makeStub == null) { | |
| makeStub = Conf['Stubs']; | |
| } | |
| if (thread.isHidden) { | |
| return; | |
| } | |
| threadRoot = thread.OP.nodes.root.parentNode; | |
| thread.isHidden = true; | |
| if (!makeStub) { | |
| return threadRoot.hidden = threadRoot.nextElementSibling.hidden = true; | |
| } | |
| return ThreadHiding.makeStub(thread, threadRoot); | |
| }, | |
| show: function(thread) { | |
| var threadRoot; | |
| if (thread.stub) { | |
| $.rm(thread.stub); | |
| delete thread.stub; | |
| } | |
| threadRoot = thread.OP.nodes.root.parentNode; | |
| return threadRoot.nextElementSibling.hidden = threadRoot.hidden = thread.isHidden = false; | |
| } | |
| }; | |
| QuoteBacklink = { | |
| init: function() { | |
| var format; | |
| if (g.VIEW === 'catalog' || !Conf['Quote Backlinks']) { | |
| return; | |
| } | |
| format = Conf['backlink'].replace(/%id/g, "' + id + '"); | |
| this.funk = Function('id', "return '" + format + "'"); | |
| this.containers = {}; | |
| Post.callbacks.push({ | |
| name: 'Quote Backlinking Part 1', | |
| cb: this.firstNode | |
| }); | |
| return Post.callbacks.push({ | |
| name: 'Quote Backlinking Part 2', | |
| cb: this.secondNode | |
| }); | |
| }, | |
| firstNode: function() { | |
| var a, clone, container, containers, frag, link, post, quote, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; | |
| if (this.isClone || !this.quotes.length) { | |
| return; | |
| } | |
| a = $.el('a', { | |
| href: "/" + this.board + "/res/" + this.thread + "#p" + this, | |
| className: this.isHidden ? 'filtered backlink' : 'backlink', | |
| textContent: (QuoteBacklink.funk(this.ID)) + (Conf['Mark Quotes of You'] && this.info.yours ? '\u00A0(You)' : '') | |
| }); | |
| _ref = this.quotes; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quote = _ref[_i]; | |
| containers = [QuoteBacklink.getContainer(quote)]; | |
| if ((post = g.posts[quote]) && post.nodes.backlinkContainer) { | |
| _ref1 = post.clones; | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| clone = _ref1[_j]; | |
| containers.push(clone.nodes.backlinkContainer); | |
| } | |
| } | |
| for (_k = 0, _len2 = containers.length; _k < _len2; _k++) { | |
| container = containers[_k]; | |
| frag = [$.tn(' '), link = a.cloneNode(true)]; | |
| if (Conf['Quote Previewing']) { | |
| $.on(link, 'mouseover', QuotePreview.mouseover); | |
| } | |
| if (Conf['Quote Inlining']) { | |
| $.on(link, 'click', QuoteInline.toggle); | |
| if (Conf['Quote Hash Navigation']) { | |
| frag.push.apply(frag, QuoteInline.qiQuote(link, $.hasClass(link, 'filtered'))); | |
| } | |
| } | |
| $.add(container, frag); | |
| } | |
| } | |
| }, | |
| secondNode: function() { | |
| var container; | |
| if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) { | |
| this.nodes.backlinkContainer = $('.container', this.nodes.info); | |
| return; | |
| } | |
| if (!(this.isReply || Conf['OP Backlinks'])) { | |
| return; | |
| } | |
| container = QuoteBacklink.getContainer(this.fullID); | |
| this.nodes.backlinkContainer = container; | |
| return $.add(this.nodes.info, container); | |
| }, | |
| getContainer: function(id) { | |
| var _base; | |
| return (_base = this.containers)[id] || (_base[id] = $.el('span', { | |
| className: 'container' | |
| })); | |
| } | |
| }; | |
| QuoteCT = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Mark Cross-thread Quotes']) { | |
| return; | |
| } | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| this.text = '\u00A0(Cross-thread)'; | |
| return Post.callbacks.push({ | |
| name: 'Mark Cross-thread Quotes', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var board, boardID, quotelink, thread, threadID, _i, _len, _ref, _ref1, _ref2; | |
| if (this.isClone && this.thread === this.context.thread) { | |
| return; | |
| } | |
| _ref = this.isClone ? this.context : this, board = _ref.board, thread = _ref.thread; | |
| _ref1 = this.nodes.quotelinks; | |
| for (_i = 0, _len = _ref1.length; _i < _len; _i++) { | |
| quotelink = _ref1[_i]; | |
| _ref2 = Get.postDataFromLink(quotelink), boardID = _ref2.boardID, threadID = _ref2.threadID; | |
| if (!threadID) { | |
| continue; | |
| } | |
| if (this.isClone) { | |
| quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, ''); | |
| } | |
| if (boardID === board.ID && threadID !== thread.ID) { | |
| $.add(quotelink, $.tn(QuoteCT.text)); | |
| } | |
| } | |
| } | |
| }; | |
| QuoteInline = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Quote Inlining']) { | |
| return; | |
| } | |
| if (Conf['Quote Hash Navigation']) { | |
| this.node = function() { | |
| var link, _i, _len, _ref; | |
| _ref = this.nodes.quotelinks.concat(__slice.call(this.nodes.backlinks)); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| link = _ref[_i]; | |
| if (!this.isClone) { | |
| $.after(link, QuoteInline.qiQuote(link, $.hasClass(link, 'filtered'))); | |
| } | |
| $.on(link, 'click', QuoteInline.toggle); | |
| } | |
| }; | |
| } else { | |
| this.node = function() { | |
| var link, _i, _len, _ref; | |
| _ref = this.nodes.quotelinks.concat(__slice.call(this.nodes.backlinks)); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| link = _ref[_i]; | |
| $.on(link, 'click', QuoteInline.toggle); | |
| } | |
| }; | |
| } | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Quote Inlining', | |
| cb: this.node | |
| }); | |
| }, | |
| qiQuote: function(link, hidden) { | |
| return [ | |
| $.tn(' '), $.el('a', { | |
| className: hidden ? 'hashlink filtered' : 'hashlink', | |
| textContent: '#', | |
| href: link.href | |
| }) | |
| ]; | |
| }, | |
| toggle: function(e) { | |
| var boardID, context, postID, threadID, _ref; | |
| if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| _ref = Get.postDataFromLink(this), boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID; | |
| context = Get.contextFromNode(this); | |
| if ($.hasClass(this, 'inlined')) { | |
| QuoteInline.rm(this, boardID, threadID, postID, context); | |
| } else { | |
| if ($.x("ancestor::div[@id='p" + postID + "']", this)) { | |
| return; | |
| } | |
| QuoteInline.add(this, boardID, threadID, postID, context); | |
| } | |
| return this.classList.toggle('inlined'); | |
| }, | |
| findRoot: function(quotelink, isBacklink) { | |
| if (isBacklink) { | |
| return quotelink.parentNode.parentNode; | |
| } else { | |
| return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink); | |
| } | |
| }, | |
| add: function(quotelink, boardID, threadID, postID, context) { | |
| var inline, isBacklink, post, qroot, root; | |
| isBacklink = $.hasClass(quotelink, 'backlink'); | |
| inline = $.el('div', { | |
| id: "i" + postID, | |
| className: 'inline' | |
| }); | |
| root = QuoteInline.findRoot(quotelink, isBacklink); | |
| $.after(root, inline); | |
| qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); | |
| $.addClass(qroot, 'hasInline'); | |
| Get.postClone(boardID, threadID, postID, inline, context); | |
| if (!((post = g.posts["" + boardID + "." + postID]) && context.thread === post.thread)) { | |
| return; | |
| } | |
| if (isBacklink && Conf['Forward Hiding']) { | |
| $.addClass(post.nodes.root, 'forwarded'); | |
| post.forwarded++ || (post.forwarded = 1); | |
| } | |
| if (!Unread.posts) { | |
| return; | |
| } | |
| return Unread.readSinglePost(post); | |
| }, | |
| rm: function(quotelink, boardID, threadID, postID, context) { | |
| var el, inlined, isBacklink, post, qroot, root, _ref; | |
| isBacklink = $.hasClass(quotelink, 'backlink'); | |
| root = QuoteInline.findRoot(quotelink, isBacklink); | |
| root = $.x("following-sibling::div[@id='i" + postID + "'][1]", root); | |
| qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); | |
| $.rm(root); | |
| if (!$('.inline', qroot)) { | |
| $.rmClass(qroot, 'hasInline'); | |
| } | |
| if (!(el = root.firstElementChild)) { | |
| return; | |
| } | |
| post = g.posts["" + boardID + "." + postID]; | |
| post.rmClone(el.dataset.clone); | |
| if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads["" + boardID + "." + threadID] && !--post.forwarded) { | |
| delete post.forwarded; | |
| $.rmClass(post.nodes.root, 'forwarded'); | |
| } | |
| while (inlined = $('.inlined', el)) { | |
| _ref = Get.postDataFromLink(inlined), boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID; | |
| QuoteInline.rm(inlined, boardID, threadID, postID, context); | |
| $.rmClass(inlined, 'inlined'); | |
| } | |
| } | |
| }; | |
| QuoteOP = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Mark OP Quotes']) { | |
| return; | |
| } | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| this.text = '\u00A0(OP)'; | |
| return Post.callbacks.push({ | |
| name: 'Mark OP Quotes', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var boardID, fullID, i, postID, quotelink, quotelinks, quotes, _ref, _ref1; | |
| if (this.isClone && this.thread === this.context.thread) { | |
| return; | |
| } | |
| if (!(quotes = this.quotes).length) { | |
| return; | |
| } | |
| quotelinks = this.nodes.quotelinks; | |
| if (this.isClone && (_ref = this.thread.fullID, __indexOf.call(quotes, _ref) >= 0)) { | |
| i = 0; | |
| while (quotelink = quotelinks[i++]) { | |
| quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, ''); | |
| } | |
| } | |
| fullID = (this.isClone ? this.context : this).thread.fullID; | |
| if (__indexOf.call(quotes, fullID) < 0) { | |
| return; | |
| } | |
| i = 0; | |
| while (quotelink = quotelinks[i++]) { | |
| _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID; | |
| if (("" + boardID + "." + postID) === fullID) { | |
| $.add(quotelink, $.tn(QuoteOP.text)); | |
| } | |
| } | |
| } | |
| }; | |
| QuotePreview = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Quote Previewing']) { | |
| return; | |
| } | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Quote Previewing', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var link, _i, _len, _ref; | |
| _ref = this.nodes.quotelinks.concat(__slice.call(this.nodes.backlinks)); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| link = _ref[_i]; | |
| $.on(link, 'mouseover', QuotePreview.mouseover); | |
| } | |
| }, | |
| mouseover: function(e) { | |
| var boardID, clone, origin, post, postID, posts, qp, quote, quoterID, threadID, _i, _j, _len, _len1, _ref, _ref1; | |
| if ($.hasClass(this, 'inlined')) { | |
| return; | |
| } | |
| _ref = Get.postDataFromLink(this), boardID = _ref.boardID, threadID = _ref.threadID, postID = _ref.postID; | |
| qp = $.el('div', { | |
| id: 'qp', | |
| className: 'dialog' | |
| }); | |
| $.add(Header.hover, qp); | |
| Get.postClone(boardID, threadID, postID, qp, Get.contextFromNode(this)); | |
| UI.hover({ | |
| root: this, | |
| el: qp, | |
| latestEvent: e, | |
| endEvents: 'mouseout click', | |
| cb: QuotePreview.mouseout, | |
| asapTest: function() { | |
| return qp.firstElementChild; | |
| } | |
| }); | |
| if (!(origin = g.posts["" + boardID + "." + postID])) { | |
| return; | |
| } | |
| if (Conf['Quote Highlighting']) { | |
| posts = [origin].concat(origin.clones); | |
| posts.pop(); | |
| for (_i = 0, _len = posts.length; _i < _len; _i++) { | |
| post = posts[_i]; | |
| $.addClass(post.nodes.post, 'qphl'); | |
| } | |
| } | |
| quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0]; | |
| clone = Get.postFromRoot(qp.firstChild); | |
| _ref1 = clone.nodes.quotelinks.concat(__slice.call(clone.nodes.backlinks)); | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| quote = _ref1[_j]; | |
| if (quote.hash.slice(2) === quoterID) { | |
| $.addClass(quote, 'forwardlink'); | |
| } | |
| } | |
| }, | |
| mouseout: function() { | |
| var clone, post, root, _i, _len, _ref; | |
| if (!(root = this.el.firstElementChild)) { | |
| return; | |
| } | |
| clone = Get.postFromRoot(root); | |
| post = clone.origin; | |
| post.rmClone(root.dataset.clone); | |
| if (!Conf['Quote Highlighting']) { | |
| return; | |
| } | |
| _ref = [post].concat(post.clones); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| post = _ref[_i]; | |
| $.rmClass(post.nodes.post, 'qphl'); | |
| } | |
| } | |
| }; | |
| QuoteStrikeThrough = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Reply Hiding Buttons'] && !Conf['Reply Hiding Link'] && !Conf['Filter']) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Strike-through Quotes', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var boardID, postID, quotelink, _i, _len, _ref, _ref1, _ref2; | |
| if (this.isClone) { | |
| return; | |
| } | |
| _ref = this.nodes.quotelinks; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quotelink = _ref[_i]; | |
| _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID; | |
| if ((_ref2 = g.posts["" + boardID + "." + postID]) != null ? _ref2.isHidden : void 0) { | |
| $.addClass(quotelink, 'filtered'); | |
| } | |
| } | |
| } | |
| }; | |
| /* | |
| <3 aeosynth | |
| */ | |
| QuoteThreading = { | |
| init: function() { | |
| var input; | |
| if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) { | |
| return; | |
| } | |
| this.enabled = true; | |
| this.controls = $.el('span', { | |
| innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>' | |
| }); | |
| input = $('input', this.controls); | |
| $.on(input, 'change', this.toggle); | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: this.controls, | |
| order: 98 | |
| }); | |
| if (!Conf['Unread Count']) { | |
| $.on(d, '4chanXInitFinished', this.setup); | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Quote Threading', | |
| cb: this.node | |
| }); | |
| }, | |
| setup: function() { | |
| $.off(d, '4chanXInitFinished', QuoteThreading.setup); | |
| return QuoteThreading.force(); | |
| }, | |
| force: function() { | |
| var ID, post, _ref; | |
| _ref = g.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| if (post.cb) { | |
| post.cb(true); | |
| } | |
| } | |
| }, | |
| node: function() { | |
| var keys, len, post, posts, quote, _i, _len, _ref; | |
| posts = g.posts; | |
| if (this.isClone || !QuoteThreading.enabled) { | |
| return; | |
| } | |
| if (Conf['Unread Count']) { | |
| Unread.posts.push(this); | |
| } | |
| if (this.thread.OP === this || !(post = posts[this.fullID]) || post.isHidden) { | |
| return; | |
| } | |
| keys = []; | |
| len = g.BOARD.ID.length + 1; | |
| _ref = this.quotes; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quote = _ref[_i]; | |
| if ((quote.slice(len) < this.ID) && quote in posts) { | |
| keys.push(quote); | |
| } | |
| } | |
| if (keys.length !== 1) { | |
| return; | |
| } | |
| this.threaded = keys[0]; | |
| return this.cb = QuoteThreading.nodeinsert; | |
| }, | |
| nodeinsert: function(force) { | |
| var bottom, height, post, posts, root, threadContainer, top, _ref; | |
| post = g.posts[this.threaded]; | |
| if (this.thread.OP === post) { | |
| return false; | |
| } | |
| posts = Unread.posts; | |
| root = post.nodes.root; | |
| if (!force) { | |
| height = doc.clientHeight; | |
| _ref = root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; | |
| if (!((Conf['Unread Count'] && posts[post.ID]) || ((bottom < height) && (top > 0)))) { | |
| return false; | |
| } | |
| } | |
| if ($.hasClass(root, 'threadOP')) { | |
| threadContainer = root.nextElementSibling; | |
| post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer)); | |
| $.add(threadContainer, this.nodes.root); | |
| } else { | |
| threadContainer = $.el('div', { | |
| className: 'threadContainer' | |
| }); | |
| $.add(threadContainer, this.nodes.root); | |
| $.after(root, threadContainer); | |
| $.addClass(root, 'threadOP'); | |
| } | |
| if (!Conf['Unread Count']) { | |
| return true; | |
| } | |
| if (posts[post.ID]) { | |
| posts.after(post, this); | |
| } else { | |
| posts.prepend(this); | |
| } | |
| return true; | |
| }, | |
| toggle: function() { | |
| var ID, container, containers, nodes, post, posts, thread, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; | |
| if (QuoteThreading.enabled = this.checked) { | |
| QuoteThreading.force(); | |
| } else { | |
| thread = $('.thread'); | |
| posts = []; | |
| nodes = []; | |
| _ref = g.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| if (!(post === post.thread.OP || post.isClone)) { | |
| posts.push(post); | |
| } | |
| } | |
| posts.sort(function(a, b) { | |
| return a.ID - b.ID; | |
| }); | |
| for (_i = 0, _len = posts.length; _i < _len; _i++) { | |
| post = posts[_i]; | |
| nodes.push(post.nodes.root); | |
| } | |
| $.add(thread, nodes); | |
| containers = $$('.threadContainer', thread); | |
| for (_j = 0, _len1 = containers.length; _j < _len1; _j++) { | |
| container = containers[_j]; | |
| $.rm(container); | |
| } | |
| _ref1 = $$('.threadOP'); | |
| for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { | |
| post = _ref1[_k]; | |
| $.rmClass(post, 'threadOP'); | |
| } | |
| } | |
| }, | |
| kb: function() { | |
| var control; | |
| control = $.id('threadingControl'); | |
| control.checked = !control.checked; | |
| return QuoteThreading.toggle.call(control); | |
| } | |
| }; | |
| QuoteYou = { | |
| init: function() { | |
| if (!(g.VIEW !== 'catalog' && Conf['Mark Quotes of You'] && Conf['Quick Reply'])) { | |
| return; | |
| } | |
| if (Conf['Highlight Own Posts']) { | |
| $.addClass(doc, 'highlight-own'); | |
| } | |
| if (Conf['Highlight Posts Quoting You']) { | |
| $.addClass(doc, 'highlight-you'); | |
| } | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| this.text = '\u00A0(You)'; | |
| return Post.callbacks.push({ | |
| name: 'Mark Quotes of You', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var quotelink, _i, _len, _ref; | |
| if (this.isClone) { | |
| return; | |
| } | |
| if (this.info.yours) { | |
| $.addClass(this.nodes.root, 'yourPost'); | |
| } | |
| if (!this.quotes.length) { | |
| return; | |
| } | |
| _ref = this.nodes.quotelinks; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quotelink = _ref[_i]; | |
| if (!(QR.db.get(Get.postDataFromLink(quotelink)))) { | |
| continue; | |
| } | |
| $.add(quotelink, $.tn(QuoteYou.text)); | |
| $.addClass(quotelink, 'you'); | |
| $.addClass(this.nodes.root, 'quotesYou'); | |
| } | |
| }, | |
| cb: { | |
| seek: function(type) { | |
| var highlight, post, posts, result, str; | |
| if (!(Conf['Mark Quotes of You'] && Conf['Quick Reply'])) { | |
| return; | |
| } | |
| if (highlight = $('.highlight')) { | |
| $.rmClass(highlight, 'highlight'); | |
| } | |
| if (!QuoteYou.lastRead) { | |
| if (!(post = QuoteYou.lastRead = $('.quotesYou'))) { | |
| new Notice('warning', 'No posts are currently quoting you, loser.', 20); | |
| return; | |
| } | |
| if (QuoteYou.cb.scroll(post)) { | |
| return; | |
| } | |
| } else { | |
| post = QuoteYou.lastRead; | |
| } | |
| str = "" + type + "::div[contains(@class,'quotesYou')]"; | |
| while (post = (result = $.X(str, post)).snapshotItem(type === 'preceding' ? result.snapshotLength - 1 : 0)) { | |
| if (QuoteYou.cb.scroll(post)) { | |
| return; | |
| } | |
| } | |
| posts = $$('.quotesYou'); | |
| return QuoteYou.cb.scroll(posts[type === 'following' ? 0 : posts.length - 1]); | |
| }, | |
| scroll: function(post) { | |
| if (Get.postFromRoot(post).isHidden) { | |
| return false; | |
| } else { | |
| QuoteYou.lastRead = post; | |
| window.location = "#" + post.id; | |
| Header.scrollToPost(post); | |
| $.addClass($('.post', post), 'highlight'); | |
| return true; | |
| } | |
| } | |
| } | |
| }; | |
| Quotify = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Resurrect Quotes']) { | |
| return; | |
| } | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Resurrect Quotes', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var deadlink, _i, _len, _ref; | |
| _ref = $$('.deadlink', this.nodes.comment); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| deadlink = _ref[_i]; | |
| if (this.isClone) { | |
| if ($.hasClass(deadlink, 'quotelink')) { | |
| this.nodes.quotelinks.push(deadlink); | |
| } | |
| } else { | |
| Quotify.parseDeadlink.call(this, deadlink); | |
| } | |
| } | |
| }, | |
| parseDeadlink: function(deadlink) { | |
| var a, boardID, m, post, postID, quote, quoteID, redirect, _ref; | |
| if ($.hasClass(deadlink.parentNode, 'prettyprint')) { | |
| Quotify.fixDeadlink(deadlink); | |
| return; | |
| } | |
| quote = deadlink.textContent; | |
| if (!(postID = (_ref = quote.match(/\d+$/)) != null ? _ref[0] : void 0)) { | |
| return; | |
| } | |
| if (postID[0] === '0') { | |
| Quotify.fixDeadlink(deadlink); | |
| return; | |
| } | |
| boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID; | |
| quoteID = "" + boardID + "." + postID; | |
| if (post = g.posts[quoteID]) { | |
| if (!post.isDead) { | |
| a = $.el('a', { | |
| href: "/" + boardID + "/res/" + post.thread + "#p" + postID, | |
| className: 'quotelink', | |
| textContent: quote | |
| }); | |
| } else { | |
| a = $.el('a', { | |
| href: "/" + boardID + "/res/" + post.thread + "#p" + postID, | |
| className: 'quotelink deadlink', | |
| target: '_blank', | |
| textContent: "" + quote + "\u00A0(Dead)" | |
| }); | |
| $.extend(a.dataset, { | |
| boardID: boardID, | |
| threadID: post.thread.ID, | |
| postID: postID | |
| }); | |
| } | |
| } else if (redirect = Redirect.to('thread', { | |
| boardID: boardID, | |
| threadID: 0, | |
| postID: postID | |
| })) { | |
| a = $.el('a', { | |
| href: redirect, | |
| className: 'deadlink', | |
| target: '_blank', | |
| textContent: "" + quote + "\u00A0(Dead)" | |
| }); | |
| if (Redirect.to('post', { | |
| boardID: boardID, | |
| postID: postID | |
| })) { | |
| $.addClass(a, 'quotelink'); | |
| $.extend(a.dataset, { | |
| boardID: boardID, | |
| postID: postID | |
| }); | |
| } | |
| } | |
| if (__indexOf.call(this.quotes, quoteID) < 0) { | |
| this.quotes.push(quoteID); | |
| } | |
| if (!a) { | |
| return deadlink.textContent = "" + quote + "\u00A0(Dead)"; | |
| } | |
| $.replace(deadlink, a); | |
| if ($.hasClass(a, 'quotelink')) { | |
| return this.nodes.quotelinks.push(a); | |
| } | |
| }, | |
| fixDeadlink: function(deadlink) { | |
| var el, green; | |
| if (!(el = deadlink.previousSibling) || el.nodeName === 'BR') { | |
| green = $.el('span', { | |
| className: 'quote' | |
| }); | |
| $.before(deadlink, green); | |
| $.add(green, deadlink); | |
| } | |
| return $.replace(deadlink, __slice.call(deadlink.childNodes)); | |
| } | |
| }; | |
| QR = { | |
| mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', ''], | |
| init: function() { | |
| var sc; | |
| if (!Conf['Quick Reply']) { | |
| return; | |
| } | |
| this.db = new DataBoard('yourPosts'); | |
| this.posts = []; | |
| if (Conf['QR Shortcut']) { | |
| sc = $.el('a', { | |
| className: "qr-shortcut fa fa-comment-o " + (!Conf['Persistent QR'] ? 'disabled' : ''), | |
| textContent: 'QR', | |
| title: 'Quick Reply', | |
| href: 'javascript:;' | |
| }); | |
| $.on(sc, 'click', function() { | |
| if (Conf['Persistent QR'] || !QR.nodes || QR.nodes.el.hidden) { | |
| $.event('CloseMenu'); | |
| QR.open(); | |
| QR.nodes.com.focus(); | |
| return $.rmClass(this, 'disabled'); | |
| } else { | |
| QR.close(); | |
| return $.addClass(this, 'disabled'); | |
| } | |
| }); | |
| Header.addShortcut(sc); | |
| } | |
| if (Conf['Hide Original Post Form']) { | |
| $.asap((function() { | |
| return doc; | |
| }), function() { | |
| return $.addClass(doc, 'hide-original-post-form'); | |
| }); | |
| } | |
| $.ready(this.initReady); | |
| if (Conf['Persistent QR']) { | |
| if (!(g.BOARD.ID === 'f' && g.VIEW === 'index')) { | |
| $.on(d, '4chanXInitFinished', this.persist); | |
| } else { | |
| $.ready(this.persist); | |
| } | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Quick Reply', | |
| cb: this.node | |
| }); | |
| }, | |
| initReady: function() { | |
| var link; | |
| QR.postingIsEnabled = !!$.id('postForm'); | |
| if (!QR.postingIsEnabled) { | |
| return; | |
| } | |
| link = $.el('h1', { | |
| innerHTML: "<a href=javascript:; class='qr-link'>" + (g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread') + "</a>", | |
| className: "qr-link-container" | |
| }); | |
| $.on(link.firstChild, 'click', function() { | |
| $.event('CloseMenu'); | |
| QR.open(); | |
| QR.nodes.com.focus(); | |
| if (Conf['QR Shortcut']) { | |
| return $.rmClass($('.qr-shortcut'), 'disabled'); | |
| } | |
| }); | |
| $.before($.id('postForm'), link); | |
| $.on(d, 'QRGetSelectedPost', function(_arg) { | |
| var cb; | |
| cb = _arg.detail; | |
| return cb(QR.selected); | |
| }); | |
| $.on(d, 'QRAddPreSubmitHook', function(_arg) { | |
| var cb; | |
| cb = _arg.detail; | |
| return QR.preSubmitHooks.push(cb); | |
| }); | |
| $.on(d, 'dragover', QR.dragOver); | |
| $.on(d, 'drop', QR.dropFile); | |
| $.on(d, 'dragstart dragend', QR.drag); | |
| switch (g.VIEW) { | |
| case 'index': | |
| return $.on(d, 'IndexRefresh', QR.generatePostableThreadsList); | |
| case 'thread': | |
| return $.on(d, 'ThreadUpdate', function() { | |
| if (g.DEAD) { | |
| return QR.abort(); | |
| } else { | |
| return QR.status(); | |
| } | |
| }); | |
| } | |
| }, | |
| node: function() { | |
| return $.on($('a[title="Quote this post"]', this.nodes.info), 'click', QR.quote); | |
| }, | |
| persist: function() { | |
| if (!QR.postingIsEnabled) { | |
| return; | |
| } | |
| QR.open(); | |
| if (Conf['Auto Hide QR'] || g.VIEW === 'catalog') { | |
| return QR.hide(); | |
| } | |
| }, | |
| open: function() { | |
| var err; | |
| if (QR.nodes) { | |
| QR.nodes.el.hidden = false; | |
| QR.unhide(); | |
| return; | |
| } | |
| try { | |
| return QR.dialog(); | |
| } catch (_error) { | |
| err = _error; | |
| delete QR.nodes; | |
| return Main.handleErrors({ | |
| message: 'Quick Reply dialog creation crashed.', | |
| error: err | |
| }); | |
| } | |
| }, | |
| close: function() { | |
| var post, _i, _len, _ref; | |
| if (QR.req) { | |
| QR.abort(); | |
| return; | |
| } | |
| QR.nodes.el.hidden = true; | |
| QR.cleanNotifications(); | |
| d.activeElement.blur(); | |
| $.rmClass(QR.nodes.el, 'dump'); | |
| if (!Conf['Captcha Warning Notifications']) { | |
| if (QR.captcha.isEnabled) { | |
| $.rmClass(QR.captcha.nodes.input, 'error'); | |
| } | |
| } | |
| if (Conf['QR Shortcut']) { | |
| $.toggleClass($('.qr-shortcut'), 'disabled'); | |
| } | |
| new QR.post(true); | |
| _ref = QR.posts.splice(0, QR.posts.length - 1); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| post = _ref[_i]; | |
| post["delete"](); | |
| } | |
| QR.cooldown.auto = false; | |
| return QR.status(); | |
| }, | |
| focusin: function() { | |
| return $.addClass(QR.nodes.el, 'focus'); | |
| }, | |
| focusout: function() { | |
| return $.rmClass(QR.nodes.el, 'focus'); | |
| }, | |
| hide: function() { | |
| d.activeElement.blur(); | |
| $.addClass(QR.nodes.el, 'autohide'); | |
| return QR.nodes.autohide.checked = true; | |
| }, | |
| unhide: function() { | |
| $.rmClass(QR.nodes.el, 'autohide'); | |
| return QR.nodes.autohide.checked = false; | |
| }, | |
| toggleHide: function() { | |
| if (this.checked) { | |
| return QR.hide(); | |
| } else { | |
| return QR.unhide(); | |
| } | |
| }, | |
| error: function(err) { | |
| var el; | |
| QR.open(); | |
| if (typeof err === 'string') { | |
| el = $.tn(err); | |
| } else { | |
| el = err; | |
| el.removeAttribute('style'); | |
| } | |
| if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) { | |
| QR.captcha.nodes.input.focus(); | |
| if (Conf['Captcha Warning Notifications'] && !d.hidden) { | |
| QR.notify(el); | |
| } else { | |
| $.addClass(QR.captcha.nodes.input, 'error'); | |
| $.on(QR.captcha.nodes.input, 'keydown', function() { | |
| return $.rmClass(QR.captcha.nodes.input, 'error'); | |
| }); | |
| } | |
| } else { | |
| QR.notify(el); | |
| } | |
| if (d.hidden) { | |
| return alert(el.textContent); | |
| } | |
| }, | |
| notify: function(el) { | |
| var notice, notif; | |
| notice = new Notice('warning', el); | |
| if (!(Header.areNotificationsEnabled && d.hidden)) { | |
| return QR.notifications.push(notice); | |
| } else { | |
| notif = new Notification(el.textContent, { | |
| body: el.textContent, | |
| icon: Favicon.logo | |
| }); | |
| return notif.onclick = function() { | |
| return window.focus(); | |
| }; | |
| } | |
| }, | |
| notifications: [], | |
| cleanNotifications: function() { | |
| var notification, _i, _len, _ref; | |
| _ref = QR.notifications; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| notification = _ref[_i]; | |
| notification.close(); | |
| } | |
| return QR.notifications = []; | |
| }, | |
| status: function() { | |
| var disabled, status, thread, value; | |
| if (!QR.nodes) { | |
| return; | |
| } | |
| thread = QR.posts[0].thread; | |
| if (thread !== 'new' && g.threads["" + g.BOARD + "." + thread].isDead) { | |
| value = 404; | |
| disabled = true; | |
| QR.cooldown.auto = false; | |
| } | |
| value = QR.req ? QR.req.progress : QR.cooldown.seconds || value; | |
| status = QR.nodes.status; | |
| status.value = !value ? 'Submit' : QR.cooldown.auto ? "Auto " + value : value; | |
| return status.disabled = disabled || false; | |
| }, | |
| quote: function(e) { | |
| var caretPos, com, index, post, range, s, sel, text, thread, _ref; | |
| if (e != null) { | |
| e.preventDefault(); | |
| } | |
| if (!QR.postingIsEnabled) { | |
| return; | |
| } | |
| sel = d.getSelection(); | |
| post = Get.postFromNode(this); | |
| text = ">>" + post + "\n"; | |
| if ((s = sel.toString().trim()) && post === Get.postFromNode(sel.anchorNode)) { | |
| s = s.replace(/\n/g, '\n>'); | |
| text += ">" + s + "\n"; | |
| } | |
| QR.open(); | |
| if (QR.selected.isLocked) { | |
| index = QR.posts.indexOf(QR.selected); | |
| (QR.posts[index + 1] || new QR.post()).select(); | |
| $.addClass(QR.nodes.el, 'dump'); | |
| QR.cooldown.auto = true; | |
| } | |
| _ref = QR.nodes, com = _ref.com, thread = _ref.thread; | |
| if (!com.value) { | |
| thread.value = Get.threadFromNode(this); | |
| } | |
| caretPos = com.selectionStart; | |
| com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd); | |
| range = caretPos + text.length; | |
| com.setSelectionRange(range, range); | |
| com.focus(); | |
| QR.selected.save(com); | |
| QR.selected.save(thread); | |
| if (Conf['QR Shortcut']) { | |
| return $.rmClass($('.qr-shortcut'), 'disabled'); | |
| } | |
| }, | |
| characterCount: function() { | |
| var count, counter; | |
| counter = QR.nodes.charCount; | |
| count = QR.nodes.com.textLength; | |
| counter.textContent = count; | |
| counter.hidden = count < 1000; | |
| return (count > 1500 ? $.addClass : $.rmClass)(counter, 'warning'); | |
| }, | |
| drag: function(e) { | |
| var toggle; | |
| toggle = e.type === 'dragstart' ? $.off : $.on; | |
| toggle(d, 'dragover', QR.dragOver); | |
| return toggle(d, 'drop', QR.dropFile); | |
| }, | |
| dragOver: function(e) { | |
| e.preventDefault(); | |
| return e.dataTransfer.dropEffect = 'copy'; | |
| }, | |
| dropFile: function(e) { | |
| if (!e.dataTransfer.files.length) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| QR.open(); | |
| return QR.handleFiles(e.dataTransfer.files); | |
| }, | |
| paste: function(e) { | |
| var blob, files, item, _i, _len, _ref; | |
| files = []; | |
| _ref = e.clipboardData.items; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| item = _ref[_i]; | |
| if (!(item.kind === 'file')) { | |
| continue; | |
| } | |
| blob = item.getAsFile(); | |
| blob.name = 'file'; | |
| if (blob.type) { | |
| blob.name += '.' + blob.type.split('/')[1]; | |
| } | |
| files.push(blob); | |
| } | |
| if (!files.length) { | |
| return; | |
| } | |
| QR.open(); | |
| QR.handleFiles(files); | |
| return $.addClass(QR.nodes.el, 'dump'); | |
| }, | |
| handleBlob: function(urlBlob, header, url) { | |
| var blob, end, endnl, endsc, mime, name, name_end, name_start, start, _ref; | |
| name = url.substr(url.lastIndexOf('/') + 1, url.length); | |
| start = header.indexOf("Content-Type: ") + 14; | |
| endsc = header.substr(start, header.length).indexOf(";"); | |
| endnl = header.substr(start, header.length).indexOf("\n") - 1; | |
| end = endnl; | |
| if (endsc !== -1 && endsc < endnl) { | |
| end = endsc; | |
| } | |
| mime = header.substr(start, end); | |
| blob = new Blob([urlBlob], { | |
| type: mime | |
| }); | |
| blob.name = url.substr(url.lastIndexOf('/') + 1, url.length); | |
| name_start = header.indexOf('name="') + 6; | |
| if (name_start - 6 !== -1) { | |
| name_end = header.substr(name_start, header.length).indexOf('"'); | |
| blob.name = header.substr(name_start, name_end); | |
| } | |
| if (blob.type === null) { | |
| return QR.error("Unsupported file type."); | |
| } | |
| if (_ref = blob.type, __indexOf.call(QR.mimeTypes, _ref) < 0) { | |
| return QR.error("Unsupported file type."); | |
| } | |
| return QR.handleFiles([blob]); | |
| }, | |
| handleUrl: function() { | |
| var url; | |
| url = prompt("Insert an url:"); | |
| if (url === null) { | |
| return; | |
| } | |
| GM_xmlhttpRequest({ | |
| method: "GET", | |
| url: url, | |
| overrideMimeType: "text/plain; charset=x-user-defined", | |
| onload: function(xhr) { | |
| var data, i, r; | |
| r = xhr.responseText; | |
| data = new Uint8Array(r.length); | |
| i = 0; | |
| while (i < r.length) { | |
| data[i] = r.charCodeAt(i); | |
| i++; | |
| } | |
| QR.handleBlob(data, xhr.responseHeaders, url); | |
| return; | |
| return { | |
| onerror: function(xhr) { | |
| return QR.error("Can't load image."); | |
| } | |
| }; | |
| } | |
| }); | |
| }, | |
| handleFiles: function(files) { | |
| var file, isSingle, max, _i, _len; | |
| if (this !== QR) { | |
| files = __slice.call(this.files); | |
| this.value = null; | |
| } | |
| if (!files.length) { | |
| return; | |
| } | |
| max = QR.nodes.fileInput.max; | |
| isSingle = files.length === 1; | |
| QR.cleanNotifications(); | |
| for (_i = 0, _len = files.length; _i < _len; _i++) { | |
| file = files[_i]; | |
| QR.handleFile(file, isSingle, max); | |
| } | |
| if (!isSingle) { | |
| return $.addClass(QR.nodes.el, 'dump'); | |
| } | |
| }, | |
| handleFile: function(file, isSingle, max) { | |
| var post, _ref; | |
| if (file.size > max) { | |
| QR.error("" + file.name + ": File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ")."); | |
| return; | |
| } else if (_ref = file.type, __indexOf.call(QR.mimeTypes, _ref) < 0) { | |
| if (!/^text/.test(file.type)) { | |
| QR.error("" + file.name + ": Unsupported file type."); | |
| return; | |
| } | |
| if (isSingle) { | |
| post = QR.selected; | |
| } else if ((post = QR.posts[QR.posts.length - 1]).com) { | |
| post = new QR.post(); | |
| } | |
| post.pasteText(file); | |
| return; | |
| } | |
| if (isSingle) { | |
| post = QR.selected; | |
| } else if ((post = QR.posts[QR.posts.length - 1]).file) { | |
| post = new QR.post(); | |
| } | |
| return post.setFile(file); | |
| }, | |
| openFileInput: function(e) { | |
| var _ref; | |
| e.stopPropagation(); | |
| if (e.shiftKey && e.type === 'click') { | |
| return QR.selected.rmFile(); | |
| } | |
| if (e.ctrlKey && e.type === 'click') { | |
| $.addClass(QR.nodes.filename, 'edit'); | |
| QR.nodes.filename.focus(); | |
| return $.on(QR.nodes.filename, 'blur', function() { | |
| return $.rmClass(QR.nodes.filename, 'edit'); | |
| }); | |
| } | |
| if (e.target.nodeName === 'INPUT' || (e.keyCode && ((_ref = e.keyCode) !== 32 && _ref !== 13)) || e.ctrlKey) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| return QR.nodes.fileInput.click(); | |
| }, | |
| generatePostableThreadsList: function() { | |
| var list, options, thread, val; | |
| if (!QR.nodes) { | |
| return; | |
| } | |
| list = QR.nodes.thread; | |
| options = [list.firstChild]; | |
| for (thread in g.BOARD.threads) { | |
| options.push($.el('option', { | |
| value: thread, | |
| textContent: "Thread No." + thread | |
| })); | |
| } | |
| val = list.value; | |
| $.rmAll(list); | |
| $.add(list, options); | |
| list.value = val; | |
| if (!list.value) { | |
| return; | |
| } | |
| return list.value = g.VIEW === 'thread' ? g.THREADID : 'new'; | |
| }, | |
| dialog: function() { | |
| var dialog, elm, event, flagSelector, i, items, key, name, node, nodes, save, value, _ref; | |
| QR.nodes = nodes = { | |
| el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div class=move><label><input type=checkbox id=autohide title=Auto-hide>Quick Reply</label><a href=javascript:; class=close title=Close>×</a><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1 tabindex=10><input name=email data-name=email list=\"list-email\" placeholder=E-mail class=field size=1 tabindex=20><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1 tabindex=30> </div><div class=textarea><textarea data-name=com placeholder=Comment class=field tabindex=40></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\" tabindex=50>+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=60><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><a id=qr-filerm href=javascript:; title='Remove file'><i class=\"fa fa-times-circle\"></i></a><a id=url-button title='Post from url'><i class=\"fa fa-link\"></i></a><a id=dump-button title='Dump list'><i class=\"fa fa-plus-square\"></i></a></span></span><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70></label><input type=submit tabindex=80></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist> ") | |
| }; | |
| _ref = { | |
| move: '.move', | |
| autohide: '#autohide', | |
| thread: 'select', | |
| threadPar: '#qr-thread-select', | |
| close: '.close', | |
| form: 'form', | |
| dumpButton: '#dump-button', | |
| urlButton: '#url-button', | |
| name: '[data-name=name]', | |
| email: '[data-name=email]', | |
| sub: '[data-name=sub]', | |
| com: '[data-name=com]', | |
| dumpList: '#dump-list', | |
| addPost: '#add-post', | |
| charCount: '#char-count', | |
| fileSubmit: '#file-n-submit', | |
| filename: '#qr-filename', | |
| fileContainer: '#qr-filename-container', | |
| fileRM: '#qr-filerm', | |
| fileExtras: '#qr-extras-container', | |
| spoiler: '#qr-file-spoiler', | |
| spoilerPar: '#qr-spoiler-label', | |
| status: '[type=submit]', | |
| fileInput: '[type=file]' | |
| }; | |
| for (key in _ref) { | |
| value = _ref[key]; | |
| nodes[key] = $(value, dialog); | |
| } | |
| nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value; | |
| QR.spoiler = !!$('input[name=spoiler]'); | |
| if (QR.spoiler) { | |
| $.addClass(QR.nodes.el, 'has-spoiler'); | |
| } else { | |
| nodes.spoiler.parentElement.hidden = true; | |
| } | |
| if (g.BOARD.ID === 'f') { | |
| nodes.flashTag = $.el('select', { | |
| name: 'filetag', | |
| innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>" | |
| }); | |
| nodes.flashTag.dataset["default"] = '4'; | |
| $.add(nodes.form, nodes.flashTag); | |
| } | |
| if (flagSelector = $('.flagSelector')) { | |
| nodes.flag = flagSelector.cloneNode(true); | |
| nodes.flag.dataset.name = 'flag'; | |
| nodes.flag.dataset["default"] = '0'; | |
| $.add(nodes.form, nodes.flag); | |
| } | |
| $.on(nodes.filename.parentNode, 'click keydown', QR.openFileInput); | |
| items = $$('*', QR.nodes.el); | |
| i = 0; | |
| while (elm = items[i++]) { | |
| $.on(elm, 'blur', QR.focusout); | |
| $.on(elm, 'focus', QR.focusin); | |
| } | |
| $.on(nodes.autohide, 'change', QR.toggleHide); | |
| $.on(nodes.close, 'click', QR.close); | |
| $.on(nodes.dumpButton, 'click', function() { | |
| return nodes.el.classList.toggle('dump'); | |
| }); | |
| $.on(nodes.urlButton, 'click', QR.handleUrl); | |
| $.on(nodes.addPost, 'click', function() { | |
| return new QR.post(true); | |
| }); | |
| $.on(nodes.form, 'submit', QR.submit); | |
| $.on(nodes.fileRM, 'click', function() { | |
| return QR.selected.rmFile(); | |
| }); | |
| $.on(nodes.fileExtras, 'click', function(e) { | |
| return e.stopPropagation(); | |
| }); | |
| $.on(nodes.spoiler, 'change', function() { | |
| return QR.selected.nodes.spoiler.click(); | |
| }); | |
| $.on(nodes.fileInput, 'change', QR.handleFiles); | |
| items = ['name', 'email', 'sub', 'com', 'filename', 'flag']; | |
| i = 0; | |
| save = function() { | |
| return QR.selected.save(this); | |
| }; | |
| while (name = items[i++]) { | |
| if (!(node = nodes[name])) { | |
| continue; | |
| } | |
| event = node.nodeName === 'SELECT' ? 'change' : 'input'; | |
| $.on(nodes[name], event, save); | |
| } | |
| if (Conf['Remember QR Size']) { | |
| $.get('QR Size', '', function(item) { | |
| return nodes.com.style.cssText = item['QR Size']; | |
| }); | |
| $.on(nodes.com, 'mouseup', function(e) { | |
| if (e.button !== 0) { | |
| return; | |
| } | |
| return $.set('QR Size', this.style.cssText); | |
| }); | |
| } | |
| QR.generatePostableThreadsList(); | |
| QR.persona.init(); | |
| new QR.post(true); | |
| QR.status(); | |
| QR.cooldown.init(); | |
| QR.captcha.init(); | |
| $.add(d.body, dialog); | |
| return $.event('QRDialogCreation', null, dialog); | |
| }, | |
| preSubmitHooks: [], | |
| submit: function(e) { | |
| var challenge, err, extra, filetag, formData, hook, options, post, response, textOnly, thread, threadID, _i, _len, _ref, _ref1; | |
| if (e != null) { | |
| e.preventDefault(); | |
| } | |
| if (QR.req) { | |
| QR.abort(); | |
| return; | |
| } | |
| if (QR.cooldown.seconds) { | |
| QR.cooldown.auto = !QR.cooldown.auto; | |
| QR.status(); | |
| return; | |
| } | |
| post = QR.posts[0]; | |
| post.forceSave(); | |
| if (g.BOARD.ID === 'f') { | |
| filetag = QR.nodes.flashTag.value; | |
| } | |
| threadID = post.thread; | |
| thread = g.BOARD.threads[threadID]; | |
| if (threadID === 'new') { | |
| threadID = null; | |
| if (g.BOARD.ID === 'vg' && !post.sub) { | |
| err = 'New threads require a subject.'; | |
| } else if (!(post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { | |
| err = 'No file selected.'; | |
| } | |
| } else if (g.BOARD.threads[threadID].isClosed) { | |
| err = 'You can\'t reply to this thread anymore.'; | |
| } else if (!(post.com || post.file)) { | |
| err = 'No file selected.'; | |
| } else if (post.file && thread.fileLimit) { | |
| err = 'Max limit of image replies has been reached.'; | |
| } else { | |
| _ref = QR.preSubmitHooks; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| hook = _ref[_i]; | |
| if (err = hook(post, thread)) { | |
| break; | |
| } | |
| } | |
| } | |
| if (QR.captcha.isEnabled && !err) { | |
| _ref1 = QR.captcha.getOne(), challenge = _ref1.challenge, response = _ref1.response; | |
| if (!response) { | |
| err = 'No valid captcha.'; | |
| } | |
| } | |
| QR.cleanNotifications(); | |
| if (err) { | |
| QR.cooldown.auto = false; | |
| QR.status(); | |
| QR.error(err); | |
| return; | |
| } | |
| QR.cooldown.auto = QR.posts.length > 1; | |
| if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { | |
| QR.hide(); | |
| } | |
| if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) { | |
| d.activeElement.blur(); | |
| } | |
| post.lock(); | |
| formData = { | |
| resto: threadID, | |
| name: post.name, | |
| email: post.email, | |
| sub: post.sub, | |
| com: post.com, | |
| upfile: post.file, | |
| filetag: filetag, | |
| spoiler: post.spoiler, | |
| flag: post.flag, | |
| textonly: textOnly, | |
| mode: 'regist', | |
| pwd: QR.persona.pwd, | |
| recaptcha_challenge_field: challenge, | |
| recaptcha_response_field: response | |
| }; | |
| options = { | |
| responseType: 'document', | |
| withCredentials: true, | |
| onload: QR.response, | |
| onerror: function() { | |
| delete QR.req; | |
| post.unlock(); | |
| QR.cooldown.auto = false; | |
| QR.status(); | |
| return QR.error($.el('span', { | |
| innerHTML: "4chan X encountered an error while posting. \n[<a href=\"//4chan.org/banned\" target=_blank>Banned?</a>] [<a href=\"https://github.com/seaweedchan/4chan-x/wiki/Frequently-Asked-Questions#what-does-4chan-x-encountered-an-error-while-posting-please-try-again-mean\" target=_blank>More info</a>]" | |
| })); | |
| } | |
| }; | |
| extra = { | |
| form: $.formData(formData), | |
| upCallbacks: { | |
| onload: function() { | |
| QR.req.isUploadFinished = true; | |
| QR.req.uploadEndTime = Date.now(); | |
| QR.req.progress = '...'; | |
| return QR.status(); | |
| }, | |
| onprogress: function(e) { | |
| QR.req.progress = "" + (Math.round(e.loaded / e.total * 100)) + "%"; | |
| return QR.status(); | |
| } | |
| } | |
| }; | |
| QR.req = $.ajax($.id('postForm').parentNode.action, options, extra); | |
| QR.req.uploadStartTime = Date.now(); | |
| QR.req.progress = '...'; | |
| return QR.status(); | |
| }, | |
| response: function() { | |
| var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; | |
| req = QR.req; | |
| delete QR.req; | |
| post = QR.posts[0]; | |
| post.unlock(); | |
| resDoc = req.response; | |
| if (ban = $('.banType', resDoc)) { | |
| board = $('.board', resDoc).innerHTML; | |
| err = $.el('span', { | |
| innerHTML: ban.textContent.toLowerCase() === 'banned' ? "You are banned on " + board + "! ;_;<br>\nClick <a href=//www.4chan.org/banned target=_blank>here</a> to see the reason." : "You were issued a warning on " + board + " as " + ($('.nameBlock', resDoc).innerHTML) + ".<br>\nReason: " + ($('.reason', resDoc).innerHTML) | |
| }); | |
| } else if (err = resDoc.getElementById('errmsg')) { | |
| if ((_ref = $('a', err)) != null) { | |
| _ref.target = '_blank'; | |
| } | |
| } else if (resDoc.title !== 'Post successful!') { | |
| err = 'Connection error with sys.4chan.org.'; | |
| } else if (req.status !== 200) { | |
| err = "Error " + req.statusText + " (" + req.status + ")"; | |
| } | |
| if (err) { | |
| if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') { | |
| if (/mistyped/i.test(err.textContent)) { | |
| err = 'You seem to have mistyped the CAPTCHA.'; | |
| } | |
| QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false; | |
| QR.cooldown.set({ | |
| delay: 2 | |
| }); | |
| } else if (err.textContent && (m = err.textContent.match(/wait\s(\d+)\ssecond/i))) { | |
| QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; | |
| QR.cooldown.set({ | |
| delay: m[1] | |
| }); | |
| } else { | |
| QR.cooldown.auto = false; | |
| } | |
| QR.status(); | |
| QR.error(err); | |
| return; | |
| } | |
| h1 = $('h1', resDoc); | |
| QR.cleanNotifications(); | |
| if (Conf['Posting Success Notifications']) { | |
| QR.notifications.push(new Notice('success', h1.textContent, 5)); | |
| } | |
| QR.persona.set(post); | |
| _ref1 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref1[0], threadID = _ref1[1], postID = _ref1[2]; | |
| postID = +postID; | |
| threadID = +threadID || postID; | |
| isReply = threadID !== postID; | |
| QR.db.set({ | |
| boardID: g.BOARD.ID, | |
| threadID: threadID, | |
| postID: postID, | |
| val: true | |
| }); | |
| ThreadUpdater.postID = postID; | |
| $.event('QRPostSuccessful', { | |
| board: g.BOARD, | |
| threadID: threadID, | |
| postID: postID | |
| }); | |
| $.event('QRPostSuccessful_', { | |
| threadID: threadID, | |
| postID: postID | |
| }); | |
| postsCount = QR.posts.length - 1; | |
| QR.cooldown.auto = postsCount && isReply; | |
| if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) { | |
| notif = new Notification('Quick reply warning', { | |
| body: "You are running low on cached captchas. Cache count: " + captchasCount + ".", | |
| icon: Favicon.logo | |
| }); | |
| notif.onclick = function() { | |
| QR.open(); | |
| QR.captcha.nodes.input.focus(); | |
| return window.focus(); | |
| }; | |
| notif.onshow = function() { | |
| return setTimeout(function() { | |
| return notif.close(); | |
| }, 7 * $.SECOND); | |
| }; | |
| } | |
| if (!(Conf['Persistent QR'] || QR.cooldown.auto)) { | |
| QR.close(); | |
| } else { | |
| post.rm(); | |
| } | |
| QR.cooldown.set({ | |
| req: req, | |
| post: post, | |
| isReply: isReply, | |
| threadID: threadID | |
| }); | |
| URL = threadID === postID ? "/" + g.BOARD + "/res/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/res/" + threadID + "#p" + postID : void 0; | |
| if (URL) { | |
| if (Conf['Open Post in New Tab']) { | |
| $.open(URL); | |
| } else { | |
| window.location = URL; | |
| } | |
| } | |
| return QR.status(); | |
| }, | |
| abort: function() { | |
| if (QR.req && !QR.req.isUploadFinished) { | |
| QR.req.abort(); | |
| delete QR.req; | |
| QR.posts[0].unlock(); | |
| QR.cooldown.auto = false; | |
| QR.notifications.push(new Notice('info', 'QR upload aborted.', 5)); | |
| } | |
| return QR.status(); | |
| } | |
| }; | |
| QR.captcha = { | |
| init: function() { | |
| var container, imgContainer, input; | |
| if (d.cookie.indexOf('pass_enabled=1') >= 0) { | |
| return; | |
| } | |
| container = $.id('captchaContainer'); | |
| if (!(this.isEnabled = !!container)) { | |
| return; | |
| } | |
| if (Conf['Auto-load captcha']) { | |
| $.globalEval('loadRecaptcha()'); | |
| } | |
| imgContainer = $.el('div', { | |
| className: 'captcha-img', | |
| title: 'Reload reCAPTCHA', | |
| innerHTML: '<img>', | |
| hidden: true | |
| }); | |
| input = $.el('input', { | |
| className: 'captcha-input field', | |
| title: 'Verification', | |
| placeholder: 'Focus to load reCAPTCHA', | |
| autocomplete: 'off', | |
| spellcheck: false, | |
| tabIndex: 45 | |
| }); | |
| this.nodes = { | |
| img: imgContainer.firstChild, | |
| input: input | |
| }; | |
| $.on(input, 'focus', this.setup); | |
| $.on(input, 'blur', QR.focusout); | |
| $.on(input, 'focus', QR.focusin); | |
| $.addClass(QR.nodes.el, 'has-captcha'); | |
| $.after(QR.nodes.com.parentNode, [imgContainer, input]); | |
| this.setupObserver = new MutationObserver(this.afterSetup); | |
| this.setupObserver.observe(container, { | |
| childList: true | |
| }); | |
| return this.afterSetup(); | |
| }, | |
| setup: function() { | |
| return $.globalEval('loadRecaptcha()'); | |
| }, | |
| afterSetup: function() { | |
| var challenge, img, input, setLifetime, _ref; | |
| if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { | |
| return; | |
| } | |
| QR.captcha.setupObserver.disconnect(); | |
| delete QR.captcha.setupObserver; | |
| setLifetime = function(e) { | |
| return QR.captcha.lifetime = e.detail; | |
| }; | |
| $.on(window, 'captcha:timeout', setLifetime); | |
| $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); | |
| $.off(window, 'captcha:timeout', setLifetime); | |
| _ref = QR.captcha.nodes, img = _ref.img, input = _ref.input; | |
| img.parentNode.hidden = false; | |
| $.off(input, 'focus', QR.captcha.setup); | |
| $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); | |
| $.on(img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); | |
| $.get('captchas', [], function(_arg) { | |
| var captchas; | |
| captchas = _arg.captchas; | |
| return QR.captcha.sync(captchas); | |
| }); | |
| $.sync('captchas', QR.captcha.sync); | |
| QR.captcha.nodes.challenge = challenge; | |
| new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { | |
| childList: true | |
| }); | |
| return QR.captcha.load(); | |
| }, | |
| sync: function(captchas) { | |
| QR.captcha.captchas = captchas; | |
| return QR.captcha.count(); | |
| }, | |
| getOne: function() { | |
| var captcha, challenge, response; | |
| this.clear(); | |
| if (captcha = this.captchas.shift()) { | |
| challenge = captcha.challenge, response = captcha.response; | |
| this.count(); | |
| $.set('captchas', this.captchas); | |
| } else { | |
| challenge = this.nodes.img.alt; | |
| if (response = this.nodes.input.value) { | |
| this.reload(); | |
| } | |
| } | |
| if (response) { | |
| response = response.trim(); | |
| if (!/\s/.test(response)) { | |
| response = "" + response + " " + response; | |
| } | |
| } | |
| return { | |
| challenge: challenge, | |
| response: response | |
| }; | |
| }, | |
| save: function() { | |
| var response; | |
| if (!(response = this.nodes.input.value.trim())) { | |
| return; | |
| } | |
| this.captchas.push({ | |
| challenge: this.nodes.img.alt, | |
| response: response, | |
| timeout: this.timeout | |
| }); | |
| this.count(); | |
| this.reload(); | |
| return $.set('captchas', this.captchas); | |
| }, | |
| clear: function() { | |
| var captcha, i, now, _i, _len, _ref; | |
| if (!this.captchas.length) { | |
| return; | |
| } | |
| now = Date.now(); | |
| _ref = this.captchas; | |
| for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { | |
| captcha = _ref[i]; | |
| if (captcha.timeout > now) { | |
| break; | |
| } | |
| } | |
| if (!i) { | |
| return; | |
| } | |
| this.captchas = this.captchas.slice(i); | |
| this.count(); | |
| return $.set('captchas', this.captchas); | |
| }, | |
| load: function() { | |
| var challenge; | |
| if (!this.nodes.challenge.firstChild) { | |
| return; | |
| } | |
| this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; | |
| challenge = this.nodes.challenge.firstChild.value; | |
| this.nodes.img.alt = challenge; | |
| this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge; | |
| this.nodes.input.value = null; | |
| return this.clear(); | |
| }, | |
| count: function() { | |
| var count; | |
| count = this.captchas ? this.captchas.length : 0; | |
| this.nodes.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.nodes.input.alt = count; | |
| }, | |
| reload: function(focus) { | |
| $.globalEval('Recaptcha.reload("t")'); | |
| if (focus) { | |
| return this.nodes.input.focus(); | |
| } | |
| }, | |
| keydown: function(e) { | |
| if (e.keyCode === 8 && !this.nodes.input.value) { | |
| this.reload(); | |
| } else if (e.keyCode === 13 && e.shiftKey) { | |
| this.save(); | |
| } else { | |
| return; | |
| } | |
| return e.preventDefault(); | |
| } | |
| }; | |
| QR.cooldown = { | |
| init: function() { | |
| var key, setTimers, type, | |
| _this = this; | |
| if (!Conf['Cooldown']) { | |
| return; | |
| } | |
| setTimers = function(e) { | |
| return QR.cooldown.types = e.detail; | |
| }; | |
| $.on(window, 'cooldown:timers', setTimers); | |
| $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); | |
| $.off(window, 'cooldown:timers', setTimers); | |
| for (type in QR.cooldown.types) { | |
| QR.cooldown.types[type] = +QR.cooldown.types[type]; | |
| } | |
| QR.cooldown.upSpd = 0; | |
| QR.cooldown.upSpdAccuracy = .5; | |
| key = "cooldown." + g.BOARD; | |
| $.get(key, {}, function(item) { | |
| QR.cooldown.cooldowns = item[key]; | |
| return QR.cooldown.start(); | |
| }); | |
| return $.sync(key, QR.cooldown.sync); | |
| }, | |
| start: function() { | |
| if (!Conf['Cooldown']) { | |
| return; | |
| } | |
| 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, delay, isReply, post, req, start, threadID, upSpd; | |
| if (!Conf['Cooldown']) { | |
| return; | |
| } | |
| req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; | |
| start = req ? req.uploadEndTime : Date.now(); | |
| if (delay) { | |
| cooldown = { | |
| delay: delay | |
| }; | |
| } else { | |
| if (post.file) { | |
| upSpd = post.file.size / ((start - req.uploadStartTime) / $.SECOND); | |
| QR.cooldown.upSpdAccuracy = ((upSpd > QR.cooldown.upSpd * .9) + QR.cooldown.upSpdAccuracy) / 2; | |
| QR.cooldown.upSpd = upSpd; | |
| } | |
| cooldown = { | |
| isReply: isReply, | |
| threadID: threadID | |
| }; | |
| } | |
| QR.cooldown.cooldowns[start] = cooldown; | |
| $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); | |
| return QR.cooldown.start(); | |
| }, | |
| unset: function(id) { | |
| delete QR.cooldown.cooldowns[id]; | |
| if (Object.keys(QR.cooldown.cooldowns).length) { | |
| return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); | |
| } else { | |
| return $["delete"]("cooldown." + g.BOARD); | |
| } | |
| }, | |
| count: function() { | |
| var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, upSpd, upSpdAccuracy, update, _ref; | |
| if (!Object.keys(QR.cooldown.cooldowns).length) { | |
| $["delete"]("" + g.BOARD + ".cooldown"); | |
| delete QR.cooldown.isCounting; | |
| delete QR.cooldown.seconds; | |
| QR.status(); | |
| return; | |
| } | |
| clearTimeout(QR.cooldown.timeout); | |
| QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); | |
| now = Date.now(); | |
| post = QR.posts[0]; | |
| isReply = post.thread !== 'new'; | |
| hasFile = !!post.file; | |
| seconds = null; | |
| _ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns, upSpd = _ref.upSpd, upSpdAccuracy = _ref.upSpdAccuracy; | |
| 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) { | |
| elapsed = Math.floor((now - start) / $.SECOND); | |
| if (elapsed < 0) { | |
| continue; | |
| } | |
| type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; | |
| maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); | |
| if (!((start <= now && now <= start + maxTimer * $.SECOND))) { | |
| QR.cooldown.unset(start); | |
| } | |
| if (isReply && +post.thread === cooldown.threadID) { | |
| type += '_intra'; | |
| } | |
| seconds = Math.max(seconds, types[type] - elapsed); | |
| } | |
| } | |
| if (seconds && Conf['Cooldown Prediction'] && hasFile && upSpd) { | |
| seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy); | |
| seconds = seconds > 0 ? seconds : 0; | |
| } | |
| update = seconds !== null || !!QR.cooldown.seconds; | |
| QR.cooldown.seconds = seconds; | |
| if (update) { | |
| QR.status(); | |
| } | |
| if (seconds === 0 && QR.cooldown.auto && !QR.req) { | |
| return QR.submit(); | |
| } | |
| } | |
| }; | |
| QR.persona = { | |
| pwd: '', | |
| always: {}, | |
| init: function() { | |
| QR.persona.getPassword(); | |
| return $.get('QR.personas', Conf['QR.personas'], function(_arg) { | |
| var arr, item, personas, type, types, _i, _len, _ref; | |
| personas = _arg['QR.personas']; | |
| types = { | |
| name: [], | |
| email: [], | |
| sub: [] | |
| }; | |
| _ref = personas.split('\n'); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| item = _ref[_i]; | |
| QR.persona.parseItem(item.trim(), types); | |
| } | |
| for (type in types) { | |
| arr = types[type]; | |
| QR.persona.loadPersonas(type, arr); | |
| } | |
| }); | |
| }, | |
| parseItem: function(item, types) { | |
| var boards, match, type, val, _ref, _ref1, _ref2; | |
| if (item[0] === '#') { | |
| return; | |
| } | |
| if (!(match = item.match(/(name|email|subject|password):"(.*)"/i))) { | |
| return; | |
| } | |
| _ref = match, match = _ref[0], type = _ref[1], val = _ref[2]; | |
| item = item.replace(match, ''); | |
| boards = ((_ref1 = item.match(/boards:([^;]+)/i)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; | |
| if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { | |
| return; | |
| } | |
| if (type === 'password') { | |
| QR.persona.pwd = val; | |
| return; | |
| } | |
| if (type === 'subject') { | |
| type = 'sub'; | |
| } | |
| if (/always/i.test(item)) { | |
| QR.persona.always[type] = val; | |
| } | |
| if (__indexOf.call(types[type], val) < 0) { | |
| return types[type].push(val); | |
| } | |
| }, | |
| loadPersonas: function(type, arr) { | |
| var list, val, _i, _len; | |
| list = $("#list-" + type, QR.nodes.el); | |
| for (_i = 0, _len = arr.length; _i < _len; _i++) { | |
| val = arr[_i]; | |
| if (val) { | |
| $.add(list, $.el('option', { | |
| textContent: val | |
| })); | |
| } | |
| } | |
| }, | |
| getPassword: function() { | |
| var input, m; | |
| if (!QR.persona.pwd) { | |
| QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : $.id('delPassword').value; | |
| } | |
| return QR.persona.pwd; | |
| }, | |
| get: function(cb) { | |
| return $.get('QR.persona', {}, function(_arg) { | |
| var persona; | |
| persona = _arg['QR.persona']; | |
| return cb(persona); | |
| }); | |
| }, | |
| set: function(post) { | |
| return $.get('QR.persona', {}, function(_arg) { | |
| var persona; | |
| persona = _arg['QR.persona']; | |
| persona = { | |
| name: post.name, | |
| email: /^sage$/.test(post.email) ? persona.email : post.email, | |
| sub: Conf['Remember Subject'] ? post.sub : void 0, | |
| flag: post.flag | |
| }; | |
| return $.set('QR.persona', persona); | |
| }); | |
| } | |
| }; | |
| QR.post = (function() { | |
| function _Class(select) { | |
| this.select = __bind(this.select, this); | |
| var el, elm, event, prev, _i, _j, _len, _len1, _ref, _ref1, | |
| _this = this; | |
| el = $.el('a', { | |
| className: 'qr-preview', | |
| draggable: true, | |
| href: 'javascript:;', | |
| innerHTML: '<a class="remove fa fa-times-circle" title=Remove></a><label hidden><input type=checkbox> Spoiler</label><span></span>' | |
| }); | |
| this.nodes = { | |
| el: el, | |
| rm: el.firstChild, | |
| label: $('label', el), | |
| spoiler: $('input', el), | |
| span: el.lastChild | |
| }; | |
| _ref = $$('*', el); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| elm = _ref[_i]; | |
| $.on(elm, 'blur', QR.focusout); | |
| $.on(elm, 'focus', QR.focusin); | |
| } | |
| $.on(el, 'click', this.select); | |
| $.on(this.nodes.rm, 'click', function(e) { | |
| e.stopPropagation(); | |
| return _this.rm(); | |
| }); | |
| $.on(this.nodes.label, 'click', function(e) { | |
| return e.stopPropagation(); | |
| }); | |
| $.on(this.nodes.spoiler, 'change', function(e) { | |
| _this.spoiler = e.target.checked; | |
| if (_this === QR.selected) { | |
| return QR.nodes.spoiler.checked = _this.spoiler; | |
| } | |
| }); | |
| $.add(QR.nodes.dumpList, el); | |
| _ref1 = ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop']; | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| event = _ref1[_j]; | |
| $.on(el, event.toLowerCase(), this[event]); | |
| } | |
| this.thread = g.VIEW === 'thread' ? g.THREADID : 'new'; | |
| prev = QR.posts[QR.posts.length - 1]; | |
| QR.posts.push(this); | |
| this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false; | |
| QR.persona.get(function(persona) { | |
| _this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name; | |
| _this.email = 'email' in QR.persona.always ? QR.persona.always.email : prev && !/^sage$/.test(prev.email) ? prev.email : persona.email; | |
| _this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : Conf['Remember Subject'] ? prev ? prev.sub : persona.sub : ''; | |
| if (QR.nodes.flag) { | |
| _this.flag = prev ? prev.flag : persona.flag; | |
| } | |
| if (QR.selected === _this) { | |
| return _this.load(); | |
| } | |
| }); | |
| if (select) { | |
| this.select(); | |
| } | |
| this.unlock(); | |
| } | |
| _Class.prototype.rm = function() { | |
| var index; | |
| this["delete"](); | |
| index = QR.posts.indexOf(this); | |
| if (QR.posts.length === 1) { | |
| new QR.post(true); | |
| $.rmClass(QR.nodes.el, 'dump'); | |
| } else if (this === QR.selected) { | |
| (QR.posts[index - 1] || QR.posts[index + 1]).select(); | |
| } | |
| QR.posts.splice(index, 1); | |
| return QR.status(); | |
| }; | |
| _Class.prototype["delete"] = function() { | |
| $.rm(this.nodes.el); | |
| return URL.revokeObjectURL(this.URL); | |
| }; | |
| _Class.prototype.lock = function(lock) { | |
| var name, node, _i, _len, _ref; | |
| if (lock == null) { | |
| lock = true; | |
| } | |
| this.isLocked = lock; | |
| if (this !== QR.selected) { | |
| return; | |
| } | |
| _ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| name = _ref[_i]; | |
| if (node = QR.nodes[name]) { | |
| node.disabled = lock; | |
| } | |
| } | |
| this.nodes.rm.style.visibility = lock ? 'hidden' : ''; | |
| (lock ? $.off : $.on)(QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput); | |
| this.nodes.spoiler.disabled = lock; | |
| return this.nodes.el.draggable = !lock; | |
| }; | |
| _Class.prototype.unlock = function() { | |
| return this.lock(false); | |
| }; | |
| _Class.prototype.select = function() { | |
| var rectEl, rectList; | |
| if (QR.selected) { | |
| QR.selected.nodes.el.id = null; | |
| QR.selected.forceSave(); | |
| } | |
| QR.selected = this; | |
| this.lock(this.isLocked); | |
| this.nodes.el.id = 'selected'; | |
| rectEl = this.nodes.el.getBoundingClientRect(); | |
| rectList = this.nodes.el.parentNode.getBoundingClientRect(); | |
| this.nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2; | |
| this.load(); | |
| return $.event('QRPostSelection', this); | |
| }; | |
| _Class.prototype.load = function() { | |
| var name, node, _i, _len, _ref; | |
| _ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| name = _ref[_i]; | |
| if (!(node = QR.nodes[name])) { | |
| continue; | |
| } | |
| node.value = this[name] || node.dataset["default"] || null; | |
| } | |
| this.showFileData(); | |
| return QR.characterCount(); | |
| }; | |
| _Class.prototype.save = function(input) { | |
| var name, _ref; | |
| if (input.type === 'checkbox') { | |
| this.spoiler = input.checked; | |
| return; | |
| } | |
| name = input.dataset.name; | |
| this[name] = input.value || input.dataset["default"] || null; | |
| switch (name) { | |
| case 'thread': | |
| return QR.status(); | |
| case 'com': | |
| this.nodes.span.textContent = this.com; | |
| QR.characterCount(); | |
| if (QR.cooldown.auto && this === QR.posts[0] && (0 < (_ref = QR.cooldown.seconds) && _ref <= 5)) { | |
| return QR.cooldown.auto = false; | |
| } | |
| break; | |
| case 'filename': | |
| if (!this.file) { | |
| return; | |
| } | |
| this.file.newName = this.filename.replace(/[/\\]/g, '-'); | |
| if (!/\.(jpe?g|png|gif|pdf|swf)$/i.test(this.filename)) { | |
| this.file.newName += '.jpg'; | |
| } | |
| return this.updateFilename(); | |
| } | |
| }; | |
| _Class.prototype.forceSave = function() { | |
| var name, node, _i, _len, _ref; | |
| if (this !== QR.selected) { | |
| return; | |
| } | |
| _ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| name = _ref[_i]; | |
| if (!(node = QR.nodes[name])) { | |
| continue; | |
| } | |
| this.save(node); | |
| } | |
| }; | |
| _Class.prototype.setFile = function(file) { | |
| this.file = file; | |
| this.filename = file.name; | |
| this.filesize = $.bytesToString(file.size); | |
| if (QR.spoiler) { | |
| this.nodes.label.hidden = false; | |
| } | |
| URL.revokeObjectURL(this.URL); | |
| if (this === QR.selected) { | |
| this.showFileData(); | |
| } | |
| if (!/^image/.test(file.type)) { | |
| this.nodes.el.style.backgroundImage = null; | |
| return; | |
| } | |
| return this.setThumbnail(); | |
| }; | |
| _Class.prototype.setThumbnail = function() { | |
| var fileURL, img, | |
| _this = this; | |
| img = $.el('img'); | |
| img.onload = function() { | |
| var cv, height, s, width; | |
| s = 90 * 2 * window.devicePixelRatio; | |
| if (_this.file.type === 'image/gif') { | |
| s *= 3; | |
| } | |
| height = img.height, width = img.width; | |
| if (height < s || width < s) { | |
| _this.URL = fileURL; | |
| _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")"; | |
| return; | |
| } | |
| if (height <= width) { | |
| width = s / height * width; | |
| height = s; | |
| } else { | |
| height = s / width * height; | |
| width = s; | |
| } | |
| cv = $.el('canvas'); | |
| cv.height = img.height = height; | |
| cv.width = img.width = width; | |
| cv.getContext('2d').drawImage(img, 0, 0, width, height); | |
| URL.revokeObjectURL(fileURL); | |
| return cv.toBlob(function(blob) { | |
| _this.URL = URL.createObjectURL(blob); | |
| return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")"; | |
| }); | |
| }; | |
| fileURL = URL.createObjectURL(this.file); | |
| return img.src = fileURL; | |
| }; | |
| _Class.prototype.rmFile = function() { | |
| if (this.isLocked) { | |
| return; | |
| } | |
| delete this.file; | |
| delete this.filename; | |
| delete this.filesize; | |
| this.nodes.el.title = null; | |
| QR.nodes.fileContainer.title = ''; | |
| this.nodes.el.style.backgroundImage = null; | |
| if (QR.spoiler) { | |
| this.nodes.label.hidden = true; | |
| } | |
| this.showFileData(); | |
| return URL.revokeObjectURL(this.URL); | |
| }; | |
| _Class.prototype.updateFilename = function() { | |
| var long; | |
| long = "" + this.filename + " (" + this.filesize + ")\nCtrl+click to edit filename. Shift+click to clear."; | |
| this.nodes.el.title = long; | |
| if (this !== QR.selected) { | |
| return; | |
| } | |
| return QR.nodes.fileContainer.title = long; | |
| }; | |
| _Class.prototype.showFileData = function() { | |
| if (this.file) { | |
| this.updateFilename(); | |
| QR.nodes.filename.value = this.filename; | |
| QR.nodes.spoiler.checked = this.spoiler; | |
| return $.addClass(QR.nodes.fileSubmit, 'has-file'); | |
| } else { | |
| return $.rmClass(QR.nodes.fileSubmit, 'has-file'); | |
| } | |
| }; | |
| _Class.prototype.pasteText = function(file) { | |
| var reader, | |
| _this = this; | |
| reader = new FileReader(); | |
| reader.onload = function(e) { | |
| var text; | |
| text = e.target.result; | |
| if (_this.com) { | |
| _this.com += "\n" + text; | |
| } else { | |
| _this.com = text; | |
| } | |
| if (QR.selected === _this) { | |
| QR.nodes.com.value = _this.com; | |
| } | |
| return _this.nodes.span.textContent = _this.com; | |
| }; | |
| return reader.readAsText(file); | |
| }; | |
| _Class.prototype.dragStart = function(e) { | |
| e.dataTransfer.setDragImage(this, e.layerX, e.layerY); | |
| return $.addClass(this, 'drag'); | |
| }; | |
| _Class.prototype.dragEnd = function() { | |
| return $.rmClass(this, 'drag'); | |
| }; | |
| _Class.prototype.dragEnter = function() { | |
| return $.addClass(this, 'over'); | |
| }; | |
| _Class.prototype.dragLeave = function() { | |
| return $.rmClass(this, 'over'); | |
| }; | |
| _Class.prototype.dragOver = function(e) { | |
| e.preventDefault(); | |
| return e.dataTransfer.dropEffect = 'move'; | |
| }; | |
| _Class.prototype.drop = function() { | |
| var el, index, newIndex, oldIndex, post; | |
| $.rmClass(this, 'over'); | |
| if (!this.draggable) { | |
| return; | |
| } | |
| el = $('.drag', this.parentNode); | |
| index = function(el) { | |
| return __slice.call(el.parentNode.children).indexOf(el); | |
| }; | |
| oldIndex = index(el); | |
| newIndex = index(this); | |
| (oldIndex < newIndex ? $.after : $.before)(this, el); | |
| post = QR.posts.splice(oldIndex, 1)[0]; | |
| QR.posts.splice(newIndex, 0, post); | |
| return QR.status(); | |
| }; | |
| return _Class; | |
| })(); | |
| AutoGIF = { | |
| init: function() { | |
| var _ref; | |
| if (g.VIEW === 'catalog' || !Conf['Auto-GIF'] || ((_ref = g.BOARD.ID) === 'gif' || _ref === 'wsg')) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Auto-GIF', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var URL, gif, style, thumb, _ref, _ref1; | |
| if (this.isClone || this.isHidden || this.thread.isHidden || !((_ref = this.file) != null ? _ref.isImage : void 0)) { | |
| return; | |
| } | |
| _ref1 = this.file, thumb = _ref1.thumb, URL = _ref1.URL; | |
| if (!(/gif$/.test(URL) && !/spoiler/.test(thumb.src))) { | |
| return; | |
| } | |
| if (this.file.isSpoiler) { | |
| style = thumb.style; | |
| style.maxHeight = style.maxWidth = this.isReply ? '125px' : '250px'; | |
| } | |
| gif = $.el('img'); | |
| $.on(gif, 'load', function() { | |
| return thumb.src = URL; | |
| }); | |
| return gif.src = URL; | |
| } | |
| }; | |
| FappeTyme = { | |
| init: function() { | |
| var el, input, lc, type, _i, _len, _ref; | |
| if (!(Conf['Fappe Tyme'] || Conf['Werk Tyme']) || g.VIEW === 'catalog' || g.BOARD === 'f') { | |
| return; | |
| } | |
| _ref = ["Fappe", "Werk"]; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| type = _ref[_i]; | |
| if (!Conf["" + type + " Tyme"]) { | |
| continue; | |
| } | |
| lc = type.toLowerCase(); | |
| el = $.el('label', { | |
| innerHTML: "<input type=checkbox name=" + lc + "> " + type + " Tyme", | |
| title: "" + type + " Tyme" | |
| }); | |
| FappeTyme[lc] = input = el.firstElementChild; | |
| $.on(input, 'change', FappeTyme.cb.toggle.bind(input)); | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: el, | |
| order: 97 | |
| }); | |
| if (Conf[lc]) { | |
| FappeTyme.cb.set(lc); | |
| } | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Fappe Tyme', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| if (this.file) { | |
| return; | |
| } | |
| return $.addClass(this.nodes.root, "noFile"); | |
| }, | |
| cb: { | |
| set: function(type) { | |
| FappeTyme[type].checked = Conf[type]; | |
| return $["" + (Conf[type] ? 'add' : 'rm') + "Class"](doc, "" + type + "Tyme"); | |
| }, | |
| toggle: function() { | |
| Conf[this.name] = !Conf[this.name]; | |
| FappeTyme.cb.set(this.name); | |
| return $.cb.checked.call(FappeTyme[this.name]); | |
| } | |
| } | |
| }; | |
| Gallery = { | |
| init: function() { | |
| var el; | |
| if (g.VIEW === 'catalog' || g.BOARD === 'f' || !Conf['Gallery']) { | |
| return; | |
| } | |
| el = $.el('a', { | |
| href: 'javascript:;', | |
| id: 'appchan-gal', | |
| title: 'Gallery', | |
| className: 'fa fa-picture-o', | |
| textContent: 'Gallery' | |
| }); | |
| $.on(el, 'click', this.cb.toggle); | |
| Header.addShortcut(el); | |
| return Post.callbacks.push({ | |
| name: 'Gallery', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var _ref; | |
| if (!((_ref = this.file) != null ? _ref.isImage : void 0)) { | |
| return; | |
| } | |
| if (Gallery.nodes) { | |
| Gallery.generateThumb($('.file', this.nodes.root)); | |
| Gallery.nodes.total.textContent = Gallery.images.length; | |
| } | |
| if (!Conf['Image Expansion']) { | |
| return $.on(this.file.thumb.parentNode, 'click', Gallery.cb.image); | |
| } | |
| }, | |
| build: function(image) { | |
| var cb, createSubEntry, dialog, el, file, files, i, key, menuButton, name, nodes, value, _ref; | |
| Gallery.images = []; | |
| nodes = Gallery.nodes = {}; | |
| nodes.el = dialog = $.el('div', { | |
| id: 'a-gallery', | |
| innerHTML: "<div class=gal-viewport>\n <span class=gal-buttons>\n <a class=\"menu-button\" href=\"javascript:;\"><i></i></a>\n <a href=javascript:; class=gal-close>×</a>\n </span>\n <a class=gal-name target=\"_blank\"></a>\n <span class=gal-count><span class='count'></span> / <span class='total'></span></a></span>\n <div class=gal-prev></div>\n <div class=gal-image>\n <a href=javascript:;><img></a>\n </div>\n <div class=gal-next></div>\n</div>\n<div class=gal-thumbnails></div>" | |
| }); | |
| _ref = { | |
| frame: '.gal-image', | |
| name: '.gal-name', | |
| count: '.count', | |
| total: '.total', | |
| thumbs: '.gal-thumbnails', | |
| next: '.gal-image a', | |
| current: '.gal-image img' | |
| }; | |
| for (key in _ref) { | |
| value = _ref[key]; | |
| nodes[key] = $(value, dialog); | |
| } | |
| menuButton = $('.menu-button', dialog); | |
| nodes.menu = new UI.Menu('gallery'); | |
| cb = Gallery.cb; | |
| $.on(nodes.frame, 'click', cb.blank); | |
| $.on(nodes.current, 'click', cb.download); | |
| $.on(nodes.next, 'click', cb.next); | |
| $.on($('.gal-prev', dialog), 'click', cb.prev); | |
| $.on($('.gal-next', dialog), 'click', cb.next); | |
| $.on($('.gal-close', dialog), 'click', cb.close); | |
| $.on(menuButton, 'click', function(e) { | |
| return nodes.menu.toggle(e, this, g); | |
| }); | |
| createSubEntry = Gallery.menu.createSubEntry; | |
| for (name in Config.gallery) { | |
| el = createSubEntry(name).el; | |
| $.event('AddMenuEntry', { | |
| type: 'gallery', | |
| el: el, | |
| order: 0 | |
| }); | |
| } | |
| $.on(d, 'keydown', cb.keybinds); | |
| $.off(d, 'keydown', Keybinds.keydown); | |
| i = 0; | |
| files = $$('.post .file'); | |
| while (file = files[i++]) { | |
| if ($('.fileDeletedRes, .fileDeleted', file)) { | |
| continue; | |
| } | |
| Gallery.generateThumb(file); | |
| } | |
| $.add(d.body, dialog); | |
| nodes.thumbs.scrollTop = 0; | |
| nodes.current.parentElement.scrollTop = 0; | |
| Gallery.cb.open.call(image ? $("[href='" + (image.href.replace(/https?:/, '')) + "']", nodes.thumbs) : Gallery.images[0]); | |
| d.body.style.overflow = 'hidden'; | |
| return nodes.total.textContent = --i; | |
| }, | |
| generateThumb: function(file) { | |
| var double, post, thumb, title; | |
| post = Get.postFromNode(file); | |
| title = ($('.fileText a', file)).textContent; | |
| thumb = post.file.thumb.parentNode.cloneNode(true); | |
| if (double = $('img + img', thumb)) { | |
| $.rm(double); | |
| } | |
| thumb.className = 'gal-thumb'; | |
| thumb.title = title; | |
| thumb.dataset.id = Gallery.images.length; | |
| thumb.dataset.post = $('a[title="Highlight this post"]', post.nodes.info).href; | |
| thumb.firstElementChild.style.cssText = ''; | |
| $.on(thumb, 'click', Gallery.cb.open); | |
| Gallery.images.push(thumb); | |
| return $.add(Gallery.nodes.thumbs, thumb); | |
| }, | |
| cb: { | |
| keybinds: function(e) { | |
| var cb, key; | |
| if (!(key = Keybinds.keyCode(e))) { | |
| return; | |
| } | |
| cb = (function() { | |
| switch (key) { | |
| case 'Esc': | |
| case Conf['Open Gallery']: | |
| return Gallery.cb.close; | |
| case 'Right': | |
| case 'Enter': | |
| return Gallery.cb.next; | |
| case 'Left': | |
| case '': | |
| return Gallery.cb.prev; | |
| } | |
| })(); | |
| if (!cb) { | |
| return; | |
| } | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| return cb(); | |
| }, | |
| open: function(e) { | |
| var el, img, name, nodes, rect, top; | |
| if (e) { | |
| e.preventDefault(); | |
| } | |
| if (!this) { | |
| return; | |
| } | |
| nodes = Gallery.nodes; | |
| name = nodes.name; | |
| if (el = $('.gal-highlight', Gallery.thumbs)) { | |
| $.rmClass(el, 'gal-highlight'); | |
| } | |
| $.addClass(this, 'gal-highlight'); | |
| img = $.el('img', { | |
| src: name.href = this.href, | |
| title: name.download = name.textContent = this.title | |
| }); | |
| $.extend(img.dataset, this.dataset); | |
| $.replace(nodes.current, img); | |
| nodes.count.textContent = +this.dataset.id + 1; | |
| nodes.current = img; | |
| nodes.frame.scrollTop = 0; | |
| nodes.next.focus(); | |
| rect = this.getBoundingClientRect(); | |
| top = rect.top; | |
| if (top > 0) { | |
| top += rect.height - doc.clientHeight; | |
| if (top < 0) { | |
| return; | |
| } | |
| } | |
| nodes.thumbs.scrollTop += top; | |
| return $.on(img, 'error', function() { | |
| return Gallery.cb.error(img, thumb); | |
| }); | |
| }, | |
| image: function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| return Gallery.build(this); | |
| }, | |
| error: function(img, thumb) { | |
| var URL, post, revived, src; | |
| post = Get.postFromLink($.el('a', { | |
| href: img.dataset.post | |
| })); | |
| delete post.file.fullImage; | |
| src = this.src.split('/'); | |
| if (src[2] === 'images.4chan.org') { | |
| URL = Redirect.to('file', { | |
| boardID: src[3], | |
| filename: src[5] | |
| }); | |
| if (URL) { | |
| thumb.href = URL; | |
| if (Gallery.nodes.current !== img) { | |
| return; | |
| } | |
| revived = $.el('img', { | |
| src: URL, | |
| title: img.title | |
| }); | |
| $.extend(revived.dataset, img.dataset); | |
| $.replace(img, revived); | |
| return; | |
| } | |
| if (g.DEAD || post.isDead || post.file.isDead) { | |
| return; | |
| } | |
| } | |
| return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", { | |
| onload: function() { | |
| var i, postObj; | |
| if (this.status !== 200) { | |
| return; | |
| } | |
| i = 0; | |
| while (postObj = JSON.parse(this.response).posts[i++]) { | |
| if (postObj.no === post.ID) { | |
| break; | |
| } | |
| } | |
| if (!postObj.no) { | |
| return post.kill(); | |
| } | |
| if (postObj.filedeleted) { | |
| return post.kill(true); | |
| } | |
| } | |
| }); | |
| }, | |
| prev: function() { | |
| return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id - 1]); | |
| }, | |
| next: function() { | |
| return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id + 1]); | |
| }, | |
| toggle: function() { | |
| return (Gallery.nodes ? Gallery.cb.close : Gallery.build)(); | |
| }, | |
| blank: function(e) { | |
| if (e.target === this) { | |
| return Gallery.cb.close(); | |
| } | |
| }, | |
| close: function() { | |
| $.rm(Gallery.nodes.el); | |
| delete Gallery.nodes; | |
| d.body.style.overflow = ''; | |
| $.off(d, 'keydown', Gallery.cb.keybinds); | |
| return $.on(d, 'keydown', Keybinds.keydown); | |
| }, | |
| setFitness: function() { | |
| return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-'))); | |
| } | |
| }, | |
| menu: { | |
| init: function() { | |
| var createSubEntry, el, name, subEntries; | |
| if (g.VIEW === 'catalog' || !Conf['Gallery']) { | |
| return; | |
| } | |
| el = $.el('span', { | |
| textContent: 'Gallery', | |
| className: 'gallery-link' | |
| }); | |
| createSubEntry = Gallery.menu.createSubEntry; | |
| subEntries = []; | |
| for (name in Config.gallery) { | |
| subEntries.push(createSubEntry(name)); | |
| } | |
| return $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: el, | |
| order: 105, | |
| subEntries: subEntries | |
| }); | |
| }, | |
| createSubEntry: function(name) { | |
| var input, label; | |
| label = $.el('label', { | |
| innerHTML: "<input type=checkbox name='" + name + "'> " + name | |
| }); | |
| input = label.firstElementChild; | |
| if (name === 'Fit Width' || name === 'Fit Height' || name === 'Hide Thumbnails') { | |
| $.on(input, 'change', Gallery.cb.setFitness); | |
| } | |
| input.checked = Conf[name]; | |
| $.event('change', null, input); | |
| $.on(input, 'change', $.cb.checked); | |
| return { | |
| el: label | |
| }; | |
| } | |
| } | |
| }; | |
| ImageExpand = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Image Expansion']) { | |
| return; | |
| } | |
| this.EAI = $.el('a', { | |
| className: 'expand-all-shortcut fa fa-expand', | |
| textContent: 'EAI', | |
| title: 'Expand All Images', | |
| href: 'javascript:;' | |
| }); | |
| $.on(this.EAI, 'click', ImageExpand.cb.toggleAll); | |
| Header.addShortcut(this.EAI, 3); | |
| return Post.callbacks.push({ | |
| name: 'Image Expansion', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var thumb, _ref; | |
| if (!((_ref = this.file) != null ? _ref.isImage : void 0)) { | |
| return; | |
| } | |
| thumb = this.file.thumb; | |
| $.on(thumb.parentNode, 'click', ImageExpand.cb.toggle); | |
| if (this.isClone && $.hasClass(thumb, 'expanding')) { | |
| ImageExpand.contract(this); | |
| ImageExpand.expand(this); | |
| return; | |
| } | |
| if (ImageExpand.on && !this.isHidden && (Conf['Expand spoilers'] || !this.file.isSpoiler)) { | |
| return ImageExpand.expand(this); | |
| } | |
| }, | |
| cb: { | |
| toggle: function(e) { | |
| if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| return ImageExpand.toggle(Get.postFromNode(this)); | |
| }, | |
| toggleAll: function() { | |
| var ID, file, func, post, _i, _len, _ref, _ref1; | |
| $.event('CloseMenu'); | |
| if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) { | |
| ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress'; | |
| ImageExpand.EAI.title = 'Contract All Images'; | |
| func = ImageExpand.expand; | |
| } else { | |
| ImageExpand.EAI.className = 'expand-all-shortcut fa fa-expand'; | |
| ImageExpand.EAI.title = 'Expand All Images'; | |
| func = ImageExpand.contract; | |
| } | |
| _ref = g.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| _ref1 = [post].concat(post.clones); | |
| for (_i = 0, _len = _ref1.length; _i < _len; _i++) { | |
| post = _ref1[_i]; | |
| file = post.file; | |
| if (!(file && file.isImage && doc.contains(post.nodes.root))) { | |
| continue; | |
| } | |
| if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { | |
| continue; | |
| } | |
| $.queueTask(func, post); | |
| } | |
| } | |
| }, | |
| setFitness: function() { | |
| return (this.checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-')); | |
| } | |
| }, | |
| toggle: function(post) { | |
| var headRect, left, root, thumb, top, x, y, _ref; | |
| thumb = post.file.thumb; | |
| if (!(post.file.isExpanded || $.hasClass(thumb, 'expanding'))) { | |
| ImageExpand.expand(post); | |
| return; | |
| } | |
| root = post.nodes.root; | |
| _ref = (Conf['Advance on contract'] ? (function() { | |
| var next; | |
| next = root; | |
| while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) { | |
| if ($('.stub', next) || next.offsetHeight === 0) { | |
| continue; | |
| } | |
| return next; | |
| } | |
| return root; | |
| })() : root).getBoundingClientRect(), top = _ref.top, left = _ref.left; | |
| if (top < 0) { | |
| y = top; | |
| if (Conf['Fixed Header'] && !Conf['Bottom Header']) { | |
| headRect = Header.bar.getBoundingClientRect(); | |
| y -= headRect.top + headRect.height; | |
| } | |
| } | |
| if (left < 0) { | |
| x = -window.scrollX; | |
| } | |
| if (x || y) { | |
| window.scrollBy(x, y); | |
| } | |
| return ImageExpand.contract(post); | |
| }, | |
| contract: function(post) { | |
| $.rmClass(post.nodes.root, 'expanded-image'); | |
| $.rmClass(post.file.thumb, 'expanding'); | |
| return post.file.isExpanded = false; | |
| }, | |
| expand: function(post, src) { | |
| var img, thumb; | |
| thumb = post.file.thumb; | |
| if (post.isHidden || post.file.isExpanded || $.hasClass(thumb, 'expanding')) { | |
| return; | |
| } | |
| $.addClass(thumb, 'expanding'); | |
| if (post.file.fullImage) { | |
| $.asap((function() { | |
| return post.file.fullImage.naturalHeight; | |
| }), function() { | |
| return ImageExpand.completeExpand(post); | |
| }); | |
| return; | |
| } | |
| post.file.fullImage = img = $.el('img', { | |
| className: 'full-image', | |
| src: src || post.file.URL | |
| }); | |
| $.on(img, 'error', ImageExpand.error); | |
| $.asap((function() { | |
| return post.file.fullImage.naturalHeight; | |
| }), function() { | |
| return ImageExpand.completeExpand(post); | |
| }); | |
| return $.after(thumb, img); | |
| }, | |
| completeExpand: function(post) { | |
| var bottom, thumb; | |
| thumb = post.file.thumb; | |
| if (!$.hasClass(thumb, 'expanding')) { | |
| return; | |
| } | |
| post.file.isExpanded = true; | |
| if (!post.nodes.root.parentNode) { | |
| $.addClass(post.nodes.root, 'expanded-image'); | |
| $.rmClass(post.file.thumb, 'expanding'); | |
| return; | |
| } | |
| bottom = post.nodes.root.getBoundingClientRect().bottom; | |
| return $.queueTask(function() { | |
| $.addClass(post.nodes.root, 'expanded-image'); | |
| $.rmClass(post.file.thumb, 'expanding'); | |
| if (!(bottom <= 0)) { | |
| return; | |
| } | |
| return window.scrollBy(0, post.nodes.root.getBoundingClientRect().bottom - bottom); | |
| }); | |
| }, | |
| error: function() { | |
| var URL, post, src, timeoutID; | |
| post = Get.postFromNode(this); | |
| $.rm(this); | |
| delete post.file.fullImage; | |
| if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) { | |
| return; | |
| } | |
| ImageExpand.contract(post); | |
| src = this.src.split('/'); | |
| if (src[2] === 'i.4cdn.org') { | |
| URL = Redirect.to('file', { | |
| boardID: src[3], | |
| filename: src[5] | |
| }); | |
| if (URL) { | |
| setTimeout(ImageExpand.expand, 10000, post, URL); | |
| return; | |
| } | |
| if (g.DEAD || post.isDead || post.file.isDead) { | |
| return; | |
| } | |
| } | |
| timeoutID = setTimeout(ImageExpand.expand, 10000, post); | |
| return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { | |
| onload: function() { | |
| var postObj, _i, _len, _ref; | |
| if (this.status !== 200) { | |
| return; | |
| } | |
| _ref = JSON.parse(this.response).posts; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| postObj = _ref[_i]; | |
| if (postObj.no === post.ID) { | |
| break; | |
| } | |
| } | |
| if (postObj.no !== post.ID) { | |
| clearTimeout(timeoutID); | |
| return post.kill(); | |
| } else if (postObj.filedeleted) { | |
| clearTimeout(timeoutID); | |
| return post.kill(true); | |
| } | |
| } | |
| }); | |
| }, | |
| menu: { | |
| init: function() { | |
| var conf, createSubEntry, el, name, subEntries, _ref; | |
| if (g.VIEW === 'catalog' || !Conf['Image Expansion']) { | |
| return; | |
| } | |
| el = $.el('span', { | |
| textContent: 'Image Expansion', | |
| className: 'image-expansion-link' | |
| }); | |
| createSubEntry = ImageExpand.menu.createSubEntry; | |
| subEntries = []; | |
| _ref = Config.imageExpansion; | |
| for (name in _ref) { | |
| conf = _ref[name]; | |
| subEntries.push(createSubEntry(name, conf[1])); | |
| } | |
| return $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: el, | |
| order: 105, | |
| subEntries: subEntries | |
| }); | |
| }, | |
| createSubEntry: function(name, desc) { | |
| var input, label; | |
| label = $.el('label', { | |
| innerHTML: "<input type=checkbox name='" + name + "'> " + name, | |
| title: desc | |
| }); | |
| input = label.firstElementChild; | |
| if (name === 'Fit width' || name === 'Fit height') { | |
| $.on(input, 'change', ImageExpand.cb.setFitness); | |
| } | |
| input.checked = Conf[name]; | |
| $.event('change', null, input); | |
| $.on(input, 'change', $.cb.checked); | |
| return { | |
| el: label | |
| }; | |
| } | |
| } | |
| }; | |
| ImageHover = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Image Hover']) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Image Hover', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var _ref; | |
| if (!((_ref = this.file) != null ? _ref.isImage : void 0)) { | |
| return; | |
| } | |
| return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover); | |
| }, | |
| mouseover: function(e) { | |
| var el, post; | |
| post = Get.postFromNode(this); | |
| el = $.el('img', { | |
| id: 'ihover', | |
| src: post.file.URL | |
| }); | |
| el.dataset.fullID = post.fullID; | |
| $.add(Header.hover, el); | |
| UI.hover({ | |
| root: this, | |
| el: el, | |
| latestEvent: e, | |
| endEvents: 'mouseout click', | |
| asapTest: function() { | |
| return el.naturalHeight; | |
| } | |
| }); | |
| return $.on(el, 'error', ImageHover.error); | |
| }, | |
| error: function() { | |
| var URL, post, src, timeoutID, | |
| _this = this; | |
| if (!doc.contains(this)) { | |
| return; | |
| } | |
| post = g.posts[this.dataset.fullID]; | |
| src = this.src.split('/'); | |
| if (src[2] === 'i.4cdn.org') { | |
| URL = Redirect.to('file', { | |
| boardID: src[3], | |
| filename: src[5].replace(/\?.+$/, '') | |
| }); | |
| if (URL) { | |
| this.src = URL; | |
| return; | |
| } | |
| if (g.DEAD || post.isDead || post.file.isDead) { | |
| return; | |
| } | |
| } | |
| timeoutID = setTimeout((function() { | |
| return _this.src = post.file.URL + '?' + Date.now(); | |
| }), 3000); | |
| return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { | |
| onload: function() { | |
| var postObj, _i, _len, _ref; | |
| if (this.status !== 200) { | |
| return; | |
| } | |
| _ref = JSON.parse(this.response).posts; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| postObj = _ref[_i]; | |
| if (postObj.no === post.ID) { | |
| break; | |
| } | |
| } | |
| if (postObj.no !== post.ID) { | |
| clearTimeout(timeoutID); | |
| return post.kill(); | |
| } else if (postObj.filedeleted) { | |
| clearTimeout(timeoutID); | |
| return post.kill(true); | |
| } | |
| } | |
| }); | |
| } | |
| }; | |
| ImageLoader = { | |
| init: function() { | |
| var prefetch; | |
| if (g.VIEW === 'catalog') { | |
| return; | |
| } | |
| if (!(Conf["Image Prefetching"] || Conf["Replace JPG"] || Conf["Replace PNG"] || Conf["Replace GIF"])) { | |
| return; | |
| } | |
| Post.callbacks.push({ | |
| name: 'Image Replace', | |
| cb: this.node | |
| }); | |
| if (!(Conf['Image Prefetching'] && g.VIEW === 'thread')) { | |
| return; | |
| } | |
| prefetch = $.el('label', { | |
| innerHTML: '<input type=checkbox name="prefetch"> Prefetch Images' | |
| }); | |
| this.el = prefetch.firstElementChild; | |
| $.on(this.el, 'change', this.toggle); | |
| return $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: prefetch, | |
| order: 104 | |
| }); | |
| }, | |
| node: function() { | |
| var URL, img, string, style, thumb, type, _ref, _ref1; | |
| if (this.isClone || this.isHidden || this.thread.isHidden || !((_ref = this.file) != null ? _ref.isImage : void 0)) { | |
| return; | |
| } | |
| _ref1 = this.file, thumb = _ref1.thumb, URL = _ref1.URL; | |
| if (!((Conf[string = "Replace " + ((type = (URL.match(/\w{3}$/))[0].toUpperCase()) === 'PEG' ? 'JPG' : type)] && !/spoiler/.test(thumb.src)) || Conf['prefetch'])) { | |
| return; | |
| } | |
| if (this.file.isSpoiler) { | |
| style = thumb.style; | |
| style.maxHeight = style.maxWidth = this.isReply ? '125px' : '250px'; | |
| } | |
| img = $.el('img'); | |
| if (Conf[string]) { | |
| $.on(img, 'load', function() { | |
| return thumb.src = URL; | |
| }); | |
| } | |
| return img.src = URL; | |
| }, | |
| toggle: function() { | |
| var enabled, id, post, _ref; | |
| enabled = Conf['prefetch'] = this.checked; | |
| if (enabled) { | |
| _ref = g.threads["" + g.BOARD.ID + "." + g.THREADID].posts; | |
| for (id in _ref) { | |
| post = _ref[id]; | |
| ImageLoader.node.call(post); | |
| } | |
| } | |
| } | |
| }; | |
| RevealSpoilers = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Reveal Spoiler Thumbnails']) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var thumb, _ref; | |
| if (this.isClone || !((_ref = this.file) != null ? _ref.isSpoiler : void 0)) { | |
| return; | |
| } | |
| thumb = this.file.thumb; | |
| thumb.removeAttribute('style'); | |
| return thumb.src = this.file.thumbURL; | |
| } | |
| }; | |
| Sauce = { | |
| init: function() { | |
| var err, link, links, _i, _len, _ref; | |
| if (g.VIEW === 'catalog' || !Conf['Sauce']) { | |
| return; | |
| } | |
| links = []; | |
| _ref = Conf['sauces'].split('\n'); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| link = _ref[_i]; | |
| try { | |
| if (link[0] !== '#') { | |
| links.push(this.createSauceLink(link.trim())); | |
| } | |
| } catch (_error) { | |
| err = _error; | |
| } | |
| } | |
| if (!links.length) { | |
| return; | |
| } | |
| this.links = links; | |
| this.link = $.el('a', { | |
| target: '_blank' | |
| }); | |
| return Post.callbacks.push({ | |
| name: 'Sauce', | |
| cb: this.node | |
| }); | |
| }, | |
| createSauceLink: function(link) { | |
| var m, text; | |
| link = link.replace(/%(T?URL|MD5|board)/ig, function(parameter) { | |
| switch (parameter) { | |
| case '%TURL': | |
| return "' + encodeURIComponent(post.file.thumbURL) + '"; | |
| case '%URL': | |
| return "' + encodeURIComponent(post.file.URL) + '"; | |
| case '%MD5': | |
| return "' + encodeURIComponent(post.file.MD5) + '"; | |
| case '%board': | |
| return "' + encodeURIComponent(post.board) + '"; | |
| default: | |
| return parameter; | |
| } | |
| }); | |
| text = (m = link.match(/;text:(.+)$/)) ? m[1] : link.match(/(\w+)\.\w+\//)[1]; | |
| link = link.replace(/;text:.+$/, ''); | |
| return Function('post', 'a', "a.href = '" + link + "';\na.textContent = '" + text + "';\nreturn a;"); | |
| }, | |
| node: function() { | |
| var link, nodes, _i, _len, _ref; | |
| if (this.isClone || !this.file) { | |
| return; | |
| } | |
| nodes = []; | |
| _ref = Sauce.links; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| link = _ref[_i]; | |
| nodes.push($.tn('\u00A0'), link(this, Sauce.link.cloneNode(true))); | |
| } | |
| return $.add(this.file.text, nodes); | |
| } | |
| }; | |
| Linkify = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Linkify']) { | |
| return; | |
| } | |
| this.regString = /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})(\/|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i; | |
| if (Conf['Comment Expansion']) { | |
| ExpandComment.callbacks.push(this.node); | |
| } | |
| if (Conf['Title Link']) { | |
| $.sync('CachedTitles', Linkify.titleSync); | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Linkify', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var data, el, end, endNode, i, index, items, length, link, links, node, result, saved, snapshot, space, test, word, _i, _len, _ref; | |
| if (this.isClone) { | |
| if (Conf['Embedding']) { | |
| i = 0; | |
| items = $$('.embed', this.nodes.comment); | |
| while (el = items[i++]) { | |
| $.on(el, 'click', Linkify.cb.toggle); | |
| if ($.hasClass(el, 'embedded')) { | |
| Linkify.cb.toggle.call(el); | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| test = /[^\s'"]+/g; | |
| space = /[\s'"]/; | |
| snapshot = $.X('.//br|.//text()', this.nodes.comment); | |
| i = 0; | |
| links = []; | |
| while (node = snapshot.snapshotItem(i++)) { | |
| data = node.data; | |
| if (node.parentElement.nodeName === "A" || !data) { | |
| continue; | |
| } | |
| while (result = test.exec(data)) { | |
| index = result.index; | |
| endNode = node; | |
| word = result[0]; | |
| if ((length = index + word.length) === data.length) { | |
| test.lastIndex = 0; | |
| while ((saved = snapshot.snapshotItem(i++))) { | |
| if (saved.nodeName === 'BR') { | |
| break; | |
| } | |
| endNode = saved; | |
| data = saved.data; | |
| word += data; | |
| length = data.length; | |
| if (end = space.exec(data)) { | |
| test.lastIndex = length = end.index; | |
| i--; | |
| break; | |
| } | |
| } | |
| } | |
| if (Linkify.regString.exec(word)) { | |
| links.push(Linkify.makeRange(node, endNode, index, length)); | |
| } | |
| if (!(test.lastIndex && node === endNode)) { | |
| break; | |
| } | |
| } | |
| } | |
| _ref = links.reverse(); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| link = _ref[_i]; | |
| this.nodes.links.push(Linkify.makeLink(link, this)); | |
| link.detach(); | |
| } | |
| if (!(Conf['Embedding'] || Conf['Link Title'])) { | |
| return; | |
| } | |
| links = this.nodes.links; | |
| i = 0; | |
| while (link = links[i++]) { | |
| if (data = Linkify.services(link)) { | |
| if (Conf['Embedding']) { | |
| Linkify.embed(data); | |
| } | |
| if (Conf['Link Title']) { | |
| Linkify.title(data); | |
| } | |
| } | |
| } | |
| }, | |
| makeRange: function(startNode, endNode, startOffset, endOffset) { | |
| var range; | |
| range = document.createRange(); | |
| range.setStart(startNode, startOffset); | |
| range.setEnd(endNode, endOffset); | |
| return range; | |
| }, | |
| makeLink: function(range) { | |
| var a, char, i, text; | |
| text = range.toString(); | |
| i = 0; | |
| while (/[(\[{<>]/.test(text.charAt(i))) { | |
| i++; | |
| } | |
| if (i) { | |
| text = text.slice(i); | |
| while (range.startOffset + i >= range.startContainer.data.length) { | |
| i--; | |
| } | |
| if (i) { | |
| range.setStart(range.startContainer, range.startOffset + i); | |
| } | |
| } | |
| i = 0; | |
| while (/[)\]}>.,]/.test(char = text.charAt(text.length - (1 + i)))) { | |
| if (!(/[.,]/.test(char) || (text.match(/[()\[\]{}<>]/g)).length % 2)) { | |
| break; | |
| } | |
| i++; | |
| } | |
| if (i) { | |
| text = text.slice(0, -i); | |
| while (range.endOffset - i < 0) { | |
| i--; | |
| } | |
| if (i) { | |
| range.setEnd(range.endContainer, range.endOffset - i); | |
| } | |
| } | |
| if (!/(https?|mailto|git|magnet|ftp|irc):/.test(text)) { | |
| text = (/@/.test(text) ? 'mailto:' : 'http://') + text; | |
| } | |
| a = $.el('a', { | |
| className: 'linkify', | |
| rel: 'nofollow noreferrer', | |
| target: '_blank', | |
| href: text | |
| }); | |
| $.add(a, range.extractContents()); | |
| range.insertNode(a); | |
| return a; | |
| }, | |
| services: function(link) { | |
| var href, key, match, type, _ref; | |
| href = link.href; | |
| _ref = Linkify.types; | |
| for (key in _ref) { | |
| type = _ref[key]; | |
| if (!(match = type.regExp.exec(href))) { | |
| continue; | |
| } | |
| return [key, match[1], match[2], link]; | |
| } | |
| }, | |
| embed: function(data) { | |
| var embed, href, key, link, name, options, uid, value, _ref; | |
| key = data[0], uid = data[1], options = data[2], link = data[3]; | |
| href = link.href; | |
| embed = $.el('a', { | |
| className: 'embedder', | |
| href: 'javascript:;', | |
| textContent: '(embed)' | |
| }); | |
| _ref = { | |
| key: key, | |
| href: href, | |
| uid: uid, | |
| options: options | |
| }; | |
| for (name in _ref) { | |
| value = _ref[name]; | |
| embed.dataset[name] = value; | |
| } | |
| embed.dataset.nodedata = link.innerHTML; | |
| $.addClass(link, "" + embed.dataset.key); | |
| $.on(embed, 'click', Linkify.cb.toggle); | |
| $.after(link, [$.tn(' '), embed]); | |
| if (Conf['Auto-embed']) { | |
| Linkify.cb.toggle.call(embed); | |
| } | |
| data.push(embed); | |
| }, | |
| title: function(data) { | |
| var embed, err, key, link, options, service, title, titles, uid; | |
| key = data[0], uid = data[1], options = data[2], link = data[3], embed = data[4]; | |
| if (!(service = Linkify.types[key].title)) { | |
| return; | |
| } | |
| titles = Conf['CachedTitles']; | |
| if (title = titles[uid]) { | |
| if (link) { | |
| link.textContent = title[0]; | |
| } | |
| if (Conf['Embedding']) { | |
| return embed.dataset.title = title[0]; | |
| } | |
| } else { | |
| try { | |
| $.cache(service.api(uid), function() { | |
| return title = Linkify.cb.title(this, data); | |
| }); | |
| } catch (_error) { | |
| err = _error; | |
| if (link) { | |
| link.innerHTML = "[" + key + "] <span class=warning>Title Link Blocked</span> (are you using NoScript?)</a>"; | |
| } | |
| return; | |
| } | |
| if (title) { | |
| titles[uid] = [title, Date.now()]; | |
| return $.set('CachedTitles', titles); | |
| } | |
| } | |
| }, | |
| titleSync: function(value) { | |
| return Conf['CachedTitles'] = value; | |
| }, | |
| cb: { | |
| toggle: function() { | |
| var string, _ref; | |
| _ref = $.hasClass(this, "embedded") ? ['unembed', '(embed)'] : ['embed', '(unembed)'], string = _ref[0], this.textContent = _ref[1]; | |
| $.replace(this.previousElementSibling, Linkify.cb[string](this)); | |
| return $.toggleClass(this, 'embedded'); | |
| }, | |
| embed: function(a) { | |
| var el, style, type; | |
| el = (type = Linkify.types[a.dataset.key]).el(a); | |
| el.style.cssText = (style = type.style) ? style : "border: 0; width: 640px; height: 390px"; | |
| return el; | |
| }, | |
| unembed: function(a) { | |
| var el; | |
| el = $.el('a', { | |
| rel: 'nofollow noreferrer', | |
| target: 'blank', | |
| className: 'linkify', | |
| href: a.dataset.href, | |
| innerHTML: a.dataset.title || a.dataset.nodedata | |
| }); | |
| $.addClass(el, a.dataset.key); | |
| return el; | |
| }, | |
| title: function(response, data) { | |
| var embed, key, link, options, service, text, uid; | |
| key = data[0], uid = data[1], options = data[2], link = data[3], embed = data[4]; | |
| service = Linkify.types[key].title; | |
| switch (response.status) { | |
| case 200: | |
| case 304: | |
| text = "" + (service.text(JSON.parse(response.responseText))); | |
| if (Conf['Embedding']) { | |
| embed.dataset.title = text; | |
| } | |
| break; | |
| case 404: | |
| text = "[" + key + "] Not Found"; | |
| break; | |
| case 403: | |
| text = "[" + key + "] Forbidden or Private"; | |
| break; | |
| default: | |
| text = "[" + key + "] " + this.status + "'d"; | |
| } | |
| if (link) { | |
| return link.textContent = text; | |
| } | |
| } | |
| }, | |
| types: { | |
| audio: { | |
| regExp: /(.*\.(mp3|ogg|wav))$/, | |
| el: function(a) { | |
| return $.el('audio', { | |
| controls: 'controls', | |
| preload: 'auto', | |
| src: a.dataset.uid | |
| }); | |
| } | |
| }, | |
| gist: { | |
| regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/, | |
| el: function(a) { | |
| var div; | |
| return div = $.el('iframe', { | |
| src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js" | |
| }); | |
| }, | |
| title: { | |
| api: function(uid) { | |
| return "https://api.github.com/gists/" + uid; | |
| }, | |
| text: function(_arg) { | |
| var file, files; | |
| files = _arg.files; | |
| for (file in files) { | |
| if (files.hasOwnProperty(file)) { | |
| return file; | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| image: { | |
| regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/, | |
| style: 'border: 0; width: auto; height: auto;', | |
| el: function(a) { | |
| return $.el('div', { | |
| innerHTML: "<a target=_blank href='" + a.dataset.href + "'><img src='" + a.dataset.href + "'></a>" | |
| }); | |
| } | |
| }, | |
| InstallGentoo: { | |
| regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/, | |
| el: function(a) { | |
| return $.el('iframe', { | |
| src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid | |
| }); | |
| } | |
| }, | |
| Twitter: { | |
| regExp: /.*twitter.com\/(.+\/status\/\d+)/, | |
| el: function(a) { | |
| return $.el('iframe', { | |
| src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid | |
| }); | |
| } | |
| }, | |
| LiveLeak: { | |
| regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/, | |
| el: function(a) { | |
| var el; | |
| el = $.el('iframe', { | |
| width: "640", | |
| height: "360", | |
| src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid, | |
| frameborder: "0" | |
| }); | |
| el.setAttribute("allowfullscreen", "true"); | |
| return el; | |
| } | |
| }, | |
| MediaCrush: { | |
| regExp: /.*(?:mediacru.sh\/)([0-9a-z_]+)/i, | |
| style: 'border: 0;', | |
| el: function(a) { | |
| var el; | |
| el = $.el('div'); | |
| $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() { | |
| var embed, file, files, status, type, _i, _j, _len, _len1, _ref; | |
| status = this.status; | |
| if (status !== 200 && status !== 304) { | |
| return div.innerHTML = "ERROR " + status; | |
| } | |
| files = JSON.parse(this.response).files; | |
| _ref = ['video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'image/svg', 'audio/mpeg']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| type = _ref[_i]; | |
| for (_j = 0, _len1 = files.length; _j < _len1; _j++) { | |
| file = files[_j]; | |
| if (file.type === type) { | |
| embed = file; | |
| break; | |
| } | |
| } | |
| if (embed) { | |
| break; | |
| } | |
| } | |
| if (!embed) { | |
| return div.innerHTML = "ERROR: Not a valid filetype"; | |
| } | |
| return el.innerHTML = (function() { | |
| switch (embed.type) { | |
| case 'video/mp4': | |
| case 'video/ogv': | |
| return "<video autoplay loop>\n <source src=\"https://mediacru.sh/" + a.dataset.uid + ".mp4\" type=\"video/mp4;\">\n <source src=\"https://mediacru.sh/" + a.dataset.uid + ".ogv\" type=\"video/ogg; codecs='theora, vorbis'\">\n</video>"; | |
| case 'image/png': | |
| case 'image/gif': | |
| case 'image/jpeg': | |
| return "<a target=_blank href='" + a.dataset.href + "'><img src='https://mediacru.sh/" + file.file + "'></a>"; | |
| case 'image/svg': | |
| case 'image/svg+xml': | |
| return "<embed src='https://mediacru.sh/" + file.file + "' type='image/svg+xml' />"; | |
| case 'audio/mpeg': | |
| return "<audio controls><source src='https://mediacru.sh/" + file.file + "'></audio>"; | |
| default: | |
| return "ERROR: No valid filetype."; | |
| } | |
| })(); | |
| }); | |
| return el; | |
| } | |
| }, | |
| pastebin: { | |
| regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/, | |
| el: function(a) { | |
| var div; | |
| return div = $.el('iframe', { | |
| src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid | |
| }); | |
| } | |
| }, | |
| gfycat: { | |
| regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/, | |
| el: function(a) { | |
| var div; | |
| return div = $.el('iframe', { | |
| src: "http://gfycat.com/iframe/" + a.dataset.uid | |
| }); | |
| } | |
| }, | |
| SoundCloud: { | |
| regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/, | |
| style: 'height: auto; width: 500px; display: inline-block;', | |
| el: function(a) { | |
| var div; | |
| div = $.el('div', { | |
| className: "soundcloud", | |
| name: "soundcloud" | |
| }); | |
| $.ajax("//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=https://www.soundcloud.com/" + a.dataset.uid, { | |
| onloadend: function() { | |
| return div.innerHTML = JSON.parse(this.responseText).html; | |
| } | |
| }, false); | |
| return div; | |
| }, | |
| title: { | |
| api: function(uid) { | |
| return "//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=https://www.soundcloud.com/" + uid; | |
| }, | |
| text: function(_) { | |
| return _.title; | |
| } | |
| } | |
| }, | |
| StrawPoll: { | |
| regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/, | |
| style: 'border: 0; width: 600px; height: 406px;', | |
| el: function(a) { | |
| return $.el('iframe', { | |
| src: "http://strawpoll.me/embed_1/" + a.dataset.uid | |
| }); | |
| } | |
| }, | |
| TwitchTV: { | |
| regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/, | |
| style: "border: none; width: 640px; height: 360px;", | |
| el: function(a) { | |
| var channel, chapter, result, _; | |
| if (result = /(\w+)\/(?:[a-z]\/)?(\d+)/i.exec(a.dataset.uid)) { | |
| _ = result[0], channel = result[1], chapter = result[2]; | |
| return $.el('object', { | |
| data: 'http://www.twitch.tv/widgets/archive_embed_player.swf', | |
| innerHTML: "<param name='allowFullScreen' value='true' />\n<param name='flashvars' value='channel=" + channel + "&start_volume=25&auto_play=false" + (chapter ? "&chapter_id=" + chapter : "") + "' />" | |
| }); | |
| } else { | |
| channel = (/(\w+)/.exec(a.dataset.uid))[0]; | |
| return $.el('object', { | |
| data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel, | |
| innerHTML: "<param name=\"allowFullScreen\" value=\"true\" />\n<param name=\"movie\" value=\"http://www.twitch.tv/widgets/live_embed_player.swf\" />\n<param name=\"flashvars\" value=\"hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25\" />" | |
| }); | |
| } | |
| } | |
| }, | |
| Vocaroo: { | |
| regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/, | |
| style: 'border: 0; width: 150px; height: 45px;', | |
| el: function(a) { | |
| return $.el('object', { | |
| innerHTML: "<embed src='http://vocaroo.com/player.swf?playMediaID=" + (a.dataset.uid.replace(/^i\//, '')) + "&autoplay=0' wmode='opaque' width='150' height='45' pluginspage='http://get.adobe.com/flashplayer/' type='application/x-shockwave-flash'></embed>" | |
| }); | |
| } | |
| }, | |
| Vimeo: { | |
| regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/, | |
| el: function(a) { | |
| return $.el('iframe', { | |
| src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque" | |
| }); | |
| }, | |
| title: { | |
| api: function(uid) { | |
| return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid; | |
| }, | |
| text: function(_) { | |
| return _.title; | |
| } | |
| } | |
| }, | |
| Vine: { | |
| regExp: /.*(?:vine.co\/)([^#\&\?]*).*/, | |
| style: 'border: none; width: 500px; height: 500px;', | |
| el: function(a) { | |
| return $.el('iframe', { | |
| src: "https://vine.co/" + a.dataset.uid + "/card" | |
| }); | |
| } | |
| }, | |
| YouTube: { | |
| regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/, | |
| el: function(a) { | |
| var el; | |
| el = $.el('iframe', { | |
| src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque" | |
| }); | |
| el.setAttribute("allowfullscreen", "true"); | |
| return el; | |
| }, | |
| title: { | |
| api: function(uid) { | |
| return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"; | |
| }, | |
| text: function(data) { | |
| return data.entry.title.$t; | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| ArchiveLink = { | |
| init: function() { | |
| var div, entry, type, _i, _len, _ref; | |
| if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Archive Link']) { | |
| return; | |
| } | |
| div = $.el('div', { | |
| textContent: 'Archive' | |
| }); | |
| entry = { | |
| type: 'post', | |
| el: div, | |
| order: 90, | |
| open: function(_arg) { | |
| var ID, board, thread; | |
| ID = _arg.ID, thread = _arg.thread, board = _arg.board; | |
| return !!Redirect.to('thread', { | |
| postID: ID, | |
| threadID: thread.ID, | |
| boardID: board.ID | |
| }); | |
| }, | |
| subEntries: [] | |
| }; | |
| _ref = [['Post', 'post'], ['Name', 'name'], ['Tripcode', 'tripcode'], ['E-mail', 'email'], ['Subject', 'subject'], ['Filename', 'filename'], ['Image MD5', 'MD5']]; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| type = _ref[_i]; | |
| entry.subEntries.push(this.createSubEntry(type[0], type[1])); | |
| } | |
| return $.event('AddMenuEntry', entry); | |
| }, | |
| createSubEntry: function(text, type) { | |
| var el, open; | |
| el = $.el('a', { | |
| textContent: text, | |
| target: '_blank' | |
| }); | |
| open = type === 'post' ? function(_arg) { | |
| var ID, board, thread; | |
| ID = _arg.ID, thread = _arg.thread, board = _arg.board; | |
| el.href = Redirect.to('thread', { | |
| postID: ID, | |
| threadID: thread.ID, | |
| boardID: board.ID | |
| }); | |
| return true; | |
| } : function(post) { | |
| var value; | |
| value = Filter[type](post); | |
| if (!value) { | |
| return false; | |
| } | |
| el.href = Redirect.to('search', { | |
| boardID: post.board.ID, | |
| type: type, | |
| value: value, | |
| isSearch: true | |
| }); | |
| return true; | |
| }; | |
| return { | |
| el: el, | |
| open: open | |
| }; | |
| } | |
| }; | |
| DeleteLink = { | |
| init: function() { | |
| var div, fileEl, fileEntry, postEl, postEntry; | |
| if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Delete Link']) { | |
| return; | |
| } | |
| div = $.el('div', { | |
| className: 'delete-link', | |
| textContent: 'Delete' | |
| }); | |
| postEl = $.el('a', { | |
| className: 'delete-post', | |
| href: 'javascript:;' | |
| }); | |
| fileEl = $.el('a', { | |
| className: 'delete-file', | |
| href: 'javascript:;' | |
| }); | |
| postEntry = { | |
| el: postEl, | |
| open: function() { | |
| postEl.textContent = 'Post'; | |
| $.on(postEl, 'click', DeleteLink["delete"]); | |
| return true; | |
| } | |
| }; | |
| fileEntry = { | |
| el: fileEl, | |
| open: function(_arg) { | |
| var file; | |
| file = _arg.file; | |
| if (!file || file.isDead) { | |
| return false; | |
| } | |
| fileEl.textContent = 'File'; | |
| $.on(fileEl, 'click', DeleteLink["delete"]); | |
| return true; | |
| } | |
| }; | |
| return $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: div, | |
| order: 40, | |
| open: function(post) { | |
| var node; | |
| if (post.isDead) { | |
| return false; | |
| } | |
| DeleteLink.post = post; | |
| node = div.firstChild; | |
| node.textContent = 'Delete'; | |
| DeleteLink.cooldown.start(post, node); | |
| return true; | |
| }, | |
| subEntries: [postEntry, fileEntry] | |
| }); | |
| }, | |
| "delete": function() { | |
| var fileOnly, form, link, post; | |
| post = DeleteLink.post; | |
| if (DeleteLink.cooldown.counting === post) { | |
| return; | |
| } | |
| $.off(this, 'click', DeleteLink["delete"]); | |
| fileOnly = $.hasClass(this, 'delete-file'); | |
| this.textContent = "Deleting " + (fileOnly ? 'file' : 'post') + "..."; | |
| form = { | |
| mode: 'usrdel', | |
| onlyimgdel: fileOnly, | |
| pwd: QR.persona.getPassword() | |
| }; | |
| form[post.ID] = 'delete'; | |
| link = this; | |
| return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), { | |
| responseType: 'document', | |
| withCredentials: true, | |
| onload: function() { | |
| return DeleteLink.load(link, post, fileOnly, this.response); | |
| }, | |
| onerror: function() { | |
| return DeleteLink.error(link); | |
| } | |
| }, { | |
| form: $.formData(form) | |
| }); | |
| }, | |
| load: function(link, post, fileOnly, resDoc) { | |
| var msg, s; | |
| if (resDoc.title === '4chan - Banned') { | |
| s = 'Banned!'; | |
| } else if (msg = resDoc.getElementById('errmsg')) { | |
| s = msg.textContent; | |
| $.on(link, 'click', DeleteLink["delete"]); | |
| } else { | |
| if (resDoc.title === 'Updating index...') { | |
| (post.origin || post).kill(fileOnly); | |
| } | |
| s = 'Deleted'; | |
| } | |
| return link.textContent = s; | |
| }, | |
| error: function(link) { | |
| link.textContent = 'Connection error, please retry.'; | |
| return $.on(link, 'click', DeleteLink["delete"]); | |
| }, | |
| cooldown: { | |
| start: function(post, node) { | |
| var length, seconds, _ref; | |
| if (!((_ref = QR.db) != null ? _ref.get({ | |
| boardID: post.board.ID, | |
| threadID: post.thread.ID, | |
| postID: post.ID | |
| }) : void 0)) { | |
| delete DeleteLink.cooldown.counting; | |
| return; | |
| } | |
| DeleteLink.cooldown.counting = post; | |
| length = 60; | |
| seconds = Math.ceil((length * $.SECOND - (Date.now() - post.info.date)) / $.SECOND); | |
| return DeleteLink.cooldown.count(post, seconds, length, node); | |
| }, | |
| count: function(post, seconds, length, node) { | |
| if (DeleteLink.cooldown.counting !== post) { | |
| return; | |
| } | |
| if (!((0 <= seconds && seconds <= length))) { | |
| if (DeleteLink.cooldown.counting === post) { | |
| node.textContent = 'Delete'; | |
| delete DeleteLink.cooldown.counting; | |
| } | |
| return; | |
| } | |
| setTimeout(DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node); | |
| return node.textContent = "Delete (" + seconds + ")"; | |
| } | |
| } | |
| }; | |
| DownloadLink = { | |
| init: function() { | |
| var a; | |
| if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Download Link']) { | |
| return; | |
| } | |
| a = $.el('a', { | |
| className: 'download-link', | |
| textContent: 'Download file' | |
| }); | |
| return $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: a, | |
| order: 100, | |
| open: function(_arg) { | |
| var file; | |
| file = _arg.file; | |
| if (!file) { | |
| return false; | |
| } | |
| a.href = file.URL; | |
| a.download = file.name; | |
| return true; | |
| } | |
| }); | |
| } | |
| }; | |
| Menu = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Menu']) { | |
| return; | |
| } | |
| this.menu = new UI.Menu('post'); | |
| return Post.callbacks.push({ | |
| name: 'Menu', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| if (this.isClone) { | |
| $.on($('.menu-button', this.nodes.info), 'click', Menu.toggle); | |
| return; | |
| } | |
| return $.add(this.nodes.info, Menu.makeButton()); | |
| }, | |
| makeButton: (function() { | |
| var a; | |
| a = $.el('a', { | |
| className: 'menu-button', | |
| innerHTML: '<i class="fa fa-angle-down"></i>', | |
| href: 'javascript:;' | |
| }); | |
| return function() { | |
| var button; | |
| button = a.cloneNode(true); | |
| $.on(button, 'click', Menu.toggle); | |
| return button; | |
| }; | |
| })(), | |
| toggle: function(e) { | |
| var post; | |
| post = Get.postFromNode(this); | |
| return Menu.menu.toggle(e, this, post); | |
| } | |
| }; | |
| ReportLink = { | |
| init: function() { | |
| var a; | |
| if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Report Link']) { | |
| return; | |
| } | |
| a = $.el('a', { | |
| className: 'report-link', | |
| href: 'javascript:;', | |
| textContent: 'Report this post' | |
| }); | |
| $.on(a, 'click', ReportLink.report); | |
| return $.event('AddMenuEntry', { | |
| type: 'post', | |
| el: a, | |
| order: 10, | |
| open: function(post) { | |
| ReportLink.post = post; | |
| return !post.isDead; | |
| } | |
| }); | |
| }, | |
| report: function() { | |
| var id, post, set, url; | |
| post = ReportLink.post; | |
| url = "//sys.4chan.org/" + post.board + "/imgboard.php?mode=report&no=" + post; | |
| id = Date.now(); | |
| set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"; | |
| return window.open(url, id, set); | |
| } | |
| }; | |
| Favicon = { | |
| init: function() { | |
| return $.ready(function() { | |
| var href; | |
| Favicon.el = $('link[rel="shortcut icon"]', d.head); | |
| Favicon.el.type = 'image/x-icon'; | |
| href = Favicon.el.href; | |
| Favicon.SFW = /ws\.ico$/.test(href); | |
| Favicon["default"] = href; | |
| return Favicon["switch"](); | |
| }); | |
| }, | |
| "switch": function() { | |
| if (Favicon.SFW) { | |
| Favicon["default"] = 'https://s.4cdn.org/image/favicon-ws.ico'; | |
| } else { | |
| Favicon["default"] = 'https://s.4cdn.org/image/favicon.ico'; | |
| } | |
| switch (Conf['favicon']) { | |
| case 'ferongr': | |
| Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAPIGAOgLAnMFAL8AAOkMA/+AgP+rqwAAAAAAACH5BAUKAAYALAAAAAAQABAAQANKaLrcDYDBF8YgAQZiswJVp1mDZ4CB+aUmmkYnq4IFphGFGoMwr0MwySSGs62KGZBAIAJZli2gcLhA9V6STTNkjAkCX803LDmVgwkAOw=='; | |
| Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII='; | |
| Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAPIGAADX8QBwfgC2zADY8nnl8qLp8gAAAAAAACH5BAUKAAYALAAAAAAQABAAQANKaLrcDYDBF8YgAQZiswJVp1mDZ4CB+aUmmkYnq4IFphGFGoMwr0MwySSGs62KGZBAIAJZli2gcLhA9V6STTNkjAkCX803LDmVgwkAOw=='; | |
| Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg=='; | |
| Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAPIGAFT+ACh5AEncAFX/Acz/su7/5gAAAAAAACH5BAUKAAYALAAAAAAQABAAQANKaLrcDYDBF8YgAQZiswJVp1mDZ4CB+aUmmkYnq4IFphGFGoMwr0MwySSGs62KGZBAIAJZli2gcLhA9V6STTNkjAkCX803LDmVgwkAOw=='; | |
| Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='; | |
| break; | |
| case 'xat-': | |
| Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42s1SQQrCMBDsQ8WDIEV6LTT2A4og2Hi0veo7fIAH04e06L1jphIJRtoVLw4MmWZnh2aT6K8Ax82uyFOV6SSJK5Kae74naIZHfhfx5HxaTC8kdeCRhNzms8ayFTaHJuTLzvKpJSE+sVoDhxIoK2qv5vgGDutoz8vfhlJd33w1gDGg5h5r9NArCzA1UNevgPtQQJplmtMeOwI99AYBW73PI8EQqXsvIbjGduAaxwPcQ/oqwF/dUw5r5GfArcLa73gAGxUeHPIycrIAAAAASUVORK5CYII='; | |
| Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA80lEQVQ4y2NgGEzgPwx3TZhYVVJRPik7O30uCIPYIDFkNRia/yNhEH9ieszB5ZlxZ0EYxMamhqAhn1KT3gPxB5I1wxT9r6r8B8T/ccnjDAOwhvaO//9nz/n/f85cMBtdHiMEQYG1DOhfkLP/V1T8A2u+eOn//0uXwAYdiIr6ZyQi8ltOWPCLm5vzVuIMuHT5///Ll8EGWEhJ/YcBfn7+lxgGlJSXT4KFNi4vwAA3N/c7DAN6J02uYsATiBqoBrzCmpCwROMHWDRaIBmANRCxGQBLSOCEBcRcUIzXAGQalpRBbB8iXMCAJPEfh9wRKG1HtRwIAJCmKOzjRex1AAAAAElFTkSuQmCC'; | |
| Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAzUlEQVQ4y2NgGEzgPwxP7OuqqigtmZSZnj0XhEFskBiyGgzNetMPwzGIH5M/8WBczfKzIAxiY1ND0JCkDZ/eA/EHkjXDFFUe+f8PiP/jkscZBiDcfvr//9lX/v+fA8QgNro8RgiCAiuuetlZkLMrgDaDNF988///pdcQg0BiIDmQGpBaogy4BDTg8htkAz7jNqAcGFWw0MbnBZAakFoMAyZP6K1CDixcgQhiQ9UyEBONH4iNRqwGwBISSQYg07CkjE0OX35gwKEAnxz5AADUHTv3RAHZ7QAAAABJRU5ErkJggg=='; | |
| Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/klEQVQ4y2NgGEzgPwxP7OuqqigtmZSZnj0XhEFskBiyGgzNetMPwzGIH5M/8WBczfKzIAxiY1ND0JCkDZ/eA/EHkjXDFFUe+f8PiP/jkscZBiDcfvr//9lX/v+fA8QgNro8RgiCAiuuetlZkLMrgDaDNF988///pdcQg6KmHPgnomL0W1BM7ours9tWogy4BDTg8huIAVLaFv9hgJ+f/yWGAeXAqIKFNi4vwAA3N/c7DAMmT+itQg4s9EBkF5dDNuAV1oSEJRo/wKKRS1ELbgDWQMRmACwhgdhSoXn/GVnZwRivAcg0LCmD2Ly6VgRdwIAk8R+H3BEobUe1HAgA7g5BI+Q2tn8AAAAASUVORK5CYII='; | |
| Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAzklEQVQ4y2NgGEzgPwxP7JpYVVZSMSk9O2suCIPYIDFkNRia084YwzGIHzMx/mDc8vizIAxiY1ND0JCST0nvgfgDyZphinr+V/4D4v+45HGGAQjP+t/+f/X/2UA8B8xGl8cIQVBgxS2LPwtyds//in8gzTf/XwTiS2CDQGIgOZAakFqiDLgF1Hzr/2WEAZ/xGFBWUj4JFtr4vABSA1KLYcDk3slVyIGFKxBBbKhaBmKi8QOx0YjVAFhCIskAZBqWlLHJ4csPDDgU4JMjHwAAAfUvfvahyqUAAAAASUVORK5CYII='; | |
| Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/klEQVQ4y2NgGEzgPwxP7JpYVVZSMSk9O2suCIPYIDFkNRia084YwzGIHzMx/mDc8vizIAxiY1ND0JCST0nvgfgDyZphinr+V/4D4v+45HGGAQjP+t/+f/X/2UA8B8xGl8cIQVBgxS2LPwtyds//in8gzTf/XwTiS2CDMg9E/ZMwEvktKCf4xdnNdStRBtwCar71/zLYADkLqf8wwM/P/xLDgLKS8kmw0MblBRjg5uZ+h2HA5N7JVciBhR6IAgocyAa8wpqQsETjB1g0iulwww3AGojYDIAlJBDbqkT2PwsHExjjNQCZhiVlEFvOhp+gCxiQJP7jkDsCpe2olgMB9UE2wvseYUsAAAAASUVORK5CYII='; | |
| break; | |
| case 'Mayhem': | |
| Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABFklEQVR4AZ2R4WqEMBCEFy1yiJQQ14gcIhIuFBFR+qPQ93+v66QMksrlTwMfkZ2ZZbMKTgVqYIDl3YAbeCM31lJP/Zul4MAEPJjBQGNDLGsz8PQ6aqLAP5PTdd1WlmU09mSKtdTDRgrkzspJPKq6RxMahfj9yhOzQEZwZAwfzrk1ox3MXibIN8hO4MAjeV72CemJGWblnRsOYOdoGw0jebB20BPAwKzUQPlrFhrXFw1Wagu9yuzZwINzVAZCURRL+gRr7Wd8Vtqg4Th/lsUmewyk9WQ/A7NiwJz5VV/GmO+MNjMrFvh/NPDMigHTaeJN09a27ZHRJmalBg54CgfvAGYSLpoHjlmpuAwFdzDy7oGS/qIpM9UPFGg1b1kUlssAAAAASUVORK5CYII='; | |
| Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABR0lEQVR4AYWSQWq0QBCFCw0SRIK0PQ4hiIhEZBhEySLyewUPEMgqR/JIXiDhzz7kKKYePIZajEzDRxfV9dWU3SO6IiVWUsVxT5R75Y4gTmwNnUh4kCulUiuV8sjChDjmKtaUcHgmHsnNrMPh0IVhiMIjKZGzNXDoyhMzF7C89z2KtFGD+FoNXEUKZdgpaPM8P++cDXTtBDca7EyQK8+bXTufYBccuvLAG26UnqN1LCgI4g/lm7zTgSux4vk0J8rnKw3+m1//pBPbBrVyGZVNmiAITviEtm3t+D+2QcJx7GUxlN4594K4ZY75Xzh0JVWqnad6TdP0H+LRNBjHcYNDV5xS32qwaC4my7Lwn6guu5QoomgbdFmWDYhnM8E8zxscuhLzPWtKA/dGqUizrityX9M0YX+DQ1ciXobnP6vgfmTOM7Znnk70B58pPaEvx+epAAAAAElFTkSuQmCC'; | |
| Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVR4AZ3RUWqEMBSF4ftQZAhSREQJIiIXpQwi+tSldkFdWPsLhyEE0ocKH2Fyzg1mNJ4KAQ1arTUeeJMH6qwTUJmCHjMcC6KKtbSIylzdXpl18J/k4fdTpUFmPLOOa9bGe+P4+n5RYYfLXuiMsAlXofBxK2QXpvwN/jqg+AY91vR+pStk+apZe0fEhhMXDhUmWXEoO9WNmrWAzvRPq7jnB2jvUGfWTEgPcJzZFTbZk/0Tnh5QI+af6lVGvq/Do2atwVL4VJ+3QrZo1lr4Pw5wzVqDWaV7SUvHrZDNmrWAHq7g0rphkS3LXDMBVqFGhxGT1gGdDFnWaab6BRmXRvbxDmYiAAAAAElFTkSuQmCC'; | |
| Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABQElEQVR4AY2SQUrEQBBFS9CMNFEkhAQdYmiCIUgcZlYGc4VsBcGVF/AuWXme4F7RtXiVWF9+Y9MYtOHRTdX/NZWaEj2RYpQTJeEdK4fKPuA7DjSGXiQkU0qlUqxySmFMEsYsNSU8zEmK4OwdEbmkKCclYoGmolfWCGyenh1O0EJE2gXNWpFC2S0IGrCQ29EbdPCPAmEHmXIxByf8hDAPD71yzAnXypatbSgoAN8Pyju5h4deMUrqJk1z+0uBN+/XX+gxfoFK2QafUJO2aRq//Q+/QIx2wr+Kwq0rusrP/QKf9MTCtbQLf9U1wNvYnz3qug45S68kSvVXgbPbx3nvYPXNOI7cRPWySukK+DcGCvA+urqZ3RmGAbmSXjFK5rpwW8nhWVJP04TYa9/3uO/goVciDiPlZhW8c8ZAHuRSeqIv32FK/GYGL8YAAAAASUVORK5CYII='; | |
| Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVR4AZ3RUWqEMBSF4ftQZAihDCKKiAQJShERQx+6o662e2p/4TCEQF468BEm95yLovFr4PBEq9PjgTd5wBcZp6559AiIWDAq6KXV3aJMUMfDOsTf7Mf/XaFBAvYiE9W16b74/vl8UeBAlKOSmWAzUiXwcavMkrrFE9QXVJ+gx5q9XvUVivmqrr1jxIYLCacCs6y6S8psGNU1hw4Bu4JHuUB3pzJBHZcviLiKV9jkyO4vxHyBx1h+qlcY5b2Wj+raE0vlU33dKrNFXWsR/7EgqmtPBIXuIw+dt8osqGsOPaIGSeeGRbZiFtVxsAYeHSbMOgd0MhSzTp3mD4RaQX4aW3NMAAAAAElFTkSuQmCC'; | |
| Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABP0lEQVR4AYWS0UqFQBCGhziImNRBRImDmUgiIaF0kWSP4AMEXXXTE/QiPpL3UdR19Crb/PAvLEtyFj5mmfn/cdxd0RUokbJXEsZYCZUd4D72NBG8wkKmlEqtVMoFhTFJmKuoKelBTVIkjbNE5IainJTIeZqaXjkg8fp+Z7GCjiLQbWgOihTKsCFowUZtoNef4HgDf4JMuTbe8n/Br8NDr5zxhBul52i3FBQE+xflmzzTA69ESmpPmubunwZfztc/6IncBrXSe7/QkK5tW3f8H7dBjHH8q6Kwt033V6Hb4JeeWPgsq42rugfYZ92psWscRwMPvZIo9bEGD2+F2YUnBizLwpeoXnYpbQM34kAB9peP58aueZ4NPPRKxPusaRoYG6UizbquyH1O04T4RA+8EvAwUr6sgjFnDuReLaUn+ANygUa7+9SCWgAAAABJRU5ErkJggg=='; | |
| break; | |
| case 'Original': | |
| Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; | |
| Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42q1RwQnAMAjMu5M4guAKXa4j5dUROo5tipSDcrFChUONd0di2m/hEGVOHDyIPufgwAFASDkpoSzmBrkJ2UMyR9LsJ3rvrqo3Rt1YMIMhhNnOxLMnoMFBxHyJAr2IOBFzA8U+6pLBdmEJTA0aMVjpDd6Loks0s5HZNwYx8tfZCZ0kll7ORffZAAAAAElFTkSuQmCC'; | |
| Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; | |
| Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eILZO5/XI0UAgm7H9tOsu0yGWAQSOoFijHOxOANGqm/LczpOaXs4gISrPZ+gc2+hO5w2xdwgOjBFUIF+sEJrhUl9JFr+badFwR+BfqlmGUJAAAAAElFTkSuQmCC'; | |
| Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; | |
| Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eIDhbn/cTVSCCTsfmw7ybbLZIBBIKkXKKU0E4M3aKT+tjCn5xiziwuIsNr7BTb7ErrDZV/AAaIHdwgV6AcnuFaU0Eeu5dt2XiUyBjCQ2bIrAAAAAElFTkSuQmCC'; | |
| break; | |
| case 'Metro': | |
| Favicon.unreadDead = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg=='; | |
| Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAHAAAdAAApAAAsAAA4AABsAACQAAC/AAD///9SVhtjAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC'; | |
| Favicon.unreadSFW = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg=='; | |
| Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAACAkAISUALzQAMTcAQEcAeokAorYA1/H///8BrzTFAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC'; | |
| Favicon.unreadNSFW = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg=='; | |
| Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAADCgANKAASOAATOwAZTAAwkQBAwQBV/wH////+Fmy4AAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC'; | |
| if (Favicon.SFW) { | |
| Favicon["default"] = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAul8Or2V3yAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg=='; | |
| } else { | |
| Favicon["default"] = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABmzDMxovcJAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg=='; | |
| } | |
| } | |
| if (Favicon.SFW) { | |
| Favicon.unread = Favicon.unreadSFW; | |
| return Favicon.unreadY = Favicon.unreadSFWY; | |
| } else { | |
| Favicon.unread = Favicon.unreadNSFW; | |
| return Favicon.unreadY = Favicon.unreadNSFWY; | |
| } | |
| }, | |
| dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', | |
| logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg==' | |
| }; | |
| ThreadExcerpt = { | |
| init: function() { | |
| if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) { | |
| return; | |
| } | |
| return Thread.callbacks.push({ | |
| name: 'Thread Excerpt', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| return d.title = Get.threadExcerpt(this); | |
| } | |
| }; | |
| ThreadStats = { | |
| init: function() { | |
| var sc, | |
| _this = this; | |
| if (g.VIEW !== 'thread' || !Conf['Thread Stats']) { | |
| return; | |
| } | |
| if (Conf['Updater and Stats in Header']) { | |
| this.dialog = sc = $.el('span', { | |
| innerHTML: "<span id=post-count>0</span> / <span id=file-count>0</span>" + (Conf["Page Count in Stats"] ? " / <span id=page-count>0</span>" : ""), | |
| id: 'thread-stats', | |
| title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "") | |
| }); | |
| $.ready(function() { | |
| return Header.addShortcut(sc); | |
| }); | |
| } else { | |
| this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', "<div class=move title='Post Count / File Count" + (Conf["Page Count in Stats"] ? " / Page Count" : "") + "'><span id=post-count>0</span> / <span id=file-count>0</span>" + (Conf["Page Count in Stats"] ? " / <span id=page-count>0</span>" : "") + "</div>"); | |
| $.ready(function() { | |
| return $.add(d.body, sc); | |
| }); | |
| } | |
| this.postCountEl = $('#post-count', sc); | |
| this.fileCountEl = $('#file-count', sc); | |
| this.pageCountEl = $('#page-count', sc); | |
| return Thread.callbacks.push({ | |
| name: 'Thread Stats', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var ID, fileCount, post, postCount, _ref; | |
| postCount = 0; | |
| fileCount = 0; | |
| _ref = this.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| postCount++; | |
| if (post.file) { | |
| fileCount++; | |
| } | |
| } | |
| ThreadStats.thread = this; | |
| ThreadStats.fetchPage(); | |
| ThreadStats.update(postCount, fileCount); | |
| return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate); | |
| }, | |
| onUpdate: function(e) { | |
| var fileCount, postCount, _ref; | |
| if (e.detail[404]) { | |
| return; | |
| } | |
| _ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount; | |
| return ThreadStats.update(postCount, fileCount); | |
| }, | |
| update: function(postCount, fileCount) { | |
| var fileCountEl, postCountEl, thread; | |
| thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl; | |
| postCountEl.textContent = postCount; | |
| fileCountEl.textContent = fileCount; | |
| (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning'); | |
| return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning'); | |
| }, | |
| fetchPage: function() { | |
| if (!Conf["Page Count in Stats"]) { | |
| return; | |
| } | |
| if (ThreadStats.thread.isDead) { | |
| ThreadStats.pageCountEl.textContent = 'Dead'; | |
| $.addClass(ThreadStats.pageCountEl, 'warning'); | |
| return; | |
| } | |
| setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); | |
| return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { | |
| onload: ThreadStats.onThreadsLoad | |
| }, { | |
| whenModified: true | |
| }); | |
| }, | |
| onThreadsLoad: function() { | |
| var page, pages, thread, _i, _j, _len, _len1, _ref; | |
| if (!(Conf["Page Count in Stats"] && this.status === 200)) { | |
| return; | |
| } | |
| pages = JSON.parse(this.response); | |
| for (_i = 0, _len = pages.length; _i < _len; _i++) { | |
| page = pages[_i]; | |
| _ref = page.threads; | |
| for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { | |
| thread = _ref[_j]; | |
| if (thread.no === ThreadStats.thread.ID) { | |
| ThreadStats.pageCountEl.textContent = page.page; | |
| (page.page === pages.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| ThreadUpdater = { | |
| init: function() { | |
| var checked, conf, el, input, name, sc, settings, subEntries, _ref, | |
| _this = this; | |
| if (g.VIEW !== 'thread' || !Conf['Thread Updater']) { | |
| return; | |
| } | |
| if (Conf['Updater and Stats in Header']) { | |
| this.dialog = sc = $.el('span', { | |
| innerHTML: "<span id=update-status></span><span id=update-timer title='Update now'></span>", | |
| id: 'updater' | |
| }); | |
| $.ready(function() { | |
| return Header.addShortcut(sc); | |
| }); | |
| } else { | |
| this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', "<div class=move></div><span id=update-status></span><span id=update-timer title='Update now'></span>"); | |
| $.addClass(doc, 'float'); | |
| $.ready(function() { | |
| $.addClass(doc, 'float'); | |
| return $.add(d.body, sc); | |
| }); | |
| } | |
| this.checkPostCount = 0; | |
| this.timer = $('#update-timer', sc); | |
| this.status = $('#update-status', sc); | |
| this.isUpdating = Conf['Auto Update']; | |
| $.on(this.timer, 'click', ThreadUpdater.update); | |
| $.on(this.status, 'click', ThreadUpdater.update); | |
| subEntries = []; | |
| _ref = Config.updater.checkbox; | |
| for (name in _ref) { | |
| conf = _ref[name]; | |
| checked = Conf[name] ? 'checked' : ''; | |
| el = $.el('label', { | |
| title: "" + conf[1], | |
| innerHTML: "<input name='" + name + "' type=checkbox " + checked + "> " + name | |
| }); | |
| input = el.firstElementChild; | |
| $.on(input, 'change', $.cb.checked); | |
| if (input.name === 'Scroll BG') { | |
| $.on(input, 'change', ThreadUpdater.cb.scrollBG); | |
| ThreadUpdater.cb.scrollBG(); | |
| } else if (input.name === 'Auto Update') { | |
| $.on(input, 'change', ThreadUpdater.cb.update); | |
| } | |
| subEntries.push({ | |
| el: el | |
| }); | |
| } | |
| settings = $.el('span', { | |
| innerHTML: '<a href=javascript:;>Interval</a>' | |
| }); | |
| $.on(settings, 'click', this.intervalShortcut); | |
| subEntries.push({ | |
| el: settings | |
| }); | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: $.el('span', { | |
| textContent: 'Updater' | |
| }), | |
| order: 110, | |
| subEntries: subEntries | |
| }); | |
| return Thread.callbacks.push({ | |
| name: 'Thread Updater', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| ThreadUpdater.thread = this; | |
| ThreadUpdater.root = this.OP.nodes.root.parentNode; | |
| ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0]; | |
| ThreadUpdater.outdateCount = 0; | |
| ThreadUpdater.cb.interval.call($.el('input', { | |
| value: Conf['Interval'] | |
| })); | |
| $.on(window, 'online offline', ThreadUpdater.cb.online); | |
| $.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost); | |
| $.on(d, 'visibilitychange', ThreadUpdater.cb.visibility); | |
| return ThreadUpdater.cb.online(); | |
| }, | |
| /* | |
| http://freesound.org/people/pierrecartoons1979/sounds/90112/ | |
| cc-by-nc-3.0 | |
| */ | |
| beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA', | |
| cb: { | |
| online: function() { | |
| if (ThreadUpdater.online = navigator.onLine) { | |
| ThreadUpdater.outdateCount = 0; | |
| ThreadUpdater.setInterval(); | |
| ThreadUpdater.set('status', null, null); | |
| return; | |
| } | |
| ThreadUpdater.set('timer', null); | |
| return ThreadUpdater.set('status', 'Offline', 'warning'); | |
| }, | |
| post: function(e) { | |
| if (!(ThreadUpdater.isUpdating && e.detail.threadID === ThreadUpdater.thread.ID)) { | |
| return; | |
| } | |
| ThreadUpdater.outdateCount = 0; | |
| if (ThreadUpdater.seconds > 2) { | |
| return setTimeout(ThreadUpdater.update, 1000); | |
| } | |
| }, | |
| checkpost: function(e) { | |
| if (!ThreadUpdater.checkPostCount) { | |
| if (e.detail.threadID !== ThreadUpdater.thread.ID) { | |
| return; | |
| } | |
| ThreadUpdater.seconds = 0; | |
| ThreadUpdater.outdateCount = 0; | |
| ThreadUpdater.set('timer', '...'); | |
| } | |
| if (!(g.DEAD || ThreadUpdater.foundPost || ThreadUpdater.checkPostCount >= 5)) { | |
| return setTimeout(ThreadUpdater.update, ++ThreadUpdater.checkPostCount * $.SECOND); | |
| } | |
| ThreadUpdater.setInterval(); | |
| ThreadUpdater.checkPostCount = 0; | |
| delete ThreadUpdater.foundPost; | |
| return delete ThreadUpdater.postID; | |
| }, | |
| visibility: function() { | |
| if (d.hidden) { | |
| return; | |
| } | |
| ThreadUpdater.outdateCount = 0; | |
| if (ThreadUpdater.seconds > ThreadUpdater.interval) { | |
| return ThreadUpdater.setInterval(); | |
| } | |
| }, | |
| scrollBG: function() { | |
| return ThreadUpdater.scrollBG = Conf['Scroll BG'] ? function() { | |
| return true; | |
| } : function() { | |
| return !d.hidden; | |
| }; | |
| }, | |
| interval: function() { | |
| var val; | |
| val = parseInt(this.value, 10); | |
| if (val < 1) { | |
| val = 1; | |
| } | |
| ThreadUpdater.interval = this.value = val; | |
| return $.cb.value.call(this); | |
| }, | |
| load: function(e) { | |
| var klass, req, text, _ref; | |
| req = ThreadUpdater.req; | |
| switch (req.status) { | |
| case 200: | |
| g.DEAD = false; | |
| ThreadUpdater.parse(JSON.parse(req.response).posts); | |
| ThreadUpdater.setInterval(); | |
| break; | |
| case 404: | |
| g.DEAD = true; | |
| ThreadUpdater.set('timer', null); | |
| ThreadUpdater.set('status', '404', 'warning'); | |
| clearTimeout(ThreadUpdater.timeoutID); | |
| ThreadUpdater.thread.kill(); | |
| $.event('ThreadUpdate', { | |
| 404: true, | |
| thread: ThreadUpdater.thread | |
| }); | |
| break; | |
| default: | |
| ThreadUpdater.outdateCount++; | |
| ThreadUpdater.setInterval(); | |
| _ref = req.status === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1]; | |
| ThreadUpdater.set('status', text, klass); | |
| } | |
| if (ThreadUpdater.postID) { | |
| return ThreadUpdater.cb.checkpost(); | |
| } | |
| } | |
| }, | |
| setInterval: function() { | |
| var cur, i, j, limit; | |
| i = ThreadUpdater.interval + 1; | |
| if (Conf['Optional Increase']) { | |
| cur = ThreadUpdater.outdateCount || 1; | |
| limit = d.hidden ? 7 : 10; | |
| j = cur <= limit ? cur : limit; | |
| cur = (Math.floor(i * 0.1) || 1) * j * j; | |
| ThreadUpdater.seconds = cur > i ? cur <= 300 ? cur : 300 : i; | |
| } else { | |
| ThreadUpdater.seconds = i; | |
| } | |
| ThreadUpdater.set('timer', ThreadUpdater.seconds); | |
| return ThreadUpdater.count(true); | |
| }, | |
| intervalShortcut: function() { | |
| var settings; | |
| Settings.open('Advanced'); | |
| settings = $.id('fourchanx-settings'); | |
| return $('input[name=Interval]', settings).focus(); | |
| }, | |
| set: function(name, text, klass) { | |
| var el, node; | |
| el = ThreadUpdater[name]; | |
| if (node = el.firstChild) { | |
| node.data = text; | |
| } else { | |
| el.textContent = text; | |
| } | |
| if (klass !== void 0) { | |
| return el.className = klass; | |
| } | |
| }, | |
| count: function(start) { | |
| clearTimeout(ThreadUpdater.timeoutID); | |
| if (start && ThreadUpdater.isUpdating && navigator.onLine) { | |
| return ThreadUpdater.timeout(); | |
| } | |
| }, | |
| timeout: function() { | |
| var n; | |
| ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000); | |
| if (!(n = --ThreadUpdater.seconds)) { | |
| return ThreadUpdater.update(); | |
| } else if (n <= -60) { | |
| ThreadUpdater.set('status', 'Retrying', null); | |
| return ThreadUpdater.update(); | |
| } else if (n > 0) { | |
| return ThreadUpdater.set('timer', n); | |
| } | |
| }, | |
| update: function() { | |
| var url, _ref; | |
| if (!navigator.onLine) { | |
| return; | |
| } | |
| ThreadUpdater.count(); | |
| if (Conf['Auto Update']) { | |
| ThreadUpdater.set('timer', '...'); | |
| } else { | |
| ThreadUpdater.set('timer', 'Update'); | |
| } | |
| if ((_ref = ThreadUpdater.req) != null) { | |
| _ref.abort(); | |
| } | |
| url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/res/" + ThreadUpdater.thread + ".json"; | |
| return ThreadUpdater.req = $.ajax(url, { | |
| onloadend: ThreadUpdater.cb.load | |
| }, { | |
| whenModified: true | |
| }); | |
| }, | |
| updateThreadStatus: function(type, status) { | |
| var change, hasChanged; | |
| if (!(hasChanged = ThreadUpdater.thread["is" + type] !== status)) { | |
| return; | |
| } | |
| ThreadUpdater.thread.setStatus(type, status); | |
| change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore'; | |
| return new Notice('info', "The thread is " + change + ".", 30); | |
| }, | |
| parse: function(postObjects) { | |
| var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, root, scroll, _i, _len, _ref; | |
| OP = postObjects[0]; | |
| Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; | |
| ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); | |
| ThreadUpdater.updateThreadStatus('Closed', !!OP.closed); | |
| ThreadUpdater.thread.postLimit = !!OP.bumplimit; | |
| ThreadUpdater.thread.fileLimit = !!OP.imagelimit; | |
| posts = []; | |
| index = []; | |
| files = []; | |
| count = 0; | |
| for (_i = 0, _len = postObjects.length; _i < _len; _i++) { | |
| postObject = postObjects[_i]; | |
| num = postObject.no; | |
| index.push(num); | |
| if (postObject.fsize) { | |
| files.push(num); | |
| } | |
| if (num <= ThreadUpdater.lastPost) { | |
| continue; | |
| } | |
| count++; | |
| node = Build.postFromObject(postObject, ThreadUpdater.thread.board.ID); | |
| posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); | |
| } | |
| deletedPosts = []; | |
| deletedFiles = []; | |
| _ref = ThreadUpdater.thread.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| ID = +ID; | |
| if (__indexOf.call(index, ID) < 0) { | |
| post.kill(); | |
| deletedPosts.push(post); | |
| } else if (post.isDead) { | |
| post.resurrect(); | |
| } else if (post.file && !(post.file.isDead || __indexOf.call(files, ID) >= 0)) { | |
| post.kill(true); | |
| deletedFiles.push(post); | |
| } | |
| if (ThreadUpdater.postID && ThreadUpdater.postID === ID) { | |
| ThreadUpdater.foundPost = true; | |
| } | |
| } | |
| if (!count) { | |
| ThreadUpdater.set('status', null, null); | |
| ThreadUpdater.outdateCount++; | |
| } else { | |
| ThreadUpdater.set('status', "+" + count, 'new'); | |
| ThreadUpdater.outdateCount = 0; | |
| if (Conf['Beep'] && d.hidden && Unread.posts && !Unread.posts.length) { | |
| if (!ThreadUpdater.audio) { | |
| ThreadUpdater.audio = $.el('audio', { | |
| src: ThreadUpdater.beep | |
| }); | |
| } | |
| ThreadUpdater.audio.play(); | |
| } | |
| ThreadUpdater.lastPost = posts[count - 1].ID; | |
| Main.callbackNodes(Post, posts); | |
| scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25; | |
| for (key in posts) { | |
| post = posts[key]; | |
| if (!posts.hasOwnProperty(key)) { | |
| continue; | |
| } | |
| root = post.nodes.root; | |
| if (post.cb) { | |
| if (!post.cb()) { | |
| $.add(ThreadUpdater.root, root); | |
| } | |
| } else { | |
| $.add(ThreadUpdater.root, root); | |
| } | |
| } | |
| if (scroll) { | |
| if (Conf['Bottom Scroll']) { | |
| window.scrollTo(0, d.body.clientHeight); | |
| } else { | |
| if (root) { | |
| Header.scrollTo(root); | |
| } | |
| } | |
| } | |
| $.queueTask(function() { | |
| var length, threadID; | |
| threadID = ThreadUpdater.thread.ID; | |
| length = $$('.thread > .postContainer', ThreadUpdater.root).length; | |
| return Fourchan.parseThread(threadID, length - count, length); | |
| }); | |
| } | |
| return $.event('ThreadUpdate', { | |
| 404: false, | |
| thread: ThreadUpdater.thread, | |
| newPosts: posts, | |
| deletedPosts: deletedPosts, | |
| deletedFiles: deletedFiles, | |
| postCount: OP.replies + 1, | |
| fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead) | |
| }); | |
| } | |
| }; | |
| ThreadWatcher = { | |
| init: function() { | |
| var now, sc; | |
| if (!Conf['Thread Watcher']) { | |
| return; | |
| } | |
| this.shortcut = sc = $.el('a', { | |
| id: 'watcher-link', | |
| textContent: 'Watcher', | |
| href: 'javascript:;', | |
| className: 'disabled fa fa-eye' | |
| }); | |
| this.db = new DataBoard('watchedThreads', this.refresh, true); | |
| this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', "<div class=\"move\">Thread Watcher <span id=\"watcher-status\"></span><a class=\"menu-button\" href=\"javascript:;\"><i class=\"fa fa-angle-down\"></i></a><a class=close href=javascript:;>×</a></span></div><div id=\"watched-threads\"></div>"); | |
| this.status = $('#watcher-status', this.dialog); | |
| this.list = this.dialog.lastElementChild; | |
| $.on(d, 'QRPostSuccessful', this.cb.post); | |
| if (g.VIEW === 'thread') { | |
| $.on(d, 'ThreadUpdate', this.cb.threadUpdate); | |
| } | |
| $.on(sc, 'click', this.toggleWatcher); | |
| $.on($('.move>.close', ThreadWatcher.dialog), 'click', this.toggleWatcher); | |
| $.on(d, '4chanXInitFinished', this.ready); | |
| switch (g.VIEW) { | |
| case 'index': | |
| $.on(d, 'IndexRefresh', this.cb.onIndexRefresh); | |
| break; | |
| case 'thread': | |
| $.on(d, 'ThreadUpdate', this.cb.onThreadRefresh); | |
| } | |
| if (Conf['Toggleable Thread Watcher']) { | |
| Header.addShortcut(sc); | |
| $.addClass(doc, 'fixed-watcher'); | |
| } | |
| now = Date.now(); | |
| if ((this.db.data.lastChecked || 0) < now - 2 * $.HOUR) { | |
| this.db.data.lastChecked = now; | |
| ThreadWatcher.fetchAllStatus(); | |
| this.db.save(); | |
| } | |
| return Thread.callbacks.push({ | |
| name: 'Thread Watcher', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var toggler; | |
| toggler = $.el('img', { | |
| className: 'watch-thread-link' | |
| }); | |
| $.on(toggler, 'click', ThreadWatcher.cb.toggle); | |
| return $.before($('input', this.OP.nodes.post), toggler); | |
| }, | |
| ready: function() { | |
| $.off(d, '4chanXInitFinished', ThreadWatcher.ready); | |
| if (!Main.isThisPageLegit()) { | |
| return; | |
| } | |
| ThreadWatcher.refresh(); | |
| $.add(d.body, ThreadWatcher.dialog); | |
| if (Conf['Toggleable Thread Watcher']) { | |
| ThreadWatcher.dialog.hidden = true; | |
| } | |
| if (!Conf['Auto Watch']) { | |
| return; | |
| } | |
| return $.get('AutoWatch', 0, function(_arg) { | |
| var AutoWatch, thread; | |
| AutoWatch = _arg.AutoWatch; | |
| if (!(thread = g.BOARD.threads[AutoWatch])) { | |
| return; | |
| } | |
| ThreadWatcher.add(thread); | |
| return $["delete"]('AutoWatch'); | |
| }); | |
| }, | |
| toggleWatcher: function() { | |
| $.toggleClass(ThreadWatcher.shortcut, 'disabled'); | |
| return ThreadWatcher.dialog.hidden = !ThreadWatcher.dialog.hidden; | |
| }, | |
| cb: { | |
| openAll: function() { | |
| var a, _i, _len, _ref; | |
| if ($.hasClass(this, 'disabled')) { | |
| return; | |
| } | |
| _ref = $$('a[title]', ThreadWatcher.list); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| a = _ref[_i]; | |
| $.open(a.href); | |
| } | |
| return $.event('CloseMenu'); | |
| }, | |
| checkThreads: function() { | |
| if ($.hasClass(this, 'disabled')) { | |
| return; | |
| } | |
| return ThreadWatcher.fetchAllStatus(); | |
| }, | |
| pruneDeads: function() { | |
| var boardID, data, threadID, _i, _len, _ref, _ref1; | |
| if ($.hasClass(this, 'disabled')) { | |
| return; | |
| } | |
| _ref = ThreadWatcher.getAll(); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| _ref1 = _ref[_i], boardID = _ref1.boardID, threadID = _ref1.threadID, data = _ref1.data; | |
| if (!data.isDead) { | |
| continue; | |
| } | |
| delete ThreadWatcher.db.data.boards[boardID][threadID]; | |
| ThreadWatcher.db.deleteIfEmpty({ | |
| boardID: boardID | |
| }); | |
| } | |
| ThreadWatcher.db.save(); | |
| ThreadWatcher.refresh(); | |
| return $.event('CloseMenu'); | |
| }, | |
| toggle: function() { | |
| return ThreadWatcher.toggle(Get.postFromNode(this).thread); | |
| }, | |
| rm: function() { | |
| var boardID, threadID, _ref; | |
| _ref = this.parentNode.dataset.fullID.split('.'), boardID = _ref[0], threadID = _ref[1]; | |
| return ThreadWatcher.rm(boardID, +threadID); | |
| }, | |
| post: function(e) { | |
| var board, postID, threadID, _ref; | |
| _ref = e.detail, board = _ref.board, postID = _ref.postID, threadID = _ref.threadID; | |
| if (postID === threadID) { | |
| if (Conf['Auto Watch']) { | |
| return $.set('AutoWatch', threadID); | |
| } | |
| } else if (Conf['Auto Watch Reply']) { | |
| return ThreadWatcher.add(board.threads[threadID]); | |
| } | |
| }, | |
| onIndexRefresh: function() { | |
| var boardID, data, db, threadID, _ref; | |
| db = ThreadWatcher.db; | |
| boardID = g.BOARD.ID; | |
| _ref = db.data.boards[boardID]; | |
| for (threadID in _ref) { | |
| data = _ref[threadID]; | |
| if (!data.isDead && !(threadID in g.BOARD.threads)) { | |
| if (Conf['Auto Prune']) { | |
| ThreadWatcher.db["delete"]({ | |
| boardID: boardID, | |
| threadID: threadID | |
| }); | |
| } else { | |
| data.isDead = true; | |
| ThreadWatcher.db.set({ | |
| boardID: boardID, | |
| threadID: threadID, | |
| val: data | |
| }); | |
| } | |
| } | |
| } | |
| return ThreadWatcher.refresh(); | |
| }, | |
| onThreadRefresh: function(e) { | |
| var thread; | |
| thread = e.detail.thread; | |
| if (!(e.detail[404] && ThreadWatcher.db.get({ | |
| boardID: thread.board.ID, | |
| threadID: thread.ID | |
| }))) { | |
| return; | |
| } | |
| return ThreadWatcher.add(thread); | |
| } | |
| }, | |
| fetchCount: { | |
| fetched: 0, | |
| fetching: 0 | |
| }, | |
| fetchAllStatus: function() { | |
| var thread, threads, _i, _len; | |
| if (!(threads = ThreadWatcher.getAll()).length) { | |
| return; | |
| } | |
| ThreadWatcher.status.textContent = '...'; | |
| for (_i = 0, _len = threads.length; _i < _len; _i++) { | |
| thread = threads[_i]; | |
| ThreadWatcher.fetchStatus(thread); | |
| } | |
| }, | |
| fetchStatus: function(_arg) { | |
| var boardID, data, fetchCount, threadID; | |
| boardID = _arg.boardID, threadID = _arg.threadID, data = _arg.data; | |
| if (data.isDead) { | |
| return; | |
| } | |
| fetchCount = ThreadWatcher.fetchCount; | |
| fetchCount.fetching++; | |
| return $.ajax("//a.4cdn.org/" + boardID + "/res/" + threadID + ".json", { | |
| onloadend: function() { | |
| var status; | |
| fetchCount.fetched++; | |
| if (fetchCount.fetched === fetchCount.fetching) { | |
| fetchCount.fetched = 0; | |
| fetchCount.fetching = 0; | |
| status = ''; | |
| } else { | |
| status = "" + (Math.round(fetchCount.fetched / fetchCount.fetching * 100)) + "%"; | |
| } | |
| ThreadWatcher.status.textContent = status; | |
| if (this.status !== 404) { | |
| return; | |
| } | |
| if (Conf['Auto Prune']) { | |
| ThreadWatcher.db["delete"]({ | |
| boardID: boardID, | |
| threadID: threadID | |
| }); | |
| } else { | |
| data.isDead = true; | |
| ThreadWatcher.db.set({ | |
| boardID: boardID, | |
| threadID: threadID, | |
| val: data | |
| }); | |
| } | |
| return ThreadWatcher.refresh(); | |
| } | |
| }, { | |
| type: 'head' | |
| }); | |
| }, | |
| getAll: function() { | |
| var all, boardID, data, threadID, threads, _ref; | |
| all = []; | |
| _ref = ThreadWatcher.db.data.boards; | |
| for (boardID in _ref) { | |
| threads = _ref[boardID]; | |
| if (Conf['Current Board'] && boardID !== g.BOARD.ID) { | |
| continue; | |
| } | |
| for (threadID in threads) { | |
| data = threads[threadID]; | |
| all.push({ | |
| boardID: boardID, | |
| threadID: threadID, | |
| data: data | |
| }); | |
| } | |
| } | |
| return all; | |
| }, | |
| makeLine: function(boardID, threadID, data) { | |
| var div, fullID, href, link, x; | |
| x = $.el('a', { | |
| className: 'fa fa-times', | |
| href: 'javascript:;' | |
| }); | |
| $.on(x, 'click', ThreadWatcher.cb.rm); | |
| if (data.isDead) { | |
| href = Redirect.to('thread', { | |
| boardID: boardID, | |
| threadID: threadID | |
| }); | |
| } | |
| link = $.el('a', { | |
| href: href || ("/" + boardID + "/res/" + threadID), | |
| textContent: data.excerpt, | |
| title: data.excerpt | |
| }); | |
| div = $.el('div'); | |
| fullID = "" + boardID + "." + threadID; | |
| div.dataset.fullID = fullID; | |
| if (g.VIEW === 'thread' && fullID === ("" + g.BOARD + "." + g.THREADID)) { | |
| $.addClass(div, 'current'); | |
| } | |
| if (data.isDead) { | |
| $.addClass(div, 'dead-thread'); | |
| } | |
| $.add(div, [x, $.tn(' '), link]); | |
| return div; | |
| }, | |
| refresh: function() { | |
| var boardID, data, helper, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3; | |
| nodes = []; | |
| _ref = ThreadWatcher.getAll(); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| _ref1 = _ref[_i], boardID = _ref1.boardID, threadID = _ref1.threadID, data = _ref1.data; | |
| nodes.push(ThreadWatcher.makeLine(boardID, threadID, data)); | |
| } | |
| list = ThreadWatcher.list; | |
| $.rmAll(list); | |
| $.add(list, nodes); | |
| _ref2 = g.BOARD.threads; | |
| for (threadID in _ref2) { | |
| thread = _ref2[threadID]; | |
| toggler = $('.watch-thread-link', thread.OP.nodes.post); | |
| watched = ThreadWatcher.db.get({ | |
| boardID: thread.board.ID, | |
| threadID: threadID | |
| }); | |
| helper = watched ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch']; | |
| $[helper[0]](toggler, 'watched'); | |
| toggler.title = "" + helper[1] + " Thread"; | |
| } | |
| _ref3 = ThreadWatcher.menu.refreshers; | |
| for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { | |
| refresher = _ref3[_j]; | |
| refresher(); | |
| } | |
| }, | |
| toggle: function(thread) { | |
| var boardID, threadID; | |
| boardID = thread.board.ID; | |
| threadID = thread.ID; | |
| if (ThreadWatcher.db.get({ | |
| boardID: boardID, | |
| threadID: threadID | |
| })) { | |
| return ThreadWatcher.rm(boardID, threadID); | |
| } else { | |
| return ThreadWatcher.add(thread); | |
| } | |
| }, | |
| add: function(thread) { | |
| var boardID, data, threadID; | |
| data = {}; | |
| boardID = thread.board.ID; | |
| threadID = thread.ID; | |
| if (thread.isDead) { | |
| if (Conf['Auto Prune'] && ThreadWatcher.db.get({ | |
| boardID: boardID, | |
| threadID: threadID | |
| })) { | |
| ThreadWatcher.rm(boardID, threadID); | |
| return; | |
| } | |
| data.isDead = true; | |
| } | |
| data.excerpt = Get.threadExcerpt(thread); | |
| ThreadWatcher.db.set({ | |
| boardID: boardID, | |
| threadID: threadID, | |
| val: data | |
| }); | |
| return ThreadWatcher.refresh(); | |
| }, | |
| rm: function(boardID, threadID) { | |
| ThreadWatcher.db["delete"]({ | |
| boardID: boardID, | |
| threadID: threadID | |
| }); | |
| return ThreadWatcher.refresh(); | |
| }, | |
| convert: function(oldFormat) { | |
| var boardID, data, newFormat, threadID, threads; | |
| newFormat = {}; | |
| for (boardID in oldFormat) { | |
| threads = oldFormat[boardID]; | |
| for (threadID in threads) { | |
| data = threads[threadID]; | |
| (newFormat[boardID] || (newFormat[boardID] = {}))[threadID] = { | |
| excerpt: data.textContent | |
| }; | |
| } | |
| } | |
| return newFormat; | |
| }, | |
| menu: { | |
| refreshers: [], | |
| init: function() { | |
| var menu; | |
| if (!Conf['Thread Watcher']) { | |
| return; | |
| } | |
| menu = new UI.Menu('thread watcher'); | |
| $.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) { | |
| return menu.toggle(e, this, ThreadWatcher); | |
| }); | |
| this.addHeaderMenuEntry(); | |
| return this.addMenuEntries(); | |
| }, | |
| addHeaderMenuEntry: function() { | |
| var entryEl; | |
| if (g.VIEW !== 'thread') { | |
| return; | |
| } | |
| entryEl = $.el('a', { | |
| href: 'javascript:;' | |
| }); | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: entryEl, | |
| order: 60 | |
| }); | |
| $.on(entryEl, 'click', function() { | |
| return ThreadWatcher.toggle(g.threads["" + g.BOARD + "." + g.THREADID]); | |
| }); | |
| return this.refreshers.push(function() { | |
| var addClass, rmClass, text, _ref; | |
| _ref = $('.current', ThreadWatcher.list) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = _ref[0], rmClass = _ref[1], text = _ref[2]; | |
| $.addClass(entryEl, addClass); | |
| $.rmClass(entryEl, rmClass); | |
| return entryEl.textContent = text; | |
| }); | |
| }, | |
| addMenuEntries: function() { | |
| var cb, conf, entries, entry, name, refresh, subEntries, _i, _len, _ref, _ref1; | |
| entries = []; | |
| entries.push({ | |
| cb: ThreadWatcher.cb.openAll, | |
| entry: { | |
| type: 'thread watcher', | |
| el: $.el('a', { | |
| textContent: 'Open all threads' | |
| }) | |
| }, | |
| refresh: function() { | |
| return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled'); | |
| } | |
| }); | |
| entries.push({ | |
| cb: ThreadWatcher.cb.checkThreads, | |
| entry: { | |
| type: 'thread watcher', | |
| el: $.el('a', { | |
| textContent: 'Check 404\'d threads' | |
| }) | |
| }, | |
| refresh: function() { | |
| return ($('div:not(.dead-thread)', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled'); | |
| } | |
| }); | |
| entries.push({ | |
| cb: ThreadWatcher.cb.pruneDeads, | |
| entry: { | |
| type: 'thread watcher', | |
| el: $.el('a', { | |
| textContent: 'Prune 404\'d threads' | |
| }) | |
| }, | |
| refresh: function() { | |
| return ($('.dead-thread', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled'); | |
| } | |
| }); | |
| subEntries = []; | |
| _ref = Config.threadWatcher; | |
| for (name in _ref) { | |
| conf = _ref[name]; | |
| subEntries.push(this.createSubEntry(name, conf[1])); | |
| } | |
| entries.push({ | |
| entry: { | |
| type: 'thread watcher', | |
| el: $.el('span', { | |
| textContent: 'Settings' | |
| }), | |
| subEntries: subEntries | |
| } | |
| }); | |
| for (_i = 0, _len = entries.length; _i < _len; _i++) { | |
| _ref1 = entries[_i], entry = _ref1.entry, cb = _ref1.cb, refresh = _ref1.refresh; | |
| if (entry.el.nodeName === 'A') { | |
| entry.el.href = 'javascript:;'; | |
| } | |
| if (cb) { | |
| $.on(entry.el, 'click', cb); | |
| } | |
| if (refresh) { | |
| this.refreshers.push(refresh.bind(entry)); | |
| } | |
| $.event('AddMenuEntry', entry); | |
| } | |
| }, | |
| createSubEntry: function(name, desc) { | |
| var entry, input; | |
| entry = { | |
| type: 'thread watcher', | |
| el: $.el('label', { | |
| innerHTML: "<input type=checkbox name='" + name + "'> " + name, | |
| title: desc | |
| }) | |
| }; | |
| input = entry.el.firstElementChild; | |
| input.checked = Conf[name]; | |
| $.on(input, 'change', $.cb.checked); | |
| if (name === 'Current Board') { | |
| $.on(input, 'change', ThreadWatcher.refresh); | |
| } | |
| return entry; | |
| } | |
| } | |
| }; | |
| Unread = { | |
| init: function() { | |
| if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) { | |
| return; | |
| } | |
| this.db = new DataBoard('lastReadPosts', this.sync); | |
| this.hr = $.el('hr', { | |
| id: 'unread-line' | |
| }); | |
| this.posts = new RandomAccessList; | |
| this.postsQuotingYou = []; | |
| return Thread.callbacks.push({ | |
| name: 'Unread', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| Unread.thread = this; | |
| Unread.title = d.title; | |
| Unread.lastReadPost = Unread.db.get({ | |
| boardID: this.board.ID, | |
| threadID: this.ID, | |
| defaultValue: 0 | |
| }); | |
| $.on(d, '4chanXInitFinished', Unread.ready); | |
| $.on(d, 'ThreadUpdate', Unread.onUpdate); | |
| $.on(d, 'scroll visibilitychange', Unread.read); | |
| if (Conf['Unread Line']) { | |
| return $.on(d, 'visibilitychange', Unread.setLine); | |
| } | |
| }, | |
| ready: function() { | |
| var ID, post, posts, _ref; | |
| $.off(d, '4chanXInitFinished', Unread.ready); | |
| posts = []; | |
| _ref = Unread.thread.posts; | |
| for (ID in _ref) { | |
| post = _ref[ID]; | |
| if (post.isReply) { | |
| posts.push(post); | |
| } | |
| } | |
| Unread.addPosts(posts); | |
| if (Conf['Quote Threading']) { | |
| QuoteThreading.force(); | |
| } | |
| if (Conf['Scroll to Last Read Post']) { | |
| return Unread.scroll(); | |
| } | |
| }, | |
| scroll: function() { | |
| var down, hash, post, posts, root; | |
| if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { | |
| return; | |
| } | |
| if (post = Unread.posts.first) { | |
| while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) { | |
| if (!(post = Get.postFromRoot(root)).isHidden) { | |
| break; | |
| } | |
| } | |
| if (!root) { | |
| return; | |
| } | |
| down = true; | |
| } else { | |
| posts = Object.keys(Unread.thread.posts); | |
| root = Unread.thread.posts[posts[posts.length - 1]].nodes.root; | |
| } | |
| if (Header.getBottomOf(root) < 0) { | |
| return Header.scrollTo(root, down); | |
| } | |
| }, | |
| sync: function() { | |
| var ID, lastReadPost, post; | |
| lastReadPost = Unread.db.get({ | |
| boardID: Unread.thread.board.ID, | |
| threadID: Unread.thread.ID, | |
| defaultValue: 0 | |
| }); | |
| if (!(Unread.lastReadPost < lastReadPost)) { | |
| return; | |
| } | |
| Unread.lastReadPost = lastReadPost; | |
| post = Unread.posts.first; | |
| while (post) { | |
| if ((ID = post.ID, post) > Unread.lastReadPost) { | |
| break; | |
| } | |
| post = post.next; | |
| Unread.posts.rm(ID); | |
| } | |
| Unread.readArray(Unread.postsQuotingYou); | |
| if (Conf['Unread Line']) { | |
| Unread.setLine(); | |
| } | |
| return Unread.update(); | |
| }, | |
| addPosts: function(posts) { | |
| var ID, post, _i, _len, _ref; | |
| for (_i = 0, _len = posts.length; _i < _len; _i++) { | |
| post = posts[_i]; | |
| ID = post.ID; | |
| if (ID <= Unread.lastReadPost || post.isHidden || QR.db.get({ | |
| boardID: post.board.ID, | |
| threadID: post.thread.ID, | |
| postID: ID | |
| })) { | |
| continue; | |
| } | |
| if (!(post.prev || post.next)) { | |
| Unread.posts.push(post); | |
| } | |
| Unread.addPostQuotingYou(post); | |
| } | |
| if (Conf['Unread Line']) { | |
| Unread.setLine((_ref = Unread.posts.first, __indexOf.call(posts, _ref) >= 0)); | |
| } | |
| Unread.read(); | |
| return Unread.update(); | |
| }, | |
| addPostQuotingYou: function(post) { | |
| var quotelink, _i, _len, _ref; | |
| if (!QR.db) { | |
| return; | |
| } | |
| _ref = post.nodes.quotelinks; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| quotelink = _ref[_i]; | |
| if (!(QR.db.get(Get.postDataFromLink(quotelink)))) { | |
| continue; | |
| } | |
| Unread.postsQuotingYou.push(post); | |
| Unread.openNotification(post); | |
| return; | |
| } | |
| }, | |
| openNotification: function(post) { | |
| var name, notif; | |
| if (!Header.areNotificationsEnabled) { | |
| return; | |
| } | |
| name = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent.trim(); | |
| notif = new Notification("" + name + " replied to you", { | |
| body: post.info.comment, | |
| icon: Favicon.logo | |
| }); | |
| notif.onclick = function() { | |
| Header.scrollToIfNeeded(post.nodes.root, true); | |
| return window.focus(); | |
| }; | |
| return notif.onshow = function() { | |
| return setTimeout(function() { | |
| return notif.close(); | |
| }, 7 * $.SECOND); | |
| }; | |
| }, | |
| onUpdate: function(e) { | |
| if (e.detail[404]) { | |
| return Unread.update(); | |
| } else { | |
| return Unread.addPosts(e.detail.newPosts); | |
| } | |
| }, | |
| readSinglePost: function(post) { | |
| var ID, i; | |
| ID = post.ID; | |
| if (!Unread.posts[ID]) { | |
| return; | |
| } | |
| if (post === Unread.posts.first) { | |
| Unread.lastReadPost = ID; | |
| Unread.saveLastReadPost(); | |
| } | |
| Unread.posts.rm(ID); | |
| if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) { | |
| Unread.postsQuotingYou.splice(i, 1); | |
| } | |
| return Unread.update(); | |
| }, | |
| readArray: function(arr) { | |
| var i, post, _i, _len; | |
| for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) { | |
| post = arr[i]; | |
| if (post.ID > Unread.lastReadPost) { | |
| break; | |
| } | |
| } | |
| return arr.splice(0, i); | |
| }, | |
| read: $.debounce(100, function(e) { | |
| var ID, height, post, posts; | |
| if (d.hidden || !Unread.posts.length) { | |
| return; | |
| } | |
| height = doc.clientHeight; | |
| posts = Unread.posts; | |
| while (post = posts.first) { | |
| if (!(Header.getBottomOf(post.nodes.root) > -1)) { | |
| break; | |
| } | |
| ID = post.ID; | |
| posts.rm(ID); | |
| if (Conf['Mark Quotes of You'] && post.info.yours) { | |
| QuoteYou.lastRead = post.nodes.root; | |
| } | |
| } | |
| if (!ID) { | |
| return; | |
| } | |
| if (Unread.lastReadPost < ID || !Unread.lastReadPost) { | |
| Unread.lastReadPost = ID; | |
| } | |
| Unread.saveLastReadPost(); | |
| Unread.readArray(Unread.postsQuotingYou); | |
| if (e) { | |
| return Unread.update(); | |
| } | |
| }), | |
| saveLastReadPost: $.debounce(2 * $.SECOND, function() { | |
| if (Unread.thread.isDead) { | |
| return; | |
| } | |
| return Unread.db.set({ | |
| boardID: Unread.thread.board.ID, | |
| threadID: Unread.thread.ID, | |
| val: Unread.lastReadPost | |
| }); | |
| }), | |
| setLine: function(force) { | |
| var post; | |
| if (!(d.hidden || force === true)) { | |
| return; | |
| } | |
| if (!(post = Unread.posts.first)) { | |
| return $.rm(Unread.hr); | |
| } | |
| if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) { | |
| return $.before(post.nodes.root, Unread.hr); | |
| } | |
| }, | |
| update: function() { | |
| var count; | |
| count = Unread.posts.length; | |
| if (Conf['Unread Count']) { | |
| d.title = "" + (Conf['Quoted Title'] && Unread.postsQuotingYou.length ? '(!) ' : '') + (count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '') + (g.DEAD ? "/" + g.BOARD + "/ - 404" : "" + Unread.title); | |
| } | |
| if (!Conf['Unread Favicon']) { | |
| return; | |
| } | |
| Favicon.el.href = g.DEAD ? Unread.postsQuotingYou[0] ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou[0] ? Favicon.unreadY : Favicon.unread : Favicon["default"]; | |
| return $.add(d.head, Favicon.el); | |
| } | |
| }; | |
| Redirect = { | |
| init: function() { | |
| var archive, archives, boardID, boards, data, files, id, name, o, record, software, type, _i, _j, _len, _len1, _ref, _ref1, _ref2; | |
| o = { | |
| thread: {}, | |
| post: {}, | |
| file: {} | |
| }; | |
| archives = {}; | |
| _ref = Redirect.archives; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| _ref1 = _ref[_i], name = _ref1.name, boards = _ref1.boards, files = _ref1.files, data = _ref1.data; | |
| archives[name] = { | |
| boards: boards, | |
| files: files, | |
| data: data | |
| }; | |
| software = data.software; | |
| for (_j = 0, _len1 = boards.length; _j < _len1; _j++) { | |
| boardID = boards[_j]; | |
| if (!(boardID in o.thread)) { | |
| o.thread[boardID] = data; | |
| } | |
| if (!(boardID in o.post || software !== 'foolfuuka')) { | |
| o.post[boardID] = data; | |
| } | |
| if (!(boardID in o.file || __indexOf.call(files, boardID) < 0)) { | |
| o.file[boardID] = data; | |
| } | |
| } | |
| } | |
| _ref2 = Conf['selectedArchives']; | |
| for (boardID in _ref2) { | |
| record = _ref2[boardID]; | |
| for (type in record) { | |
| id = record[type]; | |
| if (!((archive = archives[id]))) { | |
| continue; | |
| } | |
| boards = type === 'file' ? archive.files : archive.boards; | |
| if (__indexOf.call(boards, boardID) < 0) { | |
| continue; | |
| } | |
| o[type][boardID] = archive.data; | |
| } | |
| } | |
| return Redirect.data = o; | |
| }, | |
| archives: [ | |
| { | |
| name: "Foolz", | |
| boards: ["a", "biz", "co", "gd", "jp", "m", "sp", "tg", "tv", "v", "vg", "vp", "vr", "wsg"], | |
| files: ["a", "biz", "gd", "jp", "m", "tg", "vg", "vp", "vr", "wsg"], | |
| data: { | |
| domain: "archive.foolz.us", | |
| http: false, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "NSFW Foolz", | |
| boards: ["u"], | |
| files: ["u"], | |
| data: { | |
| domain: "nsfw.foolz.us", | |
| http: false, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "The Dark Cave", | |
| boards: ["c", "int", "out", "po"], | |
| files: ["c", "po"], | |
| data: { | |
| domain: "archive.thedarkcave.org", | |
| http: true, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "4plebs", | |
| boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"], | |
| files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"], | |
| data: { | |
| domain: "archive.4plebs.org", | |
| http: true, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "Nyafuu", | |
| boards: ["c", "e", "w", "wg"], | |
| files: ["c", "e", "w", "wg"], | |
| data: { | |
| domain: "archive.nyafuu.org", | |
| http: true, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "Love is Over", | |
| boards: ["d", "i"], | |
| files: ["d", "i"], | |
| data: { | |
| domain: "loveisover.me", | |
| http: true, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "Install Gentoo", | |
| boards: ["diy", "g", "sci"], | |
| files: [], | |
| data: { | |
| domain: "archive.installgentoo.net", | |
| http: false, | |
| https: true, | |
| software: "fuuka" | |
| } | |
| }, { | |
| name: "Rebecca Black Tech", | |
| boards: ["cgl", "g", "mu", "w"], | |
| files: ["cgl", "g", "mu", "w"], | |
| data: { | |
| domain: "archive.rebeccablacktech.com", | |
| http: true, | |
| https: true, | |
| software: "fuuka" | |
| } | |
| }, { | |
| name: "Heinessen", | |
| boards: ["an", "fit", "k", "mlp", "r9k", "toy"], | |
| files: ["an", "fit", "k", "r9k", "toy"], | |
| data: { | |
| domain: "archive.heinessen.com", | |
| http: true, | |
| software: "fuuka" | |
| } | |
| }, { | |
| name: "warosu", | |
| boards: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], | |
| files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], | |
| data: { | |
| domain: "fuuka.warosu.org", | |
| https: true, | |
| software: "fuuka" | |
| } | |
| }, { | |
| name: "fgts", | |
| boards: ["r", "soc"], | |
| files: ["r", "soc"], | |
| data: { | |
| domain: "fgst.eu", | |
| http: true, | |
| https: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "maware", | |
| boards: ["t"], | |
| files: ["t"], | |
| data: { | |
| domain: "archive.mawa.re", | |
| http: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "installgentoo.com", | |
| boards: ["g", "t"], | |
| files: ["g", "t"], | |
| data: { | |
| domain: "chan.installgentoo.com", | |
| http: true, | |
| software: "foolfuuka" | |
| } | |
| }, { | |
| name: "Foolz Beta", | |
| boards: ["a", "biz", "co", "d", "gd", "jp", "m", "mlp", "s4s", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], | |
| files: ["a", "biz", "d", "gd", "jp", "m", "s4s", "tg", "u", "vg", "vp", "vr", "wsg"], | |
| data: { | |
| domain: "beta.foolz.us", | |
| http: true, | |
| https: true, | |
| withCredentials: true, | |
| software: "foolfuuka" | |
| } | |
| } | |
| ], | |
| to: function(dest, data) { | |
| var archive; | |
| archive = (dest === 'search' ? Redirect.data.thread : Redirect.data[dest])[data.boardID]; | |
| if (!archive) { | |
| return ''; | |
| } | |
| return Redirect[dest](archive, data); | |
| }, | |
| protocol: function(archive) { | |
| var protocol; | |
| protocol = location.protocol; | |
| if (!archive[protocol.slice(0, -1)]) { | |
| protocol = protocol === 'https:' ? 'http:' : 'https:'; | |
| } | |
| return "" + protocol + "//"; | |
| }, | |
| thread: function(archive, _arg) { | |
| var boardID, path, postID, threadID; | |
| boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID; | |
| path = threadID ? "" + boardID + "/thread/" + threadID : "" + boardID + "/post/" + postID; | |
| if (archive.software === 'foolfuuka') { | |
| path += '/'; | |
| } | |
| if (threadID && postID) { | |
| path += archive.software === 'foolfuuka' ? "#" + postID : "#p" + postID; | |
| } | |
| return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; | |
| }, | |
| post: function(archive, _arg) { | |
| var URL, boardID, postID; | |
| boardID = _arg.boardID, postID = _arg.postID; | |
| URL = new String("" + (Redirect.protocol(archive)) + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID); | |
| URL.archive = archive; | |
| return URL; | |
| }, | |
| file: function(archive, _arg) { | |
| var boardID, filename; | |
| boardID = _arg.boardID, filename = _arg.filename; | |
| return "" + (Redirect.protocol(archive)) + archive.domain + "/" + boardID + "/full_image/" + filename; | |
| }, | |
| search: function(archive, _arg) { | |
| var boardID, path, type, value; | |
| boardID = _arg.boardID, type = _arg.type, value = _arg.value; | |
| type = type === 'name' ? 'username' : type === 'MD5' ? 'image' : type; | |
| value = encodeURIComponent(value); | |
| path = archive.software === 'foolfuuka' ? "" + boardID + "/search/" + type + "/" + value : "" + boardID + "/?task=search2&search_" + (type === 'image' ? 'media_hash' : type) + "=" + value; | |
| return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; | |
| } | |
| }; | |
| PSAHiding = { | |
| init: function() { | |
| if (!Conf['Announcement Hiding']) { | |
| return; | |
| } | |
| $.addClass(doc, 'hide-announcement'); | |
| return $.on(d, '4chanXInitFinished', this.setup); | |
| }, | |
| setup: function() { | |
| var btn, entry, psa; | |
| $.off(d, '4chanXInitFinished', PSAHiding.setup); | |
| if (!(psa = $.id('globalMessage'))) { | |
| $.rmClass(doc, 'hide-announcement'); | |
| return; | |
| } | |
| entry = { | |
| type: 'header', | |
| el: $.el('a', { | |
| textContent: 'Show announcement', | |
| className: 'show-announcement', | |
| href: 'javascript:;' | |
| }), | |
| order: 50, | |
| open: function() { | |
| return psa.hidden; | |
| } | |
| }; | |
| $.event('AddMenuEntry', entry); | |
| $.on(entry.el, 'click', PSAHiding.toggle); | |
| PSAHiding.btn = btn = $.el('span', { | |
| innerHTML: '[<a href=javascript:;>Dismiss</a>]', | |
| title: 'Mark announcement as read and hide.', | |
| className: 'hide-announcement', | |
| href: 'javascript:;' | |
| }); | |
| $.on(btn, 'click', PSAHiding.toggle); | |
| $.get('hiddenPSA', 0, function(_arg) { | |
| var hiddenPSA; | |
| hiddenPSA = _arg.hiddenPSA; | |
| PSAHiding.sync(hiddenPSA); | |
| $.add(psa, btn); | |
| return $.rmClass(doc, 'hide-announcement'); | |
| }); | |
| return $.sync('hiddenPSA', PSAHiding.sync); | |
| }, | |
| toggle: function(e) { | |
| var UTC; | |
| if ($.hasClass(this, 'hide-announcement')) { | |
| UTC = +$.id('globalMessage').dataset.utc; | |
| $.set('hiddenPSA', UTC); | |
| } else { | |
| $.event('CloseMenu'); | |
| $["delete"]('hiddenPSA'); | |
| } | |
| return PSAHiding.sync(UTC); | |
| }, | |
| sync: function(UTC) { | |
| var hr, psa; | |
| psa = $.id('globalMessage'); | |
| psa.hidden = PSAHiding.btn.hidden = UTC && UTC >= +psa.dataset.utc ? true : false; | |
| if ((hr = psa.nextElementSibling) && hr.nodeName === 'HR') { | |
| return hr.hidden = psa.hidden; | |
| } | |
| } | |
| }; | |
| Banner = { | |
| init: function() { | |
| return $.asap((function() { | |
| return d.body; | |
| }), function() { | |
| return $.asap((function() { | |
| return $('.abovePostForm'); | |
| }), Banner.ready); | |
| }); | |
| }, | |
| ready: function() { | |
| var banner, child, children, i; | |
| banner = $(".boardBanner"); | |
| children = banner.children; | |
| i = 0; | |
| while (child = children[i++]) { | |
| if (i === 1) { | |
| child.id = "Banner"; | |
| child.title = "Click to change"; | |
| $.on(child, 'click', Banner.cb.toggle); | |
| continue; | |
| } | |
| if (Conf['Custom Board Titles']) { | |
| Banner.custom(child).title = "Ctrl+click to edit board " + (i === 3 ? 'sub' : '') + "title"; | |
| child.spellcheck = false; | |
| } | |
| } | |
| }, | |
| cb: { | |
| toggle: (function() { | |
| var types; | |
| types = { | |
| jpg: 227, | |
| png: 270, | |
| gif: 253 | |
| }; | |
| return function() { | |
| var num, type; | |
| type = Object.keys(types)[Math.floor(3 * Math.random())]; | |
| num = Math.floor(types[type] * Math.random()); | |
| return this.src = "//static.4chan.org/image/title/" + num + "." + type; | |
| }; | |
| })(), | |
| click: function(e) { | |
| if (e.ctrlKey) { | |
| this.contentEditable = true; | |
| return this.focus(); | |
| } | |
| }, | |
| keydown: function(e) { | |
| e.stopPropagation(); | |
| if (!e.shiftKey && e.keyCode === 13) { | |
| return this.blur(); | |
| } | |
| }, | |
| focus: function() { | |
| var items, string, string2; | |
| this.textContent = this.innerHTML; | |
| string = "" + g.BOARD + "." + this.className; | |
| string2 = "" + string + ".orig"; | |
| items = { | |
| title: this.innerHTML | |
| }; | |
| items[string] = ''; | |
| items[string2] = false; | |
| $.get(items, function(items) { | |
| if (!(items[string2] && items.title === items[string])) { | |
| return $.set(string2, items.title); | |
| } | |
| }); | |
| }, | |
| blur: function() { | |
| this.innerHTML = this.textContent; | |
| this.contentEditable = false; | |
| return $.set("" + g.BOARD + "." + this.className, this.textContent); | |
| } | |
| }, | |
| custom: function(child) { | |
| var cachedTest, string; | |
| cachedTest = child.innerHTML; | |
| string = "" + g.BOARD + "." + child.className; | |
| $.on(child, 'click keydown focus blur', function(e) { | |
| return Banner.cb[e.type].apply(this, [e]); | |
| }); | |
| $.get(string, cachedTest, function(item) { | |
| var string2, title; | |
| if (!(title = item[string])) { | |
| return; | |
| } | |
| if (Conf['Persistent Custom Board Titles']) { | |
| return child.innerHTML = title; | |
| } | |
| string2 = "" + string + ".orig"; | |
| return $.get(string2, cachedTest, function(itemb) { | |
| if (cachedTest === itemb[string2]) { | |
| return child.innerHTML = title; | |
| } else { | |
| $.set(string, cachedTest); | |
| return $.set(string2, cachedTest); | |
| } | |
| }); | |
| }); | |
| return child; | |
| } | |
| }; | |
| CatalogLinks = { | |
| init: function() { | |
| var el, input; | |
| if (!Conf['Catalog Links']) { | |
| return; | |
| } | |
| CatalogLinks.el = el = $.el('label', { | |
| id: 'toggleCatalog', | |
| href: 'javascript:;', | |
| innerHTML: "<input type=checkbox " + (Conf['Header catalog links'] ? 'checked' : '') + "> Catalog Links" | |
| }); | |
| input = $('input', el); | |
| $.on(input, 'change', this.toggle); | |
| $.sync('Header catalog links', CatalogLinks.set); | |
| $.event('AddMenuEntry', { | |
| type: 'header', | |
| el: el, | |
| order: 95 | |
| }); | |
| return $.on(d, '4chanXInitFinished', function() { | |
| return CatalogLinks.set(Conf['Header catalog links']); | |
| }); | |
| }, | |
| toggle: function() { | |
| $.event('CloseMenu'); | |
| $.set('Header catalog links', this.checked); | |
| return CatalogLinks.set(this.checked); | |
| }, | |
| set: function(useCatalog) { | |
| var a, board, generateURL, path, _i, _len, _ref, _ref1; | |
| path = useCatalog ? 'catalog' : ''; | |
| generateURL = useCatalog && Conf['External Catalog'] ? CatalogLinks.external : function(board) { | |
| return a.href = "/" + board + "/" + path; | |
| }; | |
| _ref = $$("#board-list a:not(.catalog), #boardNavDesktopFoot a"); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| a = _ref[_i]; | |
| if (((_ref1 = a.hostname) !== 'boards.4chan.org' && _ref1 !== 'catalog.neet.tv' && _ref1 !== '4index.gropes.us') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan')) { | |
| continue; | |
| } | |
| a.href = generateURL(board); | |
| } | |
| return CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; | |
| }, | |
| external: function(board) { | |
| switch (board) { | |
| case 'a': | |
| case 'c': | |
| case 'g': | |
| case 'co': | |
| case 'k': | |
| case 'm': | |
| case 'o': | |
| case 'p': | |
| case 'v': | |
| case 'vg': | |
| case 'w': | |
| case 'cm': | |
| case '3': | |
| case 'adv': | |
| case 'an': | |
| case 'cgl': | |
| case 'ck': | |
| case 'diy': | |
| case 'fa': | |
| case 'fit': | |
| case 'int': | |
| case 'jp': | |
| case 'mlp': | |
| case 'lit': | |
| case 'mu': | |
| case 'n': | |
| case 'po': | |
| case 'sci': | |
| case 'toy': | |
| case 'trv': | |
| case 'tv': | |
| case 'vp': | |
| case 'x': | |
| case 'q': | |
| return "http://catalog.neet.tv/" + board; | |
| case 'd': | |
| case 'e': | |
| case 'gif': | |
| case 'h': | |
| case 'hr': | |
| case 'hc': | |
| case 'r9k': | |
| case 's': | |
| case 'pol': | |
| case 'soc': | |
| case 'u': | |
| case 'i': | |
| case 'ic': | |
| case 'hm': | |
| case 'r': | |
| case 'w': | |
| case 'wg': | |
| case 'wsg': | |
| case 't': | |
| case 'y': | |
| return "http://4index.gropes.us/" + board; | |
| default: | |
| return "/" + board + "/catalog"; | |
| } | |
| } | |
| }; | |
| CustomCSS = { | |
| init: function() { | |
| if (!Conf['Custom CSS']) { | |
| return; | |
| } | |
| return this.addStyle(); | |
| }, | |
| addStyle: function() { | |
| return this.style = $.addStyle(Conf['usercss']); | |
| }, | |
| rmStyle: function() { | |
| if (this.style) { | |
| $.rm(this.style); | |
| return delete this.style; | |
| } | |
| }, | |
| update: function() { | |
| if (!this.style) { | |
| this.addStyle(); | |
| } | |
| return this.style.textContent = Conf['usercss']; | |
| } | |
| }; | |
| Dice = { | |
| init: function() { | |
| if (g.BOARD.ID !== 'tg' || g.VIEW === 'catalog' || !Conf['Show Dice Roll']) { | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Show Dice Roll', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var dicestats, roll, _ref; | |
| if (this.isClone || !(dicestats = (_ref = this.info.email) != null ? _ref.match(/dice[+\s](\d+)d(\d+)/) : void 0)) { | |
| return; | |
| } | |
| roll = $('b', this.nodes.comment).firstChild; | |
| return roll.data = "Rolled " + dicestats[1] + "d" + dicestats[2] + ": " + (roll.data.slice(7)); | |
| } | |
| }; | |
| Emoji = { | |
| init: function() { | |
| var css, icon, name, pos, _ref; | |
| if (!Conf['Emoji']) { | |
| return; | |
| } | |
| pos = Conf['emojiPos']; | |
| css = ["a.useremail[href]:last-of-type::" + pos + " {\n vertical-align: top;\n margin-" + (pos === "before" ? "right" : "left") + ": 5px;\n}\n"]; | |
| this.icons["PlanNine"] = Emoji.icons["Plan9"]; | |
| this.icons['Sage'] = Emoji.sage[Conf['sageEmoji']]; | |
| _ref = this.icons; | |
| for (name in _ref) { | |
| icon = _ref[name]; | |
| if (!this.icons.hasOwnProperty(name)) { | |
| continue; | |
| } | |
| css.push("a.useremail[href*='" + name + "']:last-of-type::" + pos + ",\na.useremail[href*='" + (name.toLowerCase()) + "']:last-of-type::" + pos + ",\na.useremail[href*='" + (name.toUpperCase()) + "']:last-of-type::" + pos + " {\n content: url('data:image/png;base64," + icon + "');\n}\n"); | |
| } | |
| return $.addStyle(css.join(""), 'emoji'); | |
| }, | |
| sage: { | |
| '4chan SS': 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAMAAACuAq9NAAABIFBMVEUAAAAAXwAAOAAAVQAAKgAAOgAALwAAagAATwAAdAAAYAAAYwAARAAAcgAANwAAOAABcwEBZAEBXwEAQwABbwEBaQEBWgEBTwECdAICaQIIcwgBWQEIXAcARAAALgACdAICbQICdAICcAIBVQEBTgEAQgAAQwAkjCIcexomgSIcbRtCnj9IpUNEmT5LoUNYtFE9lDtClD5dtVJqwmNCmEFMoEh1zGcnfCYnfCc6jzc7kDs9kjxAlUBDmEFInUNLoEpMoExOo0tPpExQpU1Rpk1Sp0pSp1JXrFVZrlhar1Rar1pes1xftFhhtmFit19juFxkuVxovWRrwGBuw2Juw2Nuw2Ruw2V0yWx1ym14zWt6z2980W6A1XGD2HSD2XSI3XdgUJhRAAAAN3RSTlMACAkJDBobHyBERUVHR3KIiYyNkJmanZ6rrq+ws7S5vL29vsLFxsfP0dLU5eXn5+vt7e34+fn5LB88GQAAAI1JREFUGFdNzjsSwjAMRdGn2PngmZAUVHQshP0vArYQYCYDlmxLos3tTncx4xjdAMCEhR1ApLup+bPxtgsQzZ2Mr4iPYROEU129g6it0jJCv6xqFJlpKbl2kr21Zsl/Mo0IBpmrqg7ZnPfgSnKuqhrKwO+AVrSUOjmo5VcEuHzH9CEAXaTDYZ88HGh++QNCDFZ4bvbHSQAAAABJRU5ErkJggg==', | |
| 'appchan': 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAABa1BMVEUAAACqrKiCgYIAAAAAAAAAAACHmX5pgl5NUEx/hnx4hXRSUVMiIyKwrbFzn19SbkZ1d3OvtqtpaWhcX1ooMyRsd2aWkZddkEV8vWGcpZl+kHd7jHNdYFuRmI4bHRthaV5WhUFsfGZReUBFZjdJazpGVUBnamYfHB9TeUMzSSpHgS1cY1k1NDUyOC8yWiFywVBoh1lDSEAZHBpucW0ICQgUHhBjfFhCRUA+QTtEQUUBAQFyo1praWspKigWFRZHU0F6j3E9Oz5VWFN0j2hncWONk4sAAABASDxJWkJKTUgAAAAvNC0fJR0DAwMAAAA9QzoWGhQAAAA8YytvrFOJsnlqyT9oqExqtkdrsExpsUsqQx9rpVJDbzBBbi5utk9jiFRuk11iqUR64k5Wf0JIZTpadk5om1BkyjmF1GRNY0FheFdXpjVXhz86XSp2yFJwslR3w1NbxitbtDWW5nNnilhFXTtYqDRwp1dSijiJ7H99AAAAUnRSTlMAJTgNGQml71ypu3cPEN/RDh8HBbOwQN7wVg4CAQZ28vs9EDluXjo58Ge8xwMy0P3+rV8cT73sawEdTv63NAa3rQwo4cUdAl3hWQSWvS8qqYsjEDiCzAAAAIVJREFUeNpFx7GKAQAYAOD/A7GbZVAWZTBZFGQw6LyCF/MIkiTdcOmWSzYbJVE2u1KX0J1v+8QDv/EkyS0yXF/NgeEILiHfyc74mICTQltqYXBeAWU9HGxU09YqqEvAElGjyZYjPyLqitjzHSEiGkrsfMWr0VLe+oy/djGP//YwfbeP8bN3Or0bkqEVblAAAAAASUVORK5CYII=' | |
| }, | |
| icons: { | |
| 'Plan9': 'iVBORw0KGgoAAAANSUhEUgAAAAwAAAAPCAYAAAGn5h7fAAAABmJLR0QA/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': 'iVBORw0KGgoAAAANSUhEUgAAABMAAAARCAMAAAAIRmf1AAACoFBMVEUAAABnUFZoUVddU1T6+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': 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAMAAADTRh9nAAAALVBMVEUAAAC3iopWLTtWPkHnvqUcBxx5GCZyAAARERGbdXJrRUyGRUyYbY23coZFGDRFGEYfAAAAAXRSTlMAQObYZgAAAGdJREFUeNpVywkOwCAQAkAXtPb+/3PLWklTiIlMtDiH4rvpVM22K+WvY+7Z/dOxZ2xkLmYpNWo6RoKMUQJ8SYiozEYiZAuLbCZQsGB+/hC4SwZsdV2rTjSR0+J9tzXL0B4RW5f9VbE94skEEpHbpw8AAAAASUVORK5CYII=', | |
| 'Sega': 'iVBORw0KGgoAAAANSUhEUgAAACwAAAALBAMAAAD2A3K8AAAAMFBMVEUAAACMjpOChImytLmdnqMrKzDIyM55dnkODQ94foQ7PkXm5Olsb3VUUVVhZmw8Sl6klHLxAAAAAXRSTlMAQObYZgAAANFJREFUGJVjYIACRiUlJUUGDHBk4syTkxQwhO3/rQ/4ZYsuymi3YEFUqAhC4LCJZJGIi1uimKKjk3KysbOxsaMnAwNLyqoopaXhttf2it1anrJqke1pr1DlBAZhicLnM5YXZ4RWlIYoezx0zrjYqG6czCDsYRzxIko6Q/qFaKy0690Ij0MxN8K2MIhJXF+hsfxJxuwdpYGVaUU3Mm5bqgKFOZOFit3Vp23J3pgsqLxFUXpLtlD5bgcGBs45794dn6mkOVFQUOjNmXPPz8ysOcAAANw6SHLtrqolAAAAAElFTkSuQmCC', | |
| 'Sakamoto': 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAYAAADwMZRfAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAxVJREFUOE+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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACAVBMVEUAAAAAAAA/AAC/AADMAACqAAC2AADGcQBMGQCyZgDMGRnEYgDMERHJGhrOGBjHeQvSISFPGgjNgyDQHh7MHx9QHgqBGhHLeQzQHh7OGRnSHR3WJCTTJCTQGxvQHx9jLBbKfxjPGxvPHh7QOB7TJCRrLRrTJCRZJhHbljjeqmN3OibaLS1+NyXYMDC8NzPaKyvYKyvaLS3YOincQEDorFvbKCjdNjbaKCjuypjZMjLcKyveLi6GRi/hOzvhMTHrs2ftxpHXLix/OCODOCeGSjOHRy2IPyqJQSqKSy6PTjmQQS6XSDGZMSefWUKgTzehUz2jVT2kVDqkbVSlWUGmUTema02qZUqrbkyrckCsMSmwNyywbEyyQTm0XkC1YkS1ak+1gVK2Qzu3iVq5Ni26Qjy6oHy9JiG9ZUnCb0vENCLFxcXGdVLGpIzJdVDKfFbMd1HNqILQysXScU7Sck/TwrDXRS3YSEbcS0rdLy7d0cHd3Nref1XfSknfflTfhFnf3tzgwp3hLi7hgFXhglfiLi7iRUTiTU3ihVnjMTHjSknkMDDlODjlOjrlOzvmPDzmrFzm0bbnTU3oT0/oUlLpU1PpVlbpWFjp1r7qW1vqYGDrvoHs1rnt0q7vwH/yz6HzypLz2bb306L43bn50Zr62Kv637r82Kf83LD83rb837f84b0dlQysAAAAQnRSTlMAAQQEBQYHCQoKCg0PExUXFx0fISgzOTtCRUVMTVJSXFxcXGNqa3BykJegp6mqtLS1t7/Jz9DU1trb3eTn8/X2/P3IgXZJAAAA30lEQVQoz2NggAJmSUUlPgYEYDNs6es150II6HfNmNZhIAznizlOmdzdONFWCsLlENVyTixPKZs004oFxFe3CfDLyI5zi2mb2iPHwCCoqhPYXNvU1FQd7FNjJsLAwCSfl5+bXldU3B6bJsHMwKCr5+Tu6hHtHxbqHR+pBjRAu9U3YmFCeFLOPBfPKBVeRgbZipCCqoblKxdVlgQlG1uyMrCYZqbWz148f0JWoVennTRQj4DR3AUrlixdMX1OqbUM2FncJstWAMEqC2UeqLv5Nez7ZzloCiH5lV1cgRPKBAApxz0bK1ScOQAAAABJRU5ErkJggg==', | |
| 'Rabite': 'iVBORw0KGgoAAAANSUhEUgAAABIAAAAQCAYAAAAbBi9cAAAD7klEQVQYGQXBW2xTZQDA8f93zmlPe7qOrVvXyzY2B6ybTLksIAImanTiDGimb/pAohISEx980TffTHwjPviiDxiNokajMSAQIwEGIwzYTWAFcZdudOt9bdfbuXz+fuLHrz9935bFnbr3yY/6Y/3O9PQkXk8T6UyeYHuY/u1RCvkyqWyBJo+XQyOvIRQFKSVut4fJG5cO5HPJlCZz42+MjvaM3l5ZayoWu0+oiuqABAAAAEAIQa1aIbV6D6/PQAiFdCrbkU3/cjGTN08rfp/fzvyVY/fTm+9WauN/bhTKfqFogAQAAEAgsC2bSmWT1rYmfD4vdXPy84NDQX9fwN2t+CND91qsIMnzizx/xBjp27p0pVjI90g0EAIABCBAKIJisYLV0Hl0f/zkth3F40rCjaH6DEUx+s5uRgO0LfeycPY+I0cCu/cNzk+a5eWxekMiFDf1ukO10sBsSBqmws3xX4+0G1dPifU6SsJNplJaF7OTE5jJ76/2lwcPJ2cSbPTcIjbSiyUMfv69fLE3Eo6HQuYeR5HNlbJVmJlOmh2e1cMHXxzUzXMmLX1Bvl2MHxdrS3GSq/cPqInTl2P1w+6V63PM1xL49qrsOjaEt70d3edDShBCYtXr5JaWuPDVHZ5zQhT36PM388PD6rN795LLOyu37q7+W639cTTQoWsD/X6697fiHxhkYibJb+cfs2egA3DQNDdnrqXpjUWZW5h2HlpPvR3eOhhXXEKnyevG0zJwZka+PjSxmV1pGi6jx2IgdC7NrvLxTxe4Mb2I5nKDojC3lOaLKw/YMbLPaQ/FUp2dIbRgWzvegA8j2Ar19LaO8q0uJTRITfFQz+c4OTbM0PYou3aGKaSKlPLwyZu7KFmwtUVqldTsCc32faA4AjKJxy814tlvlq/+8GVvp4rt8iGlpF4s0N7i5dj+CLn4fzz6J0N18wXSCYXekI7LMGjeYh1cWiihrS/GXx20/Oe6up9hqlQDKRGlDKrmwohEsUwbKTSiQ9vp1lQya5fpDBgIVUG4VFrD/sj12wt+TatUXwn6fdSLj2iYFrViCelKoQuB1aghjS0I3UCoIJEEu7xIBxzLQqNCannt8e3ZjZJmmtWas3YXuzGHzG7hYXOGAXcWt2Hgskzk5gZ2Uyu4dLBNcGxUu4HiVMklkszOej47dGAf6tjYsXRzw3onpOGOqC1MzVSZym8kvdYGml3SPR6JapZQagWURhmlkqeWzfFgPr/x953wh31Do99FoxE0f7BrZt7leXliauI9vSH1dS0Qb+s9eupCfKa1K8tb5sT8bpdab7Vte7MuXDZaOJ+tBK5LredsOPpEMdLVjWWZ/A+8EtJREuofIwAAAABJRU5ErkJggg==', | |
| 'Arch': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABCFBMVEUAAAAA//8rqtVAqtUQj88tpdIYks46otwVldUbktEaldMjldM2qNcXk9IWktQZkdIYlc8mnNUXlNEZktEZlNIYktIWlNMXktE7o9klmdMXktFHqdkXk9EWk9EYk9IlmtQXlNEXktAWk9AWlNEYlNFDptkZldMYk9E4otg/p9kXktEXk9AXlNA4otclmdQXk9IYktEXlNEwn9YXk9IXk9FFp9o3otgXk9FPrdwXk9E2otdCptkXk9E/ptkcldIXk9Edl9IXk9EjmdUXk9EXk9EXk9EbldIcldIjmdMmmtQsndUvntYyn9YyoNYzoNc0odc1odc2odc6pNg7pNg9pdlDp9pJqttOrdzlYlFbAAAARXRSTlMAAQYMEBEVFhgcHR0mLS8zNTY3PT4/RU1kdXp6e3+Cg4WIiYqMjZGXl5mbnqSnrbS3zMzV3OPk7Ozv8fT29vf4+fz8/f7SyXIjAAAAlUlEQVQYV1XNQwIDAQBD0dS2bdvmNLV5/5t0UU52728CvGayQLx8UWz1eKoXhdBqmRaF6mbdVfzZXWgetomfpY3b4Hruqb7B97hf9rtT5mNZ+7ggyaHuHTxzzqIxgUy+LG+RWSBFjrQAgAhJF+Ak6ykA0PRJOgAj2QlKAOTISkADKMM1Mg4YJmXr585cEozw2vE3m/8J5h8V7jsI1XAAAAAASUVORK5CYII=', | |
| 'CentOS': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB5lBMVEUAAADy8tng4Ovs9tnk5O3c7bX44LLduNO1tdDh7r/eutj43q2kocX23az07N+qqsvUqcmXl7331ZXJj7r40o/Pn8T42qP63KjNw9n21p3Y387Ml7732JzR55z05MSxtMLGn8TC4Hx8eqt8e62Af6/B4HnG4oPC4HzH44fBf7LCgbOkoMTcsrmtn8PWqcfFtKrj4Jvs2ZOz2FnMqLXT3KfY5p60Z6NUU5XRuqHzwWSywqDn3JaiiLWahrWhkry5zJjRmqm1Z6P1wmb1y319fK632mK5cKi5nH+73Gu73Gy73W283W+9eK17e6y1yZS3aqRZWJdcW5ldXJplXZppaKBwb6VwcKV5eKswL306OYNPTpGkfK+m0kGpUJWq1EnEqIuXK3+Xh7ahP4qhkryMfK6BgK+CdpGMaKKMa6O9ea2+eq6+oYW/eq+NbqWVlL2Wlr7AjanA4HnA4HrBkqbBlafB33rCgbLCmKjCxIzC1mSs1UytV5mtxIWt1lCuz2evWpuvXJywxYzHjrvH4oXIjrrN2HXO5pTO5pXUlYnUlYvVl5Hb0G7e0XTg03rhr5fpzHPpzXTp0Hvtz3/wrDHytknyt0zyuE3yuVHzvVr0wGP1x3T1yHf1yXe0ZaL2zYP30o730pD31ZeRIcF5AAAAQ3RSTlMAFBkbHEhJS0xMTk5UWWBsd4SEiIiPkJCVlZaam6CjpK29wMPDxMTFxcnK193e3+Dg4uTn5+fo6e/v8/P4+fn7/P7+J4XBAAAAANNJREFUGFdjYIAAcW4oA0rJOFnywkVk1VVNWyf1OehpaQqABTW8213jJna5lfnECoMF1NqaC2MmdM5tyfKHCJi4FpV69nc05VTXK4D40hVVtR5ehjqZDUkJNjwMDKKOBeUlxcZ8EnbJiSnB5hwM7GbRuRnpulJyFvHZ7mlKLAwMXLZhofnh9tYLF8ycrs8EMkQ7Nc830K93/jznOZJgW1RcfIMiG3tmOM+aKgIWUI4KCYio6Z42e8pkiAC/oKC8VV2lgZiQEBvcP6xGioyo3uVkhvIBH9A0EWEgTIIAAAAASUVORK5CYII=', | |
| 'Debian': 'iVBORw0KGgoAAAANSUhEUgAAAA0AAAAQCAYAAADNo/U5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAZ5JREFUOE+Nkk0oBHEYxv8fu5GQj3JwcaDkIAc5IpR87M7MKnIVJVKclaIQ5Sy5OLkgR7n5OigcSNpmd2c2Vyfl4KT8/muWiVU79TTv+7zv837NCBF6PG1X+NpZyEYSD9mIc+tHnBPe23B9xKrCuTmbQA/JKfABrhBswa1hH4A38IwfOxPdX1qcjiCQxO5NyrjKV70TnSbeRPwJvGN3i4yyqnEucPY8ZZX9GSEgGK+RvFfyjk2VKZxzBNG8wJWWgh/xtDOeUXZ7Slr6TrSLYL9N4SMgYTTcwdc2ArvJcElhSVcM6mCNSV8n9hA59yTU5UWMG6HIbLhIWlglgWiC2L4Z79qTdo40D6ISuOWwKCWHyk9Fv8ldpUHOuGTuynwSBUynddPdlbEosVpP9Eu4FnOsRzUYNTsdmZN/d5LDiqM0w+2CMdAFFsFGWgfXxZnheqe/z+0puwEM0HHYV3Z9Sgz8TEz7GkQvpuJ/36ggj2AaHLrSlkULWV5x+h2E8xkZL16YVjGNaAUscfZ/f6c/k9ywLKI2MMcRWl0RLy007idmRbQJ7RIfDAAAAABJRU5ErkJggg==', | |
| 'Elementary': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAACXBIWXMAAABIAAAASABGyWs+AAAAAmJLR0QA/4ePzL8AAAFuSURBVCiRfVG/S8NQEH4VimAHcXKS+otIsNb2vpc4W0QXF8FBqKOiIEgQKTgEK/gXBKEOIoIIgmMo6KSIkxYXHRxcqhjQQpQKOojES9K0mxwc7919d/fdd8IToemKLMKGw2bLoq5E8dDFyIALixZlAWvsd/BBhog1ACKGMiqkyiVU5SGO8EQzmV66QNmHMICrK4hjHXUt49dgHM+D7ekELslggK7AJVUbxicmo7l4yY56Yqwbrq4IpmZx6FweN9MdcOVQ8CrRpoBNedGGLzkXAWgFpyF13soWcHhAP7xsMkyPdOFRpoL6DXzDYYDehwF4NBUEO+UNbQd9cvhhCANsbYK/1zA9oWm4xbKf1nrwii3K8wgmWeLKVdSxhzfK+Wk5ixoOmIPFJHnNGit3D4/tShZol1Wp0jR3VYM1A6F+YWaTNI8T3OEMZjrBPeOsrtGS+iFUsbmqyn0iqRvHepf7WJApUpmaxeq2jvX/uf8A9h7IjHC1AQkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMTVUMjI6MjM6NTQrMDg6MDCQ664gAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTE1VDIyOjIzOjU0KzA4OjAw4bYWnAAAAABJRU5ErkJggg==', | |
| 'Fedora': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABPlBMVEUAAAApQXIpQXIpQXIqQ3UpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIqQ3QpQXIpQXIqRHYpQXIpQXIqQ3QqRHYpQXI8brT///8uTYMpQnM5Zqg5ZqnS1+I4ZaY4ZactSn8uRnYrQ3MrRXgsRHUsR3s8bbM8brMtSX4wUosxVI01XZw2X50vUIguToQvR3c6X5o6aKs6aq08Un8qQnM9VIFDWINJXohKcKlXapEqQ3UvUIc2X55bhcBdcJVgcpdhfapmd5tuk8dxgqJ1hKR5jbB6iah/m8Shudq3v9C4wNG/x9bFy9nFzNnFzNrIz9zK0NzK0t/O2+3P1eA2YaDU2eTb3+jb4Oje4urj6fHm6e/s7/Tz9fj3+fz7/P38/f3+/v83YaEa/NNxAAAAHnRSTlMABAoVGyY1SVlpeIuQsLfDzdHW4+3y8/b39/n6+vr4+ns8AAAAxklEQVQYGSXBhUJCQRRF0SNYgIooiPJ0zwXE7k6wu7s7//8HdGAteeFoPNne2lhfpYpIioqWWnkNeNv3X+87HXWSIni73/b2updvq1E4hffys8/ag5toUhRv0QpAn5tCcbyiXQBZN4mSQG62ZDf9Q2PdbhplyPacmPe56TZAwcyIOy4828fj7cp4DhTk3ToU7YoKdbleoGiXlKXVOToPbNkpZTEFv25uefXJDvASIWWGF+7M7GyJf4lqqZnBw+vzowEgHQtJfyetJP7BfFOIAAAAAElFTkSuQmCC', | |
| 'FreeBSD': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADXklEQVQ4EQXBS2wUZQDA8f83j33M9rF9d7u4loaWklaDpkSo9KDGaIKUaGxshD2YSPRiuDVeTDyhBxosJCoa40ktpAkPDcUqAYVIpUSUPrAulEdD2bbb7e7ObGcfM/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/wGZnIuwraa4ZgAAAABJRU5ErkJggg==', | |
| 'Gentoo': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB9VBMVEUAAAD///+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/f4MkNJ1AAAA7UlEQVQYGQXBA2IDABAAwU1t27aNi1Pbtm0rtW277+wMgEN05nRWjBMAgCJgVUTed+sibQEg9EZEvm7V8x05LgCOJSKi1+8XdKmUhT5AyIuIvHUOLDWoyvKb/MG3uVRExuOTzvqUf6fDrthEfc/diXwczXbX/h7kpYCle+qETrQ7Y+1VDysbaYAiTER3bhhsKXpcn/QG8zgR0e7N9Cjrr0bCLTBNEJHXk4Whtv77ymArCBL5eVKvjfZuHS97mQEZn8+XhxVThuviRGcA0ss1xk3NRXW2nzUAeNZsL7Y25gbaAwCYuMUmR3jYAQDwDzDCPrxVMnjZAAAAAElFTkSuQmCC', | |
| 'Mint': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACVVBMVEUAAADh4eEAAAAAAAAAAAAAAAAAAAAsLCyXl5dgYGCnp6eTk5N3d3fBwcGqqqq8vLzNzc3Ozs7Ozs7Pz8/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+/v6LaR1mAAABDUlEQVQYGWPgk5QQFxMVERYSFODnYGZgYJA7vMfa2nrXbltbiyOW0mwMDApbuzsbq6sKslJiok0tFYECW9oalqwuyU2NjQoNjLfkYpC3tCxevDE5c/nShBUdfmZSDJw8Rr3zN0Ru2n/AaptlXa0G0FiDrnnrgizLsg95LVqfp8PAwGTQPnutv2VOmp3P9M352iABm5lrfC3T4+w8pkEEDJtmrPJeGBY8y9VmWSFQBYN+c892TzcXp2POlfVFIAGllQGWc2qSIsIz+kMqQALclsalOywXTJjUl+heDhJg1d1pcnBiy+S9+446tmoBBRjY9azMp9rbOzhMmWulycAIFGHhlVFWUVFRVVVXkwUAyhJUc5MwaMIAAAAASUVORK5CYII=', | |
| 'OpenSUSE': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gIKDigueojqlAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAADEklEQVQ4y5WTTWxUZRSGn/PdO9PpdKYjbRmmrWmm/00ptlS02FqjEgpRNCxo3Eg0auJCrJqYmGA0hEiC7ghroy40Rk0wppYiIbEBjQSlFEJFWksTqv21vwydmTv3Oy5GFho2PvvzvHnf5MA/XP5jqPPMtU8fWFr6q5P/MDT+uTM1PybcBZlbmD0dL9u0c2j8Cx2YPEqkoBzR7G9V0Y61RyueW0+Eq38NRt2XAX6c+Lqos2Zv6qcb/Wyv3pMXvNgfT9fGHioImcLcjbWLbtAEyPheNmtX1bMeG4IVwZayHmku6Z6LBst7Nld2jNxJPz/Zj/QNNqnFs1bVuOKiRgCLWBcRyNoUgqvhQETAob1sz8i+pkM7bq6Mpms3bUm5CAgB46IggBVEDKIWS44d9x6gK/6MpHLLCA7vXXysJuCEFnq3vC2fXTpkXMjfIfmNREBV8cjQXvo0YSfGwQttVIab8TTDW+2D0cMXunKTc+NfJeN1+8xdlxUH3+boLn+B01PHqSraxpttA4SdUlYyMySLt+pHV19qBMgLVFEFweD56yAWEWFm/TqJwnrm02O8+3MH4ytniQbiZHO3AxinZXhy8GEDoAgGy7pdpbfuKBFTguBwYuIw+xuP0VKyk0RBLW+0DnBl8Ttu+7dYSU+xnFvYbe70tgKFJsrVxZO88+AP7K0+SNau88HwEySKGmgt28XZ6Y85N/0hjjgE3Cglwcpl6TvVpCiIKJYcaqHAjdBbe4Sme7q5tPgtv8yeYCkzjxEl6ERoiHVmHql4/lpiY2WbvDrYqAiIku+uDhYla1PEguVUFNbzeNUrJEI1qCqz6d859+cn3Exfp654a6f0napXUSdvEIO1ihGLIICDMYbl7AxqBas59TUjyeJtNMa2Dz973/v75cDJWn/NW8w6xsVXD9cUEDAhsKDGx/dz+GTElbBJRlsDDRu6ZkNOZNdTza+NALgVRZu/fzL5ejTslup8eoLp1BhTq5fxxCNkwsTD1SSj9/vVxW23NpZW9sAAX145UgTwzegxUNUg/4P+0eP/euu/AVF+N0gj+MWXAAAAAElFTkSuQmCC', | |
| 'Osx': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABrVBMVEUAAAD///////+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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABj1BMVEUAAAD///////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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABvFBMVEUAAAAcUaYdVKwAAAAAAAUABAwWRY4YSZYhZtIhaNYHDx0KCgoFDBcKCgoRMmYSNm0fXL0fXb8AAAAYS5gaTp8fXLwgXsEGBgYFBQUZSpgZTZ4JFSgODg4IEiIOJkwOKVIkW7EnXbQLGzUTExMKGC8LHjwMIkITExMiIiIPEBEPJ00QEhMXOXAaPncOJEgoXbApXbEcHBwwMDAEAgAfHRgQDgo3NC8AAAAHBwcKCgoLCwsJCQkaGhofHx8lJSUwMDA0NDQ4ODiRkZEICQocHBweHh4GBgYHCg8mJiYnJycpKSkrKystLS0uLi4ICAgODg43NzcRERF1dXUUFBSjo6O1tbUbGxsEBAMLGS8MDA0iIiIjIyMkJCQNDQ0NHTYKCQkoKCgPDw8QEBArMDkKCgkRERIREhMxMTEyMjISIz00Njk1NTU2NjYCAgIVFRU5OTo5P0c8PD0+Pj4/QURAQEBHR0dKSkpMTExSUlJiYmJlZWVnZ2cWFhZ2dnZ4eHh8fHx9fX2FhYUXFxeVlZWXl5eYmJiZmZmcnJwZGRmlpaWrq6usrKyvr68KFiq/v7/FxcXY2Nji4uLn5+ft7e0yif9uAAAAN3RSTlMAAAApKSkqKioqg4OEhISEhoa1tra3t7y9vr7S09PT09TU+Pj5+fn5+/v7+/v7+/v7/v7+/v7+70RY/wAAAPlJREFUGBktwQNbQ2EYANC3lt3NtazltvDh+s52tm1z2f7Dfe3pHPiTllfT1V2bnw5xCVDUPruyub271VEMicCUTfQ6XEtritq/XA5MwVvw7NFydOB0e+WhQoCUzh5MxmcWPRZxxNHXmgo5doyxDd3ESPhaCNtzocrsY9BXFPHU7zdXQ+McTwhZ//lAhPC+ySZoIBuUUv77HVGbNTJYB5X4SnZh8hlBQuhYHq6ArPnphxdtP/p88vQqBBcyIKnFaD29vdO0+0tlwNiWDMCJ0ujOeejicEySRA6YUqfJs7qnur2mqRKI4wxKQFUDioGDf7psfXO9PlMHzC/HlDtslvM8zQAAAABJRU5ErkJggg==', | |
| 'Slackware': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABjFBMVEX///8AAAAAAAAAAAAAADMAAGYAAAAAHFUAGWYAF10AImYAIGAAHloAHGMAKGsAGmYAJmYAJGEAKnUAJ1gAMXYAJnEAJGQAI2EAK28AK3cAGTEAMHgALXEALXgALG0AFUAAI2oAK3EAMngANoYALXMANIAAM4IANIIAL3gANIcANokANoQANYQAOY0ANIYANooAN4kAN40AOY0APZMANIUAOY0AO5AAPZUAPJAAP5MAPpQAQJUAOYsAPpYANoUAPpoAPpUAM4AAQJkAPZIAPJEAQpgAN4cAPpQAPZUAPJEAO4oAOosAOo8AQJoAOYsAO44AQpsAO48AQp0AP5UAQpoARJwAQ58ARaAAQZgAQ54AQ50AQpgARaIARqMARaMARaIAR6QARaIARaEASakARKEAR6MASqsARKEASKcAR6MARqYAR6UATbEATa8ARqUARKAAR6oARqMASKgATK8AR6QATbIATbAASq0AR6cASKgASqwAR6UASKcATa8ASqoASqwAS6wASKoAS60ATbHn4CTpAAAAhHRSTlMAAQIFBQUGCQoLDxAREhMUFBUYGhobHB0eHh8gIiIjJCQkJCYoLC0xMTE0NDo6Oz1BQUNHSUxOVFVVVldaWl5iY2RkZWZoamtsb3FycnR1ent9f4KDhIiJioyNkJGYm5+foqOkpqamqKmqrKytsLKzs7e4uLy8v8TFxcXGx8rO0NXY2eZc4XYcAAAAzUlEQVQYV2NggAMWdX12BiQglJ+SXqIMYqmHxQdJMzAEmXLKuCcyMTBYNWYmNTVYMARzMToqlAsycFYYSBppFFVp6nqa2VkVcDCIFXK7GfrrlWWISKjqpAUwMPDkibIz+Inn5iSHxxQn8AHN9Ig1d7DRzimtrasO5QJZwqhmrMoQ2Rwix8WIcANrTaqAT6AWQoC33t5Flj1CCS7AUenqrMJk7YRQEpXFb6LoawvjMjIJZ8dZ+maKMcHMZWST8or2lmdD2MPILKYlxgziAwCs9yR3GXo0vAAAAABJRU5ErkJggg==', | |
| 'Ubuntu': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABKVBMVEX////ojFzplGf1zbnqnHLvs5P10b3yuZv1xKrytZXvtJXys5LysI32waT0n3HxiVHwg0jxhk31kFn0h0zxf0P0hUrveTv2iU3yfkD1hEfyejv5eDLybSX0aR7zZxvyayH6ZxnxZBj4YhH7XAb5WALlUQLeTwHgUAHeTgHfTwD65NzdTQDdTQHdTgD31MfcTgLcTADcTQD////xt5/31Mf54dfmfE/dUAbeVQ/jcUDcTgHeWBnnflHohFvpjGbqkGztnX342Mz53dLgXiP65d399PHdUgrtoYLyu6Xzvaf76eLfXB/rkm/fWhvupojwrpTeVhTgYSfgYynzwa30xbL1ybnngFT31snngljhZS3539XhZzDiajbibDn77OX88Ovrl3X99vTjbz1fisGCAAAAMHRSTlMABgYGBwcHJiorMDA1NXGHjY2Nl5mZmZyfn6O5u8XHzc3X193j9fj4+vr6/f39/f08OUojAAAAxklEQVQYGS3BBULDQABFwY8WLdbiHjzI201Sd8Hd3e5/CJKWGUVio0u77vJYTP/iG7RsxhXpmDe0BDsHc12SpgzkyscnhVojZ8algT34KD5YGxTq4PYpabh+es3fXJSbXy8wIgeO7Dehkr2HFZnLn1SQIXToXcGWtivN7GmayO8brGsNKjZFKGs9WNWsIVP182fp58ZnHSY0ZKBYurO29ngLbr80Y4Bzz3v3fUhI6l2krbrPQqdCPdOGtmS32oYTjjHO5KBCf5XaKpkxFPzRAAAAAElFTkSuQmCC', | |
| 'Windows': 'iVBORw0KGgoAAAANSUhEUgAAABIAAAAQCAYAAAAbBi9cAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA+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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAFo9M/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': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAHC/Qd8AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoYAywUV5gQrwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAAmJLR0QA/4ePzL8AAAHHSURBVBgZZcFPSJNxAAbgt28uanSRapCsy0KK2CWCUYecB5HBolNsglZEsmAwaUvHaIu2YYty1bKGMKmgHIRbGEhURIEY/VHUQ+uwZcxEJSL5Ft+W5ubv7TMkip4HJglrPhHeUmAchWprBK+Kni9pukoAJAOF04j4y6Z9dZtu3sIT32lxXwxeRepwidP0mAF0p5JKPfBsLOMBQs/dJ3pehzcnQ+jc6SfFKlN8KMgEodLiQJWTFOyPY52mUbLmzANY4zro+xG8DVXn8UjOvg2WFSB69Oxo8Hx3F3CM2KsE2wO8LKbp5gWvtYK78zHKooM/eZFu9t0AUOCYIIf4IRfvOrIBKn12vEJyjtDhtx3QfV+dYPgO/qipOfeAgA0qn+UaW+TE9ZQjE0g63uhrpZh2yJyOPXKFe9uWrmTxtmpaSOwCgk5bWUfQywyt3MOOHmB4f9MyBu610ii3fRvlvBgRMrOc4mPmxQs2yPpylABObQ9FJxVyhAqXuEiyyOFiQcnT6TipBWDf6k9fGjwUWZgjF8V7PmVOlPmxkNmNvxhR136muELOclZ85QR73fiHBgZshM1e+UzBdzOmRvxPgmqLlKgl8mjGul/jG/ctzIC/LwAAAABJRU5ErkJggg==', | |
| 'CrunchBang': 'iVBORw0KGgoAAAANSUhEUgAAABYAAAAQCAQAAAC45EetAAAA8ElEQVR4XnWOsUpCYQBGz1TIHYu2Qix6g0DEtSeQu/UIISJtUS8gJq61F1wcdMohcBDxKUR8hsz1xA/y44/cs3znbB+RJ0Skl3pSkeFQbUs79VAPzrwPFRmN1Ja0Ug/16I93+1oi4lKte+zMXv32WuoAm43lXMrqzbFncgWw21lORf4+/PREKpAhYqZuPXZ+T/3yXbZEajV1JavUQ104sRcq0myqc5mnHurWqc/7yhExVwuPncl+C4Bu13L60ueAwcByOtLhgAIRCzU38fRGTmSxUBvSSD3Ui1NvQkXWa7Uq1dRD9R17HiqyRUSy1NP6B7e1Yu2GtlUKAAAAAElFTkSuQmCC', | |
| 'Yuno': 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAPCAYAAAD+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() { | |
| if (g.VIEW !== 'index' || !Conf['Comment Expansion']) { | |
| return; | |
| } | |
| if (g.BOARD.ID === 'g') { | |
| this.callbacks.push(Fourchan.code); | |
| } | |
| if (g.BOARD.ID === 'sci') { | |
| this.callbacks.push(Fourchan.math); | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Comment Expansion', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var a; | |
| if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) { | |
| return $.on(a, 'click', ExpandComment.cb); | |
| } | |
| }, | |
| callbacks: [], | |
| cb: function(e) { | |
| e.preventDefault(); | |
| return ExpandComment.expand(Get.postFromNode(this)); | |
| }, | |
| expand: function(post) { | |
| var a; | |
| if (post.nodes.longComment && !post.nodes.longComment.parentNode) { | |
| $.replace(post.nodes.shortComment, post.nodes.longComment); | |
| post.nodes.comment = post.nodes.longComment; | |
| return; | |
| } | |
| if (!(a = $('.abbr > a', post.nodes.comment))) { | |
| return; | |
| } | |
| a.textContent = "Post No." + post + " Loading..."; | |
| return $.cache("//api.4chan.org" + a.pathname + ".json", function() { | |
| return ExpandComment.parse(this, a, post); | |
| }); | |
| }, | |
| contract: function(post) { | |
| var a; | |
| if (!post.nodes.shortComment) { | |
| return; | |
| } | |
| a = $('.abbr > a', post.nodes.shortComment); | |
| a.textContent = 'here'; | |
| $.replace(post.nodes.longComment, post.nodes.shortComment); | |
| return post.nodes.comment = post.nodes.shortComment; | |
| }, | |
| parse: function(req, a, post) { | |
| var callback, clone, comment, href, postObj, posts, quote, spoilerRange, status, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; | |
| status = req.status; | |
| if (status !== 200 && status !== 304) { | |
| a.textContent = "Error " + req.statusText + " (" + status + ")"; | |
| return; | |
| } | |
| posts = JSON.parse(req.response).posts; | |
| if (spoilerRange = posts[0].custom_spoiler) { | |
| Build.spoilerRange[g.BOARD] = spoilerRange; | |
| } | |
| for (_i = 0, _len = posts.length; _i < _len; _i++) { | |
| postObj = posts[_i]; | |
| if (postObj.no === post.ID) { | |
| break; | |
| } | |
| } | |
| if (postObj.no !== post.ID) { | |
| a.textContent = "Post No." + post + " not found."; | |
| return; | |
| } | |
| comment = post.nodes.comment; | |
| clone = comment.cloneNode(false); | |
| clone.innerHTML = postObj.com; | |
| _ref = $$('.quotelink', clone); | |
| for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { | |
| quote = _ref[_j]; | |
| href = quote.getAttribute('href'); | |
| if (href[0] === '/') { | |
| continue; | |
| } | |
| quote.href = "/" + post.board + "/res/" + href; | |
| } | |
| post.nodes.shortComment = comment; | |
| $.replace(comment, clone); | |
| post.nodes.comment = post.nodes.longComment = clone; | |
| post.parseComment(); | |
| post.parseQuotes(); | |
| _ref1 = ExpandComment.callbacks; | |
| for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { | |
| callback = _ref1[_k]; | |
| callback.call(post); | |
| } | |
| } | |
| }; | |
| ExpandThread = { | |
| init: function() { | |
| if (g.VIEW !== 'index' || !Conf['Thread Expansion']) { | |
| return; | |
| } | |
| this.statuses = {}; | |
| return $.on(d, 'IndexRefresh', this.onIndexRefresh); | |
| }, | |
| setButton: function(thread) { | |
| var a; | |
| if (!(a = $.x('following-sibling::a[contains(@class,"summary")][1]', thread.OP.nodes.root))) { | |
| return; | |
| } | |
| a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g)))); | |
| return $.on(a, 'click', ExpandThread.cbToggle); | |
| }, | |
| onIndexRefresh: function() { | |
| var status, thread, threadID, _ref, _ref1, _ref2; | |
| _ref = ExpandThread.statuses; | |
| for (threadID in _ref) { | |
| status = _ref[threadID]; | |
| if ((_ref1 = status.req) != null) { | |
| _ref1.abort(); | |
| } | |
| delete ExpandThread.statuses[threadID]; | |
| } | |
| _ref2 = g.BOARD.threads; | |
| for (threadID in _ref2) { | |
| thread = _ref2[threadID]; | |
| ExpandThread.setButton(thread); | |
| } | |
| }, | |
| text: function(status, posts, files) { | |
| return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); | |
| }, | |
| cbToggle: function(e) { | |
| if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| return ExpandThread.toggle(Get.threadFromNode(this)); | |
| }, | |
| toggle: function(thread) { | |
| var a, threadRoot; | |
| threadRoot = thread.OP.nodes.root.parentNode; | |
| if (!(a = $('.summary', threadRoot))) { | |
| return; | |
| } | |
| if (thread.ID in ExpandThread.statuses) { | |
| return ExpandThread.contract(thread, a, threadRoot); | |
| } else { | |
| return ExpandThread.expand(thread, a, threadRoot); | |
| } | |
| }, | |
| expand: function(thread, a, threadRoot) { | |
| var status; | |
| ExpandThread.statuses[thread] = status = {}; | |
| a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); | |
| return status.req = $.cache("//a.4cdn.org/" + thread.board + "/res/" + thread + ".json", function() { | |
| delete status.req; | |
| return ExpandThread.parse(this, thread, a); | |
| }); | |
| }, | |
| contract: function(thread, a, threadRoot) { | |
| var filesCount, inlined, num, postsCount, replies, reply, status, _i, _len; | |
| status = ExpandThread.statuses[thread]; | |
| delete ExpandThread.statuses[thread]; | |
| if (status.req) { | |
| status.req.abort(); | |
| if (a) { | |
| a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g)))); | |
| } | |
| return; | |
| } | |
| replies = $$('.thread > .replyContainer', threadRoot); | |
| if (Conf['Show Replies']) { | |
| num = (function() { | |
| if (thread.isSticky) { | |
| return 1; | |
| } else { | |
| switch (g.BOARD.ID) { | |
| case 'b': | |
| case 'vg': | |
| return 3; | |
| case 't': | |
| return 1; | |
| default: | |
| return 5; | |
| } | |
| } | |
| })(); | |
| replies = replies.slice(0, -num); | |
| } | |
| postsCount = 0; | |
| filesCount = 0; | |
| for (_i = 0, _len = replies.length; _i < _len; _i++) { | |
| reply = replies[_i]; | |
| if (Conf['Quote Inlining']) { | |
| while (inlined = $('.inlined', reply)) { | |
| inlined.click(); | |
| } | |
| } | |
| postsCount++; | |
| if ('file' in Get.postFromRoot(reply)) { | |
| filesCount++; | |
| } | |
| $.rm(reply); | |
| } | |
| return a.textContent = ExpandThread.text('+', postsCount, filesCount); | |
| }, | |
| parse: function(req, thread, a) { | |
| var data, filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref; | |
| if ((_ref = req.status) !== 200 && _ref !== 304) { | |
| a.textContent = "Error " + req.statusText + " (" + req.status + ")"; | |
| return; | |
| } | |
| data = JSON.parse(req.response).posts; | |
| Build.spoilerRange[thread.board] = data.shift().custom_spoiler; | |
| posts = []; | |
| postsRoot = []; | |
| filesCount = 0; | |
| for (_i = 0, _len = data.length; _i < _len; _i++) { | |
| postData = data[_i]; | |
| if (post = thread.posts[postData.no]) { | |
| if ('file' in post) { | |
| filesCount++; | |
| } | |
| postsRoot.push(post.nodes.root); | |
| continue; | |
| } | |
| root = Build.postFromObject(postData, thread.board.ID); | |
| post = new Post(root, thread, thread.board); | |
| if ('file' in post) { | |
| filesCount++; | |
| } | |
| posts.push(post); | |
| postsRoot.push(root); | |
| } | |
| Main.callbackNodes(Post, posts); | |
| $.after(a, postsRoot); | |
| postsCount = postsRoot.length; | |
| a.textContent = ExpandThread.text('-', postsCount, filesCount); | |
| return Fourchan.parseThread(thread.ID, 1, postsCount); | |
| } | |
| }; | |
| FileInfo = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['File Info Formatting']) { | |
| return; | |
| } | |
| this.funk = this.createFunc(Conf['fileInfo']); | |
| return Post.callbacks.push({ | |
| name: 'File Info Formatting', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| if (!this.file || this.isClone) { | |
| return; | |
| } | |
| return this.file.text.innerHTML = "<span class=file-info>" + (FileInfo.funk(FileInfo, this)) + "</span>"; | |
| }, | |
| createFunc: function(format) { | |
| var code; | |
| code = format.replace(/%(.)/g, function(s, c) { | |
| if (c in FileInfo.formatters) { | |
| return "' + FileInfo.formatters." + c + ".call(post) + '"; | |
| } else { | |
| return s; | |
| } | |
| }); | |
| return Function('FileInfo', 'post', "return '" + code + "'"); | |
| }, | |
| convertUnit: function(size, unit) { | |
| var i; | |
| if (unit === 'B') { | |
| return "" + (size.toFixed()) + " Bytes"; | |
| } | |
| i = 1 + ['KB', 'MB'].indexOf(unit); | |
| while (i--) { | |
| size /= 1024; | |
| } | |
| size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); | |
| return "" + size + " " + unit; | |
| }, | |
| escape: function(name) { | |
| return name.replace(/<|>/g, function(c) { | |
| return c === '<' && '<' || '>'; | |
| }); | |
| }, | |
| formatters: { | |
| t: function() { | |
| return this.file.URL.match(/\d+\..+$/)[0]; | |
| }, | |
| T: function() { | |
| return "<a href=" + this.file.URL + " target=_blank>" + (FileInfo.formatters.t.call(this)) + "</a>"; | |
| }, | |
| l: function() { | |
| return "<a href=" + this.file.URL + " target=_blank>" + (FileInfo.formatters.n.call(this)) + "</a>"; | |
| }, | |
| L: function() { | |
| return "<a href=" + this.file.URL + " target=_blank>" + (FileInfo.formatters.N.call(this)) + "</a>"; | |
| }, | |
| n: function() { | |
| var fullname, shortname; | |
| fullname = this.file.name; | |
| shortname = Build.shortFilename(this.file.name, this.isReply); | |
| if (fullname === shortname) { | |
| return FileInfo.escape(fullname); | |
| } else { | |
| return "<span class=fntrunc>" + (FileInfo.escape(shortname)) + "</span><span class=fnfull>" + (FileInfo.escape(fullname)) + "</span>"; | |
| } | |
| }, | |
| N: function() { | |
| return FileInfo.escape(this.file.name); | |
| }, | |
| p: function() { | |
| if (this.file.isSpoiler) { | |
| return 'Spoiler, '; | |
| } else { | |
| return ''; | |
| } | |
| }, | |
| s: function() { | |
| return this.file.size; | |
| }, | |
| B: function() { | |
| return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); | |
| }, | |
| K: function() { | |
| return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); | |
| }, | |
| M: function() { | |
| return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); | |
| }, | |
| r: function() { | |
| if (this.file.isImage) { | |
| return this.file.dimensions; | |
| } else { | |
| return 'PDF'; | |
| } | |
| } | |
| } | |
| }; | |
| Fourchan = { | |
| init: function() { | |
| var board; | |
| if (g.VIEW === 'catalog') { | |
| return; | |
| } | |
| board = g.BOARD.ID; | |
| if (board === 'g') { | |
| $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); | |
| Post.callbacks.push({ | |
| name: 'Parse /g/ code', | |
| cb: this.code | |
| }); | |
| } | |
| if (board === 'sci') { | |
| $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); | |
| return Post.callbacks.push({ | |
| name: 'Parse /sci/ math', | |
| cb: this.math | |
| }); | |
| } | |
| }, | |
| code: function() { | |
| var apply, pre, _i, _len, _ref; | |
| if (this.isClone) { | |
| return; | |
| } | |
| apply = function(e) { | |
| return pre.innerHTML = e.detail; | |
| }; | |
| $.on(window, 'prettyprint:cb', apply); | |
| _ref = $$('.prettyprint:not(.prettyprinted)', this.nodes.comment); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| pre = _ref[_i]; | |
| $.event('prettyprint', pre.innerHTML, window); | |
| } | |
| $.off(window, 'prettyprint:cb', apply); | |
| }, | |
| math: function() { | |
| if (this.isClone || !$('.math', this.nodes.comment)) { | |
| return; | |
| } | |
| return $.event('jsmath', this.nodes.post, window); | |
| }, | |
| parseThread: function(threadID, offset, limit) { | |
| return $.event('4chanParsingDone', { | |
| threadId: threadID, | |
| offset: offset, | |
| limit: limit | |
| }); | |
| } | |
| }; | |
| IDColor = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Color User IDs']) { | |
| return; | |
| } | |
| this.ids = {}; | |
| return Post.callbacks.push({ | |
| name: 'Color User IDs', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var rgb, span, style, uid; | |
| if (this.isClone || !(uid = this.info.uniqueID)) { | |
| return; | |
| } | |
| span = $('.hand', this.nodes.uniqueID); | |
| if (!(span && span.nodeName === 'SPAN')) { | |
| return; | |
| } | |
| rgb = IDColor.compute(uid); | |
| style = span.style; | |
| style.color = rgb[3]; | |
| style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; | |
| $.addClass(span, 'painted'); | |
| return span.title = 'Highlight posts by this ID'; | |
| }, | |
| compute: function(uid) { | |
| var hash, rgb; | |
| if (IDColor.ids[uid]) { | |
| return IDColor.ids[uid]; | |
| } | |
| hash = IDColor.hash(uid); | |
| rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; | |
| rgb[3] = (rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'; | |
| return this.ids[uid] = rgb; | |
| }, | |
| hash: function(uid) { | |
| var i, msg; | |
| msg = 0; | |
| i = 0; | |
| while (i < 8) { | |
| msg = (msg << 5) - msg + uid.charCodeAt(i++); | |
| } | |
| return msg; | |
| } | |
| }; | |
| InfiniScroll = { | |
| init: function() { | |
| if (!(Conf['Infinite Scrolling'] && g.VIEW === 'index' && g.BOARD !== 'f')) { | |
| return; | |
| } | |
| this.threads = g.threads; | |
| return $.on(d, '4chanXInitFinished', this.ready); | |
| }, | |
| ready: function() { | |
| $.off(d, '4chanXInitFinished', InfiniScroll.ready); | |
| $.on(d, 'scroll', InfiniScroll.scroll); | |
| return InfiniScroll.scroll(); | |
| }, | |
| scroll: $.debounce(100, function() { | |
| var url; | |
| if (InfiniScroll.isFetching || ((d.body.scrollTop || doc.scrollTop) <= doc.scrollHeight - (300 + window.innerHeight))) { | |
| return; | |
| } | |
| if (InfiniScroll.isDead) { | |
| return InfiniScroll.notice(); | |
| } | |
| if (InfiniScroll.cache && InfiniScroll.cache.time > Date.now() - $.MINUTE) { | |
| return InfiniScroll.parse(InfiniScroll.cache); | |
| } | |
| new Notice('info', "Fetching next page.", 2); | |
| InfiniScroll.isFetching = true; | |
| url = "//api.4chan.org/" + g.BOARD + "/catalog.json"; | |
| return $.ajax(url, { | |
| onloadend: InfiniScroll.cb.load | |
| }, { | |
| whenModified: true | |
| }); | |
| }), | |
| parse: function(response) { | |
| var botPostForm, el, nodes, omitted_images, omitted_posts, op, post, postlink, posts, replylink, thread, threadID, threadNodes, threads, _i, _j, _len, _len1, _ref; | |
| threads = InfiniScroll.parsePages(response); | |
| threadNodes = []; | |
| nodes = []; | |
| if (!threads.length) { | |
| InfiniScroll.notice(); | |
| return InfiniScroll.isDead = true; | |
| } | |
| for (_i = 0, _len = threads.length; _i < _len; _i++) { | |
| thread = threads[_i]; | |
| posts = []; | |
| omitted_posts = thread.omitted_posts, omitted_images = thread.omitted_images; | |
| threadID = thread.no; | |
| el = $.el('div', { | |
| className: 'thread', | |
| id: "t" + threadID | |
| }); | |
| op = Build.postFromObject(thread, g.BOARD); | |
| posts.push(op); | |
| replylink = $.el('a', { | |
| href: "res/" + threadID, | |
| className: 'replylink', | |
| textContent: 'Reply' | |
| }); | |
| postlink = $.el('div', { | |
| className: "postLink mobile", | |
| innerHTML: "<a href=\"res/" + threadID + "\" class=\"button\">View Thread</a>" | |
| }); | |
| if (omitted_posts) { | |
| posts.push($.el('span', { | |
| className: 'summary desktop', | |
| innerHTML: "" + omitted_posts + " posts " + (omitted_images ? "and " + omitted_images + " image replies" : void 0) + " omitted. Click <a class=\"replylink\" href=\"res/" + threadID + "\">here</a> to view." | |
| })); | |
| $.prepend(postlink, $.el('span', { | |
| className: 'info', | |
| innerHTML: "<strong>" + omitted_posts + " posts omitted</strong>" + (omitted_images ? "<br><em>(" + omitted_images + " have images)</em>" : "") | |
| })); | |
| } | |
| $.add($('.postInfo', op), [$.tn('\u00A0\u00A0\u00A0['), replylink, $.tn(']\u00A0')]); | |
| $.add(op, postlink); | |
| if (thread.last_replies) { | |
| _ref = thread.last_replies; | |
| for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { | |
| post = _ref[_j]; | |
| posts.push(Build.postFromObject(post, g.BOARD)); | |
| } | |
| } | |
| $.add(el, posts); | |
| threadNodes.push(el); | |
| nodes.push(el); | |
| nodes.push($.el('hr')); | |
| } | |
| InfiniScroll.features(threadNodes); | |
| if (botPostForm = $('.board > .mobile.center')) { | |
| return $.before(botPostForm, nodes); | |
| } | |
| }, | |
| parsePages: function(response) { | |
| var newThreads, number, page, pages, thread, threads, _i, _len; | |
| pages = JSON.parse(response); | |
| newThreads = []; | |
| for (number in pages) { | |
| page = pages[number]; | |
| if (!(pages.hasOwnProperty(number))) { | |
| continue; | |
| } | |
| threads = page.threads; | |
| for (_i = 0, _len = threads.length; _i < _len; _i++) { | |
| thread = threads[_i]; | |
| if (g.threads["" + g.BOARD + "." + thread.no]) { | |
| continue; | |
| } | |
| newThreads.push(thread); | |
| if (newThreads.length === 15) { | |
| return newThreads; | |
| } | |
| } | |
| } | |
| return newThreads; | |
| }, | |
| features: function(threadNodes) { | |
| var err, errors, post, posts, thread, threadRoot, threads, _i, _j, _len, _len1, _ref; | |
| posts = []; | |
| threads = []; | |
| for (_i = 0, _len = threadNodes.length; _i < _len; _i++) { | |
| threadRoot = threadNodes[_i]; | |
| thread = new Thread(+threadRoot.id.slice(1), g.BOARD); | |
| threads.push(thread); | |
| _ref = $$('.thread > .postContainer', threadRoot); | |
| for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { | |
| post = _ref[_j]; | |
| try { | |
| posts.push(new Post(post, thread, g.BOARD)); | |
| } catch (_error) { | |
| err = _error; | |
| if (!errors) { | |
| errors = []; | |
| } | |
| errors.push({ | |
| message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", | |
| error: err | |
| }); | |
| } | |
| } | |
| } | |
| if (errors) { | |
| Main.handleErrors(errors); | |
| } | |
| Main.callbackNodes(Thread, threads); | |
| return Main.callbackNodes(Post, posts); | |
| }, | |
| notice: (function() { | |
| var notify, reset; | |
| notify = false; | |
| reset = function() { | |
| return notify = false; | |
| }; | |
| return function() { | |
| if (notify) { | |
| return; | |
| } | |
| notify = true; | |
| new Notice('info', "Last page reached.", 2); | |
| return setTimeout(reset, 3 * $.SECOND); | |
| }; | |
| })(), | |
| cb: { | |
| load: function() { | |
| InfiniScroll.isFetching = false; | |
| if (this.status !== 200) { | |
| return; | |
| } | |
| InfiniScroll.cache = new String(this.response); | |
| InfiniScroll.cache.time = Date.now(); | |
| return InfiniScroll.parse(this.response); | |
| } | |
| } | |
| }; | |
| Keybinds = { | |
| init: function() { | |
| var hotkey, init; | |
| if (g.VIEW === 'catalog' || !Conf['Keybinds']) { | |
| return; | |
| } | |
| for (hotkey in Conf.hotkeys) { | |
| $.sync(hotkey, Keybinds.sync); | |
| } | |
| init = function() { | |
| var node, _i, _len, _ref; | |
| $.off(d, '4chanXInitFinished', init); | |
| $.on(d, 'keydown', Keybinds.keydown); | |
| _ref = $$('[accesskey]'); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| node = _ref[_i]; | |
| node.removeAttribute('accesskey'); | |
| } | |
| }; | |
| return $.on(d, '4chanXInitFinished', init); | |
| }, | |
| sync: function(key, hotkey) { | |
| return Conf[hotkey] = key; | |
| }, | |
| keydown: function(e) { | |
| var key, notification, notifications, op, target, thread, threadRoot, _i, _len, _ref; | |
| if (!(key = Keybinds.keyCode(e))) { | |
| return; | |
| } | |
| target = e.target; | |
| if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') { | |
| if (!/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key)) { | |
| return; | |
| } | |
| } | |
| threadRoot = Nav.getThread(); | |
| if (op = $('.op', threadRoot)) { | |
| thread = Get.postFromNode(op).thread; | |
| } | |
| switch (key) { | |
| case Conf['Toggle board list']: | |
| if (Conf['Custom Board Navigation']) { | |
| Header.toggleBoardList(); | |
| } | |
| break; | |
| case Conf['Toggle header']: | |
| Header.toggleBarVisibility(); | |
| break; | |
| case Conf['Open empty QR']: | |
| Keybinds.qr(threadRoot); | |
| break; | |
| case Conf['Open QR']: | |
| Keybinds.qr(threadRoot, true); | |
| break; | |
| case Conf['Open settings']: | |
| Settings.open(); | |
| break; | |
| case Conf['Close']: | |
| if (Settings.dialog) { | |
| Settings.close(); | |
| } else if ((notifications = $$('.notification')).length) { | |
| for (_i = 0, _len = notifications.length; _i < _len; _i++) { | |
| notification = notifications[_i]; | |
| $('.close', notification).click(); | |
| } | |
| } else if (QR.nodes) { | |
| if (Conf['Persistent QR']) { | |
| QR.hide(); | |
| } else { | |
| QR.close(); | |
| } | |
| } | |
| break; | |
| case Conf['Spoiler tags']: | |
| if (target.nodeName !== 'TEXTAREA') { | |
| return; | |
| } | |
| Keybinds.tags('spoiler', target); | |
| break; | |
| case Conf['Code tags']: | |
| if (target.nodeName !== 'TEXTAREA') { | |
| return; | |
| } | |
| Keybinds.tags('code', target); | |
| break; | |
| case Conf['Eqn tags']: | |
| if (target.nodeName !== 'TEXTAREA') { | |
| return; | |
| } | |
| Keybinds.tags('eqn', target); | |
| break; | |
| case Conf['Math tags']: | |
| if (target.nodeName !== 'TEXTAREA') { | |
| return; | |
| } | |
| Keybinds.tags('math', target); | |
| break; | |
| case Conf['Toggle sage']: | |
| if (QR.nodes) { | |
| Keybinds.sage(); | |
| } | |
| break; | |
| case Conf['Submit QR']: | |
| if (QR.nodes && !QR.status()) { | |
| QR.submit(); | |
| } | |
| break; | |
| case Conf['Update']: | |
| switch (g.VIEW) { | |
| case 'thread': | |
| ThreadUpdater.update(); | |
| break; | |
| case 'index': | |
| Index.update(); | |
| } | |
| break; | |
| case Conf['Watch']: | |
| ThreadWatcher.toggle(thread); | |
| break; | |
| case Conf['Expand image']: | |
| Keybinds.img(threadRoot); | |
| break; | |
| case Conf['Expand images']: | |
| Keybinds.img(threadRoot, true); | |
| break; | |
| case Conf['Open Gallery']: | |
| Gallery.cb.toggle(); | |
| break; | |
| case Conf['fappeTyme']: | |
| FappeTyme.cb.toggle.call({ | |
| name: 'fappe' | |
| }); | |
| break; | |
| case Conf['werkTyme']: | |
| FappeTyme.cb.toggle.call({ | |
| name: 'werk' | |
| }); | |
| break; | |
| case Conf['Front page']: | |
| if (g.VIEW === 'index') { | |
| Index.userPageNav(0); | |
| } else { | |
| window.location = "/" + g.BOARD + "/"; | |
| } | |
| break; | |
| case Conf['Open front page']: | |
| $.open("/" + g.BOARD + "/"); | |
| break; | |
| case Conf['Next page']: | |
| if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'all pages')) { | |
| return; | |
| } | |
| $('.next button', Index.pagelist).click(); | |
| break; | |
| case Conf['Previous page']: | |
| if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'all pages')) { | |
| return; | |
| } | |
| $('.prev button', Index.pagelist).click(); | |
| break; | |
| case Conf['Search form']: | |
| Index.searchInput.focus(); | |
| break; | |
| case Conf['Open catalog']: | |
| if (Conf['External Catalog']) { | |
| window.location = CatalogLinks.external(g.BOARD.ID); | |
| } else { | |
| window.location = "/" + g.BOARD + "/catalog"; | |
| } | |
| break; | |
| case Conf['Next thread']: | |
| if (g.VIEW !== 'index') { | |
| return; | |
| } | |
| Nav.scroll(+1); | |
| break; | |
| case Conf['Previous thread']: | |
| if (g.VIEW !== 'index') { | |
| return; | |
| } | |
| Nav.scroll(-1); | |
| break; | |
| case Conf['Expand thread']: | |
| ExpandThread.toggle(thread); | |
| break; | |
| case Conf['Open thread']: | |
| Keybinds.open(thread); | |
| break; | |
| case Conf['Open thread tab']: | |
| Keybinds.open(thread, true); | |
| break; | |
| case Conf['Next reply']: | |
| Keybinds.hl(+1, threadRoot); | |
| break; | |
| case Conf['Previous reply']: | |
| Keybinds.hl(-1, threadRoot); | |
| break; | |
| case Conf['Deselect reply']: | |
| Keybinds.hl(0, threadRoot); | |
| break; | |
| case Conf['Hide']: | |
| if (ThreadHiding.db) { | |
| ThreadHiding.toggle(thread); | |
| } | |
| break; | |
| case Conf['Previous Post Quoting You']: | |
| QuoteYou.cb.seek('preceding'); | |
| break; | |
| case Conf['Next Post Quoting You']: | |
| QuoteYou.cb.seek('following'); | |
| break; | |
| default: | |
| return; | |
| } | |
| e.preventDefault(); | |
| return e.stopPropagation(); | |
| }, | |
| keyCode: function(e) { | |
| var kc, key; | |
| key = (function() { | |
| switch (kc = e.keyCode) { | |
| case 8: | |
| return ''; | |
| case 13: | |
| return 'Enter'; | |
| case 27: | |
| return 'Esc'; | |
| case 37: | |
| return 'Left'; | |
| case 38: | |
| return 'Up'; | |
| case 39: | |
| return 'Right'; | |
| case 40: | |
| return 'Down'; | |
| default: | |
| if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) { | |
| return String.fromCharCode(kc).toLowerCase(); | |
| } else { | |
| return null; | |
| } | |
| } | |
| })(); | |
| if (key) { | |
| if (e.altKey) { | |
| key = 'Alt+' + key; | |
| } | |
| if (e.ctrlKey) { | |
| key = 'Ctrl+' + key; | |
| } | |
| if (e.metaKey) { | |
| key = 'Meta+' + key; | |
| } | |
| if (e.shiftKey) { | |
| key = 'Shift+' + key; | |
| } | |
| } | |
| return key; | |
| }, | |
| qr: function(thread, quote) { | |
| if (!(Conf['Quick Reply'] && QR.postingIsEnabled)) { | |
| return; | |
| } | |
| QR.open(); | |
| if (quote) { | |
| QR.quote.call($('input', $('.post.highlight', thread) || thread)); | |
| } | |
| QR.nodes.com.focus(); | |
| if (Conf['QR Shortcut']) { | |
| return $.rmClass($('.qr-shortcut'), 'disabled'); | |
| } | |
| }, | |
| tags: function(tag, ta) { | |
| var range, selEnd, selStart, value; | |
| value = ta.value; | |
| selStart = ta.selectionStart; | |
| selEnd = ta.selectionEnd; | |
| ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd); | |
| range = ("[" + tag + "]").length + selEnd; | |
| ta.setSelectionRange(range, range); | |
| return $.event('input', null, ta); | |
| }, | |
| sage: function() { | |
| var isSage; | |
| isSage = /sage/i.test(QR.nodes.email.value); | |
| return QR.nodes.email.value = isSage ? "" : "sage"; | |
| }, | |
| img: function(thread, all) { | |
| var post; | |
| if (all) { | |
| return ImageExpand.cb.toggleAll(); | |
| } else { | |
| post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); | |
| return ImageExpand.toggle(post); | |
| } | |
| }, | |
| open: function(thread, tab) { | |
| var url; | |
| if (g.VIEW !== 'index') { | |
| return; | |
| } | |
| url = "/" + thread.board + "/res/" + thread; | |
| if (tab) { | |
| return $.open(url); | |
| } else { | |
| return location.href = url; | |
| } | |
| }, | |
| hl: function(delta, thread) { | |
| var axis, height, next, postEl, replies, reply, root, _i, _len; | |
| postEl = $('.reply.highlight', thread); | |
| if (!delta) { | |
| if (postEl) { | |
| $.rmClass(postEl, 'highlight'); | |
| } | |
| return; | |
| } | |
| if (postEl) { | |
| height = postEl.getBoundingClientRect().height; | |
| if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { | |
| root = postEl.parentNode; | |
| axis = delta === +1 ? 'following' : 'preceding'; | |
| if (!(next = $.x("" + axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { | |
| return; | |
| } | |
| Header.scrollToIfNeeded(next, delta === +1); | |
| this.focus(next); | |
| $.rmClass(postEl, 'highlight'); | |
| return; | |
| } | |
| $.rmClass(postEl, 'highlight'); | |
| } | |
| replies = $$('.reply', thread); | |
| if (delta === -1) { | |
| replies.reverse(); | |
| } | |
| for (_i = 0, _len = replies.length; _i < _len; _i++) { | |
| reply = replies[_i]; | |
| if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { | |
| this.focus(reply); | |
| return; | |
| } | |
| } | |
| }, | |
| focus: function(post) { | |
| return $.addClass(post, 'highlight'); | |
| } | |
| }; | |
| Nav = { | |
| init: function() { | |
| var append, next, prev, span; | |
| switch (g.VIEW) { | |
| case 'index': | |
| if (!Conf['Index Navigation']) { | |
| return; | |
| } | |
| break; | |
| case 'thread': | |
| if (!Conf['Reply Navigation']) { | |
| return; | |
| } | |
| break; | |
| default: | |
| return; | |
| } | |
| span = $.el('span', { | |
| id: 'navlinks' | |
| }); | |
| prev = $.el('a', { | |
| textContent: '▲', | |
| href: 'javascript:;' | |
| }); | |
| next = $.el('a', { | |
| textContent: '▼', | |
| href: 'javascript:;' | |
| }); | |
| $.on(prev, 'click', this.prev); | |
| $.on(next, 'click', this.next); | |
| $.add(span, [prev, $.tn(' '), next]); | |
| append = function() { | |
| $.off(d, '4chanXInitFinished', append); | |
| return $.add(d.body, span); | |
| }; | |
| return $.on(d, '4chanXInitFinished', append); | |
| }, | |
| prev: function() { | |
| if (g.VIEW === 'thread') { | |
| return window.scrollTo(0, 0); | |
| } else { | |
| return Nav.scroll(-1); | |
| } | |
| }, | |
| next: function() { | |
| if (g.VIEW === 'thread') { | |
| return window.scrollTo(0, d.body.scrollHeight); | |
| } else { | |
| return Nav.scroll(+1); | |
| } | |
| }, | |
| getThread: function() { | |
| var thread, threadRoot, _i, _len, _ref; | |
| _ref = $$('.thread'); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| threadRoot = _ref[_i]; | |
| thread = Get.threadFromRoot(threadRoot); | |
| if (thread.isHidden && !thread.stub) { | |
| continue; | |
| } | |
| if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) { | |
| return threadRoot; | |
| } | |
| } | |
| return $('.board'); | |
| }, | |
| scroll: function(delta) { | |
| var axis, next, thread, top; | |
| thread = Nav.getThread(); | |
| axis = delta === +1 ? 'following' : 'preceding'; | |
| if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { | |
| top = Header.getTopOf(thread); | |
| if (delta === +1 && top < 5 || delta === -1 && top > -5) { | |
| thread = next; | |
| } | |
| } | |
| return Header.scrollTo(thread); | |
| } | |
| }; | |
| RelativeDates = { | |
| INTERVAL: $.MINUTE / 2, | |
| init: function() { | |
| switch (g.VIEW) { | |
| case 'index': | |
| this.flush(); | |
| $.on(d, 'visibilitychange', this.flush); | |
| if (!Conf['Relative Post Dates']) { | |
| return; | |
| } | |
| break; | |
| case 'thread': | |
| if (!Conf['Relative Post Dates']) { | |
| return; | |
| } | |
| this.flush(); | |
| if (g.VIEW === 'thread') { | |
| $.on(d, 'visibilitychange ThreadUpdate', this.flush); | |
| } | |
| break; | |
| default: | |
| return; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Relative Post Dates', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| var dateEl; | |
| if (this.isClone) { | |
| return; | |
| } | |
| dateEl = this.nodes.date; | |
| dateEl.title = dateEl.textContent; | |
| return RelativeDates.update(this); | |
| }, | |
| relative: function(diff, now, date) { | |
| var days, months, number, rounded, unit, years; | |
| unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); | |
| rounded = Math.round(number); | |
| if (rounded !== 1) { | |
| unit += 's'; | |
| } | |
| return "" + rounded + " " + unit + " ago"; | |
| }, | |
| stale: [], | |
| flush: function() { | |
| var data, now, _i, _len, _ref; | |
| if (d.hidden) { | |
| return; | |
| } | |
| now = new Date(); | |
| _ref = RelativeDates.stale; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| data = _ref[_i]; | |
| RelativeDates.update(data, now); | |
| } | |
| RelativeDates.stale = []; | |
| clearTimeout(RelativeDates.timeout); | |
| return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL); | |
| }, | |
| update: function(data, now) { | |
| var date, diff, isPost, relative, singlePost, _i, _len, _ref; | |
| isPost = data instanceof Post; | |
| date = isPost ? data.info.date : new Date(+data.dataset.utc); | |
| now || (now = new Date()); | |
| diff = now - date; | |
| relative = RelativeDates.relative(diff, now, date); | |
| if (isPost) { | |
| _ref = [data].concat(data.clones); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| singlePost = _ref[_i]; | |
| singlePost.nodes.date.firstChild.textContent = relative; | |
| } | |
| } else { | |
| data.firstChild.textContent = relative; | |
| } | |
| return RelativeDates.setOwnTimeout(diff, data); | |
| }, | |
| setOwnTimeout: function(diff, data) { | |
| var delay; | |
| delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY; | |
| return setTimeout(RelativeDates.markStale, delay, data); | |
| }, | |
| markStale: function(data) { | |
| if (__indexOf.call(RelativeDates.stale, data) >= 0) { | |
| return; | |
| } | |
| if (data instanceof Post && !g.posts[data.fullID]) { | |
| return; | |
| } | |
| return RelativeDates.stale.push(data); | |
| } | |
| }; | |
| RemoveSpoilers = { | |
| init: function() { | |
| if (Conf['Reveal Spoilers'] && !Conf['Remove Spoilers']) { | |
| $.addClass(doc, 'reveal-spoilers'); | |
| } | |
| if (!Conf['Remove Spoilers']) { | |
| return; | |
| } | |
| if (Conf['Reveal Spoilers']) { | |
| this.wrapper = function(text) { | |
| return "[spoiler]" + text + "[/spoiler]"; | |
| }; | |
| } | |
| return Post.callbacks.push({ | |
| name: 'Reveal Spoilers', | |
| cb: this.node | |
| }); | |
| }, | |
| wrapper: function(text) { | |
| return text; | |
| }, | |
| node: function(post) { | |
| var spoiler, spoilers, _i, _len; | |
| spoilers = $$('s', this.nodes.comment); | |
| for (_i = 0, _len = spoilers.length; _i < _len; _i++) { | |
| spoiler = spoilers[_i]; | |
| $.replace(spoiler, $.tn(RemoveSpoilers.wrapper(spoiler.textContent))); | |
| } | |
| } | |
| }; | |
| Report = { | |
| init: function() { | |
| if (!/report/.test(location.search)) { | |
| return; | |
| } | |
| return $.asap((function() { | |
| return $.id('recaptcha_response_field'); | |
| }), Report.ready); | |
| }, | |
| ready: function() { | |
| var field; | |
| field = $.id('recaptcha_response_field'); | |
| $.on(field, 'keydown', function(e) { | |
| if (e.keyCode === 8 && !field.value) { | |
| return $.globalEval('Recaptcha.reload("t")'); | |
| } | |
| }); | |
| return $.on($('form'), 'submit', function(e) { | |
| var response; | |
| e.preventDefault(); | |
| response = field.value.trim(); | |
| if (!/\s/.test(response)) { | |
| field.value = "" + response + " " + response; | |
| } | |
| return this.submit(); | |
| }); | |
| } | |
| }; | |
| Time = { | |
| init: function() { | |
| if (g.VIEW === 'catalog' || !Conf['Time Formatting']) { | |
| return; | |
| } | |
| this.funk = this.createFunc(Conf['time']); | |
| return Post.callbacks.push({ | |
| name: 'Time Formatting', | |
| cb: this.node | |
| }); | |
| }, | |
| node: function() { | |
| if (this.isClone) { | |
| return; | |
| } | |
| return this.nodes.date.textContent = Time.funk(Time, this.info.date); | |
| }, | |
| createFunc: function(format) { | |
| var code; | |
| code = format.replace(/%([A-Za-z])/g, function(s, c) { | |
| if (c in Time.formatters) { | |
| return "' + Time.formatters." + c + ".call(date) + '"; | |
| } else { | |
| return s; | |
| } | |
| }); | |
| return Function('Time', 'date', "return '" + code + "'"); | |
| }, | |
| day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | |
| month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], | |
| zeroPad: function(n) { | |
| if (n < 10) { | |
| return "0" + n; | |
| } else { | |
| return n; | |
| } | |
| }, | |
| formatters: { | |
| a: function() { | |
| return Time.day[this.getDay()].slice(0, 3); | |
| }, | |
| A: function() { | |
| return Time.day[this.getDay()]; | |
| }, | |
| b: function() { | |
| return Time.month[this.getMonth()].slice(0, 3); | |
| }, | |
| B: function() { | |
| return Time.month[this.getMonth()]; | |
| }, | |
| d: function() { | |
| return Time.zeroPad(this.getDate()); | |
| }, | |
| e: function() { | |
| return this.getDate(); | |
| }, | |
| H: function() { | |
| return Time.zeroPad(this.getHours()); | |
| }, | |
| I: function() { | |
| return Time.zeroPad(this.getHours() % 12 || 12); | |
| }, | |
| k: function() { | |
| return this.getHours(); | |
| }, | |
| l: function() { | |
| return this.getHours() % 12 || 12; | |
| }, | |
| m: function() { | |
| return Time.zeroPad(this.getMonth() + 1); | |
| }, | |
| M: function() { | |
| return Time.zeroPad(this.getMinutes()); | |
| }, | |
| p: function() { | |
| if (this.getHours() < 12) { | |
| return 'AM'; | |
| } else { | |
| return 'PM'; | |
| } | |
| }, | |
| P: function() { | |
| if (this.getHours() < 12) { | |
| return 'am'; | |
| } else { | |
| return 'pm'; | |
| } | |
| }, | |
| S: function() { | |
| return Time.zeroPad(this.getSeconds()); | |
| }, | |
| y: function() { | |
| return this.getFullYear().toString().slice(2); | |
| }, | |
| Y: function() { | |
| return this.getFullYear(); | |
| } | |
| } | |
| }; | |
| Settings = { | |
| init: function() { | |
| var link, settings; | |
| link = $.el('a', { | |
| className: 'settings-link fa fa-wrench', | |
| textContent: 'Settings', | |
| href: 'javascript:;' | |
| }); | |
| $.on(link, 'click', Settings.open); | |
| Header.addShortcut(link); | |
| $.get('previousversion', null, function(item) { | |
| var changelog, el, previous; | |
| if (previous = item['previousversion']) { | |
| if (previous === g.VERSION) { | |
| return; | |
| } | |
| changelog = 'https://github.com/Spittie/4chan-x/blob/master/CHANGELOG.md'; | |
| el = $.el('span', { | |
| innerHTML: "4chan X has been updated to <a href='" + changelog + "' target=_blank>version " + g.VERSION + "</a>." | |
| }); | |
| if (Conf['Show Updated Notifications']) { | |
| new Notice('info', el, 30); | |
| } | |
| } else { | |
| $.on(d, '4chanXInitFinished', Settings.open); | |
| } | |
| return $.set('previousversion', g.VERSION); | |
| }); | |
| Settings.addSection('Main', Settings.main); | |
| Settings.addSection('Filter', Settings.filter); | |
| Settings.addSection('Sauce', Settings.sauce); | |
| Settings.addSection('Advanced', Settings.advanced); | |
| Settings.addSection('Keybinds', Settings.keybinds); | |
| $.on(d, 'AddSettingsSection', Settings.addSection); | |
| $.on(d, 'OpenSettings', function(e) { | |
| return Settings.open(e.detail); | |
| }); | |
| settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; | |
| if (settings.disableAll) { | |
| return; | |
| } | |
| settings.disableAll = true; | |
| return localStorage.setItem('4chan-settings', JSON.stringify(settings)); | |
| }, | |
| open: function(openSection) { | |
| var dialog, html, link, links, overlay, section, sectionToOpen, _i, _len, _ref; | |
| $.off(d, '4chanXInitFinished', Settings.open); | |
| if (Settings.dialog) { | |
| return; | |
| } | |
| $.event('CloseMenu'); | |
| html = "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a> |<a class=import>Import</a> |<input type=file style='display: none;'><a href='http://seaweedchan.github.io/4chan-x/' target=_blank>4chan X</a> |<a href='https://github.com/Spittie/4chan-x/blob/master/CHANGELOG.md' target=_blank>" + g.VERSION + "</a> |<a href='https://github.com/Spittie/4chan-x/blob/master/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |<a href=javascript:; class=close title=Close>×</a></div></nav><div class=section-container><section></section></div>"; | |
| Settings.overlay = overlay = $.el('div', { | |
| id: 'overlay' | |
| }); | |
| Settings.dialog = dialog = $.el('div', { | |
| id: 'fourchanx-settings', | |
| className: 'dialog', | |
| innerHTML: html | |
| }); | |
| $.on($('.export', Settings.dialog), 'click', Settings["export"]); | |
| $.on($('.import', Settings.dialog), 'click', Settings["import"]); | |
| $.on($('input', Settings.dialog), 'change', Settings.onImport); | |
| links = []; | |
| _ref = Settings.sections; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| section = _ref[_i]; | |
| link = $.el('a', { | |
| className: "tab-" + section.hyphenatedTitle, | |
| textContent: section.title, | |
| href: 'javascript:;' | |
| }); | |
| $.on(link, 'click', Settings.openSection.bind(section)); | |
| links.push(link, $.tn(' | ')); | |
| if (section.title === openSection) { | |
| sectionToOpen = link; | |
| } | |
| } | |
| links.pop(); | |
| $.add($('.sections-list', dialog), links); | |
| (sectionToOpen ? sectionToOpen : links[0]).click(); | |
| $.on($('.close', dialog), 'click', Settings.close); | |
| $.on(overlay, 'click', Settings.close); | |
| $.add(d.body, [overlay, dialog]); | |
| return $.event('OpenSettings', null, dialog); | |
| }, | |
| close: function() { | |
| if (!Settings.dialog) { | |
| return; | |
| } | |
| $.rm(Settings.overlay); | |
| $.rm(Settings.dialog); | |
| delete Settings.overlay; | |
| return delete Settings.dialog; | |
| }, | |
| sections: [], | |
| addSection: function(title, open) { | |
| var hyphenatedTitle, _ref; | |
| if (typeof title !== 'string') { | |
| _ref = title.detail, title = _ref.title, open = _ref.open; | |
| } | |
| hyphenatedTitle = title.toLowerCase().replace(/\s+/g, '-'); | |
| return Settings.sections.push({ | |
| title: title, | |
| hyphenatedTitle: hyphenatedTitle, | |
| open: open | |
| }); | |
| }, | |
| openSection: function() { | |
| var section, selected; | |
| if (selected = $('.tab-selected', Settings.dialog)) { | |
| $.rmClass(selected, 'tab-selected'); | |
| } | |
| $.addClass($(".tab-" + this.hyphenatedTitle, Settings.dialog), 'tab-selected'); | |
| section = $('section', Settings.dialog); | |
| $.rmAll(section); | |
| section.className = "section-" + this.hyphenatedTitle; | |
| this.open(section, g); | |
| section.scrollTop = 0; | |
| return $.event('OpenSettings', null, section); | |
| }, | |
| main: function(section) { | |
| var arr, button, description, div, fs, hiddenNum, input, inputs, items, key, obj, _ref; | |
| items = {}; | |
| inputs = {}; | |
| _ref = Config.main; | |
| for (key in _ref) { | |
| obj = _ref[key]; | |
| fs = $.el('fieldset', { | |
| innerHTML: "<legend>" + key + "</legend>" | |
| }); | |
| for (key in obj) { | |
| arr = obj[key]; | |
| description = arr[1]; | |
| div = $.el('div', { | |
| innerHTML: "<label><input type=checkbox name=\"" + key + "\">" + key + "</label><span class=description>: " + description + "</span>" | |
| }); | |
| input = $('input', div); | |
| $.on(input, 'change', $.cb.checked); | |
| items[key] = Conf[key]; | |
| inputs[key] = input; | |
| $.add(fs, div); | |
| } | |
| $.add(section, fs); | |
| } | |
| $.get(items, function(items) { | |
| var val; | |
| for (key in items) { | |
| val = items[key]; | |
| inputs[key].checked = val; | |
| } | |
| }); | |
| div = $.el('div', { | |
| innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply." | |
| }); | |
| button = $('button', div); | |
| hiddenNum = 0; | |
| $.get('hiddenThreads', { | |
| boards: {} | |
| }, function(item) { | |
| var ID, board, thread, _ref1; | |
| _ref1 = item.hiddenThreads.boards; | |
| for (ID in _ref1) { | |
| board = _ref1[ID]; | |
| for (ID in board) { | |
| thread = board[ID]; | |
| hiddenNum++; | |
| } | |
| } | |
| return button.textContent = "Hidden: " + hiddenNum; | |
| }); | |
| $.get('hiddenPosts', { | |
| boards: {} | |
| }, function(item) { | |
| var ID, board, post, thread, _ref1; | |
| _ref1 = item.hiddenPosts.boards; | |
| for (ID in _ref1) { | |
| board = _ref1[ID]; | |
| for (ID in board) { | |
| thread = board[ID]; | |
| for (ID in thread) { | |
| post = thread[ID]; | |
| hiddenNum++; | |
| } | |
| } | |
| } | |
| return button.textContent = "Hidden: " + hiddenNum; | |
| }); | |
| $.on(button, 'click', function() { | |
| this.textContent = 'Hidden: 0'; | |
| return $.get('hiddenThreads', { | |
| boards: {} | |
| }, function(item) { | |
| var boardID; | |
| for (boardID in item.hiddenThreads.boards) { | |
| localStorage.removeItem("4chan-hide-t-" + boardID); | |
| } | |
| return $["delete"](['hiddenThreads', 'hiddenPosts']); | |
| }); | |
| }); | |
| return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); | |
| }, | |
| "export": function(now, data) { | |
| var a, db, p, _i, _len, _ref; | |
| if (typeof now !== 'number') { | |
| now = Date.now(); | |
| data = { | |
| version: g.VERSION, | |
| date: now | |
| }; | |
| _ref = DataBoard.keys; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| db = _ref[_i]; | |
| Conf[db] = { | |
| boards: {} | |
| }; | |
| } | |
| $.get(Conf, function(Conf) { | |
| delete Conf['archives']; | |
| data.Conf = Conf; | |
| return Settings["export"](now, data); | |
| }); | |
| return; | |
| } | |
| a = $.el('a', { | |
| className: 'warning', | |
| textContent: 'Save me!', | |
| download: "4chan X v" + g.VERSION + "-" + now + ".json", | |
| href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))), | |
| target: '_blank' | |
| }); | |
| p = $('.imp-exp-result', Settings.dialog); | |
| $.rmAll(p); | |
| return $.add(p, a); | |
| }, | |
| "import": function() { | |
| return this.nextElementSibling.click(); | |
| }, | |
| onImport: function() { | |
| var file, output, reader; | |
| if (!(file = this.files[0])) { | |
| return; | |
| } | |
| output = $('.imp-exp-result'); | |
| if (!confirm('Your current settings will be entirely overwritten, are you sure?')) { | |
| output.textContent = 'Import aborted.'; | |
| return; | |
| } | |
| reader = new FileReader(); | |
| reader.onload = function(e) { | |
| var data, err; | |
| try { | |
| data = JSON.parse(e.target.result); | |
| Settings.loadSettings(data); | |
| if (confirm('Import successful. Reload now?')) { | |
| return window.location.reload(); | |
| } | |
| } catch (_error) { | |
| err = _error; | |
| output.textContent = 'Import failed due to an error.'; | |
| return c.error(err.stack); | |
| } | |
| }; | |
| return reader.readAsText(file); | |
| }, | |
| loadSettings: function(data) { | |
| var key, val, version, _ref; | |
| version = data.version.split('.'); | |
| if (version[0] === '2') { | |
| data = Settings.convertSettings(data, { | |
| 'Disable 4chan\'s extension': '', | |
| 'Catalog Links': '', | |
| 'Reply Navigation': '', | |
| 'Show Stubs': 'Stubs', | |
| 'Image Auto-Gif': 'Auto-GIF', | |
| 'Expand From Current': '', | |
| 'Unread Tab Icon': 'Unread Favicon', | |
| 'Post in Title': 'Thread Excerpt', | |
| 'Auto Hide QR': '', | |
| 'Open Reply in New Tab': '', | |
| 'Remember QR size': '', | |
| 'Quote Inline': 'Quote Inlining', | |
| 'Quote Preview': 'Quote Previewing', | |
| 'Indicate OP quote': 'Mark OP Quotes', | |
| 'Indicate Cross-thread Quotes': 'Mark Cross-thread Quotes', | |
| 'Reply Hiding': 'Reply Hiding Buttons', | |
| 'Thread Hiding': 'Thread Hiding Buttons', | |
| 'uniqueid': 'uniqueID', | |
| 'mod': 'capcode', | |
| 'country': 'flag', | |
| 'md5': 'MD5', | |
| 'openEmptyQR': 'Open empty QR', | |
| 'openQR': 'Open QR', | |
| 'openOptions': 'Open settings', | |
| 'close': 'Close', | |
| 'spoiler': 'Spoiler tags', | |
| 'code': 'Code tags', | |
| 'submit': 'Submit QR', | |
| 'watch': 'Watch', | |
| 'update': 'Update', | |
| 'unreadCountTo0': '', | |
| 'expandAllImages': 'Expand images', | |
| 'expandImage': 'Expand image', | |
| 'zero': 'Front page', | |
| 'nextPage': 'Next page', | |
| 'previousPage': 'Previous page', | |
| 'nextThread': 'Next thread', | |
| 'previousThread': 'Previous thread', | |
| 'expandThread': 'Expand thread', | |
| 'openThreadTab': 'Open thread', | |
| 'openThread': 'Open thread tab', | |
| 'nextReply': 'Next reply', | |
| 'previousReply': 'Previous reply', | |
| 'hide': 'Hide', | |
| 'Scrolling': 'Auto Scroll', | |
| 'Verbose': '' | |
| }); | |
| data.Conf.sauces = data.Conf.sauces.replace(/\$\d/g, function(c) { | |
| switch (c) { | |
| case '$1': | |
| return '%TURL'; | |
| case '$2': | |
| return '%URL'; | |
| case '$3': | |
| return '%MD5'; | |
| case '$4': | |
| return '%board'; | |
| default: | |
| return c; | |
| } | |
| }); | |
| _ref = Config.hotkeys; | |
| for (key in _ref) { | |
| val = _ref[key]; | |
| if (key in data.Conf) { | |
| data.Conf[key] = data.Conf[key].replace(/ctrl|alt|meta/g, function(s) { | |
| return "" + (s[0].toUpperCase()) + s.slice(1); | |
| }).replace(/(^|.+\+)[A-Z]$/g, function(s) { | |
| return "Shift+" + s.slice(0, -1) + (s.slice(-1).toLowerCase()); | |
| }); | |
| } | |
| } | |
| data.Conf['WatchedThreads'] = data.WatchedThreads; | |
| } | |
| if (data.Conf['WatchedThreads']) { | |
| data.Conf['watchedThreads'] = { | |
| boards: ThreadWatcher.convert(data.Conf['WatchedThreads']) | |
| }; | |
| delete data.Conf['WatchedThreads']; | |
| } | |
| return $.set(data.Conf); | |
| }, | |
| convertSettings: function(data, map) { | |
| var newKey, prevKey; | |
| for (prevKey in map) { | |
| newKey = map[prevKey]; | |
| if (newKey) { | |
| data.Conf[newKey] = data.Conf[prevKey]; | |
| } | |
| delete data.Conf[prevKey]; | |
| } | |
| return data; | |
| }, | |
| filter: function(section) { | |
| var select; | |
| section.innerHTML = "<select name=filter><option value=guide>Guide</option><option value=name>Name</option><option value=uniqueID>Unique ID</option><option value=tripcode>Tripcode</option><option value=capcode>Capcode</option><option value=email>E-mail</option><option value=subject>Subject</option><option value=comment>Comment</option><option value=flag>Flag</option><option value=filename>Filename</option><option value=dimensions>Image dimensions</option><option value=filesize>Filesize</option><option value=MD5>Image MD5</option></select><div></div>"; | |
| select = $('select', section); | |
| $.on(select, 'change', Settings.selectFilter); | |
| return Settings.selectFilter.call(select); | |
| }, | |
| selectFilter: function() { | |
| var div, name, ta; | |
| div = this.nextElementSibling; | |
| if ((name = this.value) !== 'guide') { | |
| $.rmAll(div); | |
| ta = $.el('textarea', { | |
| name: name, | |
| className: 'field', | |
| spellcheck: false | |
| }); | |
| $.get(name, Conf[name], function(item) { | |
| return ta.value = item[name]; | |
| }); | |
| $.on(ta, 'change', $.cb.value); | |
| $.add(div, ta); | |
| return; | |
| } | |
| return div.innerHTML = "<div class=warning " + (Conf['Filter'] ? 'hidden' : '') + "><code>Filter</code> is disabled.</div><p>Use <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\">regular expressions</a>, one per line.<br>Lines starting with a <code>#</code> will be ignored.<br>For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>MD5 filtering uses exact string matching, not regular expressions.</p><ul>You can use these settings with each regular expression, separate them with semicolons:<li>Per boards, separate them with commas. It is global if not specified.<br>For example: <code>boards:a,jp;</code>.</li><li>Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.</li><li>Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>For example: <code>stub:yes;</code> or <code>stub:no;</code>.</li><li>Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.</li><li>Highlighted OPs will have their threads put on top of the board index by default.<br>For example: <code>top:yes;</code> or <code>top:no;</code>.</li></ul>"; | |
| }, | |
| sauce: function(section) { | |
| var ta; | |
| section.innerHTML = "<div class=warning " + (Conf['Sauce'] ? 'hidden' : '') + "><code>Sauce</code> is disabled.</div><div>Lines starting with a <code>#</code> will be ignored.</div><div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div><ul>These parameters will be replaced by their corresponding values:<li><code>%TURL</code>: Thumbnail URL.</li><li><code>%URL</code>: Full image URL.</li><li><code>%MD5</code>: MD5 hash.</li><li><code>%board</code>: Current board.</li></ul><textarea name=sauces class=field spellcheck=false></textarea>"; | |
| ta = $('textarea', section); | |
| $.get('sauces', Conf['sauces'], function(item) { | |
| return ta.value = item['sauces']; | |
| }); | |
| return $.on(ta, 'change', $.cb.value); | |
| }, | |
| advanced: function(section) { | |
| var archive, boardID, boardOptions, boardSelect, boards, data, event, input, inputs, item, items, name, row, rows, ta, table, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4; | |
| section.innerHTML = "<fieldset><legend>Archiver</legend><div class=\"warning\" " + (Conf['404 Redirect'] ? 'hidden' : '') + "><code>404 Redirect</code> is disabled.</div><select id='archive-board-select'></select><table id='archive-table'><thead><th>Thread redirection</th><th>Post fetching</th><th>File redirection</th></thead><tbody></tbody></table><span class=note>Disabled selections indicate that only one archive is available for that board and redirection type.</span></fieldset><fieldset><legend>Custom Board Navigation</span></legend><div><textarea name=boardnav class=field spellcheck=false></textarea></div><span class=note>New lines will be converted into spaces.</span><br><br><div class=note>In the following examples for /g/, <code>g</code> can be changed to a different board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Twitter link (<code>@</code>).</div><div>Board link: <code>g</code></div><div>Title link: <code>g-title</code></div><div>Board link (Replace with title when on that board): <code>g-replace</code></div><div>Full text link: <code>g-full</code></div><div>Custom text link: <code>g-text:\"Install Gentoo\"</code></div><div>Index-only link: <code>g-index</code></div><div>Catalog-only link: <code>g-catalog</code></div><div>External link: <code>external-text:\"Google\",\"http://www.google.com\"</code></div><div>Combinations are possible: <code>g-index-text:\"Technology Index\"</code></div><div>Full board list toggle: <code>toggle-all</code></div><br><div class=note><code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]</code><br>will give you<br><code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>if you are on /g/.</div></fieldset><fieldset><legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend><div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div><div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div><div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div><div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div><div>Year: <code>%y</code>, <code>%Y</code></div><div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div><div>Minute: <code>%M</code></div><div>Second: <code>%S</code></div></fieldset><fieldset><legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend><div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div></fieldset><fieldset><legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend><div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div><div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div><div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div><div>Spoiler indicator: <code>%p</code></div><div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div><div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div></fieldset><fieldset><legend>Quick Reply Personas <span class=\"warning\" " + (Conf['Quick Reply'] ? 'hidden' : '') + ">is disabled.</span></legend><textarea class=personafield name=\"QR.personas\" class=\"field\" spellcheck=\"false\"></textarea><p>One item per line.<br>Items will be added in the relevant input's auto-completion list.<br>Password items will always be used, since there is no password input.<br>Lines starting with a <code>#</code> will be ignored.</p><ul>You can use these settings with each item, separate them with semicolons:<li>Possible items are: <code>name</code>, <code>email</code>, <code>subject</code> and <code>password</code>.</li><li>Wrap values of items with quotes, like this: <code>email:\"sage\"</code>.</li><li>Force values as defaults with the <code>always</code> keyword, for example: <code>email:\"sage\";always</code>.</li><li>Select specific boards for an item, separated with commas, for example: <code>email:\"sage\";boards:jp;always</code>.</li></ul></fieldset><fieldset><legend>Unread Favicon <span class=warning " + (Conf['Unread Favicon'] ? 'hidden' : '') + ">is disabled.</span></legend><select name=favicon><option value=ferongr>ferongr</option><option value=xat->xat-</option><option value=Mayhem>Mayhem</option><option value=Original>Original</option><option value=Metro>Metro</option></select><span class=favicon-preview></span></fieldset><fieldset><legend>Emoji <span class=warning " + (Conf['Emoji'] ? 'hidden' : '') + ">is disabled.</span></legend><div>Sage Icon: <select name=sageEmoji><option value=\"4chan SS\">4chan SS</option><option value=\"appchan\">appchan</option></select><span class=sage-icon-preview></span></div><div>Position: <select name=emojiPos><option value=\"before\">Before</option><option value=\"after\">After</option></select></div></fieldset><fieldset><legend>Thread Updater <span class=warning " + (Conf['Thread Updater'] ? 'hidden' : '') + ">is disabled.</span></legend><div>Interval: <input type=number name=Interval class=field min=1 value=" + Conf['Interval'] + "></div></fieldset><fieldset><legend><label><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</label></legend><button id=apply-css>Apply CSS</button><textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea></fieldset>"; | |
| items = {}; | |
| inputs = {}; | |
| _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| name = _ref[_i]; | |
| input = $("[name=" + name + "]", section); | |
| items[name] = Conf[name]; | |
| inputs[name] = input; | |
| event = name === 'favicon' || name === 'usercss' || name === 'sageEmoji' || name === 'emojiPos' ? 'change' : 'input'; | |
| $.on(input, event, $.cb.value); | |
| } | |
| ta = $('.personafield', section); | |
| $.get('QR.personas', Conf['QR.personas'], function(item) { | |
| return ta.value = item['QR.personas']; | |
| }); | |
| $.on(ta, 'change', $.cb.value); | |
| $.get(items, function(items) { | |
| var key, val; | |
| for (key in items) { | |
| val = items[key]; | |
| if (key === 'emojiPos') { | |
| continue; | |
| } | |
| input = inputs[key]; | |
| input.value = val; | |
| if (key === 'usercss') { | |
| continue; | |
| } | |
| $.on(input, event, Settings[key]); | |
| Settings[key].call(input); | |
| } | |
| }); | |
| $.on($('input[name=Interval]', section), 'change', ThreadUpdater.cb.interval); | |
| $.on($('input[name="Custom CSS"]', section), 'change', Settings.togglecss); | |
| $.on($.id('apply-css'), 'click', Settings.usercss); | |
| boards = {}; | |
| _ref1 = Redirect.archives; | |
| for (name in _ref1) { | |
| archive = _ref1[name]; | |
| _ref2 = archive.boards; | |
| for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { | |
| boardID = _ref2[_j]; | |
| data = boards[boardID] || (boards[boardID] = { | |
| thread: [], | |
| post: [], | |
| file: [] | |
| }); | |
| data.thread.push(name); | |
| if (archive.software === 'foolfuuka') { | |
| data.post.push(name); | |
| } | |
| if (__indexOf.call(archive.files, boardID) >= 0) { | |
| data.file.push(name); | |
| } | |
| } | |
| } | |
| rows = []; | |
| boardOptions = []; | |
| _ref3 = Object.keys(boards).sort(); | |
| for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { | |
| boardID = _ref3[_k]; | |
| row = $.el('tr', { | |
| className: "board-" + boardID | |
| }); | |
| row.hidden = boardID !== g.BOARD.ID; | |
| boardOptions.push($.el('option', { | |
| textContent: "/" + boardID + "/", | |
| value: "board-" + boardID, | |
| selected: boardID === g.BOARD.ID | |
| })); | |
| data = boards[boardID]; | |
| _ref4 = ['thread', 'post', 'file']; | |
| for (_l = 0, _len3 = _ref4.length; _l < _len3; _l++) { | |
| item = _ref4[_l]; | |
| $.add(row, Settings.addArchiveCell(boardID, data, item)); | |
| } | |
| rows.push(row); | |
| } | |
| $.add($('tbody', section), rows); | |
| boardSelect = $('#archive-board-select', section); | |
| $.add(boardSelect, boardOptions); | |
| table = $.id('archive-table'); | |
| $.on(boardSelect, 'change', function() { | |
| $('tbody > :not([hidden])', table).hidden = true; | |
| return $("tbody > ." + this.value, table).hidden = false; | |
| }); | |
| $.get('selectedArchives', Conf['selectedArchives'], function(_arg) { | |
| var option, selectedArchives, type; | |
| selectedArchives = _arg.selectedArchives; | |
| for (boardID in selectedArchives) { | |
| data = selectedArchives[boardID]; | |
| for (type in data) { | |
| name = data[type]; | |
| if (option = $("select[data-boardid='" + boardID + "'][data-type='" + type + "'] > option[value='" + name + "']", section)) { | |
| option.selected = true; | |
| } | |
| } | |
| } | |
| }); | |
| }, | |
| addArchiveCell: function(boardID, data, type) { | |
| var archive, i, length, options, select, td; | |
| length = data[type].length; | |
| td = $.el('td', { | |
| className: 'archive-cell' | |
| }); | |
| if (!length) { | |
| td.textContent = '--'; | |
| return td; | |
| } | |
| options = []; | |
| i = 0; | |
| while (i < length) { | |
| archive = data[type][i++]; | |
| options.push($.el('option', { | |
| textContent: archive, | |
| value: archive | |
| })); | |
| } | |
| td.innerHTML = '<select></select>'; | |
| select = td.firstElementChild; | |
| if (!(select.disabled = length === 1)) { | |
| select.setAttribute('data-boardid', boardID); | |
| select.setAttribute('data-type', type); | |
| $.on(select, 'change', Settings.saveSelectedArchive); | |
| } | |
| $.add(select, options); | |
| return td; | |
| }, | |
| saveSelectedArchive: function() { | |
| var _this = this; | |
| return $.get('selectedArchives', Conf['selectedArchives'], function(_arg) { | |
| var selectedArchives, _name; | |
| selectedArchives = _arg.selectedArchives; | |
| (selectedArchives[_name = _this.dataset.boardid] || (selectedArchives[_name] = {}))[_this.dataset.type] = _this.value; | |
| return $.set('selectedArchives', selectedArchives); | |
| }); | |
| }, | |
| boardnav: function() { | |
| return Header.generateBoardList(this.value); | |
| }, | |
| time: function() { | |
| var funk; | |
| funk = Time.createFunc(this.value); | |
| return this.nextElementSibling.textContent = funk(Time, new Date()); | |
| }, | |
| backlink: function() { | |
| return this.nextElementSibling.textContent = this.value.replace(/%id/, '123456789'); | |
| }, | |
| fileInfo: function() { | |
| var data, funk; | |
| data = { | |
| isReply: true, | |
| file: { | |
| URL: '//i.4cdn.org/g/src/1334437723720.jpg', | |
| name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg', | |
| size: '276 KB', | |
| sizeInBytes: 276 * 1024, | |
| dimensions: '1280x720', | |
| isImage: true, | |
| isSpoiler: true | |
| } | |
| }; | |
| funk = FileInfo.createFunc(this.value); | |
| return this.nextElementSibling.innerHTML = funk(FileInfo, data); | |
| }, | |
| favicon: function() { | |
| Favicon["switch"](); | |
| if (g.VIEW === 'thread' && Conf['Unread Favicon']) { | |
| Unread.update(); | |
| } | |
| return this.nextElementSibling.innerHTML = "<img src=" + Favicon["default"] + ">\n<img src=" + Favicon.unreadSFW + ">\n<img src=" + Favicon.unreadNSFW + ">\n<img src=" + Favicon.unreadDead + ">"; | |
| }, | |
| sageEmoji: function() { | |
| return this.nextElementSibling.innerHTML = "<img src=data:image/png;base64," + Emoji.sage[this.value] + ">"; | |
| }, | |
| togglecss: function() { | |
| if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = !this.checked) { | |
| CustomCSS.rmStyle(); | |
| } else { | |
| CustomCSS.addStyle(); | |
| } | |
| return $.cb.checked.call(this); | |
| }, | |
| usercss: function() { | |
| return CustomCSS.update(); | |
| }, | |
| keybinds: function(section) { | |
| var arr, input, inputs, items, key, tbody, tr, _ref; | |
| section.innerHTML = "<div class=warning " + (Conf['Keybinds'] ? 'hidden' : '') + "><code>Keybinds</code> are disabled.</div><div>Allowed keys: <kbd>a-z</kbd>, <kbd>0-9</kbd>, <kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>, <kbd>Enter</kbd>, <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>, <kbd>Right</kbd>, <kbd>Left</kbd>.</div><div>Press <kbd>Backspace</kbd> to disable a keybind.</div><table><tbody><tr><th>Actions</th><th>Keybinds</th></tr></tbody></table>"; | |
| tbody = $('tbody', section); | |
| items = {}; | |
| inputs = {}; | |
| _ref = Config.hotkeys; | |
| for (key in _ref) { | |
| arr = _ref[key]; | |
| tr = $.el('tr', { | |
| innerHTML: "<td>" + arr[1] + "</td><td><input class=field></td>" | |
| }); | |
| input = $('input', tr); | |
| input.name = key; | |
| input.spellcheck = false; | |
| items[key] = Conf[key]; | |
| inputs[key] = input; | |
| $.on(input, 'keydown', Settings.keybind); | |
| $.add(tbody, tr); | |
| } | |
| return $.get(items, function(items) { | |
| var val; | |
| for (key in items) { | |
| val = items[key]; | |
| inputs[key].value = val; | |
| } | |
| }); | |
| }, | |
| keybind: function(e) { | |
| var key; | |
| if (e.keyCode === 9) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if ((key = Keybinds.keyCode(e)) == null) { | |
| return; | |
| } | |
| this.value = key; | |
| return $.cb.value.call(this); | |
| } | |
| }; | |
| Main = { | |
| init: function() { | |
| var db, flatten, pathname, _i, _len, _ref, _ref1; | |
| pathname = location.pathname.split('/'); | |
| g.BOARD = new Board(pathname[1]); | |
| if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { | |
| return; | |
| } | |
| g.VIEW = (function() { | |
| switch (pathname[2]) { | |
| case 'res': | |
| return 'thread'; | |
| case 'catalog': | |
| return 'catalog'; | |
| default: | |
| return 'index'; | |
| } | |
| })(); | |
| if (g.VIEW === 'thread') { | |
| g.THREADID = +pathname[3]; | |
| } | |
| flatten = function(parent, obj) { | |
| var key, val; | |
| if (obj instanceof Array) { | |
| Conf[parent] = obj[0]; | |
| } else if (typeof obj === 'object') { | |
| for (key in obj) { | |
| val = obj[key]; | |
| flatten(key, val); | |
| } | |
| } else { | |
| Conf[parent] = obj; | |
| } | |
| }; | |
| flatten(null, Config); | |
| _ref1 = DataBoard.keys; | |
| for (_i = 0, _len = _ref1.length; _i < _len; _i++) { | |
| db = _ref1[_i]; | |
| Conf[db] = { | |
| boards: {} | |
| }; | |
| } | |
| Conf['selectedArchives'] = {}; | |
| Conf['CachedTitles'] = []; | |
| $.get(Conf, function(items) { | |
| $.extend(Conf, items); | |
| return Main.initFeatures(); | |
| }); | |
| return $.on(d, '4chanMainInit', Main.initStyle); | |
| }, | |
| initFeatures: function() { | |
| var init; | |
| switch (location.hostname) { | |
| case 'a.4cdn.org': | |
| return; | |
| case 'sys.4chan.org': | |
| Report.init(); | |
| return; | |
| case 'i.4cdn.org': | |
| $.ready(function() { | |
| var URL, pathname, _ref; | |
| if (Conf['404 Redirect'] && ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found')) { | |
| Redirect.init(); | |
| pathname = location.pathname.split('/'); | |
| URL = Redirect.to('file', { | |
| boardID: g.BOARD.ID, | |
| filename: pathname[pathname.length - 1] | |
| }); | |
| if (URL) { | |
| return location.replace(URL); | |
| } | |
| } | |
| }); | |
| return; | |
| } | |
| init = function(features) { | |
| var err, module, name; | |
| for (name in features) { | |
| module = features[name]; | |
| try { | |
| module.init(); | |
| } catch (_error) { | |
| err = _error; | |
| Main.handleErrors({ | |
| message: "\"" + name + "\" initialization crashed.", | |
| error: err | |
| }); | |
| } | |
| } | |
| }; | |
| init({ | |
| 'Polyfill': Polyfill, | |
| 'Redirect': Redirect, | |
| 'Header': Header, | |
| 'Catalog Links': CatalogLinks, | |
| 'Settings': Settings, | |
| 'Index Generator': Index, | |
| 'Announcement Hiding': PSAHiding, | |
| 'Fourchan thingies': Fourchan, | |
| 'Emoji': Emoji, | |
| 'Color User IDs': IDColor, | |
| 'Custom CSS': CustomCSS, | |
| 'Linkify': Linkify, | |
| 'Reveal Spoilers': RemoveSpoilers, | |
| 'Resurrect Quotes': Quotify, | |
| 'Filter': Filter, | |
| 'Thread Hiding Buttons': ThreadHiding, | |
| 'Reply Hiding Buttons': PostHiding, | |
| 'Recursive': Recursive, | |
| 'Strike-through Quotes': QuoteStrikeThrough, | |
| 'Quick Reply': QR, | |
| 'Menu': Menu, | |
| 'Report Link': ReportLink, | |
| 'Thread Hiding (Menu)': ThreadHiding.menu, | |
| 'Reply Hiding (Menu)': PostHiding.menu, | |
| 'Delete Link': DeleteLink, | |
| 'Filter (Menu)': Filter.menu, | |
| 'Download Link': DownloadLink, | |
| 'Archive Link': ArchiveLink, | |
| 'Quote Inlining': QuoteInline, | |
| 'Quote Previewing': QuotePreview, | |
| 'Quote Backlinks': QuoteBacklink, | |
| 'Mark Quotes of You': QuoteYou, | |
| 'Mark OP Quotes': QuoteOP, | |
| 'Mark Cross-thread Quotes': QuoteCT, | |
| 'Anonymize': Anonymize, | |
| 'Time Formatting': Time, | |
| 'Relative Post Dates': RelativeDates, | |
| 'File Info Formatting': FileInfo, | |
| 'Fappe Tyme': FappeTyme, | |
| 'Gallery': Gallery, | |
| 'Gallery (menu)': Gallery.menu, | |
| 'Sauce': Sauce, | |
| 'Image Expansion': ImageExpand, | |
| 'Image Expansion (Menu)': ImageExpand.menu, | |
| 'Reveal Spoiler Thumbnails': RevealSpoilers, | |
| 'Image Loading': ImageLoader, | |
| 'Image Hover': ImageHover, | |
| 'Thread Expansion': ExpandThread, | |
| 'Thread Excerpt': ThreadExcerpt, | |
| 'Favicon': Favicon, | |
| 'Unread': Unread, | |
| 'Quote Threading': QuoteThreading, | |
| 'Thread Stats': ThreadStats, | |
| 'Thread Updater': ThreadUpdater, | |
| 'Thread Watcher': ThreadWatcher, | |
| 'Thread Watcher (Menu)': ThreadWatcher.menu, | |
| 'Index Navigation': Nav, | |
| 'Keybinds': Keybinds, | |
| 'Show Dice Roll': Dice, | |
| 'Banner': Banner | |
| }); | |
| $.on(d, 'AddCallback', Main.addCallback); | |
| return $.ready(Main.initReady); | |
| }, | |
| initStyle: function() { | |
| var mainStyleSheet, setStyle, style, styleSheets, _ref; | |
| $.off(d, '4chanMainInit', Main.initStyle); | |
| if (!Main.isThisPageLegit() || $.hasClass(doc, 'fourchan-x')) { | |
| return; | |
| } | |
| if ((_ref = $('link[href*=mobile]', d.head)) != null) { | |
| _ref.disabled = true; | |
| } | |
| $.addClass(doc, 'gecko'); | |
| $.addClass(doc, 'fourchan-x'); | |
| $.addClass(doc, 'seaweedchan'); | |
| $.addClass(doc, g.VIEW); | |
| $.addStyle(Main.css); | |
| if (g.VIEW === 'catalog') { | |
| $.addClass(doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace(/_+/g, '-')); | |
| return; | |
| } | |
| style = 'yotsuba-b'; | |
| mainStyleSheet = $('link[title=switch]', d.head); | |
| styleSheets = $$('link[rel="alternate stylesheet"]', d.head); | |
| setStyle = function() { | |
| var styleSheet, _i, _len; | |
| $.rmClass(doc, style); | |
| for (_i = 0, _len = styleSheets.length; _i < _len; _i++) { | |
| styleSheet = styleSheets[_i]; | |
| if (styleSheet.href === mainStyleSheet.href) { | |
| style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); | |
| break; | |
| } | |
| } | |
| return $.addClass(doc, style); | |
| }; | |
| setStyle(); | |
| if (!mainStyleSheet) { | |
| return; | |
| } | |
| return new MutationObserver(setStyle).observe(mainStyleSheet, { | |
| attributes: true, | |
| attributeFilter: ['href'] | |
| }); | |
| }, | |
| initReady: function() { | |
| var GMver, err, href, i, passLink, styleSelector, v, _i, _len, _ref, _ref1; | |
| if ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found') { | |
| if (Conf['404 Redirect'] && g.VIEW === 'thread') { | |
| href = Redirect.to('thread', { | |
| boardID: g.BOARD.ID, | |
| threadID: g.THREADID, | |
| postID: +location.hash.match(/\d+/) | |
| }); | |
| location.replace(href || ("/" + g.BOARD + "/")); | |
| } | |
| return; | |
| } | |
| Main.initStyle(); | |
| if (g.VIEW === 'thread') { | |
| Main.initThread(); | |
| } else { | |
| $.event('4chanXInitFinished'); | |
| } | |
| if (styleSelector = $.id('styleSelector')) { | |
| passLink = $.el('a', { | |
| textContent: '4chan Pass', | |
| href: 'javascript:;' | |
| }); | |
| $.on(passLink, 'click', function() { | |
| return window.open('//sys.4chan.org/auth', 'This will steal your data.', 'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'); | |
| }); | |
| $.before(styleSelector.previousSibling, [$.tn('['), passLink, $.tn(']\u00A0\u00A0')]); | |
| } | |
| GMver = GM_info.version.split('.'); | |
| _ref1 = "1.13".split('.'); | |
| for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) { | |
| v = _ref1[i]; | |
| if (v === GMver[i]) { | |
| continue; | |
| } | |
| (v < GMver[i]) || new Notice('warning', "Your version of Greasemonkey is outdated (v" + GM_info.version + " instead of v1.13 minimum) and 4chan X may not operate correctly.", 30); | |
| break; | |
| } | |
| try { | |
| return localStorage.getItem('4chan-settings'); | |
| } catch (_error) { | |
| err = _error; | |
| return new Notice('warning', 'Cookies need to be enabled on 4chan for 4chan X to operate properly.', 30); | |
| } | |
| }, | |
| initThread: function() { | |
| var err, errors, postRoot, posts, thread, threadRoot, _i, _len, _ref; | |
| if (!(threadRoot = $('.thread'))) { | |
| return; | |
| } | |
| thread = new Thread(+threadRoot.id.slice(1), g.BOARD); | |
| posts = []; | |
| _ref = $$('.thread > .postContainer', threadRoot); | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| postRoot = _ref[_i]; | |
| try { | |
| posts.push(new Post(postRoot, thread, g.BOARD, { | |
| isOriginalMarkup: true | |
| })); | |
| } catch (_error) { | |
| err = _error; | |
| if (!errors) { | |
| errors = []; | |
| } | |
| errors.push({ | |
| message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", | |
| error: err | |
| }); | |
| } | |
| } | |
| if (errors) { | |
| Main.handleErrors(errors); | |
| } | |
| Main.callbackNodes(Thread, [thread]); | |
| return Main.callbackNodesDB(Post, posts, function() { | |
| return $.event('4chanXInitFinished'); | |
| }); | |
| }, | |
| callbackNodes: function(klass, nodes) { | |
| var cb, i, node; | |
| i = 0; | |
| cb = klass.callbacks; | |
| while (node = nodes[i++]) { | |
| cb.execute(node); | |
| } | |
| }, | |
| callbackNodesDB: function(klass, nodes, cb) { | |
| var cbs, errors, fn, i, len, softTask; | |
| errors = null; | |
| len = 0; | |
| i = 0; | |
| cbs = klass.callbacks; | |
| fn = function() { | |
| var node; | |
| node = nodes[i++]; | |
| cbs.execute(node); | |
| return i % 25; | |
| }; | |
| softTask = function() { | |
| while (fn()) { | |
| if (len === i) { | |
| if (cb) { | |
| cb(); | |
| } | |
| return; | |
| } | |
| } | |
| return setTimeout(softTask, 0); | |
| }; | |
| len = nodes.length; | |
| return softTask(); | |
| }, | |
| addCallback: function(e) { | |
| var Klass, obj; | |
| obj = e.detail; | |
| if (typeof obj.callback.name !== 'string') { | |
| throw new Error("Invalid callback name: " + obj.callback.name); | |
| } | |
| switch (obj.type) { | |
| case 'Post': | |
| Klass = Post; | |
| break; | |
| case 'Thread': | |
| Klass = Thread; | |
| break; | |
| default: | |
| return; | |
| } | |
| obj.callback.isAddon = true; | |
| return Klass.callbacks.push(obj.callback); | |
| }, | |
| handleErrors: function(errors) { | |
| var div, error, logs, _i, _len; | |
| if (!(errors instanceof Array)) { | |
| error = errors; | |
| } else if (errors.length === 1) { | |
| error = errors[0]; | |
| } | |
| if (error) { | |
| new Notice('error', Main.parseError(error), 15); | |
| return; | |
| } | |
| div = $.el('div', { | |
| innerHTML: "" + errors.length + " errors occurred. [<a href=javascript:;>show</a>]" | |
| }); | |
| $.on(div.lastElementChild, 'click', function() { | |
| var _ref; | |
| return _ref = this.textContent === 'show' ? ['hide', false] : ['show', true], this.textContent = _ref[0], logs.hidden = _ref[1], _ref; | |
| }); | |
| logs = $.el('div', { | |
| hidden: true | |
| }); | |
| for (_i = 0, _len = errors.length; _i < _len; _i++) { | |
| error = errors[_i]; | |
| $.add(logs, Main.parseError(error)); | |
| } | |
| return new Notice('error', [div, logs], 30); | |
| }, | |
| parseError: function(data) { | |
| var error, message; | |
| c.error(data.message, data.error.stack); | |
| message = $.el('div', { | |
| textContent: data.message | |
| }); | |
| error = $.el('div', { | |
| textContent: data.error | |
| }); | |
| return [message, error]; | |
| }, | |
| isThisPageLegit: function() { | |
| var _ref; | |
| if (!('thisPageIsLegit' in Main)) { | |
| Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((_ref = d.title) !== '4chan - Temporarily Offline' && _ref !== '4chan - Error' && _ref !== '504 Gateway Time-out'); | |
| } | |
| return Main.thisPageIsLegit; | |
| }, | |
| css: "/*! * Font Awesome 4.0.3 * the iconic font designed for Bootstrap * ------------------------------------------------------------------------------ * The full suite of pictographic icons, examples, and documentation can be * found at http://fontawesome.io. Stay up to date on Twitter at * http://twitter.com/fontawesome. * * License * ------------------------------------------------------------------------------ * - The Font Awesome font is licensed under SIL OFL 1.1 - * http://scripts.sil.org/OFL * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - * http://opensource.org/licenses/mit-license.html * - Font Awesome documentation licensed under CC BY 3.0 - * http://creativecommons.org/licenses/by/3.0/ * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: * \"Font Awesome by Dave Gandy - http://fontawesome.io\" * * Author - Dave Gandy * ------------------------------------------------------------------------------ * Email: dave@fontawesome.io * Twitter: http://twitter.com/davegandy * Work: Lead Product Designer @ Kyruus - http://kyruus.com */ @font-face{font-family:FontAwesome;src:url('http://netdna.bootstrapcdn.com/font-awesome/3.1.1/font/fontawesome-webfont.woff?v=3.1.0') format('woff');font-weight:400;font-style:normal}.fa::before{font-family:FontAwesome;font-weight:400;font-style:normal;-webkit-font-smoothing:antialiased;*margin-right:.3em;text-decoration:inherit;display:none;speak:none} :root.shortcut-icons .fa::before, .menu-button .fa::before, .hide-reply-button .fa::before, .hide-thread-button .fa::before {display:inline-block;font-size:13px;visibility:visible} :root.shortcut-icons #shortcuts .fa::before{font-size:15px!important;margin-top:-3px!important;position:relative;top:1px} :root.shortcut-icons .fa, .menu-button .fa{font-size:0;visibility:hidden} :root.shortcut-icons .shortcut.brackets-wrap::after,:root.shortcut-icons .shortcut.brackets-wrap::before{display:none} :root.shortcut-icons a .fa, .menu-button .fa, .hide-reply-button .fa, .hide-thread-button .fa {display:inline} /* Update this line only */ .fa-glass:before{content:\"\\f000\"}.fa-music:before{content:\"\\f001\"}.fa-search:before{content:\"\\f002\"}.fa-envelope-o:before{content:\"\\f003\"}.fa-heart:before{content:\"\\f004\"}.fa-star:before{content:\"\\f005\"}.fa-star-o:before{content:\"\\f006\"}.fa-user:before{content:\"\\f007\"}.fa-film:before{content:\"\\f008\"}.fa-th-large:before{content:\"\\f009\"}.fa-th:before{content:\"\\f00a\"}.fa-th-list:before{content:\"\\f00b\"}.fa-check:before{content:\"\\f00c\"}.fa-times:before{content:\"\\f00d\"}.fa-search-plus:before{content:\"\\f00e\"}.fa-search-minus:before{content:\"\\f010\"}.fa-power-off:before{content:\"\\f011\"}.fa-signal:before{content:\"\\f012\"}.fa-gear:before,.fa-cog:before{content:\"\\f013\"}.fa-trash-o:before{content:\"\\f014\"}.fa-home:before{content:\"\\f015\"}.fa-file-o:before{content:\"\\f016\"}.fa-clock-o:before{content:\"\\f017\"}.fa-road:before{content:\"\\f018\"}.fa-download:before{content:\"\\f019\"}.fa-arrow-circle-o-down:before{content:\"\\f01a\"}.fa-arrow-circle-o-up:before{content:\"\\f01b\"}.fa-inbox:before{content:\"\\f01c\"}.fa-play-circle-o:before{content:\"\\f01d\"}.fa-rotate-right:before,.fa-repeat:before{content:\"\\f01e\"}.fa-refresh:before{content:\"\\f021\"}.fa-list-alt:before{content:\"\\f022\"}.fa-lock:before{content:\"\\f023\"}.fa-flag:before{content:\"\\f024\"}.fa-headphones:before{content:\"\\f025\"}.fa-volume-off:before{content:\"\\f026\"}.fa-volume-down:before{content:\"\\f027\"}.fa-volume-up:before{content:\"\\f028\"}.fa-qrcode:before{content:\"\\f029\"}.fa-barcode:before{content:\"\\f02a\"}.fa-tag:before{content:\"\\f02b\"}.fa-tags:before{content:\"\\f02c\"}.fa-book:before{content:\"\\f02d\"}.fa-bookmark:before{content:\"\\f02e\"}.fa-print:before{content:\"\\f02f\"}.fa-camera:before{content:\"\\f030\"}.fa-font:before{content:\"\\f031\"}.fa-bold:before{content:\"\\f032\"}.fa-italic:before{content:\"\\f033\"}.fa-text-height:before{content:\"\\f034\"}.fa-text-width:before{content:\"\\f035\"}.fa-align-left:before{content:\"\\f036\"}.fa-align-center:before{content:\"\\f037\"}.fa-align-right:before{content:\"\\f038\"}.fa-align-justify:before{content:\"\\f039\"}.fa-list:before{content:\"\\f03a\"}.fa-dedent:before,.fa-outdent:before{content:\"\\f03b\"}.fa-indent:before{content:\"\\f03c\"}.fa-video-camera:before{content:\"\\f03d\"}.fa-picture-o:before{content:\"\\f03e\"}.fa-pencil:before{content:\"\\f040\"}.fa-map-marker:before{content:\"\\f041\"}.fa-adjust:before{content:\"\\f042\"}.fa-tint:before{content:\"\\f043\"}.fa-edit:before,.fa-pencil-square-o:before{content:\"\\f044\"}.fa-share-square-o:before{content:\"\\f045\"}.fa-check-square-o:before{content:\"\\f046\"}.fa-arrows:before{content:\"\\f047\"}.fa-step-backward:before{content:\"\\f048\"}.fa-fast-backward:before{content:\"\\f049\"}.fa-backward:before{content:\"\\f04a\"}.fa-play:before{content:\"\\f04b\"}.fa-pause:before{content:\"\\f04c\"}.fa-stop:before{content:\"\\f04d\"}.fa-forward:before{content:\"\\f04e\"}.fa-fast-forward:before{content:\"\\f050\"}.fa-step-forward:before{content:\"\\f051\"}.fa-eject:before{content:\"\\f052\"}.fa-chevron-left:before{content:\"\\f053\"}.fa-chevron-right:before{content:\"\\f054\"}.fa-plus-circle:before{content:\"\\f055\"}.fa-minus-circle:before{content:\"\\f056\"}.fa-times-circle:before{content:\"\\f057\"}.fa-check-circle:before{content:\"\\f058\"}.fa-question-circle:before{content:\"\\f059\"}.fa-info-circle:before{content:\"\\f05a\"}.fa-crosshairs:before{content:\"\\f05b\"}.fa-times-circle-o:before{content:\"\\f05c\"}.fa-check-circle-o:before{content:\"\\f05d\"}.fa-ban:before{content:\"\\f05e\"}.fa-arrow-left:before{content:\"\\f060\"}.fa-arrow-right:before{content:\"\\f061\"}.fa-arrow-up:before{content:\"\\f062\"}.fa-arrow-down:before{content:\"\\f063\"}.fa-mail-forward:before,.fa-share:before{content:\"\\f064\"}.fa-expand:before{content:\"\\f065\"}.fa-compress:before{content:\"\\f066\"}.fa-plus:before{content:\"\\f067\"}.fa-minus:before{content:\"\\f068\"}.fa-asterisk:before{content:\"\\f069\"}.fa-exclamation-circle:before{content:\"\\f06a\"}.fa-gift:before{content:\"\\f06b\"}.fa-leaf:before{content:\"\\f06c\"}.fa-fire:before{content:\"\\f06d\"}.fa-eye:before{content:\"\\f06e\"}.fa-eye-slash:before{content:\"\\f070\"}.fa-warning:before,.fa-exclamation-triangle:before{content:\"\\f071\"}.fa-plane:before{content:\"\\f072\"}.fa-calendar:before{content:\"\\f073\"}.fa-random:before{content:\"\\f074\"}.fa-comment:before{content:\"\\f075\"}.fa-magnet:before{content:\"\\f076\"}.fa-chevron-up:before{content:\"\\f077\"}.fa-chevron-down:before{content:\"\\f078\"}.fa-retweet:before{content:\"\\f079\"}.fa-shopping-cart:before{content:\"\\f07a\"}.fa-folder:before{content:\"\\f07b\"}.fa-folder-open:before{content:\"\\f07c\"}.fa-arrows-v:before{content:\"\\f07d\"}.fa-arrows-h:before{content:\"\\f07e\"}.fa-bar-chart-o:before{content:\"\\f080\"}.fa-twitter-square:before{content:\"\\f081\"}.fa-facebook-square:before{content:\"\\f082\"}.fa-camera-retro:before{content:\"\\f083\"}.fa-key:before{content:\"\\f084\"}.fa-gears:before,.fa-cogs:before{content:\"\\f085\"}.fa-comments:before{content:\"\\f086\"}.fa-thumbs-o-up:before{content:\"\\f087\"}.fa-thumbs-o-down:before{content:\"\\f088\"}.fa-star-half:before{content:\"\\f089\"}.fa-heart-o:before{content:\"\\f08a\"}.fa-sign-out:before{content:\"\\f08b\"}.fa-linkedin-square:before{content:\"\\f08c\"}.fa-thumb-tack:before{content:\"\\f08d\"}.fa-external-link:before{content:\"\\f08e\"}.fa-sign-in:before{content:\"\\f090\"}.fa-trophy:before{content:\"\\f091\"}.fa-github-square:before{content:\"\\f092\"}.fa-upload:before{content:\"\\f093\"}.fa-lemon-o:before{content:\"\\f094\"}.fa-phone:before{content:\"\\f095\"}.fa-square-o:before{content:\"\\f096\"}.fa-bookmark-o:before{content:\"\\f097\"}.fa-phone-square:before{content:\"\\f098\"}.fa-twitter:before{content:\"\\f099\"}.fa-facebook:before{content:\"\\f09a\"}.fa-github:before{content:\"\\f09b\"}.fa-unlock:before{content:\"\\f09c\"}.fa-credit-card:before{content:\"\\f09d\"}.fa-rss:before{content:\"\\f09e\"}.fa-hdd-o:before{content:\"\\f0a0\"}.fa-bullhorn:before{content:\"\\f0a1\"}.fa-bell:before{content:\"\\f0f3\"}.fa-certificate:before{content:\"\\f0a3\"}.fa-hand-o-right:before{content:\"\\f0a4\"}.fa-hand-o-left:before{content:\"\\f0a5\"}.fa-hand-o-up:before{content:\"\\f0a6\"}.fa-hand-o-down:before{content:\"\\f0a7\"}.fa-arrow-circle-left:before{content:\"\\f0a8\"}.fa-arrow-circle-right:before{content:\"\\f0a9\"}.fa-arrow-circle-up:before{content:\"\\f0aa\"}.fa-arrow-circle-down:before{content:\"\\f0ab\"}.fa-globe:before{content:\"\\f0ac\"}.fa-wrench:before{content:\"\\f0ad\"}.fa-tasks:before{content:\"\\f0ae\"}.fa-filter:before{content:\"\\f0b0\"}.fa-briefcase:before{content:\"\\f0b1\"}.fa-arrows-alt:before{content:\"\\f0b2\"}.fa-group:before,.fa-users:before{content:\"\\f0c0\"}.fa-chain:before,.fa-link:before{content:\"\\f0c1\"}.fa-cloud:before{content:\"\\f0c2\"}.fa-flask:before{content:\"\\f0c3\"}.fa-cut:before,.fa-scissors:before{content:\"\\f0c4\"}.fa-copy:before,.fa-files-o:before{content:\"\\f0c5\"}.fa-paperclip:before{content:\"\\f0c6\"}.fa-save:before,.fa-floppy-o:before{content:\"\\f0c7\"}.fa-square:before{content:\"\\f0c8\"}.fa-bars:before{content:\"\\f0c9\"}.fa-list-ul:before{content:\"\\f0ca\"}.fa-list-ol:before{content:\"\\f0cb\"}.fa-strikethrough:before{content:\"\\f0cc\"}.fa-underline:before{content:\"\\f0cd\"}.fa-table:before{content:\"\\f0ce\"}.fa-magic:before{content:\"\\f0d0\"}.fa-truck:before{content:\"\\f0d1\"}.fa-pinterest:before{content:\"\\f0d2\"}.fa-pinterest-square:before{content:\"\\f0d3\"}.fa-google-plus-square:before{content:\"\\f0d4\"}.fa-google-plus:before{content:\"\\f0d5\"}.fa-money:before{content:\"\\f0d6\"}.fa-caret-down:before{content:\"\\f0d7\"}.fa-caret-up:before{content:\"\\f0d8\"}.fa-caret-left:before{content:\"\\f0d9\"}.fa-caret-right:before{content:\"\\f0da\"}.fa-columns:before{content:\"\\f0db\"}.fa-unsorted:before,.fa-sort:before{content:\"\\f0dc\"}.fa-sort-down:before,.fa-sort-asc:before{content:\"\\f0dd\"}.fa-sort-up:before,.fa-sort-desc:before{content:\"\\f0de\"}.fa-envelope:before{content:\"\\f0e0\"}.fa-linkedin:before{content:\"\\f0e1\"}.fa-rotate-left:before,.fa-undo:before{content:\"\\f0e2\"}.fa-legal:before,.fa-gavel:before{content:\"\\f0e3\"}.fa-dashboard:before,.fa-tachometer:before{content:\"\\f0e4\"}.fa-comment-o:before{content:\"\\f0e5\"}.fa-comments-o:before{content:\"\\f0e6\"}.fa-flash:before,.fa-bolt:before{content:\"\\f0e7\"}.fa-sitemap:before{content:\"\\f0e8\"}.fa-umbrella:before{content:\"\\f0e9\"}.fa-paste:before,.fa-clipboard:before{content:\"\\f0ea\"}.fa-lightbulb-o:before{content:\"\\f0eb\"}.fa-exchange:before{content:\"\\f0ec\"}.fa-cloud-download:before{content:\"\\f0ed\"}.fa-cloud-upload:before{content:\"\\f0ee\"}.fa-user-md:before{content:\"\\f0f0\"}.fa-stethoscope:before{content:\"\\f0f1\"}.fa-suitcase:before{content:\"\\f0f2\"}.fa-bell-o:before{content:\"\\f0a2\"}.fa-coffee:before{content:\"\\f0f4\"}.fa-cutlery:before{content:\"\\f0f5\"}.fa-file-text-o:before{content:\"\\f0f6\"}.fa-building-o:before{content:\"\\f0f7\"}.fa-hospital-o:before{content:\"\\f0f8\"}.fa-ambulance:before{content:\"\\f0f9\"}.fa-medkit:before{content:\"\\f0fa\"}.fa-fighter-jet:before{content:\"\\f0fb\"}.fa-beer:before{content:\"\\f0fc\"}.fa-h-square:before{content:\"\\f0fd\"}.fa-plus-square:before{content:\"\\f0fe\"}.fa-angle-double-left:before{content:\"\\f100\"}.fa-angle-double-right:before{content:\"\\f101\"}.fa-angle-double-up:before{content:\"\\f102\"}.fa-angle-double-down:before{content:\"\\f103\"}.fa-angle-left:before{content:\"\\f104\"}.fa-angle-right:before{content:\"\\f105\"}.fa-angle-up:before{content:\"\\f106\"}.fa-angle-down:before{content:\"\\f107\"}.fa-desktop:before{content:\"\\f108\"}.fa-laptop:before{content:\"\\f109\"}.fa-tablet:before{content:\"\\f10a\"}.fa-mobile-phone:before,.fa-mobile:before{content:\"\\f10b\"}.fa-circle-o:before{content:\"\\f10c\"}.fa-quote-left:before{content:\"\\f10d\"}.fa-quote-right:before{content:\"\\f10e\"}.fa-spinner:before{content:\"\\f110\"}.fa-circle:before{content:\"\\f111\"}.fa-mail-reply:before,.fa-reply:before{content:\"\\f112\"}.fa-github-alt:before{content:\"\\f113\"}.fa-folder-o:before{content:\"\\f114\"}.fa-folder-open-o:before{content:\"\\f115\"}.fa-smile-o:before{content:\"\\f118\"}.fa-frown-o:before{content:\"\\f119\"}.fa-meh-o:before{content:\"\\f11a\"}.fa-gamepad:before{content:\"\\f11b\"}.fa-keyboard-o:before{content:\"\\f11c\"}.fa-flag-o:before{content:\"\\f11d\"}.fa-flag-checkered:before{content:\"\\f11e\"}.fa-terminal:before{content:\"\\f120\"}.fa-code:before{content:\"\\f121\"}.fa-reply-all:before{content:\"\\f122\"}.fa-mail-reply-all:before{content:\"\\f122\"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:\"\\f123\"}.fa-location-arrow:before{content:\"\\f124\"}.fa-crop:before{content:\"\\f125\"}.fa-code-fork:before{content:\"\\f126\"}.fa-unlink:before,.fa-chain-broken:before{content:\"\\f127\"}.fa-question:before{content:\"\\f128\"}.fa-info:before{content:\"\\f129\"}.fa-exclamation:before{content:\"\\f12a\"}.fa-superscript:before{content:\"\\f12b\"}.fa-subscript:before{content:\"\\f12c\"}.fa-eraser:before{content:\"\\f12d\"}.fa-puzzle-piece:before{content:\"\\f12e\"}.fa-microphone:before{content:\"\\f130\"}.fa-microphone-slash:before{content:\"\\f131\"}.fa-shield:before{content:\"\\f132\"}.fa-calendar-o:before{content:\"\\f133\"}.fa-fire-extinguisher:before{content:\"\\f134\"}.fa-rocket:before{content:\"\\f135\"}.fa-maxcdn:before{content:\"\\f136\"}.fa-chevron-circle-left:before{content:\"\\f137\"}.fa-chevron-circle-right:before{content:\"\\f138\"}.fa-chevron-circle-up:before{content:\"\\f139\"}.fa-chevron-circle-down:before{content:\"\\f13a\"}.fa-html5:before{content:\"\\f13b\"}.fa-css3:before{content:\"\\f13c\"}.fa-anchor:before{content:\"\\f13d\"}.fa-unlock-alt:before{content:\"\\f13e\"}.fa-bullseye:before{content:\"\\f140\"}.fa-ellipsis-h:before{content:\"\\f141\"}.fa-ellipsis-v:before{content:\"\\f142\"}.fa-rss-square:before{content:\"\\f143\"}.fa-play-circle:before{content:\"\\f144\"}.fa-ticket:before{content:\"\\f145\"}.fa-minus-square:before{content:\"\\f146\"}.fa-minus-square-o:before{content:\"\\f147\"}.fa-level-up:before{content:\"\\f148\"}.fa-level-down:before{content:\"\\f149\"}.fa-check-square:before{content:\"\\f14a\"}.fa-pencil-square:before{content:\"\\f14b\"}.fa-external-link-square:before{content:\"\\f14c\"}.fa-share-square:before{content:\"\\f14d\"}.fa-compass:before{content:\"\\f14e\"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:\"\\f150\"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:\"\\f151\"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:\"\\f152\"}.fa-euro:before,.fa-eur:before{content:\"\\f153\"}.fa-gbp:before{content:\"\\f154\"}.fa-dollar:before,.fa-usd:before{content:\"\\f155\"}.fa-rupee:before,.fa-inr:before{content:\"\\f156\"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:\"\\f157\"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:\"\\f158\"}.fa-won:before,.fa-krw:before{content:\"\\f159\"}.fa-bitcoin:before,.fa-btc:before{content:\"\\f15a\"}.fa-file:before{content:\"\\f15b\"}.fa-file-text:before{content:\"\\f15c\"}.fa-sort-alpha-asc:before{content:\"\\f15d\"}.fa-sort-alpha-desc:before{content:\"\\f15e\"}.fa-sort-amount-asc:before{content:\"\\f160\"}.fa-sort-amount-desc:before{content:\"\\f161\"}.fa-sort-numeric-asc:before{content:\"\\f162\"}.fa-sort-numeric-desc:before{content:\"\\f163\"}.fa-thumbs-up:before{content:\"\\f164\"}.fa-thumbs-down:before{content:\"\\f165\"}.fa-youtube-square:before{content:\"\\f166\"}.fa-youtube:before{content:\"\\f167\"}.fa-xing:before{content:\"\\f168\"}.fa-xing-square:before{content:\"\\f169\"}.fa-youtube-play:before{content:\"\\f16a\"}.fa-dropbox:before{content:\"\\f16b\"}.fa-stack-overflow:before{content:\"\\f16c\"}.fa-instagram:before{content:\"\\f16d\"}.fa-flickr:before{content:\"\\f16e\"}.fa-adn:before{content:\"\\f170\"}.fa-bitbucket:before{content:\"\\f171\"}.fa-bitbucket-square:before{content:\"\\f172\"}.fa-tumblr:before{content:\"\\f173\"}.fa-tumblr-square:before{content:\"\\f174\"}.fa-long-arrow-down:before{content:\"\\f175\"}.fa-long-arrow-up:before{content:\"\\f176\"}.fa-long-arrow-left:before{content:\"\\f177\"}.fa-long-arrow-right:before{content:\"\\f178\"}.fa-apple:before{content:\"\\f179\"}.fa-windows:before{content:\"\\f17a\"}.fa-android:before{content:\"\\f17b\"}.fa-linux:before{content:\"\\f17c\"}.fa-dribbble:before{content:\"\\f17d\"}.fa-skype:before{content:\"\\f17e\"}.fa-foursquare:before{content:\"\\f180\"}.fa-trello:before{content:\"\\f181\"}.fa-female:before{content:\"\\f182\"}.fa-male:before{content:\"\\f183\"}.fa-gittip:before{content:\"\\f184\"}.fa-sun-o:before{content:\"\\f185\"}.fa-moon-o:before{content:\"\\f186\"}.fa-archive:before{content:\"\\f187\"}.fa-bug:before{content:\"\\f188\"}.fa-vk:before{content:\"\\f189\"}.fa-weibo:before{content:\"\\f18a\"}.fa-renren:before{content:\"\\f18b\"}.fa-pagelines:before{content:\"\\f18c\"}.fa-stack-exchange:before{content:\"\\f18d\"}.fa-arrow-circle-o-right:before{content:\"\\f18e\"}.fa-arrow-circle-o-left:before{content:\"\\f190\"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:\"\\f191\"}.fa-dot-circle-o:before{content:\"\\f192\"}.fa-wheelchair:before{content:\"\\f193\"}.fa-vimeo-square:before{content:\"\\f194\"}.fa-turkish-lira:before,.fa-try:before{content:\"\\f195\"}.fa-plus-square-o:before{content:\"\\f196\"} /* */ .fa-spin::before{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}\n/* General */ .dialog { border: 1px solid; display: block; } .dialog:not(#qr):not(#thread-watcher):not(#header-bar) { box-shadow: 0 1px 2px rgba(0, 0, 0, .15); } #qr, #thread-watcher { box-shadow: -1px 2px 2px rgba(0, 0, 0, 0.25); } .captcha-img, .field { background-color: #FFF; border: 1px solid #CCC; -moz-box-sizing: border-box; box-sizing: border-box; color: #333; font: 13px sans-serif; outline: none; transition: color .25s, border-color .25s; transition: color .25s, border-color .25s; } .field::-moz-placeholder, .field:hover::-moz-placeholder { color: #AAA !important; font-size: 13px !important; opacity: 1.0 !important; } .captch-img:hover, .field:hover { border-color: #999; } .field:hover, .field:focus { color: #000; } .field[disabled] { background-color: #F2F2F2; color: #888; } .field::-webkit-search-decoration { display: none; } .move { cursor: move; overflow: hidden; } label, .watch-thread-link { cursor: pointer; } a[href=\"javascript:;\"] { text-decoration: none; } .warning { color: red; } #boardNavDesktop { display: none !important; } a { outline: none !important; } .painted { border-radius: 3px; padding: 0px 2px; } body>hr, .ad-plea-bottom + hr { display: none; } .board > hr:last-of-type { border-top-color: transparent !important; } div.navLinks { margin-bottom: -10px !important; } .ad-plea { display: none; } .ad-cnt { margin: 10px !important; } /* 4chan style fixes */ .opContainer, .op { display: block !important; overflow: visible !important; } .reply > .file > .fileText { margin: 0 20px; } [hidden] { display: none !important; } div.center:not(.ad-cnt) { display: none !important; } .page-num { margin-right: -8px; } /* fixed, z-index */ #overlay, #fourchanx-settings, #qp, #ihover, #navlinks, .fixed #header-bar, :root.float #updater, :root.float #thread-stats, #qr { position: fixed; } #fourchanx-settings { z-index: 999; } #overlay { z-index: 900; } #notifications { z-index: 70; } #qp, #ihover { z-index: 60; } #menu { z-index: 50; } #navlinks, #updater, #thread-stats { z-index: 40; } .fixed #header-bar.autohide { z-index: 35; } #qr { z-index: 30; } #thread-watcher { z-index: 8; } :root.fixed-watcher #thread-watcher { z-index: 20; } .fixed #header-bar { z-index: 10; } /* Header */ .fixed.top-header body { padding-top: 2em; } .fixed.bottom-header body { padding-bottom: 2em; } .fixed #header-bar { right: 0; left: 0; padding: 3px 4px 4px; } .fixed.top-header #header-bar { top: 0; } .fixed.bottom-header #header-bar { bottom: 0; } #header-bar { border-width: 0; transition: all .1s .05s ease-in-out; } :root.fixed #header-bar { box-shadow: -5px 1px 10px rgba(0, 0, 0, 0.20); } #custom-board-list .current { padding: 1px 1px 4px 1px; } :root.centered-links #shortcuts { width: 300px; text-align: right; } :root.centered-links #header-bar { text-align: center; } #board-list { font-size: 13px; } :root.centered-links #custom-board-list { position: relative; left: 150px; } .fixed.top-header #header-bar { border-bottom-width: 1px; } .fixed.bottom-header #header-bar { box-shadow: 0 -1px 2px rgba(0, 0, 0, .15); border-top-width: 1px; } .fixed.bottom-header #header-bar .menu-button i { border-top: none; border-bottom: 6px solid; } #board-list { text-align: center; } .fixed #header-bar.autohide:not(:hover) { box-shadow: none; transition: all .8s .6s cubic-bezier(.55, .055, .675, .19); } .fixed.top-header #header-bar.autohide:not(:hover) { margin-bottom: -1em; -webkit-transform: translateY(-100%); transform: translateY(-100%); } .fixed.bottom-header #header-bar.autohide:not(:hover) { -webkit-transform: translateY(100%); transform: translateY(100%); } #scroll-marker { left: 0; right: 0; height: 10px; position: absolute; } :root:not(.autohide) #scroll-marker { pointer-events: none; } #header-bar #scroll-marker { display: none; } .fixed #header-bar #scroll-marker { display: block; } .fixed.top-header #header-bar #scroll-marker { top: 100%; } .fixed.bottom-header #header-bar #scroll-marker { bottom: 100%; } #header-bar a:not(.entry):not(.close) { text-decoration: none; } #header-bar a:not(.entry):not(.close):not(.current) { padding: 1px; } #header-bar input { margin: 0; vertical-align: bottom; } #shortcuts:empty { display: none; } .brackets-wrap::before { content: \"\\00a0[\"; } .brackets-wrap::after { content: \"]\\00a0\"; } .dead-thread, .disabled { opacity: .45; } #shortcuts { float: right; } .shortcut { margin-left: 3px; } #navbotright, #navtopright { display: none; } #toggleMsgBtn { display: none !important; } .current { font-weight: bold; } /* 4chan X link brackets */ .brackets-wrap::after { content: \"]\"; } .brackets-wrap::before { content: \"[\"; } /* Notifications */ #notifications { position: fixed; top: 0; height: 0; text-align: center; right: 0; left: 0; transition: all .8s .6s cubic-bezier(.55, .055, .675, .19); } .fixed.top-header #header-bar #notifications { position: absolute; top: 100%; } .notification { color: #FFF; font-weight: 700; text-shadow: 0 1px 2px rgba(0, 0, 0, .5); box-shadow: 0 1px 2px rgba(0, 0, 0, .15); border-radius: 2px; margin: 1px auto; width: 500px; max-width: 100%; position: relative; transition: all .25s ease-in-out; } .notification.error { background-color: hsla(0, 100%, 38%, .9); } .notification.warning { background-color: hsla(36, 100%, 38%, .9); } .notification.info { background-color: hsla(200, 100%, 38%, .9); } .notification.success { background-color: hsla(104, 100%, 38%, .9); } .notification a { color: white; } .notification > .close { padding: 7px; top: 0px; right: 5px; position: absolute; } .notification > .fa-times::before { font-size: 11px !important; } .message { -moz-box-sizing: border-box; box-sizing: border-box; padding: 6px 20px; max-height: 200px; width: 100%; overflow: auto; } /* Settings */ :root.fourchan-x body { -moz-box-sizing: border-box; box-sizing: border-box; } #overlay { background-color: rgba(0, 0, 0, .5); top: 0; left: 0; height: 100%; width: 100%; } #fourchanx-settings { -moz-box-sizing: border-box; box-sizing: border-box; box-shadow: 0 0 15px rgba(0, 0, 0, .15); height: 600px; max-height: 100%; width: 900px; max-width: 100%; margin: auto; padding: 3px; top: 50%; left: 50%; -moz-transform: translate(-50%, -50%); -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } #fourchanx-settings > nav { padding: 2px 2px 0; height: 15px; } #fourchanx-settings > nav a { text-decoration: underline; } #fourchanx-settings > nav a.close { text-decoration: none; padding: 0 2px; } .section-container { overflow: auto; position: absolute; top: 2.1em; right: 5px; bottom: 5px; left: 5px; padding-right: 5px; } .sections-list { padding: 0 3px; float: left; } .credits { float: right; } .tab-selected { font-weight: 700; } .section-sauce ul, .section-advanced ul { list-style: none; margin: 0; } .section-sauce ul { padding: 8px; } .section-advanced ul { padding: 0px; } .section-sauce li, .section-advanced li { padding-left: 4px; } .section-main label { text-decoration: underline; } .section-filter ul { padding: 0; } .section-filter li { margin: 10px 40px; } .section-filter textarea { height: 500px; } .section-sauce textarea { height: 350px; } .section-advanced .field[name=\"boardnav\"] { width: 100%; } .section-advanced textarea { height: 150px; } .section-advanced .archive-cell { min-width: 160px; text-align: center; } .section-advanced #archive-board-select { position: absolute; } .section-advanced .note { font-size: 0.8em; font-style: italic; margin-left: 10px; } .section-advanced .note code { font-style: normal; font-size: 11px; } .section-keybinds .field { font-family: monospace; } #fourchanx-settings fieldset { border: 1px solid; border-radius: 3px; } #fourchanx-settings legend { font-weight: 700; } #fourchanx-settings textarea { font-family: monospace; min-width: 100%; max-width: 100%; } #fourchanx-settings code { color: #000; background-color: #FFF; padding: 0 2px; } .unscroll { overflow: hidden; } /* Index */ :root.index-loading .navLinks, :root.index-loading .board, :root.index-loading .pagelist { display: none; } #index-search { padding-right: 1.5em; width: 100px; transition: color .25s, border-color .25s, width .25s; } #index-search:focus, #index-search[data-searching] { width: 200px; } #index-search-clear { color: gray; margin-left: -1em; } #index-search:not([data-searching]) + #index-search-clear { display: none; } .summary { text-decoration: none; } /* Announcement Hiding */ :root.hide-announcement #globalMessage { display: none; } span.hide-announcement { font-size: 11px; position: relative; bottom: 5px; } .globalMessage, h2, h3 { color: inherit !important; font-size: 13px; font-weight: 100; } /* Unread */ #unread-line { margin: 0; border-color: rgb(255,0,0); } /* Thread Updater */ #updater { background: none; border: none; box-shadow: none; } #updater > .move { padding: 5px 3px 0px; margin-bottom: -3px; } #updater > div:last-child { text-align: center; } #updater input[type=number] { width: 4em; } :root.float #updater { padding: 0px 3px; } .new { color: limegreen; } #update-status.new { margin-right: 5px; } #update-timer { cursor: pointer; } /* Thread Watcher */ #thread-watcher { position: absolute; } #thread-watcher { padding-bottom: 3px; padding-left: 3px; overflow: hidden; white-space: nowrap; min-width: 136px; max-height: 92%; overflow-y: auto; } #thread-watcher .menu-button { bottom: 1px; } :root.fixed-watcher #thread-watcher { position: fixed; } :root:not(.fixed-watcher) #thread-watcher:not(:hover) { max-height: 210px; overflow-y: hidden; } #thread-watcher > .move { padding-top: 3px; } #watched-threads > div { max-width: 250px; overflow: hidden; padding-left: 3px; padding-right: 3px; text-overflow: ellipsis; } #thread-watcher a { text-decoration: none; } #thread-watcher .move>.close { position: absolute; right: 0px; top: 0px; padding: 0px 4px; } .watch-thread-link { padding-top: 18px; width: 18px; height: 0px; display: inline-block; background-repeat: no-repeat; opacity: 0.2; position: relative; top: 1px; } .watch-thread-link.watched { opacity: 1; } /* Thread Stats */ #thread-stats { background: none; border: none; box-shadow: none; } :root.float #post-count, :root.float #file-count { pointer-events: none; } :root.float #thread-stats { padding: 0px 3px; } /* Quote */ .deadlink { text-decoration: none !important; } .backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) { text-decoration: underline !important; } .inlined { opacity: .5; } #qp input, .forwarded { display: none; } .quotelink.forwardlink, .backlink.forwardlink { text-decoration: none; border-bottom: 1px dashed; } .filtered { text-decoration: underline line-through; } :root.hide-backlinks .backlink.filtered { display: none; } .inline { border: 1px solid; display: table; margin: 2px 0; } .inline .post { border: 0 !important; background-color: transparent !important; display: table !important; margin: 0 !important; padding: 1px 2px !important; } #qp > .opContainer::after { content: ''; clear: both; display: table; } #qp .post { border: none; margin: 0; padding: 2px 2px 5px; } #qp img { max-height: 80vh; max-width: 50vw; } .qphl { outline: 2px solid rgba(216, 94, 49, .7); } :root.highlight-own .yourPost > .reply, :root.highlight-you .quotesYou > .reply { border-left: 2px solid rgba(221,0,0,.5); } /* Quote Threading */ .threadContainer { margin-left: 20px; border-left: 1px solid rgba(128,128,128,.3); } .threadOP { clear: both; } /* File */ .fileText:hover .fntrunc, .fileText:not(:hover) .fnfull, .expanded-image > .post > .file > .fileThumb > img[data-md5], :not(.expanded-image) > .post > .file > .fileThumb > .full-image { display: none; } .expanding { opacity: .5; } :root.fit-height .full-image { max-height: 100vh; } :root.fit-width .full-image { max-width: 100%; } :root.gecko.fit-width .full-image { width: 100%; } #ihover { -moz-box-sizing: border-box; box-sizing: border-box; max-height: 100%; max-width: 75%; padding-bottom: 16px; } /* Fappe Tyme */ .fappeTyme .thread > .noFile, .fappeTyme .threadContainer > .noFile { display: none; } /* Werk Tyme */ .werkTyme .post .file { display: none; } /* Index/Reply Navigation */ #navlinks { font-size: 16px; top: 25px; right: 10px; } /* Filter */ .opContainer.filter-highlight { box-shadow: inset 5px 0 rgba(255, 0, 0, .5); } .filter-highlight > .reply { box-shadow: -5px 0 rgba(255, 0, 0, .5); } /* Spoiler text */ :root.reveal-spoilers s { color: white !important; } /* Thread & Reply Hiding */ .hide-thread-button, .hide-reply-button { float: left; margin-right: 4px; padding: 2px; } .hide-thread-button:not(:hover), .hide-reply-button:not(:hover) { opacity: 0.4; } .threadContainer .hide-reply-button { margin-left: 2px !important; position: relative; left: 1px; } .hide-thread-button { margin-top: -1px; } .stub ~ * { display: none !important; } .stub input { display: inline-block; } /* QR */ :root.hide-original-post-form #postForm, :root.hide-original-post-form .postingMode, :root.hide-original-post-form #togglePostForm, #qr.autohide:not(.has-focus):not(:hover) > form, .postingMode ~ #qr select[data-name=thread], #file-n-submit:not(.has-file) #qr-filerm { display: none; } #qr select, #dump-button, #url-button, .remove, .captcha-img { cursor: pointer; } #qr { z-index: 20; position: fixed; padding: 1px; border: 1px solid transparent; min-width: 300px; border-radius: 3px 3px 0 0; } #qrtab { border-radius: 3px 3px 0 0; } #qrtab { margin-bottom: 1px; } #qr .close { float: right; padding: 0 3px; } #qr .warning { min-height: 1.6em; vertical-align: middle; padding: 0 1px; border-width: 1px; border-style: solid; } .qr-link-container { text-align: center; } .qr-link { border-radius: 3px; padding: 6px 10px 5px; font-weight: bold; vertical-align: middle; border-style: solid; border-width: 1px; font-size: 10pt; } .persona { width: 248px; max-width: 100%; min-width: 100%; } #dump-button { width: 10%; margin: 0; margin-right: 4px; font: 13px sans-serif; padding: 1px 0px 2px; opacity: 0.6; } #url-button { width: 10%; margin: 0; margin-right: 4px; font: 13px sans-serif; padding: 1px 0px 2px; opacity: 0.6; } .persona .field:not(#dump) { width: 95px; min-width: 33.3%; max-width: 33.3%; } #qr textarea.field { height: 14.8em; min-height: 9em; } #qr.has-captcha textarea.field { height: 9em; } input.field.tripped:not(:hover):not(:focus) { color: transparent !important; text-shadow: none !important; } #qr textarea { resize: both; } .captcha-img { margin: 0px; text-align: center; background-image: #fff; font-size: 0px; min-height: 59px; min-width: 302px; } .captcha-input{ width: 100%; margin: 1px 0 0; } .captcha-input.error:focus { border-color: rgb(255,0,0) !important; } .field { -moz-box-sizing: border-box; margin: 0px; padding: 2px 4px 3px; } #qr textarea { min-width: 100%; } #qr [type='submit'] { width: 25%; vertical-align: top; } :root.webkit #qr [type='submit'] { height: 24px; } #qr label input[type=\"checkbox\"] { position: relative; top: 2px; } /* Fake File Input */ input#qr-filename { border: none !important; width: 80%; padding: 0px 4px; position: relative; bottom: 1px; background: none !important; } input#qr-filename:not(.edit) { pointer-events: none; } #qr-filename, #qr-filesize, .has-file #qr-no-file { display: none; } #qr-no-file, .has-file #qr-filename, .has-file #qr-filesize { display: inline-block; margin: 0 0 2px; overflow: hidden; text-overflow: ellipsis; vertical-align: top; } #qr-no-file { color: #AAA; padding: 1px 4px; } #qr-filename-container { -moz-box-sizing: border-box; display: inline-block; position: relative; width: 100px; min-width: 74.6%; max-width: 74.6%; margin-right: 0.4%; margin-top: 1px; overflow: hidden; padding: 2px 1px 0; height: 22px; } #qr-filename-container:hover { cursor: text; } #qr-extras-container { position: absolute; right: 0px; } #qr-filerm { margin-right: 3px; z-index: 2; } #file-n-submit { height: 23px; } #qr input[type=file] { visibility: hidden; position: absolute; } /* Thread Select / Spoiler Label */ #qr select[data-name=thread] { float: right; } #qr.has-spoiler .has-file #qr-spoiler-label { width: 6.7%; min-width: 6.7%; max-width: 6.7%; display: inline-block; text-align: center; vertical-align: top; } #qr.has-spoiler #file-n-submit:not(.has-file) #qr-spoiler-label { display: none; } #qr.has-spoiler .has-file #qr-filename-container { max-width: 67.9%; min-width: 67.9%; } #qr-spoiler-label input { position: relative; top: 3px; } /* Dumping UI */ .dump #dump-list-container { display: block; } #dump-list-container { display: none; position: relative; overflow-y: hidden; margin-top: 1px; } #dump-list { overflow-x: auto; overflow-y: hidden; white-space: nowrap; width: 248px; max-width: 100%; min-width: 100%; } #dump-list:hover { overflow-x: auto; } .qr-preview { -moz-box-sizing: border-box; counter-increment: thumbnails; cursor: move; display: inline-block; height: 90px; width: 90px; padding: 2px; opacity: .5; overflow: hidden; position: relative; text-shadow: 0 0 2px #000; -moz-transition: opacity .25s ease-in-out; vertical-align: top; background-size: cover; } .qr-preview:hover, .qr-preview:focus { opacity: .9; } .qr-preview::before { content: counter(thumbnails); color: #fff; position: absolute; top: 3px; right: 3px; text-shadow: 0 0 3px #000, 0 0 8px #000; } .qr-preview#selected { opacity: 1; } .qr-preview.drag { box-shadow: 0 0 10px rgba(0,0,0,.5); } .qr-preview.over { border-color: #fff; } .qr-preview > span { color: #fff; } .remove { background: none; color: #e00; padding: 1px; } a:only-of-type > .remove { display: none; } .remove:hover::after { content: \" Remove\"; } .qr-preview > label { background: rgba(0,0,0,.5); color: #fff; right: 0; bottom: 0; left: 0; position: absolute; text-align: center; } .qr-preview > label > input { margin: 0; } #add-post { cursor: pointer; font-size: 2em; position: absolute; top: 50%; right: 10px; -moz-transform: translateY(-50%); } .textarea { position: relative; } :root.webkit .textarea { margin-bottom: -2px; } #char-count { color: #000; background: hsla(0, 0%, 100%, .5); font-size: 8pt; position: absolute; bottom: 1px; right: 1px; pointer-events: none; } /* Menu */ .menu-button:not(.fa-bars) { display: inline-block; position: relative; cursor: pointer; } .menu-button i { border-top: 6px solid; border-right: 4px solid transparent; border-left: 4px solid transparent; display: inline-block; margin: 2px; vertical-align: middle; } .reply .menu-button, .op .menu-button, #thread-watcher .menu-button { margin-left: -1px !important; position: relative; } .op .menu-button, #thread-watcher .menu-button { top: 1px; } :root.blink .reply .menu-button { position: relative; top: 2px; } :root.blink .op .menu-button, :root.blink #thread-watcher .menu-button { top: 3px; } .menu-button + .container:not(:empty) { margin-left: -5px !important; } #menu { position: fixed; outline: none; } #menu, .submenu { border-radius: 3px; padding-top: 1px; padding-bottom: 3px; } .entry { cursor: pointer; display: block; outline: none; padding: 2px 10px; position: relative; text-decoration: none; white-space: nowrap; min-width: 70px; } .left>.entry.has-submenu { padding-right: 17px !important; } .entry input[type=\"checkbox\"], .entry input[type=\"radio\"] { margin: 0px; position: relative; top: 2px; } .has-submenu::after { content: \"\"; border-left: .5em solid; border-top: .3em solid transparent; border-bottom: .3em solid transparent; display: inline-block; margin: .3em; position: absolute; right: 3px; } .left .has-submenu::after { border-left: 0; border-right: .5em solid; } .submenu { display: none; position: absolute; left: 100%; top: -1px; margin-left: 0px; margin-top: -2px; } .focused > .submenu { display: block; } .imp-exp-result { position: absolute; text-align: center; margin: auto; right: 0px; left: 0px; width: 200px; } .export, .import { cursor: pointer; text-decoration: none !important; } /* Custom Board Titles */ .boardTitle[contenteditable=\"true\"], .boardSubtitle[contenteditable=\"true\"] { cursor: text !important; } div.boardTitle { font-weight: 400 !important; } /* Link Title Favicons */ .linkify.YouTube { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAYAAABr5z2BAAABIklEQVQoz53LvUrDUBjG8bOoOammSf1IoBSvoCB4JeIqOHgBLt6AIMRBBQelWurQ2kERnMRBsBUcIp5FJSBI5oQsJVkkUHh8W0o5nhaFHvjBgef/Mq+Q46RJBMkI/vE+aOus956tnEswIZe1LV0QyJ5sE2GzgZfVMtRNIdiDpccEssdlB1mW4bvTwdvWJtRdErM7U+8S/FJykCRJX5qm+KpVce8UMNLRLbulz4iSjTAMh6Iowsd5BeNadp3nUF0VlxAEwZBotXC0Usa4ll3meZdA1iguwvf9vpvDA2wvmKgYGtSud8suDB4TyGr2PF49D/vra9jRZ1BVdknMzgwuCGSnZEObwu6sBnVTCHZiaC7BhFx2PKdxUidiAH/4lLo9Mv0DELVs9qsOHXwAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.Vimeo { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAIdZUKh6sLlLkLmr4LmsAMp88NrdYVW3MZj7Acstkrt9s1e5E7vN5EfI9JvdtKwuBijp5kpbl30eiDt8aG1uqRr7qTyNehxM+k4PCy3enB3OTg6Ovv9PXw+fz////L9U5WAAAAAXRSTlMAQObYZgAAAIFJREFUeNplz90OwiAMBWAQpAoyxclkP3je/y0H2AQXz0WT8100rRD6kNI9/cRroemQL3hXhoujZYj4OHoAmBvYGcBISwbWBvfXCrytnIDUQMkbsBpagMA7zhtQdyTFQAmIG7IkYniiZuh3XGsPqoOZkMOJOpAcLqUzNFGGu/57fwc1hgtp0mVSyQAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.SoundCloud { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.audio { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAitJREFUOE9jYCAWKJWwavr0KyXWb/FIbDtUFFyzJx6nVofE2Xo5nXsj0rqPNSR0nVkR2Hjmgmfd+U9Otdf+m5Vf/6+SfeU/R9ChVVgNYDRtlfJuuPA/rPfe/4QpD/6nznj0P27Kw/9unff/69Xf+69c/+C/SO7N/0z+OAxgMmmRCe++/r9i3ev/KWvf/vdY8PK/bt/9/wrNV3/IN5y/IVt1YqNg4pGTTP4HsbuA2bhZ2qvpyn+xjIObxAp3VwqlrgngLFyryVy5nhPmZJHANS2cwYexG8BmVC/pWn3hP4NZlzWuQDJI3dIiFnUUuwEsQAOcq87jNcC7fHeLUtJxHF4AGmBWeAavAWH1+1rUUk7giAWjOknllON4DXAs2NEiG4/DBQxAF/CFHfrPYI4jDFSLuJVjNrUJhB/B7gIGo1pJRt99GAZYJK7wLJ1z7Xzl4vu/7aqv/GRBj0bjqAX2qb0nJ7mXH17C4HcUxQA+hymWtSue/C5a9up/9Ozn/7Vr7v1nRY7GqMb91T3b3v6vWvPmf/S0p/9ZQk+DDLCBRSOz06Jqk+o7/21nvfqvsebDf7kZL/5zBaxphkezd+OFn7HzXvz3Wvjmv9a8N//5Ek//ZTBpVYUrMG2X5wjcdl68+uI/wa5Lr3hSNjczGFeywOVZ/bbcVGp//F9izfv/Ql03f3P4LC/HSEQquYwMFnUCDJ7dzBhyjGZNQpye89M5gpfnMvtNUyE2h4PUAQBovvT7lyNljwAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.LiveLeak { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAlNJREFUGBkFwU2LVmUYAODrPu8Z5x1xSpRBXQyFoLsBE+wfiO5atJOgnf9DUPwFgtGinUgEaQsRhHYuMtpEiEWuG5iNjuOcj+c8z911xXcXL/68c3Dw1fzhg0QgEQAAEYGUKXFie9vxlSs/xk/rdavjGEkmkWSih65z4osv9GfOiK6LzEyZ2uGh4dUrmzs72ddlUUhkoiMr4PT167589Mh6c1N0nSRlqrX67dat+PDyZXRT19m5edPnt28rGFHxMcJ6d9fprS1/37tneP3aemPD1uamUydPOru3p5DdGOH0tWsu3LhhxIQJM2qEpRT/Pn3q/du3AhARSmvGTH0lplKMrVkiYpVpQaJlighzhDkzhmEA0fcWoqAfyaFW4zTlgCABxlrNmY4ylUzLsiREprFWc0T2M+ZSjKWY0AEaltZUjJixZJIpuk5pTWlNP2BYFvOyKJkCAKU1tTXHrZlqVWolUxdhxsfVSj9FmJfFMM9GdICGGa01HyMstYpMIFPJVNDPmYZSTOPoOEKHzNRlKpmWWh1j6TpLa2SKTKVWU6Z+Qolwdm/P9QcPZKa2LH69e9eIMs+WCL/cv2/98CGZPrt61am+V9APq1X89eyZ/968obVYaiXT4dGREgG+vnPHeHgYMsH2+fP+efEihtVKv7SWw/6+9/v7KYLMhIywTJPamvOXLomukyRsrNf+ePzYkpl9dJ3SWgSCSCQCfz5/7pMLF2yfO6eLiAQcHRz4/cmT+HR7O+Ob3d0fNt69+7a2BiICQCJbA0EgE5lpvbXl1OXL3/8Pfax4+6SjSukAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.Vocaroo { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAw9JREFUOE9jYMABuMwYmCyTJKUCGlSnFSy02TTzeOyCiQcDViX26qVz2TAyYtWmEMwuoZ3M7V40LcB79pHkc0svpvzY8jD//87nxf+3Pyn8v/ZO8v+VNyP/2mZJumI1QCWSI8232Hjumitlfw5+qPp/9l8TCt76JP//xkdx/wsXWCzjtWFkwTCkbWFe9plPk/+ga4Txz/xt/D/hkN//gMXif21a+NbyWjIwoRiy6GDT5rP/mlFsPfyp5n/NpOj/22+0gMUXXIz/H7hC/L/bFKFbPDZMrHAD5H35OPt2J9zacDv/f3V7xv9FhwrBGubsT/1//Pjx/1GJ/mD+/nfl/1v3Ovy3KRJNQbHdOlXCvOO03/+pm1P/v3v37n90hhtYw9HPtf8Xb2v937cmHswHeWPRxYj/LvkK3igGKARwicTO07118H3V/5kbi/4vPZMJtK3s/6YH2f+Pfq1B8VbjWrdnMu5s4nAD9CNFhKwz5DTUvLl419zKvAcLtG1P84BRl/b/5M/6/6f/NPzf/qzo84yj0Uus0xUU4Zor54bm9+4OfZG02OCuoAMTb9ZkC9ull1Nvrr2Z+XvRpaRfc65H/68F+jl9svEhzyLFWoccWVc+eyTHq/twydjlKRln7jX9bNMkMJnbhoFRL1xCqmKx6/yi2fYXa/c5/e846PV/5fW0/7OPx/yfcjzop34ulxdGGvDuU8mMXaX507lBuiN6ueadmQeT/p/93vf/1O+G//sP5fw/eL3o/5JLif8zVxs+Tlir9S26UyeFQQvJGBE7FvaFZ9LfN+1y+WjbItSb3GmXvXd15v8zroH/HxgE/D+aGPx/18vi/z07PeZNPRKxe/Kh0Ae8toxscCO4zBkYXArk9C1SxJUYjBkYPPIVtbbuTftz3cz//2O9wP/75iSAXdO72/dt2HL5F6YlfBW4MiJYXMiBiW3t7azHBx+V/t89N+H/8a+1//e9K/9attDp5LQjYX8SuvVL8RoAkmxa65299Erq1FnHo0qrl7t4BddriIs4MrM3rfWcFd+pGwVSAwBZ0bKP8yrZPAAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.pastebin { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB1FBMVEUAAAAAAAAAAABWYWwAAABbY3BbYm5dZnFdZXJeZnMEBAQHCAhYYGpdZnFdZnBgaHIlJyomKCooKi09QkdESU5eZGtdYmhdYmleY2lrcXdqb3Rqb3Rqb3SSmJ+SlJeWmJutr7GtrrCWm6ChpKhbW1tmZmZvb290dHR3d3d4eHh5eXl6enp8fHx+gIJ/f3+CgoKDg4OEhISFhYWHh4eKioqKjI2Li4uMjIyOjo6Pj4+QkJCRkZGSkpKUlJSVl5mWlpaYmZqZm52ampqbm5ucnJydnZ2enp6fn5+hoaGioqKkpKSkpaalpaWmp6mmp6qnqauoqKioqquoqq2qqqqrrK2srKysra6srrCsrrGurq6vr6+wsLCxsbGysrKztLa0tLS1t7m2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr7AwMDAwsTBwcHExcfFxcXFxsnGxsbHx8fIyMjJycnMzMzNzc3Ozs7O0NLPz8/Q0NDR0dHR09XT09PV1dXV1dbV1tfV19rW1tbX19fX19jY2tzZ2dnZ2tva2tra3N3a3N7c3Nze3t7f39/f4OHg4ODi4uLl5+jm5ubs7Ozs7e3u7u7v7+/v8PDw8PDx8fHy8vLz8/P29vYSoLMZAAAAJHRSTlMABAUGCwsNHCAiLzMzMzZEYGJwgIuOnJycnqmqq9bc3+/w8fkZ0N/uAAAA/klEQVQoU2NgYGDl5YMDdgYGBmZZ3964CYFtIR3e9Q7K/AwMHI55KfaFmcHWMy3K3MwlGRg4wz0zdYpcorRbNbL0LaWAAp3ts2umV8wo6MupTauQBgqUG03VL7W3sfZSb1erAgm02M+yzYrVCXUy6zapAQlUx/dEdyX3J3ZHVUYVywAF8o2rDNN1Go2jzGLMokAC2QbuSc42mXmaOXop9iAtCXrJ5qXWjT59Abl2ESJAAX/tSIMMiyrrqQ3T6uS5gQK6kSqpqkUermGTexQFmYACflqR+hlWZSamzQpCLEDPsSmVVDT1TJw0JUhOAMRnYOARFRMTE5cQF+ZiBPIAII5B3EVG0b4AAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.gist { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABblBMVEXc3NykpKTW1tbb29ugoKCdnZ0AAAACAgIEDRcKCgoMDAwODg4QIzYRDAoTExMUDwwVAg0WICsaEw8aGhoiCBklGxUmERwwKCQ7LSU7Ozs8LSZFLyNINi1JNyxJNy1KSklMOi5VR1FXV1daQTRkZGRseYZwU0F4eHh7dnR8bWV/YE6IdGiKcGCKkJaNgYeNjY2RdGOScWCUcWCZmZmhoaGkpKSoqKirfmaurq6xsbG1tbW6urq+vr7AbmzBb23CwsLGxsbHx8fHyMjJycnJysrMzMzOiYbPi4fQ0NDRoYbT09PU1NTW1tbY2NjZqIzZ2dnb29vd3d3f39/i4uLktZrk5OTl5eXm5ubn5+fo6Ojq6urs7OzttKLu7u7wuqbw8PDx8fHz8/P4+Pj5+fn7uZj8vpz9ya79ybD/tZf/upr/wZ//w6H/xKH/xaL/xrH/yqj/y7T/zqv/z7D/07D/17n/2Lv/2Lz/3L//38n/4Mk3Q/ZuAAAABnRSTlMSFcbGzc5MNKFvAAAA1klEQVQoz2NgYPZHAswMDEwRSclwkBTBxOARn4gE4j0YXBOiJNUDg7y8Ar1UlOITXBkcY73Z2Li42dg42dn4wmIdGeyjQ7nZoEA4PNqewSZKlw0O9KJsGKwjBdl4ZeWkJGQUhNjEIq0ZrMI5+D0ri7Jz8itCRAXCrRgsQ3mUy+xicrPSbfO0REItGSyCVaVL3ONSU9LcCtQUgy0YzIJ85M1LizMzCsv9xF2CzBhMAwN99TV1DI0MtDWcAgNNGUycA5CAswkDi5kDwrMOZiwMjKzGSICVEQDhZj0UQV7PewAAAABJRU5ErkJggg==') center left no-repeat!important; padding-left: 18px; } .linkify.image { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAs5JREFUOE+lk/tvi1EYx98/xT8gW4REIpGFMEQWl2FiM9ZMZhm2xRAyOsmujFFmdFRHu0tWm87UypxStr69zPauN5e5rHVp3IYhbOvHy+wHEQlxkm+ek+d8nm9OznkeSfrfldmgJC7QyUlTymsJTfuTZ25z4HdWYwyLreYhtpgekGPw0+kKvo1Eo+IXRSIiEhkWZuc9tqnsJD9EqTUopCxjSGTpB0iueczSo1HyW8cpsExQ1DbxI2pt45j9cXpexul4FEd79RnZphAa/SD7WvuFtO6UItbU9LC+YQxNI2w0wwYT5LRAdhOU3oBTIXC9gXP3oUSGgz2vST3gYHejR0jptT1C332f8yrUEYHrz8CgxDnpm6DKCUfc0KnmXa/AEVPPwnDcD0cvetA2uYRk67Ive/lpjO7YBO1PPuF8Df3vwf4cbNE4tqdw7YVq8HYyHx6FvhE1hkMEg8HDUqvFkjT4aIjMqkqyqkswDSrcfBfH+Q561YLAZ/B+BLda6FXlU/cPv0AoEPhuoP1h4Av7Wbh9E/Py15NWWUjeSR3nZDfeN+N0DY9hG/7K1eGP3P0S5/EYRFUF/IOTBrUXHPm9fT6mr1xEwupkZqxbzLyiDJYUZ5NSnkdqdSHpxyrYdFpPgdmAsdfJwPMI/Yr65bf7tZLGGBQ7DNdJWFtIYvoOZmbuZE7OXpIKKli86zAr9p9gTVktWTVnKTI2U95uRWe3U2IJUDbVB5p6hVm5x5m9Vc/cnedZUNzC8lILaQesZBy6hEZ3maKzgvJWFzVWD9XtXvVGQbSWASFtMATVRlJIKbOTWtlJXaeXepuPM1f6MNp9GLt8mLvvYLmp0OhQ2Fwvk6m7xaqDTvY0eYWUVtcnllXfYlGpnfklVuraHHg8HjxuN+6fktUHlWWZPaZeUo/ILK0UKttBcbNbSB9GP0yLxWJJUxoZGUn80zD9C/vXQ/4NHY10h3M1zmQAAAAASUVORK5CYII=') center left no-repeat!important; padding-left: 18px; } .linkify.InstallGentoo { background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABcVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3dIYAAAAAAAAAAAAbGh4BBAcCBgoBBgoCBwsCCQ/QzucCCA7MyuXZ1eUBBQmTh8fo5/i9svIAAADh3vQAAAACCA0CCQ8CCQ4DDBQbGCUDChDr6vgAAAAAAAAREBIDCxK6tdfe2fTv7/cDCxIDDBQEDRUHDhgMJjXk4PZdXWdLUFoUNEYOKDgSMUMRLUBneI4eTGj08/QmW3onW3rTzvfOx/giU3IiVHMkWHdEaYJobHv3+PokWHpua6TNy9xZgZ+1quz8/foQKj0XPFInWn0nW38tZ4o6fqg8gq48grA9hrU/i7pAhrNAiLdBjLtEjr1FksNIjr5Il8pImMtKWnNqhL97odKFqti5q/q5rPq60+nCt/vLw/vPx/jV0vHY0/rc1/rg2/vh3fzn4fzu6/vx8vf19Pv19Pz49/v5+Pv8/Pv8/fr9/vv+/frziVtUAAAAT3RSTlMABQYHCAoNDhARGRobL0ZOV1xdXV5fYGBmZnB0eX2MjZSaoaGio6mqqqustLq7zubo6Ojo6evt7u/x8fLy9/f4+Pj5+vr6+vr6+/39/v7+XKgUSwAAAMhJREFUKM9jYGDg4OZmZgABKINT1dBAhBHIYFMxMBIDisjbhoZbCTExsCu5hoeY8DEwcOkEx8fY6MqpucTGB0izglVEplcU5/gmRYWBVQDNMK+s0hN3SvMyBpsBNJxXw0NfwTEjVQZqHQMHj5RfWW5mliSEC7TPzK6yJD/bXZQRzGdXcisqLy309okA2Q4Eis4peQWmstqBCdGW/CABraC45ERBBs3A6Fh/AbAKTwsHa34QZW8NVsGuLqwswQSjQICTmYMFQaEDAAF8JHLfKGswAAAAAElFTkSuQmCC') center left no-repeat!important; padding-left: 18px; } /* Gallery */ #a-gallery { position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: 30; display: flex; flex-direction: row; background: rgba(0,0,0,0.7); } .gal-viewport { display: flex; align-items: stretch; flex-direction: row; flex: 1 1 auto; } .gal-thumbnails { flex: 0 0 150px; overflow-y: auto; display: flex; flex-direction: column; align-items: stretch; text-align: center; background: rgba(0,0,0,.5); border-left: 1px solid #222; } .gal-hide-thumbnails .gal-thumbnails { display: none; } .gal-thumb img { max-width: 125px; max-height: 125px; height: auto; width: auto; } .gal-thumb { flex: 0 0 auto; padding: 3px; line-height: 0; transition: background .2s linear; } .gal-highlight { background: rgba(0, 190, 255,.8); } .gal-prev { order: 0; border-right: 1px solid #222; } .gal-next { order: 2; border-left: 1px solid #222; } .gal-prev, .gal-next { flex: 0 0 20px; position: relative; cursor: pointer; opacity: 0.7; background-color: rgba(0, 0, 0, 0.3); } .gal-prev:hover, .gal-next:hover { opacity: 1; } .gal-prev::after, .gal-next::after { position: absolute; top: 48.6%; transform: translateY(-50%) display: inline-block; border-top: 11px solid transparent; border-bottom: 11px solid transparent; content: \"\"; } .gal-prev::after { border-right: 12px solid #fff; right: 5px; } .gal-next::after { border-left: 12px solid #fff; right: 3px; } .gal-image { order: 1; flex: 1 0 auto; display: flex; align-items: flex-start; justify-content: space-around; overflow: hidden; /* Flex > Non-Flex child max-width and overflow fix (Firefox only?) */ width: 1%; } :root:not(.gal-fit-height) .gal-image { overflow-y: scroll !important; } :root:not(.gal-fit-width) .gal-image { overflow-x: scroll !important; } .gal-image a { margin: auto; line-height: 0; } .gal-fit-width .gal-image img { max-width: 100%; } .gal-fit-height .gal-image img { /* Chrome doesn't support viewpoint units in calc() http://bugs.chromium.org/168840 \"It looks like the original author of viewport units in WebKit is not coming back to fix this stuff.\" Well, fuck. */ max-height: 95vh; max-height: calc(100vh - 25px); } .gal-buttons { font-size: 2em; margin-right: 10px; top: 5px; } .gal-buttons i { vertical-align: baseline; border-top-width: .4em; border-right-width: .25em; border-left-width: .25em; } .gal-buttons .menu-button { bottom: 2px; color: #ffffff; text-shadow: 0px 0px 1px #000000; } .gal-close { color: #ffffff; text-shadow: 0px 0px 1px #000000; } .gal-buttons, .gal-name, .gal-count { position: fixed; right: 178px; } .gal-hide-thumbnails .gal-buttons, .gal-hide-thumbnails .gal-count, .gal-hide-thumbnails .gal-name { right: 28px; } .gal-name { bottom: 6px; background: rgba(0,0,0,0.6) !important; border-radius: 3px; padding: 1px 5px 2px 5px; text-decoration: none !important; color: white !important; } .gal-name:hover, .gal-close:hover, .gal-buttons .menu-button:hover { color: rgb(95, 95, 101) !important; } .gal-count { bottom: 27px; background: rgba(0,0,0,0.6) !important; border-radius: 3px; padding: 1px 5px 2px 5px; color: #ffffff !important; } :root:not(.gal-fit-width) .gal-name { bottom: 23px !important; } :root:not(.gal-fit-width) .gal-count { bottom: 44px !important; } :root:not(.gal-fit-height):not(.gal-hide-thumbnails) .gal-buttons, :root:not(.gal-fit-height):not(.gal-hide-thumbnails) .gal-name, :root:not(.gal-fit-height):not(.gal-hide-thumbnails) .gal-count { right: 195px !important; } :root.gal-hide-thumbnails:not(.gal-fit-height) .gal-buttons, :root.gal-hide-thumbnails:not(.gal-fit-height) .gal-name, :root.gal-hide-thumbnails:not(.gal-fit-height) .gal-count { right: 44px !important; } @media screen and (resolution: 1dppx) { .fa-bars { font-size: 14px; } #shortcuts .fa-bars { vertical-align: -1px; } }\n/* General */ :root.yotsuba .dialog { background-color: #F0E0D6; border-color: #D9BFB7; } :root.yotsuba .field:focus { border-color: #EA8; } /* Header */ :root.yotsuba #header-bar.dialog { background-color: rgba(240,224,214,0.98); } :root.yotsuba #header-bar, :root.yotsuba #notifications { font-size: 9pt; color: #B86; } :root.yotsuba #board-list a, :root.yotsuba #shortcuts a { color: #800000; } :root.yotsuba.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(178,0,0,0.2); } :root.yotsuba.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.yotsuba #fourchanx-settings fieldset { border-color: #D9BFB7; } /* Quote */ :root.yotsuba .backlink.deadlink { color: #00E !important; } :root.yotsuba .inline { border-color: #D9BFB7; background-color: rgba(255, 255, 255, .14); } /* QR */ .yotsuba #dump-list::-webkit-scrollbar-thumb { background-color: #F0E0D6; border-color: #D9BFB7; } :root.yotsuba .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.yotsuba .qr-link { border-color: rgb(225, 209, 199) rgb(225, 209, 199) rgb(210, 194, 184); background: linear-gradient(#FFEFE5, #F0E0D6) repeat scroll 0% 0% transparent; } :root.yotsuba .qr-link:hover { background: #F0E0D6; } /* Menu */ :root.yotsuba #menu { color: #800000; } :root.yotsuba .entry { font-size: 10pt; } :root.yotsuba .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.yotsuba .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(128,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.yotsuba div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(100,0,0,0.6); }\n/* General */ :root.yotsuba-b .dialog { background-color: #D6DAF0; border-color: #B7C5D9; } :root.yotsuba-b .field:focus { border-color: #98E; } /* Header */ :root.yotsuba-b #header-bar.dialog { background-color: rgba(214,218,240,0.98); } :root.yotsuba-b #header-bar, :root.yotsuba-b #notifications { font-size: 9pt; color: #89A; } :root.yotsuba-b #board-list a, :root.yotsuba-b #shortcuts a { color: #34345C; } :root.yotsuba-b.fixed #custom-board-list .current { border-bottom: 1px solid rgba(30, 30, 255, 0.2); } :root.yotsuba-b.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.yotsuba-b #fourchanx-settings fieldset { border-color: #B7C5D9; } /* Quote */ :root.yotsuba-b .backlink.deadlink { color: #34345C !important; } :root.yotsuba-b .inline { border-color: #B7C5D9; background-color: rgba(255, 255, 255, .14); } /* QR */ .yotsuba-b #dump-list::-webkit-scrollbar-thumb { background-color: #D6DAF0; border-color: #B7C5D9; } :root.yotsuba-b .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.yotsuba-b .qr-link { border-color: rgb(199, 203, 225) rgb(199, 203, 225) rgb(184, 188, 210); background: linear-gradient(#E5E9FF, #D6DAF0) repeat scroll 0% 0% transparent; } :root.yotsuba-b .qr-link:hover { background: #D9DDF3; } /* Menu */ :root.yotsuba-b #menu { color: #000; } :root.yotsuba-b .entry { font-size: 10pt; } :root.yotsuba-b .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.yotsuba-b .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(0,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.yotsuba-b div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(105,10,15,0.6); }\n/* General */ :root.futaba .dialog { background-color: #F0E0D6; border-color: #D9BFB7; } :root.futaba .field:focus { border-color: #EA8; } /* Header */ :root.futaba #header-bar.dialog { background-color: rgba(240,224,214,0.98); } :root.futaba #header-bar, :root.futaba #notifications { font-size: 11pt; color: #B86; } :root.futaba #header-bar a, :root.futaba #notifications a { color: #800000; } :root.futaba.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(178,0,0,0.2); } :root.futaba.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.futaba #fourchanx-settings fieldset { border-color: #D9BFB7; } /* Quote */ :root.futaba .backlink.deadlink { color: #00E !important; } :root.futaba .inline { border-color: #D9BFB7; background-color: rgba(255, 255, 255, .14); } /* QR */ .futaba #dump-list::-webkit-scrollbar-thumb { background-color: #F0E0D6; border-color: #D9BFB7; } :root.futaba .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.futaba .qr-link { border-color: rgb(225, 209, 199) rgb(225, 209, 199) rgb(210, 194, 184); background: linear-gradient(#FFEFE5, #F0E0D6) repeat scroll 0% 0% transparent; } :root.futaba .qr-link:hover { background: #F0E0D6; } /* Menu */ :root.futaba #menu { color: #800000; } :root.futaba .entry { font-size: 12pt; } :root.futaba .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.futaba .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(128,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); }\n/* General */ :root.burichan .dialog { background-color: #D6DAF0; border-color: #B7C5D9; } :root.burichan .field:focus { border-color: #98E; } /* Header */ :root.burichan #header-bar.dialog { background-color: rgba(214,218,240,0.98); } :root.burichan #header-bar, :root.burichan #header-bar #notifications { font-size: 11pt; color: #89A; } :root.burichan #header-bar a, :root.burichan #header-bar #notifications a { color: #34345C; } :root.burichan.fixed #custom-board-list .current { border-bottom: 1px solid rgba(30, 30, 255, 0.2); } :root.burichan.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,0,0,0.2); } /* Settings */ :root.burichan #fourchanx-settings fieldset { border-color: #B7C5D9; } /* Quote */ :root.burichan .backlink.deadlink { color: #34345C !important; } :root.burichan .inline { border-color: #B7C5D9; background-color: rgba(255, 255, 255, .14); } /* QR */ .burichan #dump-list::-webkit-scrollbar-thumb { background-color: #D6DAF0; border-color: #B7C5D9; } :root.burichan .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.burichan .qr-link { border-color: rgb(199, 203, 225) rgb(199, 203, 225) rgb(184, 188, 210); background: linear-gradient(#E5E9FF, #D6DAF0) repeat scroll 0% 0% transparent; } :root.burichan .qr-link:hover { background: #D9DDF3; } /* Menu */ :root.burichan #menu { color: #000000; } :root.burichan .entry { font-size: 12pt; } :root.burichan .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.burichan .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(0,0,0)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); }\n/* General */ :root.tomorrow .dialog { background-color: #282A2E; border-color: #111; } /* Header */ :root.tomorrow #header-bar.dialog { background-color: rgba(40,42,46,0.9); } :root.tomorrow #header-bar, :root.tomorrow #notifications { font-size: 9pt; color: #C5C8C6; } :root.tomorrow #header-bar a, :root.tomorrow #notifications a { color: #81A2BE; } :root.tomorrow.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(83,124,160,0.4); } :root.tomorrow.fixed #custom-board-list .current:hover { border-bottom-color: rgba(95,137,172,0.4); } /* Settings */ :root.tomorrow #fourchanx-settings fieldset { border-color: #111; } /* Quote */ :root.tomorrow .backlink.deadlink { color: #81A2BE !important; } :root.tomorrow .inline { border-color: #111; background-color: rgba(0, 0, 0, .14); } /* QR */ .tomorrow #dump-list::-webkit-scrollbar-thumb { background-color: #282A2E; border-color: #111; } :root.tomorrow .qr-preview { background-color: rgba(255, 255, 255, .15); } :root.tomorrow #qr .field { background-color: rgb(26, 27, 29); color: rgb(197,200,198); border-color: rgb(40, 41, 42); } :root.tomorrow #qr .field:focus { border-color: rgb(129, 162, 190) !important; background-color: rgb(30,32,36); } :root.tomorrow .qr-link { border-color: rgb(25, 27, 31) rgb(25, 27, 31) rgb(10, 12, 16); background: linear-gradient(#37393D, #282A2E) repeat scroll 0% 0% transparent; } :root.tomorrow .qr-link:hover { background: #282A2E; } /* Menu */ :root.tomorrow #menu { color: #C5C8C6; } :root.tomorrow .entry { font-size: 10pt; } :root.tomorrow .focused.entry { background: rgba(0, 0, 0, .33); } /* Watcher Favicon */ :root.tomorrow .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(197,200,198)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.tomorrow div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(167,170,168,0.6); }\n/* General */ :root.photon .dialog { background-color: #DDD; border-color: #CCC; } :root.photon .field:focus { border-color: #EA8; } /* Header */ :root.photon #header-bar.dialog { background-color: rgba(221,221,221,0.98); } :root.photon #header-bar, :root.photon #notifications { font-size: 9pt; color: #333; } :root.photon #header-bar a, :root.photon #notifications a { color: #FF6600; } :root.photon.fixed #custom-board-list a.current { border-bottom: 1px solid rgba(0,74,153,0.2); } :root.photon.fixed #custom-board-list .current:hover { border-bottom-color: rgba(255,51,0,0.2); } /* Settings */ :root.photon #fourchanx-settings fieldset { border-color: #CCC; } /* Quote */ :root.photon .backlink.deadlink { color: #F60 !important; } :root.photon .inline { border-color: #CCC; background-color: rgba(255, 255, 255, .14); } /* QR */ .photon #dump-list::-webkit-scrollbar-thumb { background-color: #DDD; border-color: #CCC; } :root.photon .qr-preview { background-color: rgba(0, 0, 0, .15); } :root.photon .qr-link { border-color: rgb(206, 206, 206) rgb(206, 206, 206) rgb(191, 191, 191); background: linear-gradient(#ECECEC, #DDD) repeat scroll 0% 0% transparent; } :root.photon .qr-link:hover { background: #DDDDDD; } /* Menu */ :root.photon #menu { color: #333; } :root.photon .entry { font-size: 10pt; } :root.photon .focused.entry { background: rgba(255, 255, 255, .33); } /* Watcher Favicon */ :root.photon .watch-thread-link { background-image: url(\"data:image/svg+xml,<svg viewBox='0 0 26 26' preserveAspectRatio='true' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(51,51,51)' d='M24.132,7.971c-2.203-2.205-5.916-2.098-8.25,0.235L15.5,8.588l-0.382-0.382c-2.334-2.333-6.047-2.44-8.25-0.235c-2.204,2.203-2.098,5.916,0.235,8.249l8.396,8.396l8.396-8.396C26.229,13.887,26.336,10.174,24.132,7.971z'/></svg>\"); } /* Board Title */ :root.photon div.boardTitle { font-family: sans-serif !important; text-shadow: 1px 1px 1px rgba(0,74,153,0.6); }" | |
| }; | |
| Main.init(); | |
| }).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment