RainbowDashite

4chanX 2.38.8 FIXED

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