diff --git a/4chan_x.user.js b/4chan_x.user.js
index b2c02cce2..ed8bb0977 100644
--- a/4chan_x.user.js
+++ b/4chan_x.user.js
@@ -91,6 +91,7 @@
'Anonymize': [false, 'Make everybody anonymous'],
'Filter': [false, 'Self-moderation placebo'],
'Filter OPs': [false, 'Filter OPs along with their threads'],
+ 'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively'],
'Reply Hiding': [true, 'Hide single replies'],
'Thread Hiding': [true, 'Hide entire threads'],
'Show Stubs': [true, 'Of hidden threads / replies']
@@ -105,6 +106,7 @@
Monitoring: {
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'],
'Unread Count': [true, 'Show unread post count in tab title'],
+ 'Unread Favicon': [true, 'Show a different favicon when there are unread posts'],
'Post in Title': [true, 'Show the op\'s post in the tab title'],
'Thread Stats': [true, 'Display reply and image count'],
'Thread Watcher': [true, 'Bookmark threads'],
@@ -168,7 +170,7 @@
expandImages: ['m', 'Expand selected image'],
expandAllImages: ['M', 'Expand all images'],
update: ['u', 'Update now'],
- unreadCountTo0: ['z', 'Reset unread count to 0']
+ unreadCountTo0: ['z', 'Reset unread status']
},
updater: {
checkbox: {
@@ -187,17 +189,15 @@
(flatten = function(parent, obj) {
var key, val, _results;
- if (typeof obj === 'object') {
- if (obj.length) {
- return conf[parent] = obj[0];
- } else {
- _results = [];
- for (key in obj) {
- val = obj[key];
- _results.push(flatten(key, val));
- }
- return _results;
+ if (obj instanceof Array) {
+ return conf[parent] = obj[0];
+ } else if (typeof obj === 'object') {
+ _results = [];
+ for (key in obj) {
+ val = obj[key];
+ _results.push(flatten(key, val));
}
+ return _results;
} else {
return conf[parent] = obj;
}
@@ -613,23 +613,16 @@
strikethroughQuotes = {
init: function() {
return g.callbacks.push(function(root) {
- var el, quote, _i, _len, _ref, _results;
+ var el, quote, _i, _len, _ref;
if (root.className === 'inline') return;
_ref = $$('.quotelink', root);
- _results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i];
- if (el = $.id(quote.hash.slice(1))) {
- if (el.parentNode.parentNode.parentNode.hidden) {
- _results.push($.addClass(quote, 'filtered'));
- } else {
- _results.push(void 0);
- }
- } else {
- _results.push(void 0);
+ if ((el = $.id(quote.hash.slice(1))) && el.parentNode.parentNode.parentNode.hidden) {
+ $.addClass(quote, 'filtered');
+ if (conf['Recursive Filtering']) root.hidden = true;
}
}
- return _results;
});
}
};
@@ -898,7 +891,7 @@
break;
case conf.close:
if (o = $.id('overlay')) {
- $.rm(o);
+ options.close.call(o);
} else if (qr.el) {
qr.close();
}
@@ -974,8 +967,7 @@
break;
case conf.unreadCountTo0:
unread.replies = [];
- unread.updateTitle();
- Favicon.update();
+ unread.update();
break;
default:
return;
@@ -1201,11 +1193,13 @@
if (!$.id('recaptcha_challenge_field_holder')) return;
if (conf['Hide Original Post Form']) {
link = $.el('h1', {
- innerHTML: "" + (g.REPLY ? 'Open the Quick Reply' : 'Create a New Thread') + ""
+ innerHTML: "" + (g.REPLY ? 'Quick Reply' : 'New Thread') + ""
+ });
+ $.on($('a', link), 'click', function() {
+ qr.open();
+ return $('textarea', qr.el).focus();
});
- $.on($('a', link), 'click', qr.open);
form = d.forms[0];
- form.hidden = true;
$.before(form, link);
}
g.callbacks.push(function(root) {
@@ -1214,7 +1208,7 @@
iframe = $.el('iframe', {
id: 'iframe',
hidden: true,
- src: 'http://sys.4chan.org/post'
+ src: 'http://sys.4chan.org/robots.txt'
});
$.on(iframe, 'error', function() {
return this.src = this.src;
@@ -1223,20 +1217,22 @@
if (!qr.status.ready) {
iframe.src = 'about:blank';
return setTimeout((function() {
- return iframe.src = 'http://sys.4chan.org/post';
+ return iframe.src = 'http://sys.4chan.org/robots.txt';
}), 250);
}
};
$.on(iframe, 'load', function() {
- if (this.src !== 'about:blank') return setTimeout(loadChecking, 250, this);
+ if (this.src !== 'about:blank') return setTimeout(loadChecking, 500, this);
});
$.add(d.body, iframe);
if (conf['Persistent QR']) {
qr.dialog();
if (conf['Auto Hide QR']) qr.hide();
}
- $.on(d, 'dragover', qr.fileDrop);
- $.on(d, 'drop', qr.fileDrop);
+ $.on(d, 'dragover', qr.dragOver);
+ $.on(d, 'drop', qr.dropFile);
+ $.on(d, 'dragstart', qr.drag);
+ $.on(d, 'dragend', qr.drag);
return window.location = 'javascript:void(Recaptcha.focus_response_field=function(){})';
},
open: function() {
@@ -1284,6 +1280,7 @@
el.textContent = err;
if (node) $.replace(el.firstChild, node);
qr.open();
+ if (/captcha|verification/i.test(err)) $('[autocomplete]', qr.el).focus();
if (d.hidden || d.oHidden || d.mozHidden || d.webkitHidden) {
return alert(err);
}
@@ -1359,17 +1356,22 @@
ta.focus();
return ta.selectionEnd = ta.selectionStart = caretPos + text.length;
},
- fileDrop: function(e) {
- if (/TEXTAREA|INPUT/.test(e.target.nodeName)) return;
+ drag: function(e) {
+ var i;
+ i = e.type === 'dragstart' ? 'off' : 'on';
+ $[i](d, 'dragover', qr.dragOver);
+ return $[i](d, 'drop', qr.dropFile);
+ },
+ dragOver: function(e) {
e.preventDefault();
- e.stopPropagation();
- e.dataTransfer.dropEffect = 'copy';
- if (e.type === 'drop') {
- if (!e.dataTransfer.files.length) return;
- qr.open();
- qr.fileInput.call(e.dataTransfer);
- return $.addClass(qr.el, 'dump');
- }
+ return e.dataTransfer.dropEffect = 'copy';
+ },
+ dropFile: function(e) {
+ if (!e.dataTransfer.files.length) return;
+ e.preventDefault();
+ qr.open();
+ qr.fileInput.call(e.dataTransfer);
+ return $.addClass(qr.el, 'dump');
},
fileInput: function() {
var file, _i, _len, _ref;
@@ -1544,7 +1546,7 @@
case 0:
return 'Verification (Shift + Enter to cache)';
case 1:
- return 'Vertification (1 cached captcha)';
+ return 'Verification (1 cached captcha)';
default:
return "Verification (" + count + " cached captchas)";
}
@@ -1586,7 +1588,7 @@
\
');
if (conf['Remember QR size'] && engine === 'gecko') {
- $.on(ta = qr.el.querySelector('textarea'), 'mouseup', function() {
+ $.on(ta = $('textarea', qr.el), 'mouseup', function() {
return $.set('qr.size', this.style.cssText);
});
ta.style.cssText = $.get('qr.size', '');
@@ -1654,7 +1656,7 @@
}
$.sync('qr.persona', function(persona) {
var key, val, _results;
- if (qr.replies.length !== 1) return;
+ if (!qr.el.hidden) return;
_results = [];
for (key in persona) {
val = persona[key];
@@ -1817,6 +1819,7 @@
textContent: "window.addEventListener('message'," + code + ",false)"
});
ready = function() {
+ $.add(d.documentElement, script);
if (location.hostname === 'sys.4chan.org') {
qr.message.send({
req: 'status',
@@ -1826,14 +1829,10 @@
return $.rm(script);
};
if (d.documentElement) {
- $.add(d.documentElement, script);
- ready();
- return;
- }
- return $.ready(function() {
- $.add(d.head, script);
return ready();
- });
+ } else {
+ return $.ready(ready);
+ }
},
send: function(data) {
data.changeContext = true;
@@ -2035,21 +2034,20 @@
Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P\
Minutes: %M\
\
- Unread Count is disabled.
\
+ Unread Favicon is disabled.
\
Unread favicons
\
\
\
\
\
\
Keybinds are disabled.
\
-
Allowed keys: Ctrl, Alt, a-z, A-Z, 0-1, Up, Down, Right, Left.
\
+
Allowed keys: Ctrl, Alt, a-z, A-Z, 0-9, Up, Down, Right, Left.
\
\
@@ -2122,18 +2120,21 @@
overlay = $.el('div', {
id: 'overlay'
});
- $.on(overlay, 'click', function() {
- return $.rm(overlay);
- });
+ $.on(overlay, 'click', options.close);
$.on(dialog, 'click', function(e) {
return e.stopPropagation();
});
$.add(overlay, dialog);
$.add(d.body, overlay);
+ d.body.style.setProperty('overflow', 'hidden', null);
options.backlink.call(back);
options.time.call(time);
return options.favicon.call(favicon);
},
+ close: function() {
+ $.rm(this);
+ return d.body.style.removeProperty('overflow');
+ },
clearHidden: function() {
$["delete"]("hiddenReplies/" + g.BOARD + "/");
$["delete"]("hiddenThreads/" + g.BOARD + "/");
@@ -2158,7 +2159,7 @@
},
favicon: function() {
Favicon["switch"]();
- if (g.REPLY && conf['Unread Count']) Favicon.update();
+ unread.update(true);
return this.nextElementSibling.innerHTML = "

";
}
};
@@ -2374,8 +2375,13 @@
updater.count.textContent = 404;
updater.count.className = 'warning';
clearTimeout(updater.timeoutID);
- d.title = d.title.match(/^.+-/)[0] + ' 404';
g.dead = true;
+ if (conf['Unread Count']) {
+ unread.title = unread.title.match(/^.+-/)[0] + ' 404';
+ } else {
+ d.title = d.title.match(/^.+-/)[0] + ' 404';
+ }
+ unread.update(true);
qr.message.send({
req: 'abort'
});
@@ -2581,12 +2587,24 @@
sauce = {
init: function() {
- var link, links, _i, _len;
+ var domain, fc, link, links, _i, _len;
links = conf['sauces'].match(/^[^#].+$/gm);
+ if (!links.length) return;
this.links = [];
for (_i = 0, _len = links.length; _i < _len; _i++) {
link = links[_i];
- this.links.push([link, link.match(/(\w+)\.\w+\//)[1]]);
+ domain = link.match(/(\w+)\.\w+\//)[1];
+ fc = link.replace(/\$\d/, function(fragment) {
+ switch (fragment) {
+ case '$1':
+ return "' + img.src + '";
+ case '$2':
+ return "' + img.parentNode.href + '";
+ case '$3':
+ return "' + img.getAttribute('md5').replace(/\=*$/, '') + '";
+ }
+ });
+ this.links.push([Function('img', "return '" + fc + "'"), domain]);
}
return g.callbacks.push(this.node);
},
@@ -2598,24 +2616,12 @@
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
link = _ref[_i];
a = $.el('a', {
- textContent: link[1],
- href: sauce.href(link[0], img),
- target: '_blank'
+ href: link[0](img),
+ target: '_blank',
+ textContent: link[1]
});
$.add(span, $.tn(' '), a);
}
- },
- href: function(link, img) {
- return link.replace(/\$\d/, function(fragment) {
- switch (fragment) {
- case '$1':
- return img.src;
- case '$2':
- return img.parentNode.href;
- case '$3':
- return img.getAttribute('md5').replace(/\=+$/, '');
- }
- });
}
};
@@ -2839,10 +2845,9 @@
root = q.parentNode.nodeName === 'FONT' ? q.parentNode : q.nextSibling ? q.nextSibling : q;
if (el = $.id(id)) {
inline = quoteInline.table(id, el.innerHTML);
- if (g.REPLY && conf['Unread Count'] && (i = unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) {
+ if ((i = unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) {
unread.replies.splice(i, 1);
- unread.updateTitle();
- Favicon.update();
+ unread.update();
}
if (/\bbacklink\b/.test(q.className)) {
$.after(q.parentNode, inline);
@@ -3112,7 +3117,8 @@
unread = {
init: function() {
- d.title = '(0) ' + d.title;
+ this.title = d.title;
+ unread.update();
$.on(window, 'scroll', unread.scroll);
return g.callbacks.push(unread.node);
},
@@ -3120,8 +3126,7 @@
node: function(root) {
if (root.hidden || root.className) return;
unread.replies.push(root);
- unread.updateTitle();
- if (unread.replies.length === 1) return Favicon.update();
+ return unread.update();
},
scroll: function() {
var bottom, height, i, reply, _len, _ref;
@@ -3134,20 +3139,25 @@
}
if (i === 0) return;
unread.replies = unread.replies.slice(i);
- unread.updateTitle();
- if (unread.replies.length === 0) return Favicon.update();
+ return unread.update();
},
- updateTitle: function() {
- return d.title = d.title.replace(/\d+/, unread.replies.length);
+ update: function(forceUpdate) {
+ var count;
+ if (!g.REPLY) return;
+ count = unread.replies.length;
+ if (conf['Unread Count']) d.title = "(" + count + ") " + unread.title;
+ if (!(conf['Unread Favicon'] && count < 2 || forceUpdate)) return;
+ Favicon.el.href = g.dead ? count ? Favicon.unreadDead : Favicon.dead : count ? Favicon.unread : Favicon["default"];
+ return $.add(d.head, Favicon.el);
}
};
Favicon = {
init: function() {
- var favicon, href;
- favicon = $('link[rel="shortcut icon"]', d.head);
- favicon.type = 'image/x-icon';
- href = favicon.href;
+ var href;
+ this.el = $('link[rel="shortcut icon"]', d.head);
+ this.el.type = 'image/x-icon';
+ href = this.el.href;
this.SFW = /ws.ico$/.test(href);
this["default"] = href;
return this["switch"]();
@@ -3173,43 +3183,34 @@
this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
this.unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
- break;
- case 'None':
- this.unreadDead = this.dead;
- this.unreadSFW = 'http://static.4chan.org/image/favicon-ws.ico';
- this.unreadNSFW = 'http://static.4chan.org/image/favicon.ico';
}
return this.unread = this.SFW ? this.unreadSFW : this.unreadNSFW;
},
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
- dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
- update: function() {
- var favicon, l;
- l = unread.replies.length;
- favicon = $('link[rel="shortcut icon"]', d.head);
- favicon.href = g.dead ? l ? this.unreadDead : this.dead : l ? this.unread : this["default"];
- if (engine !== 'webkit') return $.add(d.head, $.rm(favicon));
- }
+ dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
};
redirect = {
init: function() {
var url;
- url = location.hostname === 'images.4chan.org' ? redirect.image(g.BOARD, location.pathname.split('/')[3]) : /^\d+$/.test(g.THREAD_ID) ? redirect.thread() : void 0;
+ url = location.hostname === 'images.4chan.org' ? redirect.image(location.href) : /^\d+$/.test(g.THREAD_ID) ? redirect.thread() : void 0;
if (url) return location.href = url;
},
- image: function(board, filename) {
- switch (board) {
+ image: function(href) {
+ href = href.split('/');
+ if (!conf['404 Redirect']) return;
+ switch (href[3]) {
case 'a':
case 'jp':
case 'm':
case 'tg':
case 'tv':
case 'u':
- return "http://archive.foolz.us/" + board + "/full_image/" + filename;
+ return "http://archive.foolz.us/" + href[3] + "/full_image/" + href[5];
}
},
thread: function() {
+ if (!conf['404 Redirect']) return;
switch (g.BOARD) {
case 'a':
case 'jp':
@@ -3249,7 +3250,7 @@
case 'x':
return "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
default:
- return "http://boards.4chan.org/" + g.BOARD;
+ return "http://boards.4chan.org/" + g.BOARD + "/";
}
}
};
@@ -3380,17 +3381,16 @@
img = $.el('img', {
src: url || a.href
});
- if (conf['404 Redirect']) $.on(img, 'error', imgExpand.error);
+ $.on(img, 'error', imgExpand.error);
return $.add(a, img);
},
error: function() {
- var href, src, thumb, timeoutID, url;
+ var href, thumb, timeoutID, url;
href = this.parentNode.href;
thumb = this.previousSibling;
- src = href.split('/');
imgExpand.contract(thumb);
$.rm(this);
- if (!(this.src.split('/')[2] === 'images.4chan.org' && (url = redirect.image(src[3], src[5])))) {
+ if (!(this.src.split('/')[2] === 'images.4chan.org' && (url = redirect.image(href)))) {
if (g.dead) return;
url = href + '?' + Date.now();
}
@@ -3440,7 +3440,7 @@
}
$.on(window, 'message', Main.message);
if (location.hostname === 'sys.4chan.org') {
- if (location.pathname === '/post') {
+ if (location.pathname === '/robots.txt') {
qr.message.init();
} else if (/report/.test(location.search)) {
$.ready(function() {
@@ -3501,17 +3501,21 @@
if (conf['Quote Backlinks']) quoteBacklink.init();
if (conf['Indicate OP quote']) quoteOP.init();
if (conf['Indicate Cross-thread Quotes']) quoteDR.init();
+ if (conf['Quick Reply'] && conf['Hide Original Post Form']) {
+ Main.css += 'form[name=post] { display: none; }';
+ }
+ Main.addStyle();
return $.ready(Main.ready);
},
ready: function() {
var callback, form, node, nodes, _i, _j, _len, _len2, _ref;
- if (conf['404 Redirect'] && d.title === '4chan - 404') {
+ if (d.title === '4chan - 404') {
redirect.init();
return;
}
if (!$.id('navtopr')) return;
+ $.addClass(d.body, "chanx_" + (VERSION.match(/\.(\d+)/)[1]));
$.addClass(d.body, engine);
- $.addStyle(Main.css);
threading.init();
Favicon.init();
if (conf['Quick Reply']) qr.init();
@@ -3523,7 +3527,7 @@
if (conf['Thread Stats']) threadStats.init();
if (conf['Reply Navigation']) nav.init();
if (conf['Post in Title']) titlePost.init();
- if (conf['Unread Count']) unread.init();
+ if (conf['Unread Count'] || conf['Unread Favicon']) unread.init();
} else {
if (conf['Thread Hiding']) threadHiding.init();
if (conf['Thread Expansion']) expandThread.init();
@@ -3546,6 +3550,14 @@
}
return $.on(form, 'DOMNodeInserted', Main.node);
},
+ addStyle: function() {
+ $.off(d, 'DOMNodeInserted', Main.addStyle);
+ if (d.head) {
+ return $.addStyle(Main.css);
+ } else {
+ return $.on(d, 'DOMNodeInserted', Main.addStyle);
+ }
+ },
message: function(e) {
var data, version;
data = e.data;
@@ -3756,6 +3768,7 @@ textarea.field {\
}\
#qr [type=submit] {\
margin: 1px 0;\
+ padding: 1px; /* not Gecko */\
padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */\
width: 30%;\
}\
@@ -3901,7 +3914,8 @@ img[md5], img[md5] + img {\
}\
.filtered {\
text-decoration: line-through;\
-}'
+}\
+'
};
Main.init();
diff --git a/changelog b/changelog
index 5776a75a8..48ae74871 100644
--- a/changelog
+++ b/changelog
@@ -1,9 +1,15 @@
master
- desuwa
New option: remember the size of the QR on Firefox.
+- aeosynth
+ prevent post form flicker
- Mayhem
+ Load QR's iframe to sys.4chan.org faster, unless you use Greasemonkey. Thanks desuwa.
Increase Sauce linking possibilites:
Thumbnails, full images, MD5 hashes.
+ New option: Recursive Filtering: Filter replies of filtered posts.
+ Unread Favicon is now optional, independent of Unread Count.
+ Fix some compatibility issues with file drag and drop, notably with QuickDrag extension.
2.25.5
- Mayhem
diff --git a/script.coffee b/script.coffee
index 0f3f6771a..0602c234b 100644
--- a/script.coffee
+++ b/script.coffee
@@ -14,6 +14,7 @@ config =
'Anonymize': [false, 'Make everybody anonymous']
'Filter': [false, 'Self-moderation placebo']
'Filter OPs': [false, 'Filter OPs along with their threads']
+ 'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively']
'Reply Hiding': [true, 'Hide single replies']
'Thread Hiding': [true, 'Hide entire threads']
'Show Stubs': [true, 'Of hidden threads / replies']
@@ -26,6 +27,7 @@ config =
Monitoring:
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.']
'Unread Count': [true, 'Show unread post count in tab title']
+ 'Unread Favicon': [true, 'Show a different favicon when there are unread posts']
'Post in Title': [true, 'Show the op\'s post in the tab title']
'Thread Stats': [true, 'Display reply and image count']
'Thread Watcher': [true, 'Bookmark threads']
@@ -97,7 +99,7 @@ config =
expandImages: ['m', 'Expand selected image']
expandAllImages: ['M', 'Expand all images']
update: ['u', 'Update now']
- unreadCountTo0: ['z', 'Reset unread count to 0']
+ unreadCountTo0: ['z', 'Reset unread status']
updater:
checkbox:
'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.']
@@ -113,12 +115,10 @@ log = console.log.bind? console
# flatten the config
conf = {}
(flatten = (parent, obj) ->
- if typeof obj is 'object'
- # array
- if obj.length
- conf[parent] = obj[0]
- # object
- else for key, val of obj
+ if obj instanceof Array
+ conf[parent] = obj[0]
+ else if typeof obj is 'object'
+ for key, val of obj
flatten key, val
else # string or number
conf[parent] = obj
@@ -458,9 +458,10 @@ strikethroughQuotes =
g.callbacks.push (root) ->
return if root.className is 'inline'
for quote in $$ '.quotelink', root
- if el = $.id quote.hash[1..]
- if el.parentNode.parentNode.parentNode.hidden
- $.addClass quote, 'filtered'
+ if (el = $.id quote.hash[1..]) and el.parentNode.parentNode.parentNode.hidden
+ $.addClass quote, 'filtered'
+ root.hidden = true if conf['Recursive Filtering']
+ return
expandComment =
init: ->
@@ -658,7 +659,7 @@ keybinds =
options.dialog() unless $.id 'overlay'
when conf.close
if o = $.id 'overlay'
- $.rm o
+ options.close.call o
else if qr.el
qr.close()
when conf.spoiler
@@ -716,8 +717,7 @@ keybinds =
qr.submit() if qr.el and !qr.status()
when conf.unreadCountTo0
unread.replies = []
- unread.updateTitle()
- Favicon.update()
+ unread.update()
else
return
e.preventDefault()
@@ -869,10 +869,11 @@ qr =
init: ->
return unless $.id 'recaptcha_challenge_field_holder'
if conf['Hide Original Post Form']
- link = $.el 'h1', innerHTML: "
#{if g.REPLY then 'Open the Quick Reply' else 'Create a New Thread'}"
- $.on $('a', link), 'click', qr.open
+ link = $.el 'h1', innerHTML: "
#{if g.REPLY then 'Quick Reply' else 'New Thread'}"
+ $.on $('a', link), 'click', ->
+ qr.open()
+ $('textarea', qr.el).focus()
form = d.forms[0]
- form.hidden = true
$.before form, link
g.callbacks.push (root) ->
$.on $('.quotejs + .quotejs', root), 'click', qr.quote
@@ -880,21 +881,23 @@ qr =
iframe = $.el 'iframe',
id: 'iframe'
hidden: true
- src: 'http://sys.4chan.org/post'
+ src: 'http://sys.4chan.org/robots.txt'
$.on iframe, 'error', -> @src = @src
# Greasemonkey ghetto fix
loadChecking = (iframe) ->
unless qr.status.ready
iframe.src = 'about:blank'
- setTimeout (-> iframe.src = 'http://sys.4chan.org/post'), 250
- $.on iframe, 'load', -> unless @src is 'about:blank' then setTimeout loadChecking, 250, @
+ setTimeout (-> iframe.src = 'http://sys.4chan.org/robots.txt'), 250
+ $.on iframe, 'load', -> unless @src is 'about:blank' then setTimeout loadChecking, 500, @
$.add d.body, iframe
if conf['Persistent QR']
qr.dialog()
qr.hide() if conf['Auto Hide QR']
- $.on d, 'dragover', qr.fileDrop
- $.on d, 'drop', qr.fileDrop
+ $.on d, 'dragover', qr.dragOver
+ $.on d, 'drop', qr.dropFile
+ $.on d, 'dragstart', qr.drag
+ $.on d, 'dragend', qr.drag
# prevent original captcha input from being focused on reload
window.location = 'javascript:void(Recaptcha.focus_response_field=function(){})'
@@ -931,6 +934,9 @@ qr =
el.textContent = err
$.replace el.firstChild, node if node
qr.open()
+ if /captcha|verification/i.test err
+ # Focus the captcha input on captcha error.
+ $('[autocomplete]', qr.el).focus()
alert err if d.hidden or d.oHidden or d.mozHidden or d.webkitHidden
cleanError: ->
$('.warning', qr.el).textContent = null
@@ -1004,16 +1010,21 @@ qr =
# Move the caret to the end of the new quote.
ta.selectionEnd = ta.selectionStart = caretPos + text.length
- fileDrop: (e) ->
- return if /TEXTAREA|INPUT/.test e.target.nodeName
+ drag: (e) ->
+ # Let it drag anything from the page.
+ i = if e.type is 'dragstart' then 'off' else 'on'
+ $[i] d, 'dragover', qr.dragOver
+ $[i] d, 'drop', qr.dropFile
+ dragOver: (e) ->
e.preventDefault()
- e.stopPropagation()
e.dataTransfer.dropEffect = 'copy' # cursor feedback
- if e.type is 'drop'
- return unless e.dataTransfer.files.length # let it only drop files
- qr.open()
- qr.fileInput.call e.dataTransfer
- $.addClass qr.el, 'dump'
+ dropFile: (e) ->
+ # Let it only handle files from the desktop.
+ return unless e.dataTransfer.files.length
+ e.preventDefault()
+ qr.open()
+ qr.fileInput.call e.dataTransfer
+ $.addClass qr.el, 'dump'
fileInput: ->
qr.cleanError()
# Set or change current reply's file.
@@ -1144,7 +1155,7 @@ qr =
when 0
'Verification (Shift + Enter to cache)'
when 1
- 'Vertification (1 cached captcha)'
+ 'Verification (1 cached captcha)'
else
"Verification (#{count} cached captchas)"
@input.alt = count # For XTRM RICE.
@@ -1180,7 +1191,7 @@ qr =
'
if conf['Remember QR size'] and engine is 'gecko'
- $.on ta = qr.el.querySelector('textarea'), 'mouseup', ->
+ $.on ta = $('textarea', qr.el), 'mouseup', ->
$.set 'qr.size', @style.cssText
ta.style.cssText = $.get 'qr.size', ''
@@ -1229,7 +1240,7 @@ qr =
$.on input, 'change', -> qr.selected[@name] = @value
# sync between tabs
$.sync 'qr.persona', (persona) ->
- return if qr.replies.length isnt 1
+ return unless qr.el.hidden
for key, val of persona
qr.selected[key] = val
$("[name=#{key}]", qr.el).value = val
@@ -1395,18 +1406,15 @@ qr =
parent.postMessage data, '*'
script = $.el 'script', textContent: "window.addEventListener('message',#{code},false)"
ready = ->
+ $.add d.documentElement, script
if location.hostname is 'sys.4chan.org'
qr.message.send req: 'status', ready: true
$.rm script
# Chrome can access the documentElement on document-start
if d.documentElement
- $.add d.documentElement, script
ready()
- return
# other browsers will have to wait
- $.ready ->
- $.add d.head, script
- ready()
+ else $.ready ready
send: (data) ->
data.changeContext = true
data.qr = true
@@ -1579,21 +1587,20 @@ options =
Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P
Minutes: %M
-
Unread Count is disabled.
+
Unread Favicon is disabled.
Unread favicons
Keybinds are disabled.
-
Allowed keys: Ctrl, Alt, a-z, A-Z, 0-1, Up, Down, Right, Left.
+
Allowed keys: Ctrl, Alt, a-z, A-Z, 0-9, Up, Down, Right, Left.
@@ -1656,15 +1663,20 @@ options =
indicators[@name].hidden = @checked
overlay = $.el 'div', id: 'overlay'
- $.on overlay, 'click', -> $.rm overlay
+ $.on overlay, 'click', options.close
$.on dialog, 'click', (e) -> e.stopPropagation()
$.add overlay, dialog
$.add d.body, overlay
+ d.body.style.setProperty 'overflow', 'hidden', null
options.backlink.call back
options.time.call time
options.favicon.call favicon
+ close: ->
+ $.rm this
+ d.body.style.removeProperty 'overflow'
+
clearHidden: ->
#'hidden' might be misleading; it's the number of IDs we're *looking* for,
# not the number of posts actually hidden on the page.
@@ -1687,7 +1699,7 @@ options =
$.id('backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
favicon: ->
Favicon.switch()
- Favicon.update() if g.REPLY and conf['Unread Count']
+ unread.update true
@nextElementSibling.innerHTML = "

"
threading =
@@ -1871,8 +1883,12 @@ updater =
updater.count.textContent = 404
updater.count.className = 'warning'
clearTimeout updater.timeoutID
- d.title = d.title.match(/^.+-/)[0] + ' 404'
g.dead = true
+ if conf['Unread Count']
+ unread.title = unread.title.match(/^.+-/)[0] + ' 404'
+ else
+ d.title = d.title.match(/^.+-/)[0] + ' 404'
+ unread.update true
qr.message.send req: 'abort'
qr.status()
Favicon.update()
@@ -2042,31 +2058,31 @@ anonymize =
sauce =
init: ->
- # return unless
links = conf['sauces'].match /^[^#].+$/gm
+ return unless links.length
@links = []
for link in links
- @links.push [link, link.match(/(\w+)\.\w+\//)[1]]
+ domain = link.match(/(\w+)\.\w+\//)[1]
+ fc = link.replace /\$\d/, (fragment) ->
+ switch fragment
+ when '$1'
+ "' + img.src + '"
+ when '$2'
+ "' + img.parentNode.href + '"
+ when '$3'
+ "' + img.getAttribute('md5').replace(/\=*$/, '') + '"
+ @links.push [Function('img', "return '#{fc}'"), domain]
g.callbacks.push @node
node: (root) ->
return if root.className is 'inline' or not span = $ '.filesize', root
img = $ 'img', root
for link in sauce.links
a = $.el 'a',
- textContent: link[1]
- href: sauce.href link[0], img
+ href: link[0] img
target: '_blank'
+ textContent: link[1]
$.add span, $.tn(' '), a
return
- href: (link, img) ->
- link.replace /\$\d/, (fragment) ->
- switch fragment
- when '$1'
- img.src
- when '$2'
- img.parentNode.href
- when '$3'
- img.getAttribute('md5').replace /\=+$/, ''
revealSpoilers =
init: ->
@@ -2223,10 +2239,9 @@ quoteInline =
root = if q.parentNode.nodeName is 'FONT' then q.parentNode else if q.nextSibling then q.nextSibling else q
if el = $.id id
inline = quoteInline.table id, el.innerHTML
- if g.REPLY and conf['Unread Count'] and (i = unread.replies.indexOf el.parentNode.parentNode.parentNode) isnt -1
+ if (i = unread.replies.indexOf el.parentNode.parentNode.parentNode) isnt -1
unread.replies.splice i, 1
- unread.updateTitle()
- Favicon.update()
+ unread.update()
if /\bbacklink\b/.test q.className
$.after q.parentNode, inline
$.addClass $.x('ancestor::table', el), 'forwarded' if conf['Forward Hiding']
@@ -2398,7 +2413,8 @@ threadStats =
unread =
init: ->
- d.title = '(0) ' + d.title
+ @title = d.title
+ unread.update()
$.on window, 'scroll', unread.scroll
g.callbacks.push unread.node
@@ -2407,9 +2423,7 @@ unread =
node: (root) ->
return if root.hidden or root.className
unread.replies.push root
- unread.updateTitle()
- if unread.replies.length is 1
- Favicon.update()
+ unread.update()
scroll: ->
height = d.body.clientHeight
@@ -2420,18 +2434,41 @@ unread =
return if i is 0
unread.replies = unread.replies[i..]
- unread.updateTitle()
- if unread.replies.length is 0
- Favicon.update()
+ unread.update()
- updateTitle: ->
- d.title = d.title.replace /\d+/, unread.replies.length
+ update: (forceUpdate) ->
+ return unless g.REPLY
+
+ count = unread.replies.length
+
+ if conf['Unread Count']
+ d.title = "(#{count}) #{unread.title}"
+
+ unless conf['Unread Favicon'] and count < 2 or forceUpdate
+ return
+
+ Favicon.el.href =
+ if g.dead
+ if count
+ Favicon.unreadDead
+ else
+ Favicon.dead
+ else
+ if count
+ Favicon.unread
+ else
+ Favicon.default
+
+ #`favicon.href = href` doesn't work on Firefox
+ #`favicon.href = href` isn't enough on Opera
+ #Opera won't always update the favicon if the href didn't not change
+ $.add d.head, Favicon.el
Favicon =
init: ->
- favicon = $ 'link[rel="shortcut icon"]', d.head
- favicon.type = 'image/x-icon'
- {href} = favicon
+ @el = $ 'link[rel="shortcut icon"]', d.head
+ @el.type = 'image/x-icon'
+ {href} = @el
@SFW = /ws.ico$/.test href
@default = href
@switch()
@@ -2454,50 +2491,28 @@ Favicon =
@unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='
@unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='
@unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='
- when 'None'
- @unreadDead = @dead
- @unreadSFW = 'http://static.4chan.org/image/favicon-ws.ico'
- @unreadNSFW = 'http://static.4chan.org/image/favicon.ico'
@unread = if @SFW then @unreadSFW else @unreadNSFW
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
- update: ->
- l = unread.replies.length
-
- favicon = $ 'link[rel="shortcut icon"]', d.head
- favicon.href =
- if g.dead
- if l
- @unreadDead
- else
- @dead
- else
- if l
- @unread
- else
- @default
-
- #`favicon.href = href` doesn't work on Firefox
- #`favicon.href = href` isn't enough on Opera
- #Opera won't always update the favicon if the href do not change
- if engine isnt 'webkit'
- $.add d.head, $.rm favicon
-
redirect =
init: ->
url =
if location.hostname is 'images.4chan.org'
- redirect.image g.BOARD, location.pathname.split('/')[3]
+ redirect.image location.href
else if /^\d+$/.test g.THREAD_ID
redirect.thread()
location.href = url if url
- image: (board, filename) -> #board must be given, the image can originate from a cross-quote
- switch board
+ image: (href) ->
+ href = href.split '/'
+ # Do not use g.BOARD, the image url can originate from a cross-quote.
+ return unless conf['404 Redirect']
+ switch href[3]
when 'a', 'jp', 'm', 'tg', 'tv', 'u'
- "http://archive.foolz.us/#{board}/full_image/#{filename}"
+ "http://archive.foolz.us/#{href[3]}/full_image/#{href[5]}"
thread: ->
+ return unless conf['404 Redirect']
switch g.BOARD
when 'a', 'jp', 'm', 'tg', 'tv', 'u'
"http://archive.foolz.us/#{g.BOARD}/thread/#{g.THREAD_ID}/"
@@ -2508,7 +2523,7 @@ redirect =
when '3', 'adv', 'an', 'ck', 'co', 'fa', 'fit', 'int', 'k', 'mu', 'n', 'o', 'p', 'po', 'pol', 'r9k', 'soc', 'sp', 'toy', 'trv', 'v', 'vp', 'x'
"http://archive.no-ip.org/#{g.BOARD}/thread/#{g.THREAD_ID}"
else
- "http://boards.4chan.org/#{g.BOARD}"
+ "http://boards.4chan.org/#{g.BOARD}/"
imgHover =
init: ->
@@ -2599,16 +2614,15 @@ imgExpand =
a = thumb.parentNode
img = $.el 'img',
src: url or a.href
- $.on img, 'error', imgExpand.error if conf['404 Redirect']
+ $.on img, 'error', imgExpand.error
$.add a, img
error: ->
href = @parentNode.href
thumb = @previousSibling
- src = href.split '/'
imgExpand.contract thumb
$.rm @
- unless @src.split('/')[2] is 'images.4chan.org' and url = redirect.image src[3], src[5]
+ unless @src.split('/')[2] is 'images.4chan.org' and url = redirect.image href
return if g.dead
# CloudFlare may cache banned pages instead of images.
# This will fool CloudFlare's cache.
@@ -2653,7 +2667,7 @@ Main =
$.on window, 'message', Main.message
if location.hostname is 'sys.4chan.org'
- if location.pathname is '/post'
+ if location.pathname is '/robots.txt'
qr.message.init()
else if /report/.test location.search
$.ready ->
@@ -2736,17 +2750,21 @@ Main =
if conf['Indicate Cross-thread Quotes']
quoteDR.init()
+ if conf['Quick Reply'] and conf['Hide Original Post Form']
+ Main.css += 'form[name=post] { display: none; }'
+
+ Main.addStyle()
$.ready Main.ready
ready: ->
- if conf['404 Redirect'] and d.title is '4chan - 404'
+ if d.title is '4chan - 404'
redirect.init()
return
if not $.id 'navtopr'
return
+ $.addClass d.body, "chanx_#{VERSION.match(/\.(\d+)/)[1]}"
$.addClass d.body, engine
- $.addStyle Main.css
threading.init()
Favicon.init()
@@ -2776,7 +2794,7 @@ Main =
if conf['Post in Title']
titlePost.init()
- if conf['Unread Count']
+ if conf['Unread Count'] or conf['Unread Favicon']
unread.init()
else #not reply
@@ -2803,6 +2821,13 @@ Main =
alert err
$.on form, 'DOMNodeInserted', Main.node
+ addStyle: ->
+ $.off d, 'DOMNodeInserted', Main.addStyle
+ if d.head
+ $.addStyle Main.css
+ else # XXX fox
+ $.on d, 'DOMNodeInserted', Main.addStyle
+
message: (e) ->
{data} = e
{version} = data
@@ -3004,6 +3029,7 @@ textarea.field {
}
#qr [type=submit] {
margin: 1px 0;
+ padding: 1px; /* not Gecko */
padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */
width: 30%;
}
@@ -3149,6 +3175,7 @@ img[md5], img[md5] + img {
}
.filtered {
text-decoration: line-through;
-}'
+}
+'
Main.init()