Merge branch 'v3' into Av2

Conflicts:
	builds/4chan-X.js
	builds/4chan-X.meta.js
	builds/4chan-X.user.js
	builds/crx.crx
	builds/crx/manifest.json
	builds/crx/script.js
	css/style.css
	package.json
	src/features.coffee
	src/main.coffee
	src/qr.coffee
This commit is contained in:
Zixaphir 2013-04-16 14:16:49 -07:00
commit b0e1748f0c
21 changed files with 19373 additions and 451 deletions

View File

@ -1,3 +1,20 @@
### 3.1.3 - *2013-04-16*
- Fix Chrome freezing when switching from the `Filter` tab to another tab in the settings.
### 3.1.2 - *2013-04-16*
- Fix error with successful posting.
### 3.1.1 - *2013-04-16*
- Styling adjustments for the announcement toggler.
## 3.1.0 - *2013-04-16*
- **New feature**: `Announcement Hiding`, enabled by default.
- Fix support for www.4chan.org/frames on Chrome.
- Fix quote features not working on dead quotelinks in inlined posts.
- Fix resurrecting dead quotelinks on HTTP.
### 3.0.6 - *2013-04-14*

9246
builds/4chan-X.js Normal file

File diff suppressed because one or more lines are too long

21
builds/4chan-X.meta.js Normal file
View File

@ -0,0 +1,21 @@
// ==UserScript==
// @name 4chan X
// @version 3.1.3
// @namespace 4chan-X
// @description Cross-browser extension for productive lurking on 4chan.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
// @copyright 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @match *://api.4chan.org/*
// @match *://boards.4chan.org/*
// @match *://images.4chan.org/*
// @match *://sys.4chan.org/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://4chan-x.just-believe.in/builds/4chan-X.meta.js
// @downloadURL https://4chan-x.just-believe.in/builds/4chan-X.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript==

9265
builds/4chan-X.user.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg==
// ==/UserScript==
/* appchan x - Version 2.0.0 - 2013-04-14
/* appchan x - Version 2.0.0 - 2013-04-16
* http://zixaphir.github.com/appchan-x/
*
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
@ -42,8 +42,9 @@
*/
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -55,6 +56,7 @@
'Enable 4chan\'s Extension': [false, 'Compatibility between appchan x and 4chan\'s inline extension is NOT guaranteed.'],
'Fixed Header': [false, 'Mayhem X\'s Fixed Header (kinda).'],
'Custom Board Navigation': [false, 'Show custom links instead of the full board list.'],
'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'],
'404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
'Time Formatting': [true, 'Localize and format timestamps.'],
@ -246,7 +248,7 @@
sauces: "https://www.google.com/searchbyimage?image_url=%TURL\nhttp://iqdb.org/?url=%TURL\n#//tineye.com/search?url=%TURL\n#http://saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#http://imgur.com/upload?url=%URL;text:Upload to imgur\n#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/",
'Boards Navigation': 'sticky top',
'Custom CSS': false,
'Bottom header': false,
'Boards Navigation': 'sticky top',
'Header auto-hide': false,
'Header catalog links': false,
boardnav: '[ toggle-all ] [current-title]',
@ -2449,6 +2451,7 @@
innerHTML: html,
id: id
});
el.style.cssText = position;
$.get("" + id + ".position", position, function(item) {
return el.style.cssText = item["" + id + ".position"];
});
@ -2852,6 +2855,8 @@
return root.querySelector(selector);
};
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$$ = function(selector, root) {
if (root == null) {
root = d.body;
@ -2924,10 +2929,7 @@
}
});
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$.extend($, {
engine: 'presto',
id: function(id) {
return d.getElementById(id);
},
@ -3088,7 +3090,7 @@
var node;
while (node = root.firstChild) {
$.rm(node);
root.removeChild(node);
}
},
tn: function(s) {
@ -3354,12 +3356,6 @@
Style = {
init: function() {
this.agent = {
'gecko': '-moz-',
'webkit': '-webkit-',
'presto': '-o-'
}[$.engine];
this.sizing = "" + ($.engine === 'gecko' ? this.agent : '') + "box-sizing";
$.asap((function() {
return d.body;
}), MascotTools.init);
@ -3384,6 +3380,8 @@
});
return this.setup();
},
agent: "-moz-",
sizing: "-moz-box-sizing",
setup: function() {
this.addStyleReady();
if (d.head) {
@ -5268,10 +5266,6 @@
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))),
target: '_blank'
});
if ($.engine !== 'gecko') {
a.click();
return;
}
p = $('.imp-exp-result', Settings.dialog);
$.rmAll(p);
return $.add(p, a);
@ -5442,7 +5436,7 @@
rice: function(section) {
var event, input, inputs, items, name, _i, _len, _ref;
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>\n For example:<br>\n <code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]</code><br>\n will give you<br>\n <code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>\n if you are on /g/.\n </div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
items = {};
inputs = {};
_ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'];
@ -6243,26 +6237,20 @@
return $.set('Boards Navigation', this.textContent);
},
changeBarPosition: function(setting) {
$.rmClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
$.rmClass(doc, 'hide');
switch (setting) {
case 'sticky top':
$.addClass(doc, 'top');
$.addClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'top');
case 'sticky bottom':
$.rmClass(doc, 'top');
$.addClass(doc, 'fixed');
$.addClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'bottom');
case 'top':
$.addClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'top');
case 'hide':
$.rmClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.addClass(doc, 'hide');
}
},
@ -6289,7 +6277,7 @@
headRect = Header.bar.getBoundingClientRect();
top += -headRect.top - headRect.height;
}
return ($.engine === 'webkit' ? d.body : doc).scrollTop += top;
return doc.scrollTop += top;
},
toggleBarVisibility: function(e) {
var hide, message;
@ -6438,6 +6426,67 @@
}
};
PSAHiding = {
init: function() {
if (!Conf['Announcement Hiding']) {
return;
}
$.addClass(doc, 'hide-announcement');
return $.on(d, '4chanXInitFinished', this.setup);
},
setup: function() {
var btn, psa, text;
$.off(d, '4chanXInitFinished', PSAHiding.setup);
if (!(psa = $.id('globalMessage'))) {
$.rmClass(doc, 'hide-announcement');
return;
}
PSAHiding.btn = btn = $.el('a', {
title: 'Toggle announcement.',
href: 'javascript:;'
});
$.on(btn, 'click', PSAHiding.toggle);
text = PSAHiding.trim(psa);
$.get('hiddenPSAs', [], function(item) {
PSAHiding.sync(item['hiddenPSAs']);
$.before(psa, btn);
return $.rmClass(doc, 'hide-announcement');
});
return $.sync('hiddenPSAs', PSAHiding.sync);
},
toggle: function(e) {
var hide, text;
hide = $.hasClass(this, 'hide-announcement');
text = PSAHiding.trim($.id('globalMessage'));
return $.get('hiddenPSAs', [], function(item) {
var hiddenPSAs, i;
hiddenPSAs = item.hiddenPSAs;
if (hide) {
hiddenPSAs.push(text);
} else {
i = hiddenPSAs.indexOf(text);
hiddenPSAs.splice(i, 1);
}
hiddenPSAs = hiddenPSAs.slice(-5);
PSAHiding.sync(hiddenPSAs);
return $.set('hiddenPSAs', hiddenPSAs);
});
},
sync: function(hiddenPSAs) {
var btn, psa, _ref, _ref1;
btn = PSAHiding.btn;
psa = $.id('globalMessage');
return _ref1 = (_ref = PSAHiding.trim(psa), __indexOf.call(hiddenPSAs, _ref) >= 0) ? [true, '<span>[&nbsp;+&nbsp;]</span>', 'show-announcement'] : [false, '<span>[&nbsp;-&nbsp;]</span>', 'hide-announcement'], psa.hidden = _ref1[0], btn.innerHTML = _ref1[1], btn.className = _ref1[2], _ref1;
},
trim: function(psa) {
return psa.textContent.replace(/\W+/g, '').toLowerCase();
}
};
Fourchan = {
init: function() {
var board;
@ -7616,15 +7665,12 @@
}
if (!((0 <= seconds && seconds <= length))) {
if (DeleteLink.cooldown.counting === post) {
node.textContent = 'Delete';
delete DeleteLink.cooldown.counting;
}
return;
}
setTimeout(DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node);
if (seconds === 0) {
node.textContent = 'Delete';
return;
}
return node.textContent = "Delete (" + seconds + ")";
}
}
@ -7637,7 +7683,7 @@
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Download Link']) {
return;
}
if ($.engine === 'gecko' || $.el('a').download === void 0) {
if (!('download' in $.el('a'))) {
return;
}
a = $.el('a', {
@ -7738,19 +7784,23 @@
Keybinds = {
init: function() {
var init;
if (g.VIEW === 'catalog' || !Conf['Keybinds']) {
return;
}
return $.on(d, '4chanXInitFinished', function() {
init = function() {
var node, _i, _len, _ref;
$.off(d, '4chanXInitFinished', init);
$.on(d, 'keydown', Keybinds.keydown);
_ref = $$('[accesskey]');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
node.removeAttribute('accesskey');
}
});
};
return $.on(d, '4chanXInitFinished', init);
},
keydown: function(e) {
var form, key, notification, notifications, op, target, thread, threadRoot, _i, _len;
@ -8034,7 +8084,7 @@
Nav = {
init: function() {
var next, prev, span;
var append, next, prev, span;
switch (g.VIEW) {
case 'index':
@ -8062,9 +8112,11 @@
$.on(prev, 'click', this.prev);
$.on(next, 'click', this.next);
$.add(span, [prev, $.tn(' '), next]);
return $.on(d, '4chanXInitFinished', function() {
append = function() {
$.off(d, '4chanXInitFinished', append);
return $.add(d.body, span);
});
};
return $.on(d, '4chanXInitFinished', append);
},
prev: function() {
if (g.VIEW === 'thread') {
@ -8683,71 +8735,78 @@
});
},
node: function() {
var a, boardID, deadlink, m, post, postID, quote, quoteID, redirect, _i, _len, _ref, _ref1;
var deadlink, _i, _len, _ref;
if (this.isClone) {
return;
}
_ref = $$('.deadlink', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
deadlink = _ref[_i];
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, __slice.call(deadlink.childNodes));
continue;
}
quote = deadlink.textContent;
if (!(postID = (_ref1 = quote.match(/\d+$/)) != null ? _ref1[0] : void 0)) {
continue;
}
boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink',
textContent: quote
});
} else {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', postID);
if (this.isClone) {
if ($.hasClass(deadlink, 'quotelink')) {
this.nodes.quotelinks.push(deadlink);
}
} else if (redirect = Redirect.to({
boardID: boardID,
threadID: 0,
postID: postID
})) {
} else {
Quotify.parseDeadlink.call(this, deadlink);
}
}
},
parseDeadlink: function(deadlink) {
var a, boardID, m, post, postID, quote, quoteID, redirect, _ref;
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, __slice.call(deadlink.childNodes));
return;
}
quote = deadlink.textContent;
if (!(postID = (_ref = quote.match(/\d+$/)) != null ? _ref[0] : void 0)) {
return;
}
boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink',
textContent: quote
});
} else {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(boardID, postID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-postid', postID);
}
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', postID);
}
if (!this.quotes.contains(quoteID)) {
this.quotes.push(quoteID);
} else if (redirect = Redirect.to({
boardID: boardID,
threadID: 0,
postID: postID
})) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(boardID, postID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-postid', postID);
}
if (!a) {
deadlink.textContent += "\u00A0(Dead)";
continue;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
this.nodes.quotelinks.push(a);
}
a = null;
}
if (__indexOf.call(this.quotes, quoteID) < 0) {
this.quotes.push(quoteID);
}
if (!a) {
deadlink.textContent = "" + quote + "\u00A0(Dead)";
return;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
return this.nodes.quotelinks.push(a);
}
}
};
@ -9615,7 +9674,7 @@
}
headRect = Header.bar.getBoundingClientRect();
top = rect.top - headRect.top - headRect.height;
root = $.engine === 'webkit' ? d.body : doc;
root = doc;
if (rect.top < 0) {
root.scrollTop += top;
}
@ -9678,7 +9737,7 @@
if (!(prev.top + prev.height <= 0)) {
return;
}
root = $.engine === 'webkit' ? d.body : doc;
root = doc;
curr = post.nodes.root.getBoundingClientRect();
return root.scrollTop += curr.height - prev.height + curr.top - prev.top;
});
@ -10890,7 +10949,7 @@
$.add(ThreadUpdater.root, nodes);
if (scroll) {
if (Conf['Bottom Scroll']) {
($.engine === 'webkit' ? d.body : doc).scrollTop = d.body.clientHeight;
doc.scrollTop = d.body.clientHeight;
} else {
Header.scrollToPost(nodes[0]);
}
@ -10954,6 +11013,7 @@
});
},
ready: function() {
$.off(d, '4chanXInitFinished', ThreadWatcher.ready);
if (!Main.isThisPageLegit()) {
return;
}
@ -11380,13 +11440,23 @@
});
},
initReady: function() {
$.off(d, '4chanXInitFinished', QR.initReady);
QR.postingIsEnabled = !!$.id('postForm');
if (!QR.postingIsEnabled) {
return;
}
if ($.engine === 'webkit') {
$.on(d, 'paste', QR.paste);
}
$.on(d, 'QRGetSelectedPost', function(_arg) {
var cb;
cb = _arg.detail;
return cb(QR.selected);
});
$.on(d, 'QRAddPreSubmitHook', function(_arg) {
var cb;
cb = _arg.detail;
return QR.preSubmitHooks.push(cb);
});
$.on(d, 'dragover', QR.dragOver);
$.on(d, 'drop', QR.dropFile);
$.on(d, 'dragstart dragend', QR.drag);
@ -11664,7 +11734,7 @@
}
},
quote: function(e) {
var OP, caretPos, com, post, range, s, sel, selectionRoot, text, thread, _ref;
var OP, caretPos, com, index, post, range, s, sel, selectionRoot, text, thread, _ref;
if (e != null) {
e.preventDefault();
@ -11682,6 +11752,12 @@
text += ">" + s + "\n";
}
QR.open();
if (QR.selected.isLocked) {
index = QR.posts.indexOf(QR.selected);
(QR.posts[index + 1] || new QR.post()).select();
$.addClass(QR.nodes.el, 'dump');
QR.cooldown.auto = true;
}
_ref = QR.nodes, com = _ref.com, thread = _ref.thread;
if (!com.value) {
thread.value = OP.ID;
@ -11914,7 +11990,8 @@
rectEl = this.nodes.el.getBoundingClientRect();
rectList = this.nodes.el.parentNode.getBoundingClientRect();
this.nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
return this.load();
this.load();
return $.event('QRPostSelection', this);
};
_Class.prototype.load = function() {
@ -12356,9 +12433,6 @@
QR.mimeTypes = mimeTypes.split(', ');
QR.mimeTypes.push('');
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
if ($.engine !== 'presto') {
nodes.fileInput.accept = "text/*, " + mimeTypes;
}
QR.spoiler = !!$('input[name=spoiler]');
nodes.spoiler.parentElement.hidden = !QR.spoiler;
if (g.BOARD.ID === 'f') {
@ -12426,8 +12500,9 @@
return $.rmClass(this, 'tripped');
}
},
preSubmitHooks: [],
submit: function(e) {
var callbacks, challenge, err, filetag, m, opts, post, postData, response, textOnly, thread, threadID, _ref;
var callbacks, challenge, err, filetag, hook, m, opts, post, postData, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
if (e != null) {
e.preventDefault();
@ -12459,11 +12534,19 @@
err = 'You can\'t reply to this thread anymore.';
} else if (!(post.com || post.file)) {
err = 'No file selected.';
} else if (post.file && thread.fileLimit && !thread.isSticky) {
} else if (post.file && thread.fileLimit) {
err = 'Max limit of image replies has been reached.';
} else {
_ref = QR.preSubmitHooks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
hook = _ref[_i];
if (err = hook(post, thread)) {
break;
}
}
}
if (QR.captcha.isEnabled && !err) {
_ref = QR.captcha.getOne(), challenge = _ref.challenge, response = _ref.response;
_ref1 = QR.captcha.getOne(), challenge = _ref1.challenge, response = _ref1.response;
if (!response) {
err = 'No valid captcha.';
}
@ -12605,7 +12688,7 @@
board: g.BOARD,
threadID: threadID,
postID: postID
}, QR.nodes.el);
});
QR.cooldown.auto = QR.posts.length > 1 && isReply;
if (!(Conf['Persistent QR'] || QR.cooldown.auto)) {
QR.close();
@ -12676,7 +12759,8 @@
DataBoard = (function() {
function DataBoard(key, sync) {
var _this = this;
var init,
_this = this;
this.key = key;
this.data = Conf[key];
@ -12685,9 +12769,11 @@
if (!sync) {
return;
}
$.on(d, '4chanXInitFinished', function() {
init = function() {
$.off(d, '4chanXInitFinished', init);
return _this.sync = sync;
});
};
$.on(d, '4chanXInitFinished', init);
}
DataBoard.prototype["delete"] = function(_arg) {
@ -13241,7 +13327,7 @@
return $.get(Conf, Main.initFeatures);
},
initFeatures: function(items) {
var initFeatures, pathname;
var init, pathname;
Conf = items;
pathname = location.pathname.split('/');
@ -13273,6 +13359,8 @@
Conf["theme"] = Conf["theme_" + g.TYPE];
}
switch (location.hostname) {
case 'api.4chan.org':
return;
case 'sys.4chan.org':
Report.init();
return;
@ -13289,7 +13377,7 @@
});
return;
}
initFeatures = function(features) {
init = function(features) {
var err, module, name;
for (name in features) {
@ -13305,7 +13393,7 @@
}
}
};
initFeatures({
init({
'Polyfill': Polyfill,
'Emoji': Emoji,
'Style': Style,
@ -13315,6 +13403,7 @@
'Header': Header,
'Catalog Links': CatalogLinks,
'Settings': Settings,
'Announcement Hiding': PSAHiding,
'Fourchan thingies': Fourchan,
'Custom CSS': CustomCSS,
'Linkify': Linkify,

View File

@ -19,7 +19,7 @@
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg==
// ==/UserScript==
/* appchan x - Version 2.0.0 - 2013-04-14
/* appchan x - Version 2.0.0 - 2013-04-16
* http://zixaphir.github.com/appchan-x/
*
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
@ -42,8 +42,9 @@
*/
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -55,6 +56,7 @@
'Enable 4chan\'s Extension': [false, 'Compatibility between appchan x and 4chan\'s inline extension is NOT guaranteed.'],
'Fixed Header': [false, 'Mayhem X\'s Fixed Header (kinda).'],
'Custom Board Navigation': [false, 'Show custom links instead of the full board list.'],
'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'],
'404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
'Time Formatting': [true, 'Localize and format timestamps.'],
@ -247,7 +249,7 @@
sauces: "https://www.google.com/searchbyimage?image_url=%TURL\nhttp://iqdb.org/?url=%TURL\n#//tineye.com/search?url=%TURL\n#http://saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#http://imgur.com/upload?url=%URL;text:Upload to imgur\n#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/",
'Boards Navigation': 'sticky top',
'Custom CSS': false,
'Bottom header': false,
'Boards Navigation': 'sticky top',
'Header auto-hide': false,
'Header catalog links': false,
boardnav: '[ toggle-all ] [current-title]',
@ -2446,6 +2448,7 @@
innerHTML: html,
id: id
});
el.style.cssText = position;
$.get("" + id + ".position", position, function(item) {
return el.style.cssText = item["" + id + ".position"];
});
@ -2849,6 +2852,8 @@
return root.querySelector(selector);
};
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$$ = function(selector, root) {
if (root == null) {
root = d.body;
@ -2921,10 +2926,7 @@
}
});
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$.extend($, {
engine: 'gecko',
id: function(id) {
return d.getElementById(id);
},
@ -3085,7 +3087,7 @@
var node;
while (node = root.firstChild) {
$.rm(node);
root.removeChild(node);
}
},
tn: function(s) {
@ -3345,12 +3347,6 @@
Style = {
init: function() {
this.agent = {
'gecko': '-moz-',
'webkit': '-webkit-',
'presto': '-o-'
}[$.engine];
this.sizing = "" + ($.engine === 'gecko' ? this.agent : '') + "box-sizing";
$.asap((function() {
return d.body;
}), MascotTools.init);
@ -3375,6 +3371,8 @@
});
return this.setup();
},
agent: "-o-",
sizing: "box-sizing",
setup: function() {
this.addStyleReady();
if (d.head) {
@ -5259,10 +5257,8 @@
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))),
target: '_blank'
});
if ($.engine !== 'gecko') {
a.click();
return;
}
a.click();
return;
p = $('.imp-exp-result', Settings.dialog);
$.rmAll(p);
return $.add(p, a);
@ -5433,7 +5429,7 @@
rice: function(section) {
var event, input, inputs, items, name, _i, _len, _ref;
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>\n For example:<br>\n <code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]</code><br>\n will give you<br>\n <code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>\n if you are on /g/.\n </div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
items = {};
inputs = {};
_ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'];
@ -6234,26 +6230,20 @@
return $.set('Boards Navigation', this.textContent);
},
changeBarPosition: function(setting) {
$.rmClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
$.rmClass(doc, 'hide');
switch (setting) {
case 'sticky top':
$.addClass(doc, 'top');
$.addClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'top');
case 'sticky bottom':
$.rmClass(doc, 'top');
$.addClass(doc, 'fixed');
$.addClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'bottom');
case 'top':
$.addClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'top');
case 'hide':
$.rmClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.addClass(doc, 'hide');
}
},
@ -6280,7 +6270,7 @@
headRect = Header.bar.getBoundingClientRect();
top += -headRect.top - headRect.height;
}
return ($.engine === 'webkit' ? d.body : doc).scrollTop += top;
return doc.scrollTop += top;
},
toggleBarVisibility: function(e) {
var hide, message;
@ -6429,6 +6419,67 @@
}
};
PSAHiding = {
init: function() {
if (!Conf['Announcement Hiding']) {
return;
}
$.addClass(doc, 'hide-announcement');
return $.on(d, '4chanXInitFinished', this.setup);
},
setup: function() {
var btn, psa, text;
$.off(d, '4chanXInitFinished', PSAHiding.setup);
if (!(psa = $.id('globalMessage'))) {
$.rmClass(doc, 'hide-announcement');
return;
}
PSAHiding.btn = btn = $.el('a', {
title: 'Toggle announcement.',
href: 'javascript:;'
});
$.on(btn, 'click', PSAHiding.toggle);
text = PSAHiding.trim(psa);
$.get('hiddenPSAs', [], function(item) {
PSAHiding.sync(item['hiddenPSAs']);
$.before(psa, btn);
return $.rmClass(doc, 'hide-announcement');
});
return $.sync('hiddenPSAs', PSAHiding.sync);
},
toggle: function(e) {
var hide, text;
hide = $.hasClass(this, 'hide-announcement');
text = PSAHiding.trim($.id('globalMessage'));
return $.get('hiddenPSAs', [], function(item) {
var hiddenPSAs, i;
hiddenPSAs = item.hiddenPSAs;
if (hide) {
hiddenPSAs.push(text);
} else {
i = hiddenPSAs.indexOf(text);
hiddenPSAs.splice(i, 1);
}
hiddenPSAs = hiddenPSAs.slice(-5);
PSAHiding.sync(hiddenPSAs);
return $.set('hiddenPSAs', hiddenPSAs);
});
},
sync: function(hiddenPSAs) {
var btn, psa, _ref, _ref1;
btn = PSAHiding.btn;
psa = $.id('globalMessage');
return _ref1 = (_ref = PSAHiding.trim(psa), __indexOf.call(hiddenPSAs, _ref) >= 0) ? [true, '<span>[&nbsp;+&nbsp;]</span>', 'show-announcement'] : [false, '<span>[&nbsp;-&nbsp;]</span>', 'hide-announcement'], psa.hidden = _ref1[0], btn.innerHTML = _ref1[1], btn.className = _ref1[2], _ref1;
},
trim: function(psa) {
return psa.textContent.replace(/\W+/g, '').toLowerCase();
}
};
Fourchan = {
init: function() {
var board;
@ -7607,15 +7658,12 @@
}
if (!((0 <= seconds && seconds <= length))) {
if (DeleteLink.cooldown.counting === post) {
node.textContent = 'Delete';
delete DeleteLink.cooldown.counting;
}
return;
}
setTimeout(DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node);
if (seconds === 0) {
node.textContent = 'Delete';
return;
}
return node.textContent = "Delete (" + seconds + ")";
}
}
@ -7625,10 +7673,11 @@
init: function() {
var a;
return;
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Download Link']) {
return;
}
if ($.engine === 'gecko' || $.el('a').download === void 0) {
if (!('download' in $.el('a'))) {
return;
}
a = $.el('a', {
@ -7729,19 +7778,23 @@
Keybinds = {
init: function() {
var init;
if (g.VIEW === 'catalog' || !Conf['Keybinds']) {
return;
}
return $.on(d, '4chanXInitFinished', function() {
init = function() {
var node, _i, _len, _ref;
$.off(d, '4chanXInitFinished', init);
$.on(d, 'keydown', Keybinds.keydown);
_ref = $$('[accesskey]');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
node.removeAttribute('accesskey');
}
});
};
return $.on(d, '4chanXInitFinished', init);
},
keydown: function(e) {
var form, key, notification, notifications, op, target, thread, threadRoot, _i, _len;
@ -8025,7 +8078,7 @@
Nav = {
init: function() {
var next, prev, span;
var append, next, prev, span;
switch (g.VIEW) {
case 'index':
@ -8053,9 +8106,11 @@
$.on(prev, 'click', this.prev);
$.on(next, 'click', this.next);
$.add(span, [prev, $.tn(' '), next]);
return $.on(d, '4chanXInitFinished', function() {
append = function() {
$.off(d, '4chanXInitFinished', append);
return $.add(d.body, span);
});
};
return $.on(d, '4chanXInitFinished', append);
},
prev: function() {
if (g.VIEW === 'thread') {
@ -8674,71 +8729,78 @@
});
},
node: function() {
var a, boardID, deadlink, m, post, postID, quote, quoteID, redirect, _i, _len, _ref, _ref1;
var deadlink, _i, _len, _ref;
if (this.isClone) {
return;
}
_ref = $$('.deadlink', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
deadlink = _ref[_i];
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, __slice.call(deadlink.childNodes));
continue;
}
quote = deadlink.textContent;
if (!(postID = (_ref1 = quote.match(/\d+$/)) != null ? _ref1[0] : void 0)) {
continue;
}
boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink',
textContent: quote
});
} else {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', postID);
if (this.isClone) {
if ($.hasClass(deadlink, 'quotelink')) {
this.nodes.quotelinks.push(deadlink);
}
} else if (redirect = Redirect.to({
boardID: boardID,
threadID: 0,
postID: postID
})) {
} else {
Quotify.parseDeadlink.call(this, deadlink);
}
}
},
parseDeadlink: function(deadlink) {
var a, boardID, m, post, postID, quote, quoteID, redirect, _ref;
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, __slice.call(deadlink.childNodes));
return;
}
quote = deadlink.textContent;
if (!(postID = (_ref = quote.match(/\d+$/)) != null ? _ref[0] : void 0)) {
return;
}
boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink',
textContent: quote
});
} else {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(boardID, postID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-postid', postID);
}
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', postID);
}
if (!this.quotes.contains(quoteID)) {
this.quotes.push(quoteID);
} else if (redirect = Redirect.to({
boardID: boardID,
threadID: 0,
postID: postID
})) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(boardID, postID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-postid', postID);
}
if (!a) {
deadlink.textContent += "\u00A0(Dead)";
continue;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
this.nodes.quotelinks.push(a);
}
a = null;
}
if (__indexOf.call(this.quotes, quoteID) < 0) {
this.quotes.push(quoteID);
}
if (!a) {
deadlink.textContent = "" + quote + "\u00A0(Dead)";
return;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
return this.nodes.quotelinks.push(a);
}
}
};
@ -9606,7 +9668,7 @@
}
headRect = Header.bar.getBoundingClientRect();
top = rect.top - headRect.top - headRect.height;
root = $.engine === 'webkit' ? d.body : doc;
root = doc;
if (rect.top < 0) {
root.scrollTop += top;
}
@ -9669,7 +9731,7 @@
if (!(prev.top + prev.height <= 0)) {
return;
}
root = $.engine === 'webkit' ? d.body : doc;
root = doc;
curr = post.nodes.root.getBoundingClientRect();
return root.scrollTop += curr.height - prev.height + curr.top - prev.top;
});
@ -10881,7 +10943,7 @@
$.add(ThreadUpdater.root, nodes);
if (scroll) {
if (Conf['Bottom Scroll']) {
($.engine === 'webkit' ? d.body : doc).scrollTop = d.body.clientHeight;
doc.scrollTop = d.body.clientHeight;
} else {
Header.scrollToPost(nodes[0]);
}
@ -10945,6 +11007,7 @@
});
},
ready: function() {
$.off(d, '4chanXInitFinished', ThreadWatcher.ready);
if (!Main.isThisPageLegit()) {
return;
}
@ -11371,13 +11434,23 @@
});
},
initReady: function() {
$.off(d, '4chanXInitFinished', QR.initReady);
QR.postingIsEnabled = !!$.id('postForm');
if (!QR.postingIsEnabled) {
return;
}
if ($.engine === 'webkit') {
$.on(d, 'paste', QR.paste);
}
$.on(d, 'QRGetSelectedPost', function(_arg) {
var cb;
cb = _arg.detail;
return cb(QR.selected);
});
$.on(d, 'QRAddPreSubmitHook', function(_arg) {
var cb;
cb = _arg.detail;
return QR.preSubmitHooks.push(cb);
});
$.on(d, 'dragover', QR.dragOver);
$.on(d, 'drop', QR.dropFile);
$.on(d, 'dragstart dragend', QR.drag);
@ -11655,7 +11728,7 @@
}
},
quote: function(e) {
var OP, caretPos, com, post, range, s, sel, selectionRoot, text, thread, _ref;
var OP, caretPos, com, index, post, range, s, sel, selectionRoot, text, thread, _ref;
if (e != null) {
e.preventDefault();
@ -11673,6 +11746,12 @@
text += ">" + s + "\n";
}
QR.open();
if (QR.selected.isLocked) {
index = QR.posts.indexOf(QR.selected);
(QR.posts[index + 1] || new QR.post()).select();
$.addClass(QR.nodes.el, 'dump');
QR.cooldown.auto = true;
}
_ref = QR.nodes, com = _ref.com, thread = _ref.thread;
if (!com.value) {
thread.value = OP.ID;
@ -11911,7 +11990,8 @@
rectEl = this.nodes.el.getBoundingClientRect();
rectList = this.nodes.el.parentNode.getBoundingClientRect();
this.nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
return this.load();
this.load();
return $.event('QRPostSelection', this);
};
_Class.prototype.load = function() {
@ -12355,9 +12435,7 @@
QR.mimeTypes = mimeTypes.split(', ');
QR.mimeTypes.push('');
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
if ($.engine !== 'presto') {
nodes.fileInput.accept = "text/*, " + mimeTypes;
}
nodes.fileInput.accept = "text/*, " + mimeTypes;
QR.spoiler = !!$('input[name=spoiler]');
nodes.spoiler.parentElement.hidden = !QR.spoiler;
if (g.BOARD.ID === 'f') {
@ -12442,8 +12520,9 @@
return $.rmClass(this, 'tripped');
}
},
preSubmitHooks: [],
submit: function(e) {
var callbacks, challenge, err, filetag, m, opts, post, postData, response, textOnly, thread, threadID, _ref;
var callbacks, challenge, err, filetag, hook, m, opts, post, postData, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
if (e != null) {
e.preventDefault();
@ -12475,11 +12554,19 @@
err = 'You can\'t reply to this thread anymore.';
} else if (!(post.com || post.file)) {
err = 'No file selected.';
} else if (post.file && thread.fileLimit && !thread.isSticky) {
} else if (post.file && thread.fileLimit) {
err = 'Max limit of image replies has been reached.';
} else {
_ref = QR.preSubmitHooks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
hook = _ref[_i];
if (err = hook(post, thread)) {
break;
}
}
}
if (QR.captcha.isEnabled && !err) {
_ref = QR.captcha.getOne(), challenge = _ref.challenge, response = _ref.response;
_ref1 = QR.captcha.getOne(), challenge = _ref1.challenge, response = _ref1.response;
if (!response) {
err = 'No valid captcha.';
}
@ -12620,7 +12707,7 @@
board: g.BOARD,
threadID: threadID,
postID: postID
}, QR.nodes.el);
});
QR.cooldown.auto = QR.posts.length > 1 && isReply;
if (!(Conf['Persistent QR'] || QR.cooldown.auto)) {
QR.close();
@ -12691,7 +12778,8 @@
DataBoard = (function() {
function DataBoard(key, sync) {
var _this = this;
var init,
_this = this;
this.key = key;
this.data = Conf[key];
@ -12700,9 +12788,11 @@
if (!sync) {
return;
}
$.on(d, '4chanXInitFinished', function() {
init = function() {
$.off(d, '4chanXInitFinished', init);
return _this.sync = sync;
});
};
$.on(d, '4chanXInitFinished', init);
}
DataBoard.prototype["delete"] = function(_arg) {
@ -13256,7 +13346,7 @@
return $.get(Conf, Main.initFeatures);
},
initFeatures: function(items) {
var initFeatures, pathname;
var init, pathname;
Conf = items;
pathname = location.pathname.split('/');
@ -13288,6 +13378,8 @@
Conf["theme"] = Conf["theme_" + g.TYPE];
}
switch (location.hostname) {
case 'api.4chan.org':
return;
case 'sys.4chan.org':
Report.init();
return;
@ -13304,7 +13396,7 @@
});
return;
}
initFeatures = function(features) {
init = function(features) {
var err, module, name;
for (name in features) {
@ -13320,7 +13412,7 @@
}
}
};
initFeatures({
init({
'Polyfill': Polyfill,
'Emoji': Emoji,
'Style': Style,
@ -13330,6 +13422,7 @@
'Header': Header,
'Catalog Links': CatalogLinks,
'Settings': Settings,
'Announcement Hiding': PSAHiding,
'Fourchan thingies': Fourchan,
'Custom CSS': CustomCSS,
'Linkify': Linkify,

Binary file not shown.

Binary file not shown.

View File

@ -11,6 +11,7 @@
"content_scripts": [{
"js": ["script.js"],
"matches": ["*://api.4chan.org/*","*://boards.4chan.org/*","*://images.4chan.org/*","*://sys.4chan.org/*"],
"all_frames": true,
"run_at": "document_start"
}],
"homepage_url": "http://zixaphir.github.com/appchan-x/",

View File

@ -1,4 +1,4 @@
/* appchan x - Version 2.0.0 - 2013-04-14
/* appchan x - Version 2.0.0 - 2013-04-16
* http://zixaphir.github.com/appchan-x/
*
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
@ -21,8 +21,9 @@
*/
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -34,6 +35,7 @@
'Enable 4chan\'s Extension': [false, 'Compatibility between appchan x and 4chan\'s inline extension is NOT guaranteed.'],
'Fixed Header': [false, 'Mayhem X\'s Fixed Header (kinda).'],
'Custom Board Navigation': [false, 'Show custom links instead of the full board list.'],
'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'],
'404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
'Time Formatting': [true, 'Localize and format timestamps.'],
@ -225,7 +227,7 @@
sauces: "https://www.google.com/searchbyimage?image_url=%TURL\nhttp://iqdb.org/?url=%TURL\n#//tineye.com/search?url=%TURL\n#http://saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#http://imgur.com/upload?url=%URL;text:Upload to imgur\n#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/",
'Boards Navigation': 'sticky top',
'Custom CSS': false,
'Bottom header': false,
'Boards Navigation': 'sticky top',
'Header auto-hide': false,
'Header catalog links': false,
boardnav: '[ toggle-all ] [current-title]',
@ -2424,6 +2426,7 @@
innerHTML: html,
id: id
});
el.style.cssText = position;
$.get("" + id + ".position", position, function(item) {
return el.style.cssText = item["" + id + ".position"];
});
@ -2827,6 +2830,8 @@
return root.querySelector(selector);
};
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$$ = function(selector, root) {
if (root == null) {
root = d.body;
@ -2899,10 +2904,7 @@
}
});
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$.extend($, {
engine: 'webkit',
id: function(id) {
return d.getElementById(id);
},
@ -3063,7 +3065,7 @@
var node;
while (node = root.firstChild) {
$.rm(node);
root.removeChild(node);
}
},
tn: function(s) {
@ -3290,12 +3292,6 @@
Style = {
init: function() {
this.agent = {
'gecko': '-moz-',
'webkit': '-webkit-',
'presto': '-o-'
}[$.engine];
this.sizing = "" + ($.engine === 'gecko' ? this.agent : '') + "box-sizing";
$.asap((function() {
return d.body;
}), MascotTools.init);
@ -3320,6 +3316,8 @@
});
return this.setup();
},
agent: "-webkit-",
sizing: "box-sizing",
setup: function() {
this.addStyleReady();
if (d.head) {
@ -5204,10 +5202,8 @@
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))),
target: '_blank'
});
if ($.engine !== 'gecko') {
a.click();
return;
}
a.click();
return;
p = $('.imp-exp-result', Settings.dialog);
$.rmAll(p);
return $.add(p, a);
@ -5378,7 +5374,7 @@
rice: function(section) {
var event, input, inputs, items, name, _i, _len, _ref;
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>\n For example:<br>\n <code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]</code><br>\n will give you<br>\n <code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>\n if you are on /g/.\n </div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
items = {};
inputs = {};
_ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'];
@ -6179,26 +6175,20 @@
return $.set('Boards Navigation', this.textContent);
},
changeBarPosition: function(setting) {
$.rmClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
$.rmClass(doc, 'hide');
switch (setting) {
case 'sticky top':
$.addClass(doc, 'top');
$.addClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'top');
case 'sticky bottom':
$.rmClass(doc, 'top');
$.addClass(doc, 'fixed');
$.addClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'bottom');
case 'top':
$.addClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.rmClass(doc, 'hide');
return $.addClass(doc, 'top');
case 'hide':
$.rmClass(doc, 'top');
$.rmClass(doc, 'fixed');
$.rmClass(doc, 'bottom');
return $.addClass(doc, 'hide');
}
},
@ -6225,7 +6215,7 @@
headRect = Header.bar.getBoundingClientRect();
top += -headRect.top - headRect.height;
}
return ($.engine === 'webkit' ? d.body : doc).scrollTop += top;
return d.body.scrollTop += top;
},
toggleBarVisibility: function(e) {
var hide, message;
@ -6374,6 +6364,67 @@
}
};
PSAHiding = {
init: function() {
if (!Conf['Announcement Hiding']) {
return;
}
$.addClass(doc, 'hide-announcement');
return $.on(d, '4chanXInitFinished', this.setup);
},
setup: function() {
var btn, psa, text;
$.off(d, '4chanXInitFinished', PSAHiding.setup);
if (!(psa = $.id('globalMessage'))) {
$.rmClass(doc, 'hide-announcement');
return;
}
PSAHiding.btn = btn = $.el('a', {
title: 'Toggle announcement.',
href: 'javascript:;'
});
$.on(btn, 'click', PSAHiding.toggle);
text = PSAHiding.trim(psa);
$.get('hiddenPSAs', [], function(item) {
PSAHiding.sync(item['hiddenPSAs']);
$.before(psa, btn);
return $.rmClass(doc, 'hide-announcement');
});
return $.sync('hiddenPSAs', PSAHiding.sync);
},
toggle: function(e) {
var hide, text;
hide = $.hasClass(this, 'hide-announcement');
text = PSAHiding.trim($.id('globalMessage'));
return $.get('hiddenPSAs', [], function(item) {
var hiddenPSAs, i;
hiddenPSAs = item.hiddenPSAs;
if (hide) {
hiddenPSAs.push(text);
} else {
i = hiddenPSAs.indexOf(text);
hiddenPSAs.splice(i, 1);
}
hiddenPSAs = hiddenPSAs.slice(-5);
PSAHiding.sync(hiddenPSAs);
return $.set('hiddenPSAs', hiddenPSAs);
});
},
sync: function(hiddenPSAs) {
var btn, psa, _ref, _ref1;
btn = PSAHiding.btn;
psa = $.id('globalMessage');
return _ref1 = (_ref = PSAHiding.trim(psa), __indexOf.call(hiddenPSAs, _ref) >= 0) ? [true, '<span>[&nbsp;+&nbsp;]</span>', 'show-announcement'] : [false, '<span>[&nbsp;-&nbsp;]</span>', 'hide-announcement'], psa.hidden = _ref1[0], btn.innerHTML = _ref1[1], btn.className = _ref1[2], _ref1;
},
trim: function(psa) {
return psa.textContent.replace(/\W+/g, '').toLowerCase();
}
};
Fourchan = {
init: function() {
var board;
@ -7552,15 +7603,12 @@
}
if (!((0 <= seconds && seconds <= length))) {
if (DeleteLink.cooldown.counting === post) {
node.textContent = 'Delete';
delete DeleteLink.cooldown.counting;
}
return;
}
setTimeout(DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node);
if (seconds === 0) {
node.textContent = 'Delete';
return;
}
return node.textContent = "Delete (" + seconds + ")";
}
}
@ -7573,7 +7621,7 @@
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Download Link']) {
return;
}
if ($.engine === 'gecko' || $.el('a').download === void 0) {
if (!('download' in $.el('a'))) {
return;
}
a = $.el('a', {
@ -7674,19 +7722,23 @@
Keybinds = {
init: function() {
var init;
if (g.VIEW === 'catalog' || !Conf['Keybinds']) {
return;
}
return $.on(d, '4chanXInitFinished', function() {
init = function() {
var node, _i, _len, _ref;
$.off(d, '4chanXInitFinished', init);
$.on(d, 'keydown', Keybinds.keydown);
_ref = $$('[accesskey]');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
node.removeAttribute('accesskey');
}
});
};
return $.on(d, '4chanXInitFinished', init);
},
keydown: function(e) {
var form, key, notification, notifications, op, target, thread, threadRoot, _i, _len;
@ -7970,7 +8022,7 @@
Nav = {
init: function() {
var next, prev, span;
var append, next, prev, span;
switch (g.VIEW) {
case 'index':
@ -7998,9 +8050,11 @@
$.on(prev, 'click', this.prev);
$.on(next, 'click', this.next);
$.add(span, [prev, $.tn(' '), next]);
return $.on(d, '4chanXInitFinished', function() {
append = function() {
$.off(d, '4chanXInitFinished', append);
return $.add(d.body, span);
});
};
return $.on(d, '4chanXInitFinished', append);
},
prev: function() {
if (g.VIEW === 'thread') {
@ -8619,71 +8673,78 @@
});
},
node: function() {
var a, boardID, deadlink, m, post, postID, quote, quoteID, redirect, _i, _len, _ref, _ref1;
var deadlink, _i, _len, _ref;
if (this.isClone) {
return;
}
_ref = $$('.deadlink', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
deadlink = _ref[_i];
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, __slice.call(deadlink.childNodes));
continue;
}
quote = deadlink.textContent;
if (!(postID = (_ref1 = quote.match(/\d+$/)) != null ? _ref1[0] : void 0)) {
continue;
}
boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink',
textContent: quote
});
} else {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', postID);
if (this.isClone) {
if ($.hasClass(deadlink, 'quotelink')) {
this.nodes.quotelinks.push(deadlink);
}
} else if (redirect = Redirect.to({
boardID: boardID,
threadID: 0,
postID: postID
})) {
} else {
Quotify.parseDeadlink.call(this, deadlink);
}
}
},
parseDeadlink: function(deadlink) {
var a, boardID, m, post, postID, quote, quoteID, redirect, _ref;
if (deadlink.parentNode.className === 'prettyprint') {
$.replace(deadlink, __slice.call(deadlink.childNodes));
return;
}
quote = deadlink.textContent;
if (!(postID = (_ref = quote.match(/\d+$/)) != null ? _ref[0] : void 0)) {
return;
}
boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) {
if (!post.isDead) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink',
textContent: quote
});
} else {
a = $.el('a', {
href: "/" + boardID + "/" + post.thread + "/res/#p" + postID,
className: 'quotelink deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(boardID, postID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-postid', postID);
}
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', postID);
}
if (!this.quotes.contains(quoteID)) {
this.quotes.push(quoteID);
} else if (redirect = Redirect.to({
boardID: boardID,
threadID: 0,
postID: postID
})) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(boardID, postID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-boardid', boardID);
a.setAttribute('data-postid', postID);
}
if (!a) {
deadlink.textContent += "\u00A0(Dead)";
continue;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
this.nodes.quotelinks.push(a);
}
a = null;
}
if (__indexOf.call(this.quotes, quoteID) < 0) {
this.quotes.push(quoteID);
}
if (!a) {
deadlink.textContent = "" + quote + "\u00A0(Dead)";
return;
}
$.replace(deadlink, a);
if ($.hasClass(a, 'quotelink')) {
return this.nodes.quotelinks.push(a);
}
}
};
@ -9551,7 +9612,7 @@
}
headRect = Header.bar.getBoundingClientRect();
top = rect.top - headRect.top - headRect.height;
root = $.engine === 'webkit' ? d.body : doc;
root = d.body;
if (rect.top < 0) {
root.scrollTop += top;
}
@ -9614,7 +9675,7 @@
if (!(prev.top + prev.height <= 0)) {
return;
}
root = $.engine === 'webkit' ? d.body : doc;
root = d.body;
curr = post.nodes.root.getBoundingClientRect();
return root.scrollTop += curr.height - prev.height + curr.top - prev.top;
});
@ -10831,7 +10892,7 @@
$.add(ThreadUpdater.root, nodes);
if (scroll) {
if (Conf['Bottom Scroll']) {
($.engine === 'webkit' ? d.body : doc).scrollTop = d.body.clientHeight;
d.body.scrollTop = d.body.clientHeight;
} else {
Header.scrollToPost(nodes[0]);
}
@ -10895,6 +10956,7 @@
});
},
ready: function() {
$.off(d, '4chanXInitFinished', ThreadWatcher.ready);
if (!Main.isThisPageLegit()) {
return;
}
@ -11321,13 +11383,24 @@
});
},
initReady: function() {
$.off(d, '4chanXInitFinished', QR.initReady);
QR.postingIsEnabled = !!$.id('postForm');
if (!QR.postingIsEnabled) {
return;
}
if ($.engine === 'webkit') {
$.on(d, 'paste', QR.paste);
}
$.on(d, 'QRGetSelectedPost', function(_arg) {
var cb;
cb = _arg.detail;
return cb(QR.selected);
});
$.on(d, 'QRAddPreSubmitHook', function(_arg) {
var cb;
cb = _arg.detail;
return QR.preSubmitHooks.push(cb);
});
$.on(d, 'paste', QR.paste);
$.on(d, 'dragover', QR.dragOver);
$.on(d, 'drop', QR.dropFile);
$.on(d, 'dragstart dragend', QR.drag);
@ -11605,7 +11678,7 @@
}
},
quote: function(e) {
var OP, caretPos, com, post, range, s, sel, selectionRoot, text, thread, _ref;
var OP, caretPos, com, index, post, range, s, sel, selectionRoot, text, thread, _ref;
if (e != null) {
e.preventDefault();
@ -11623,6 +11696,12 @@
text += ">" + s + "\n";
}
QR.open();
if (QR.selected.isLocked) {
index = QR.posts.indexOf(QR.selected);
(QR.posts[index + 1] || new QR.post()).select();
$.addClass(QR.nodes.el, 'dump');
QR.cooldown.auto = true;
}
_ref = QR.nodes, com = _ref.com, thread = _ref.thread;
if (!com.value) {
thread.value = OP.ID;
@ -11855,7 +11934,8 @@
rectEl = this.nodes.el.getBoundingClientRect();
rectList = this.nodes.el.parentNode.getBoundingClientRect();
this.nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width / 2 - rectList.left - rectList.width / 2;
return this.load();
this.load();
return $.event('QRPostSelection', this);
};
_Class.prototype.load = function() {
@ -12297,9 +12377,7 @@
QR.mimeTypes = mimeTypes.split(', ');
QR.mimeTypes.push('');
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
if ($.engine !== 'presto') {
nodes.fileInput.accept = "text/*, " + mimeTypes;
}
nodes.fileInput.accept = "text/*, " + mimeTypes;
QR.spoiler = !!$('input[name=spoiler]');
nodes.spoiler.parentElement.hidden = !QR.spoiler;
if (g.BOARD.ID === 'f') {
@ -12367,8 +12445,9 @@
return $.rmClass(this, 'tripped');
}
},
preSubmitHooks: [],
submit: function(e) {
var callbacks, challenge, err, filetag, m, opts, post, postData, response, textOnly, thread, threadID, _ref;
var callbacks, challenge, err, filetag, hook, m, opts, post, postData, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
if (e != null) {
e.preventDefault();
@ -12400,11 +12479,19 @@
err = 'You can\'t reply to this thread anymore.';
} else if (!(post.com || post.file)) {
err = 'No file selected.';
} else if (post.file && thread.fileLimit && !thread.isSticky) {
} else if (post.file && thread.fileLimit) {
err = 'Max limit of image replies has been reached.';
} else {
_ref = QR.preSubmitHooks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
hook = _ref[_i];
if (err = hook(post, thread)) {
break;
}
}
}
if (QR.captcha.isEnabled && !err) {
_ref = QR.captcha.getOne(), challenge = _ref.challenge, response = _ref.response;
_ref1 = QR.captcha.getOne(), challenge = _ref1.challenge, response = _ref1.response;
if (!response) {
err = 'No valid captcha.';
}
@ -12545,7 +12632,7 @@
board: g.BOARD,
threadID: threadID,
postID: postID
}, QR.nodes.el);
});
QR.cooldown.auto = QR.posts.length > 1 && isReply;
if (!(Conf['Persistent QR'] || QR.cooldown.auto)) {
QR.close();
@ -12616,7 +12703,8 @@
DataBoard = (function() {
function DataBoard(key, sync) {
var _this = this;
var init,
_this = this;
this.key = key;
this.data = Conf[key];
@ -12625,9 +12713,11 @@
if (!sync) {
return;
}
$.on(d, '4chanXInitFinished', function() {
init = function() {
$.off(d, '4chanXInitFinished', init);
return _this.sync = sync;
});
};
$.on(d, '4chanXInitFinished', init);
}
DataBoard.prototype["delete"] = function(_arg) {
@ -13181,7 +13271,7 @@
return $.get(Conf, Main.initFeatures);
},
initFeatures: function(items) {
var initFeatures, pathname;
var init, pathname;
Conf = items;
pathname = location.pathname.split('/');
@ -13213,6 +13303,8 @@
Conf["theme"] = Conf["theme_" + g.TYPE];
}
switch (location.hostname) {
case 'api.4chan.org':
return;
case 'sys.4chan.org':
Report.init();
return;
@ -13229,7 +13321,7 @@
});
return;
}
initFeatures = function(features) {
init = function(features) {
var err, module, name;
for (name in features) {
@ -13245,7 +13337,7 @@
}
}
};
initFeatures({
init({
'Polyfill': Polyfill,
'Emoji': Emoji,
'Style': Style,
@ -13255,6 +13347,7 @@
'Header': Header,
'Catalog Links': CatalogLinks,
'Settings': Settings,
'Announcement Hiding': PSAHiding,
'Fourchan thingies': Fourchan,
'Custom CSS': CustomCSS,
'Linkify': Linkify,

View File

@ -4,6 +4,8 @@
$ = (selector, root=d.body) ->
root.querySelector selector
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$$ = (selector, root=d.body) ->
[root.querySelectorAll(selector)...]
@ -49,10 +51,7 @@ $.extend String::,
contains: (string) ->
@indexOf(string) > -1
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$.extend $,
engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
id: (id) ->
d.getElementById id
ready: (fc) ->
@ -148,7 +147,8 @@ $.extend $,
rmAll: (root) ->
# jsperf.com/emptify-element
while node = root.firstChild
$.rm node
# HTMLSelectElement.remove !== Element.remove
root.removeChild node
return
tn: (s) ->
d.createTextNode s

View File

@ -4,14 +4,17 @@ UI = do ->
className: 'dialog'
innerHTML: html
id: id
el.style.cssText = position
$.get "#{id}.position", position, (item) ->
el.style.cssText = item["#{id}.position"]
move = $ '.move', el
$.on move, 'touchstart mousedown', dragstart
for child in move.children
continue unless child.tagName
$.on child, 'touchstart mousedown', (e) ->
e.stopPropagation()
el
class Menu

View File

@ -21,7 +21,7 @@
"grunt-bump": "~0.0.0",
"grunt-contrib-clean": "~0.4.0",
"grunt-contrib-coffee": "~0.6.6",
"grunt-contrib-compress": "~0.4.9",
"grunt-contrib-compress": "~0.4.10",
"grunt-contrib-concat": "~0.2.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-watch": "~0.3.1",

View File

@ -1,12 +1,5 @@
Style =
init: ->
@agent = {
'gecko': '-moz-'
'webkit': '-webkit-'
'presto': '-o-'
}[$.engine]
@sizing = "#{if $.engine is 'gecko' then @agent else ''}box-sizing"
$.asap (-> d.body), MascotTools.init
@ -27,6 +20,10 @@ Style =
@setup()
agent: "<% if (type === 'crx') { %>-webkit-<% } else if (type === 'userjs') { %>-moz-<% } else { %>-o-<% } %>"
sizing: "<% if (type === 'userjs') { %>-moz-<% } else { %><% } %>box-sizing"
setup: ->
@addStyleReady()
if d.head

View File

@ -21,6 +21,10 @@ Config =
false
'Show custom links instead of the full board list.'
]
'Announcement Hiding': [
true
'Add button to hide 4chan announcements.'
]
'404 Redirect': [
true
'Redirect dead threads and images.'
@ -729,7 +733,7 @@ http://iqdb.org/?url=%TURL
'Custom CSS': false
'Bottom header': false
'Boards Navigation': 'sticky top'
'Header auto-hide': false

View File

@ -8,7 +8,10 @@ class DataBoard
return unless sync
# Chrome also fires the onChanged callback on the current tab,
# so we only start syncing when we're ready.
$.on d, '4chanXInitFinished', => @sync = sync
init = =>
$.off d, '4chanXInitFinished', init
@sync = sync
$.on d, '4chanXInitFinished', init
delete: ({boardID, threadID, postID}) ->
if postID

View File

@ -59,7 +59,7 @@ Header =
createSubEntry: (setting)->
label = $.el 'label',
textContent: "#{setting}"
$.on label, 'click', Header.setBarPosition
el: label
@ -156,26 +156,20 @@ Header =
$.set 'Boards Navigation', @textContent
changeBarPosition: (setting) ->
$.rmClass doc, 'top'
$.rmClass doc, 'fixed'
$.rmClass doc, 'bottom'
$.rmClass doc, 'hide'
switch setting
when 'sticky top'
$.addClass doc, 'top'
$.addClass doc, 'fixed'
$.rmClass doc, 'bottom'
$.rmClass doc, 'hide'
$.addClass doc, 'top'
when 'sticky bottom'
$.rmClass doc, 'top'
$.addClass doc, 'fixed'
$.addClass doc, 'bottom'
$.rmClass doc, 'hide'
when 'top'
$.addClass doc, 'top'
$.rmClass doc, 'fixed'
$.rmClass doc, 'bottom'
$.rmClass doc, 'hide'
when 'hide'
$.rmClass doc, 'top'
$.rmClass doc, 'fixed'
$.rmClass doc, 'bottom'
$.addClass doc, 'hide'
setBarVisibility: (hide) ->
@ -192,7 +186,7 @@ Header =
if Conf['Boards Navigation'] is 'sticky top'
headRect = Header.bar.getBoundingClientRect()
top += - headRect.top - headRect.height
(if $.engine is 'webkit' then d.body else doc).scrollTop += top
<% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop += top
toggleBarVisibility: (e) ->
return if e.type is 'mousedown' and e.button isnt 0 # not LMB
@ -307,6 +301,55 @@ CatalogLinks =
$.add d.body, catalogLink
catalogLink.id = 'catalog'
PSAHiding =
init: ->
return if !Conf['Announcement Hiding']
$.addClass doc, 'hide-announcement'
$.on d, '4chanXInitFinished', @setup
setup: ->
$.off d, '4chanXInitFinished', PSAHiding.setup
unless psa = $.id 'globalMessage'
$.rmClass doc, 'hide-announcement'
return
PSAHiding.btn = btn = $.el 'a',
title: 'Toggle announcement.'
href: 'javascript:;'
$.on btn, 'click', PSAHiding.toggle
text = PSAHiding.trim psa
$.get 'hiddenPSAs', [], (item) ->
PSAHiding.sync item['hiddenPSAs']
$.before psa, btn
$.rmClass doc, 'hide-announcement'
$.sync 'hiddenPSAs', PSAHiding.sync
toggle: (e) ->
hide = $.hasClass @, 'hide-announcement'
text = PSAHiding.trim $.id 'globalMessage'
$.get 'hiddenPSAs', [], (item) ->
{hiddenPSAs} = item
if hide
hiddenPSAs.push text
else
i = hiddenPSAs.indexOf text
hiddenPSAs.splice i, 1
hiddenPSAs = hiddenPSAs[-5..]
PSAHiding.sync hiddenPSAs
$.set 'hiddenPSAs', hiddenPSAs
sync: (hiddenPSAs) ->
{btn} = PSAHiding
psa = $.id 'globalMessage'
[psa.hidden, btn.innerHTML, btn.className] = if PSAHiding.trim(psa) in hiddenPSAs
[true, '<span>[&nbsp;+&nbsp;]</span>', 'show-announcement']
else
[false, '<span>[&nbsp;-&nbsp;]</span>', 'hide-announcement']
trim: (psa) ->
psa.textContent.replace(/\W+/g, '').toLowerCase()
Fourchan =
init: ->
return if g.VIEW is 'catalog'
@ -1217,21 +1260,23 @@ DeleteLink =
return if DeleteLink.cooldown.counting isnt post
unless 0 <= seconds <= length
if DeleteLink.cooldown.counting is post
node.textContent = 'Delete'
delete DeleteLink.cooldown.counting
return
setTimeout DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node
if seconds is 0
node.textContent = 'Delete'
return
node.textContent = "Delete (#{seconds})"
DownloadLink =
init: ->
<% if (type === 'userscript') { %>
# Firefox won't let us download cross-domain content.
return
<% } %>
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Download Link']
# Firefox won't let us download cross-domain content.
# Test for download feature support.
return if $.engine is 'gecko' or $.el('a').download is undefined
return unless 'download' of $.el 'a'
a = $.el 'a',
className: 'download-link'
textContent: 'Download file'
@ -1305,11 +1350,13 @@ Keybinds =
init: ->
return if g.VIEW is 'catalog' or !Conf['Keybinds']
$.on d, '4chanXInitFinished', ->
init = ->
$.off d, '4chanXInitFinished', init
$.on d, 'keydown', Keybinds.keydown
for node in $$ '[accesskey]'
node.removeAttribute 'accesskey'
return
$.on d, '4chanXInitFinished', init
keydown: (e) ->
return unless key = Keybinds.keyCode e
@ -1526,7 +1573,10 @@ Nav =
$.on next, 'click', @next
$.add span, [prev, $.tn(' '), next]
$.on d, '4chanXInitFinished', -> $.add d.body, span
append = ->
$.off d, '4chanXInitFinished', append
$.add d.body, span
$.on d, '4chanXInitFinished', append
prev: ->
if g.VIEW is 'thread'
@ -2141,67 +2191,71 @@ Quotify =
name: 'Resurrect Quotes'
cb: @node
node: ->
return if @isClone
for deadlink in $$ '.deadlink', @nodes.comment
if deadlink.parentNode.className is 'prettyprint'
# Don't quotify deadlinks inside code tags,
# un-`span` them.
$.replace deadlink, [deadlink.childNodes...]
continue
if @isClone
if $.hasClass deadlink, 'quotelink'
@nodes.quotelinks.push deadlink
else
Quotify.parseDeadlink.call @, deadlink
return
quote = deadlink.textContent
continue unless postID = quote.match(/\d+$/)?[0]
boardID =
if m = quote.match /^>>>\/([a-z\d]+)/
m[1]
else
@board.ID
quoteID = "#{boardID}.#{postID}"
parseDeadlink: (deadlink) ->
if deadlink.parentNode.className is 'prettyprint'
# Don't quotify deadlinks inside code tags,
# un-`span` them.
$.replace deadlink, [deadlink.childNodes...]
return
# \u00A0 is nbsp
if post = g.posts[quoteID]
unless post.isDead
# Don't (Dead) when quotifying in an archived post,
# and we know the post still exists.
a = $.el 'a',
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink'
textContent: quote
else
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-threadid', post.thread.ID
a.setAttribute 'data-postid', postID
else if redirect = Redirect.to {boardID, threadID: 0, postID}
quote = deadlink.textContent
return unless postID = quote.match(/\d+$/)?[0]
boardID = if m = quote.match /^>>>\/([a-z\d]+)/
m[1]
else
@board.ID
quoteID = "#{boardID}.#{postID}"
if post = g.posts[quoteID]
unless post.isDead
# Don't (Dead) when quotifying in an archived post,
# and we know the post still exists.
a = $.el 'a',
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink'
textContent: quote
else
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: redirect
className: 'deadlink'
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
if Redirect.post boardID, postID
# Make it function as a normal quote if we can fetch the post.
$.addClass a, 'quotelink'
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-postid', postID
unless @quotes.contains quoteID
@quotes.push quoteID
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-threadid', post.thread.ID
a.setAttribute 'data-postid', postID
else if redirect = Redirect.to {boardID, threadID: 0, postID}
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: redirect
className: 'deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
if Redirect.post boardID, postID
# Make it function as a normal quote if we can fetch the post.
$.addClass a, 'quotelink'
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-postid', postID
unless a
deadlink.textContent += "\u00A0(Dead)"
continue
unless quoteID in @quotes
@quotes.push quoteID
$.replace deadlink, a
if $.hasClass a, 'quotelink'
@nodes.quotelinks.push a
a = null
return
unless a
deadlink.textContent = "#{quote}\u00A0(Dead)"
return
$.replace deadlink, a
if $.hasClass a, 'quotelink'
@nodes.quotelinks.push a
QuoteInline =
init: ->
@ -2851,11 +2905,13 @@ ImageExpand =
ImageExpand.contract post
rect = post.nodes.root.getBoundingClientRect()
return unless rect.top <= 0 or rect.left <= 0
# Scroll back to the thumbnail when contracting the image
# to avoid being left miles away from the relevant post.
headRect = Header.bar.getBoundingClientRect()
top = rect.top - headRect.top - headRect.height
root = if $.engine is 'webkit' then d.body else doc
root = <% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>
root.scrollTop += top if rect.top < 0
root.scrollLeft = 0 if rect.left < 0
@ -2897,7 +2953,7 @@ ImageExpand =
$.addClass post.nodes.root, 'expanded-image'
$.rmClass post.file.thumb, 'expanding'
return unless prev.top + prev.height <= 0
root = if $.engine is 'webkit' then d.body else doc
root = <% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>
curr = post.nodes.root.getBoundingClientRect()
root.scrollTop += curr.height - prev.height + curr.top - prev.top
@ -3811,7 +3867,7 @@ ThreadUpdater =
$.add ThreadUpdater.root, nodes
if scroll
if Conf['Bottom Scroll']
(if $.engine is 'webkit' then d.body else doc).scrollTop = d.body.clientHeight
<% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop = d.body.clientHeight
else
Header.scrollToPost nodes[0]
@ -3859,6 +3915,7 @@ ThreadWatcher =
$.delete 'AutoWatch'
ready: ->
$.off d, '4chanXInitFinished', ThreadWatcher.ready
return unless Main.isThisPageLegit()
ThreadWatcher.refresh()
$.add d.body, ThreadWatcher.dialog
@ -3932,6 +3989,7 @@ ThreadWatcher =
ThreadWatcher.refresh watched
$.set 'WatchedThreads', watched
Linkify =
init: ->
return if g.VIEW is 'catalog' or not Conf['Linkify']
@ -4069,7 +4127,7 @@ Linkify =
if style = type.style
el.style.cssText = style
else
items =
items =
'embedWidth': Config['embedWidth']
'embedHeight': Config['embedHeight']
$.get items, (items) ->

View File

@ -349,6 +349,8 @@ Main =
Conf["theme"] = Conf["theme_#{g.TYPE}"]
switch location.hostname
when 'api.4chan.org'
return
when 'sys.4chan.org'
Report.init()
return
@ -359,7 +361,7 @@ Main =
location.href = url if url
return
initFeatures = (features) ->
init = (features) ->
for name, module of features
# c.time "#{name} initialization"
try
@ -374,7 +376,7 @@ Main =
# c.time 'All initializations'
initFeatures
init
'Polyfill': Polyfill
'Emoji': Emoji
'Style': Style
@ -384,6 +386,7 @@ Main =
'Header': Header
'Catalog Links': CatalogLinks
'Settings': Settings
'Announcement Hiding': PSAHiding
'Fourchan thingies': Fourchan
'Custom CSS': CustomCSS
'Linkify': Linkify

View File

@ -11,6 +11,7 @@
"content_scripts": [{
"js": ["script.js"],
"matches": <%= JSON.stringify(meta.matches) %>,
"all_frames": true,
"run_at": "document_start"
}],
"homepage_url": "<%= meta.page %>",

View File

@ -30,11 +30,18 @@ QR =
cb: @node
initReady: ->
$.off d, '4chanXInitFinished', QR.initReady
QR.postingIsEnabled = !!$.id 'postForm'
return unless QR.postingIsEnabled
if $.engine is 'webkit'
$.on d, 'paste', QR.paste
$.on d, 'QRGetSelectedPost', ({detail: cb}) ->
cb QR.selected
$.on d, 'QRAddPreSubmitHook', ({detail: cb}) ->
QR.preSubmitHooks.push cb
<% if (type === 'crx') { %>
$.on d, 'paste', QR.paste
<% } %>
$.on d, 'dragover', QR.dragOver
$.on d, 'drop', QR.dropFile
$.on d, 'dragstart dragend', QR.drag
@ -278,6 +285,11 @@ QR =
text += ">#{s}\n"
QR.open()
if QR.selected.isLocked
index = QR.posts.indexOf QR.selected
(QR.posts[index+1] or new QR.post()).select()
$.addClass QR.nodes.el, 'dump'
QR.cooldown.auto = true
{com, thread} = QR.nodes
thread.value = OP.ID unless com.value
@ -467,7 +479,8 @@ QR =
rectList = @nodes.el.parentNode.getBoundingClientRect()
@nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2
@load()
$.event 'QRPostSelection', @
load: ->
# Load this post's values.
for name in ['thread', 'name', 'email', 'sub', 'com']
@ -837,7 +850,10 @@ QR =
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push ''
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
nodes.fileInput.accept = "text/*, #{mimeTypes}" if $.engine isnt 'presto' # Opera's accept attribute is fucked up
<% if (type !== 'userjs') { %>
# Opera's accept attribute is fucked up
nodes.fileInput.accept = "text/*, #{mimeTypes}"
<% } %>
QR.spoiler = !!$ 'input[name=spoiler]'
nodes.spoiler.parentElement.hidden = !QR.spoiler
@ -918,6 +934,7 @@ QR =
if check and !@.className.match "\\btripped\\b" then $.addClass @, 'tripped'
else if !check and @.className.match "\\btripped\\b" then $.rmClass @, 'tripped'
preSubmitHooks: []
submit: (e) ->
e?.preventDefault()
@ -948,8 +965,11 @@ QR =
err = 'You can\'t reply to this thread anymore.'
else unless post.com or post.file
err = 'No file selected.'
else if post.file and thread.fileLimit and !thread.isSticky
else if post.file and thread.fileLimit
err = 'Max limit of image replies has been reached.'
else for hook in QR.preSubmitHooks
if err = hook post, thread
break
if QR.captcha.isEnabled and !err
{challenge, response} = QR.captcha.getOne()
@ -1110,7 +1130,7 @@ QR =
board: g.BOARD
threadID
postID
}, QR.nodes.el
}
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.posts.length > 1 and isReply

View File

@ -208,9 +208,10 @@ Settings =
download: "<%= meta.name %> v#{g.VERSION}-#{now}.json"
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}"
target: '_blank'
if $.engine isnt 'gecko'
a.click()
return
<% if (type !== 'userjs') { %>
a.click()
return
<% } %>
# XXX Firefox won't let us download automatically.
p = $ '.imp-exp-result', Settings.dialog
$.rmAll p
@ -406,6 +407,13 @@ Settings =
<legend>Custom Board Navigation <span class=warning #{if Conf['Custom Board Navigation'] then 'hidden' else ''}>is disabled.</span></legend>
<div><input name=boardnav class=field spellcheck=false></div>
<div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>
<div>
For example:<br>
<code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]</code><br>
will give you<br>
<code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>
if you are on /g/.
</div>
<div>Board link: <code>board</code></div>
<div>Title link: <code>board-title</code></div>
<div>Full text link: <code>board-full</code></div>