Merge branch 'master' of git://github.com/MayhemYDG/4chan-x into qrsize

Conflicts:
	changelog
This commit is contained in:
Desuwa 2012-02-10 00:59:02 +01:00
commit b4bfe17a95
3 changed files with 276 additions and 229 deletions

View File

@ -91,6 +91,7 @@
'Anonymize': [false, 'Make everybody anonymous'], 'Anonymize': [false, 'Make everybody anonymous'],
'Filter': [false, 'Self-moderation placebo'], 'Filter': [false, 'Self-moderation placebo'],
'Filter OPs': [false, 'Filter OPs along with their threads'], 'Filter OPs': [false, 'Filter OPs along with their threads'],
'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively'],
'Reply Hiding': [true, 'Hide single replies'], 'Reply Hiding': [true, 'Hide single replies'],
'Thread Hiding': [true, 'Hide entire threads'], 'Thread Hiding': [true, 'Hide entire threads'],
'Show Stubs': [true, 'Of hidden threads / replies'] 'Show Stubs': [true, 'Of hidden threads / replies']
@ -105,6 +106,7 @@
Monitoring: { Monitoring: {
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'], 'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'],
'Unread Count': [true, 'Show unread post count in tab title'], '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'], 'Post in Title': [true, 'Show the op\'s post in the tab title'],
'Thread Stats': [true, 'Display reply and image count'], 'Thread Stats': [true, 'Display reply and image count'],
'Thread Watcher': [true, 'Bookmark threads'], 'Thread Watcher': [true, 'Bookmark threads'],
@ -168,7 +170,7 @@
expandImages: ['m', 'Expand selected image'], expandImages: ['m', 'Expand selected image'],
expandAllImages: ['M', 'Expand all images'], expandAllImages: ['M', 'Expand all images'],
update: ['u', 'Update now'], update: ['u', 'Update now'],
unreadCountTo0: ['z', 'Reset unread count to 0'] unreadCountTo0: ['z', 'Reset unread status']
}, },
updater: { updater: {
checkbox: { checkbox: {
@ -187,17 +189,15 @@
(flatten = function(parent, obj) { (flatten = function(parent, obj) {
var key, val, _results; var key, val, _results;
if (typeof obj === 'object') { if (obj instanceof Array) {
if (obj.length) { return conf[parent] = obj[0];
return conf[parent] = obj[0]; } else if (typeof obj === 'object') {
} else { _results = [];
_results = []; for (key in obj) {
for (key in obj) { val = obj[key];
val = obj[key]; _results.push(flatten(key, val));
_results.push(flatten(key, val));
}
return _results;
} }
return _results;
} else { } else {
return conf[parent] = obj; return conf[parent] = obj;
} }
@ -613,23 +613,16 @@
strikethroughQuotes = { strikethroughQuotes = {
init: function() { init: function() {
return g.callbacks.push(function(root) { return g.callbacks.push(function(root) {
var el, quote, _i, _len, _ref, _results; var el, quote, _i, _len, _ref;
if (root.className === 'inline') return; if (root.className === 'inline') return;
_ref = $$('.quotelink', root); _ref = $$('.quotelink', root);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i]; quote = _ref[_i];
if (el = $.id(quote.hash.slice(1))) { if ((el = $.id(quote.hash.slice(1))) && el.parentNode.parentNode.parentNode.hidden) {
if (el.parentNode.parentNode.parentNode.hidden) { $.addClass(quote, 'filtered');
_results.push($.addClass(quote, 'filtered')); if (conf['Recursive Filtering']) root.hidden = true;
} else {
_results.push(void 0);
}
} else {
_results.push(void 0);
} }
} }
return _results;
}); });
} }
}; };
@ -898,7 +891,7 @@
break; break;
case conf.close: case conf.close:
if (o = $.id('overlay')) { if (o = $.id('overlay')) {
$.rm(o); options.close.call(o);
} else if (qr.el) { } else if (qr.el) {
qr.close(); qr.close();
} }
@ -974,8 +967,7 @@
break; break;
case conf.unreadCountTo0: case conf.unreadCountTo0:
unread.replies = []; unread.replies = [];
unread.updateTitle(); unread.update();
Favicon.update();
break; break;
default: default:
return; return;
@ -1201,11 +1193,13 @@
if (!$.id('recaptcha_challenge_field_holder')) return; if (!$.id('recaptcha_challenge_field_holder')) return;
if (conf['Hide Original Post Form']) { if (conf['Hide Original Post Form']) {
link = $.el('h1', { link = $.el('h1', {
innerHTML: "<a href=javascript:;>" + (g.REPLY ? 'Open the Quick Reply' : 'Create a New Thread') + "</a>" innerHTML: "<a href=javascript:;>" + (g.REPLY ? 'Quick Reply' : 'New Thread') + "</a>"
});
$.on($('a', link), 'click', function() {
qr.open();
return $('textarea', qr.el).focus();
}); });
$.on($('a', link), 'click', qr.open);
form = d.forms[0]; form = d.forms[0];
form.hidden = true;
$.before(form, link); $.before(form, link);
} }
g.callbacks.push(function(root) { g.callbacks.push(function(root) {
@ -1214,7 +1208,7 @@
iframe = $.el('iframe', { iframe = $.el('iframe', {
id: 'iframe', id: 'iframe',
hidden: true, hidden: true,
src: 'http://sys.4chan.org/post' src: 'http://sys.4chan.org/robots.txt'
}); });
$.on(iframe, 'error', function() { $.on(iframe, 'error', function() {
return this.src = this.src; return this.src = this.src;
@ -1223,20 +1217,22 @@
if (!qr.status.ready) { if (!qr.status.ready) {
iframe.src = 'about:blank'; iframe.src = 'about:blank';
return setTimeout((function() { return setTimeout((function() {
return iframe.src = 'http://sys.4chan.org/post'; return iframe.src = 'http://sys.4chan.org/robots.txt';
}), 250); }), 250);
} }
}; };
$.on(iframe, 'load', function() { $.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); $.add(d.body, iframe);
if (conf['Persistent QR']) { if (conf['Persistent QR']) {
qr.dialog(); qr.dialog();
if (conf['Auto Hide QR']) qr.hide(); if (conf['Auto Hide QR']) qr.hide();
} }
$.on(d, 'dragover', qr.fileDrop); $.on(d, 'dragover', qr.dragOver);
$.on(d, 'drop', qr.fileDrop); $.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(){})'; return window.location = 'javascript:void(Recaptcha.focus_response_field=function(){})';
}, },
open: function() { open: function() {
@ -1284,6 +1280,7 @@
el.textContent = err; el.textContent = err;
if (node) $.replace(el.firstChild, node); if (node) $.replace(el.firstChild, node);
qr.open(); qr.open();
if (/captcha|verification/i.test(err)) $('[autocomplete]', qr.el).focus();
if (d.hidden || d.oHidden || d.mozHidden || d.webkitHidden) { if (d.hidden || d.oHidden || d.mozHidden || d.webkitHidden) {
return alert(err); return alert(err);
} }
@ -1359,17 +1356,22 @@
ta.focus(); ta.focus();
return ta.selectionEnd = ta.selectionStart = caretPos + text.length; return ta.selectionEnd = ta.selectionStart = caretPos + text.length;
}, },
fileDrop: function(e) { drag: function(e) {
if (/TEXTAREA|INPUT/.test(e.target.nodeName)) return; 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.preventDefault();
e.stopPropagation(); return e.dataTransfer.dropEffect = 'copy';
e.dataTransfer.dropEffect = 'copy'; },
if (e.type === 'drop') { dropFile: function(e) {
if (!e.dataTransfer.files.length) return; if (!e.dataTransfer.files.length) return;
qr.open(); e.preventDefault();
qr.fileInput.call(e.dataTransfer); qr.open();
return $.addClass(qr.el, 'dump'); qr.fileInput.call(e.dataTransfer);
} return $.addClass(qr.el, 'dump');
}, },
fileInput: function() { fileInput: function() {
var file, _i, _len, _ref; var file, _i, _len, _ref;
@ -1544,7 +1546,7 @@
case 0: case 0:
return 'Verification (Shift + Enter to cache)'; return 'Verification (Shift + Enter to cache)';
case 1: case 1:
return 'Vertification (1 cached captcha)'; return 'Verification (1 cached captcha)';
default: default:
return "Verification (" + count + " cached captchas)"; return "Verification (" + count + " cached captchas)";
} }
@ -1586,7 +1588,7 @@
<div class=warning></div>\ <div class=warning></div>\
</form>'); </form>');
if (conf['Remember QR size'] && engine === 'gecko') { 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); return $.set('qr.size', this.style.cssText);
}); });
ta.style.cssText = $.get('qr.size', ''); ta.style.cssText = $.get('qr.size', '');
@ -1654,7 +1656,7 @@
} }
$.sync('qr.persona', function(persona) { $.sync('qr.persona', function(persona) {
var key, val, _results; var key, val, _results;
if (qr.replies.length !== 1) return; if (!qr.el.hidden) return;
_results = []; _results = [];
for (key in persona) { for (key in persona) {
val = persona[key]; val = persona[key];
@ -1817,6 +1819,7 @@
textContent: "window.addEventListener('message'," + code + ",false)" textContent: "window.addEventListener('message'," + code + ",false)"
}); });
ready = function() { ready = function() {
$.add(d.documentElement, script);
if (location.hostname === 'sys.4chan.org') { if (location.hostname === 'sys.4chan.org') {
qr.message.send({ qr.message.send({
req: 'status', req: 'status',
@ -1826,14 +1829,10 @@
return $.rm(script); return $.rm(script);
}; };
if (d.documentElement) { if (d.documentElement) {
$.add(d.documentElement, script);
ready();
return;
}
return $.ready(function() {
$.add(d.head, script);
return ready(); return ready();
}); } else {
return $.ready(ready);
}
}, },
send: function(data) { send: function(data) {
data.changeContext = true; data.changeContext = true;
@ -2035,21 +2034,20 @@
<li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li>\ <li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li>\
<li>Minutes: %M</li>\ <li>Minutes: %M</li>\
</ul>\ </ul>\
<div class=warning><code>Unread Count</code> is disabled.</div>\ <div class=warning><code>Unread Favicon</code> is disabled.</div>\
Unread favicons<br>\ Unread favicons<br>\
<select name=favicon>\ <select name=favicon>\
<option value=ferongr>ferongr</option>\ <option value=ferongr>ferongr</option>\
<option value=xat->xat-</option>\ <option value=xat->xat-</option>\
<option value=Mayhem>Mayhem</option>\ <option value=Mayhem>Mayhem</option>\
<option value=Original>Original</option>\ <option value=Original>Original</option>\
<option value=None>None</option>\
</select>\ </select>\
<span></span>\ <span></span>\
</div>\ </div>\
<input type=radio name=tab hidden id=keybinds_tab>\ <input type=radio name=tab hidden id=keybinds_tab>\
<div>\ <div>\
<div class=warning><code>Keybinds</code> are disabled.</div>\ <div class=warning><code>Keybinds</code> are disabled.</div>\
<div>Allowed keys: Ctrl, Alt, a-z, A-Z, 0-1, Up, Down, Right, Left.</div>\ <div>Allowed keys: Ctrl, Alt, a-z, A-Z, 0-9, Up, Down, Right, Left.</div>\
<table><tbody>\ <table><tbody>\
<tr><th>Actions</th><th>Keybinds</th></tr>\ <tr><th>Actions</th><th>Keybinds</th></tr>\
</tbody></table>\ </tbody></table>\
@ -2122,18 +2120,21 @@
overlay = $.el('div', { overlay = $.el('div', {
id: 'overlay' id: 'overlay'
}); });
$.on(overlay, 'click', function() { $.on(overlay, 'click', options.close);
return $.rm(overlay);
});
$.on(dialog, 'click', function(e) { $.on(dialog, 'click', function(e) {
return e.stopPropagation(); return e.stopPropagation();
}); });
$.add(overlay, dialog); $.add(overlay, dialog);
$.add(d.body, overlay); $.add(d.body, overlay);
d.body.style.setProperty('overflow', 'hidden', null);
options.backlink.call(back); options.backlink.call(back);
options.time.call(time); options.time.call(time);
return options.favicon.call(favicon); return options.favicon.call(favicon);
}, },
close: function() {
$.rm(this);
return d.body.style.removeProperty('overflow');
},
clearHidden: function() { clearHidden: function() {
$["delete"]("hiddenReplies/" + g.BOARD + "/"); $["delete"]("hiddenReplies/" + g.BOARD + "/");
$["delete"]("hiddenThreads/" + g.BOARD + "/"); $["delete"]("hiddenThreads/" + g.BOARD + "/");
@ -2158,7 +2159,7 @@
}, },
favicon: function() { favicon: function() {
Favicon["switch"](); Favicon["switch"]();
if (g.REPLY && conf['Unread Count']) Favicon.update(); unread.update(true);
return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">"; return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">";
} }
}; };
@ -2374,8 +2375,13 @@
updater.count.textContent = 404; updater.count.textContent = 404;
updater.count.className = 'warning'; updater.count.className = 'warning';
clearTimeout(updater.timeoutID); clearTimeout(updater.timeoutID);
d.title = d.title.match(/^.+-/)[0] + ' 404';
g.dead = true; 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({ qr.message.send({
req: 'abort' req: 'abort'
}); });
@ -2581,12 +2587,24 @@
sauce = { sauce = {
init: function() { init: function() {
var link, links, _i, _len; var domain, fc, link, links, _i, _len;
links = conf['sauces'].match(/^[^#].+$/gm); links = conf['sauces'].match(/^[^#].+$/gm);
if (!links.length) return;
this.links = []; this.links = [];
for (_i = 0, _len = links.length; _i < _len; _i++) { for (_i = 0, _len = links.length; _i < _len; _i++) {
link = links[_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); return g.callbacks.push(this.node);
}, },
@ -2598,24 +2616,12 @@
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
link = _ref[_i]; link = _ref[_i];
a = $.el('a', { a = $.el('a', {
textContent: link[1], href: link[0](img),
href: sauce.href(link[0], img), target: '_blank',
target: '_blank' textContent: link[1]
}); });
$.add(span, $.tn(' '), a); $.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; root = q.parentNode.nodeName === 'FONT' ? q.parentNode : q.nextSibling ? q.nextSibling : q;
if (el = $.id(id)) { if (el = $.id(id)) {
inline = quoteInline.table(id, el.innerHTML); 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.replies.splice(i, 1);
unread.updateTitle(); unread.update();
Favicon.update();
} }
if (/\bbacklink\b/.test(q.className)) { if (/\bbacklink\b/.test(q.className)) {
$.after(q.parentNode, inline); $.after(q.parentNode, inline);
@ -3112,7 +3117,8 @@
unread = { unread = {
init: function() { init: function() {
d.title = '(0) ' + d.title; this.title = d.title;
unread.update();
$.on(window, 'scroll', unread.scroll); $.on(window, 'scroll', unread.scroll);
return g.callbacks.push(unread.node); return g.callbacks.push(unread.node);
}, },
@ -3120,8 +3126,7 @@
node: function(root) { node: function(root) {
if (root.hidden || root.className) return; if (root.hidden || root.className) return;
unread.replies.push(root); unread.replies.push(root);
unread.updateTitle(); return unread.update();
if (unread.replies.length === 1) return Favicon.update();
}, },
scroll: function() { scroll: function() {
var bottom, height, i, reply, _len, _ref; var bottom, height, i, reply, _len, _ref;
@ -3134,20 +3139,25 @@
} }
if (i === 0) return; if (i === 0) return;
unread.replies = unread.replies.slice(i); unread.replies = unread.replies.slice(i);
unread.updateTitle(); return unread.update();
if (unread.replies.length === 0) return Favicon.update();
}, },
updateTitle: function() { update: function(forceUpdate) {
return d.title = d.title.replace(/\d+/, unread.replies.length); 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 = { Favicon = {
init: function() { init: function() {
var favicon, href; var href;
favicon = $('link[rel="shortcut icon"]', d.head); this.el = $('link[rel="shortcut icon"]', d.head);
favicon.type = 'image/x-icon'; this.el.type = 'image/x-icon';
href = favicon.href; href = this.el.href;
this.SFW = /ws.ico$/.test(href); this.SFW = /ws.ico$/.test(href);
this["default"] = href; this["default"] = href;
return this["switch"](); return this["switch"]();
@ -3173,43 +3183,34 @@
this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
this.unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAKECAAAAAGbMM////////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; return this.unread = this.SFW ? this.unreadSFW : this.unreadNSFW;
}, },
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', 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));
}
}; };
redirect = { redirect = {
init: function() { init: function() {
var url; 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; if (url) return location.href = url;
}, },
image: function(board, filename) { image: function(href) {
switch (board) { href = href.split('/');
if (!conf['404 Redirect']) return;
switch (href[3]) {
case 'a': case 'a':
case 'jp': case 'jp':
case 'm': case 'm':
case 'tg': case 'tg':
case 'tv': case 'tv':
case 'u': case 'u':
return "http://archive.foolz.us/" + board + "/full_image/" + filename; return "http://archive.foolz.us/" + href[3] + "/full_image/" + href[5];
} }
}, },
thread: function() { thread: function() {
if (!conf['404 Redirect']) return;
switch (g.BOARD) { switch (g.BOARD) {
case 'a': case 'a':
case 'jp': case 'jp':
@ -3249,7 +3250,7 @@
case 'x': case 'x':
return "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID; return "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
default: default:
return "http://boards.4chan.org/" + g.BOARD; return "http://boards.4chan.org/" + g.BOARD + "/";
} }
} }
}; };
@ -3380,17 +3381,16 @@
img = $.el('img', { img = $.el('img', {
src: url || a.href src: url || a.href
}); });
if (conf['404 Redirect']) $.on(img, 'error', imgExpand.error); $.on(img, 'error', imgExpand.error);
return $.add(a, img); return $.add(a, img);
}, },
error: function() { error: function() {
var href, src, thumb, timeoutID, url; var href, thumb, timeoutID, url;
href = this.parentNode.href; href = this.parentNode.href;
thumb = this.previousSibling; thumb = this.previousSibling;
src = href.split('/');
imgExpand.contract(thumb); imgExpand.contract(thumb);
$.rm(this); $.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; if (g.dead) return;
url = href + '?' + Date.now(); url = href + '?' + Date.now();
} }
@ -3440,7 +3440,7 @@
} }
$.on(window, 'message', Main.message); $.on(window, 'message', Main.message);
if (location.hostname === 'sys.4chan.org') { if (location.hostname === 'sys.4chan.org') {
if (location.pathname === '/post') { if (location.pathname === '/robots.txt') {
qr.message.init(); qr.message.init();
} else if (/report/.test(location.search)) { } else if (/report/.test(location.search)) {
$.ready(function() { $.ready(function() {
@ -3501,17 +3501,21 @@
if (conf['Quote Backlinks']) quoteBacklink.init(); if (conf['Quote Backlinks']) quoteBacklink.init();
if (conf['Indicate OP quote']) quoteOP.init(); if (conf['Indicate OP quote']) quoteOP.init();
if (conf['Indicate Cross-thread Quotes']) quoteDR.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); return $.ready(Main.ready);
}, },
ready: function() { ready: function() {
var callback, form, node, nodes, _i, _j, _len, _len2, _ref; 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(); redirect.init();
return; return;
} }
if (!$.id('navtopr')) return; if (!$.id('navtopr')) return;
$.addClass(d.body, "chanx_" + (VERSION.match(/\.(\d+)/)[1]));
$.addClass(d.body, engine); $.addClass(d.body, engine);
$.addStyle(Main.css);
threading.init(); threading.init();
Favicon.init(); Favicon.init();
if (conf['Quick Reply']) qr.init(); if (conf['Quick Reply']) qr.init();
@ -3523,7 +3527,7 @@
if (conf['Thread Stats']) threadStats.init(); if (conf['Thread Stats']) threadStats.init();
if (conf['Reply Navigation']) nav.init(); if (conf['Reply Navigation']) nav.init();
if (conf['Post in Title']) titlePost.init(); if (conf['Post in Title']) titlePost.init();
if (conf['Unread Count']) unread.init(); if (conf['Unread Count'] || conf['Unread Favicon']) unread.init();
} else { } else {
if (conf['Thread Hiding']) threadHiding.init(); if (conf['Thread Hiding']) threadHiding.init();
if (conf['Thread Expansion']) expandThread.init(); if (conf['Thread Expansion']) expandThread.init();
@ -3546,6 +3550,14 @@
} }
return $.on(form, 'DOMNodeInserted', Main.node); 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) { message: function(e) {
var data, version; var data, version;
data = e.data; data = e.data;
@ -3756,6 +3768,7 @@ textarea.field {\
}\ }\
#qr [type=submit] {\ #qr [type=submit] {\
margin: 1px 0;\ margin: 1px 0;\
padding: 1px; /* not Gecko */\
padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */\ padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */\
width: 30%;\ width: 30%;\
}\ }\
@ -3901,7 +3914,8 @@ img[md5], img[md5] + img {\
}\ }\
.filtered {\ .filtered {\
text-decoration: line-through;\ text-decoration: line-through;\
}' }\
'
}; };
Main.init(); Main.init();

View File

@ -1,9 +1,15 @@
master master
- desuwa - desuwa
New option: remember the size of the QR on Firefox. New option: remember the size of the QR on Firefox.
- aeosynth
prevent post form flicker
- Mayhem - Mayhem
Load QR's iframe to sys.4chan.org faster, unless you use Greasemonkey. Thanks desuwa.
Increase Sauce linking possibilites: Increase Sauce linking possibilites:
Thumbnails, full images, MD5 hashes. 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 2.25.5
- Mayhem - Mayhem

View File

@ -14,6 +14,7 @@ config =
'Anonymize': [false, 'Make everybody anonymous'] 'Anonymize': [false, 'Make everybody anonymous']
'Filter': [false, 'Self-moderation placebo'] 'Filter': [false, 'Self-moderation placebo']
'Filter OPs': [false, 'Filter OPs along with their threads'] 'Filter OPs': [false, 'Filter OPs along with their threads']
'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively']
'Reply Hiding': [true, 'Hide single replies'] 'Reply Hiding': [true, 'Hide single replies']
'Thread Hiding': [true, 'Hide entire threads'] 'Thread Hiding': [true, 'Hide entire threads']
'Show Stubs': [true, 'Of hidden threads / replies'] 'Show Stubs': [true, 'Of hidden threads / replies']
@ -26,6 +27,7 @@ config =
Monitoring: Monitoring:
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'] 'Thread Updater': [true, 'Update threads. Has more options in its own dialog.']
'Unread Count': [true, 'Show unread post count in tab title'] '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'] 'Post in Title': [true, 'Show the op\'s post in the tab title']
'Thread Stats': [true, 'Display reply and image count'] 'Thread Stats': [true, 'Display reply and image count']
'Thread Watcher': [true, 'Bookmark threads'] 'Thread Watcher': [true, 'Bookmark threads']
@ -97,7 +99,7 @@ config =
expandImages: ['m', 'Expand selected image'] expandImages: ['m', 'Expand selected image']
expandAllImages: ['M', 'Expand all images'] expandAllImages: ['M', 'Expand all images']
update: ['u', 'Update now'] update: ['u', 'Update now']
unreadCountTo0: ['z', 'Reset unread count to 0'] unreadCountTo0: ['z', 'Reset unread status']
updater: updater:
checkbox: checkbox:
'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'] '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 # flatten the config
conf = {} conf = {}
(flatten = (parent, obj) -> (flatten = (parent, obj) ->
if typeof obj is 'object' if obj instanceof Array
# array conf[parent] = obj[0]
if obj.length else if typeof obj is 'object'
conf[parent] = obj[0] for key, val of obj
# object
else for key, val of obj
flatten key, val flatten key, val
else # string or number else # string or number
conf[parent] = obj conf[parent] = obj
@ -458,9 +458,10 @@ strikethroughQuotes =
g.callbacks.push (root) -> g.callbacks.push (root) ->
return if root.className is 'inline' return if root.className is 'inline'
for quote in $$ '.quotelink', root for quote in $$ '.quotelink', root
if el = $.id quote.hash[1..] if (el = $.id quote.hash[1..]) and el.parentNode.parentNode.parentNode.hidden
if el.parentNode.parentNode.parentNode.hidden $.addClass quote, 'filtered'
$.addClass quote, 'filtered' root.hidden = true if conf['Recursive Filtering']
return
expandComment = expandComment =
init: -> init: ->
@ -658,7 +659,7 @@ keybinds =
options.dialog() unless $.id 'overlay' options.dialog() unless $.id 'overlay'
when conf.close when conf.close
if o = $.id 'overlay' if o = $.id 'overlay'
$.rm o options.close.call o
else if qr.el else if qr.el
qr.close() qr.close()
when conf.spoiler when conf.spoiler
@ -716,8 +717,7 @@ keybinds =
qr.submit() if qr.el and !qr.status() qr.submit() if qr.el and !qr.status()
when conf.unreadCountTo0 when conf.unreadCountTo0
unread.replies = [] unread.replies = []
unread.updateTitle() unread.update()
Favicon.update()
else else
return return
e.preventDefault() e.preventDefault()
@ -869,10 +869,11 @@ qr =
init: -> init: ->
return unless $.id 'recaptcha_challenge_field_holder' return unless $.id 'recaptcha_challenge_field_holder'
if conf['Hide Original Post Form'] if conf['Hide Original Post Form']
link = $.el 'h1', innerHTML: "<a href=javascript:;>#{if g.REPLY then 'Open the Quick Reply' else 'Create a New Thread'}</a>" link = $.el 'h1', innerHTML: "<a href=javascript:;>#{if g.REPLY then 'Quick Reply' else 'New Thread'}</a>"
$.on $('a', link), 'click', qr.open $.on $('a', link), 'click', ->
qr.open()
$('textarea', qr.el).focus()
form = d.forms[0] form = d.forms[0]
form.hidden = true
$.before form, link $.before form, link
g.callbacks.push (root) -> g.callbacks.push (root) ->
$.on $('.quotejs + .quotejs', root), 'click', qr.quote $.on $('.quotejs + .quotejs', root), 'click', qr.quote
@ -880,21 +881,23 @@ qr =
iframe = $.el 'iframe', iframe = $.el 'iframe',
id: 'iframe' id: 'iframe'
hidden: true hidden: true
src: 'http://sys.4chan.org/post' src: 'http://sys.4chan.org/robots.txt'
$.on iframe, 'error', -> @src = @src $.on iframe, 'error', -> @src = @src
# Greasemonkey ghetto fix # Greasemonkey ghetto fix
loadChecking = (iframe) -> loadChecking = (iframe) ->
unless qr.status.ready unless qr.status.ready
iframe.src = 'about:blank' iframe.src = 'about:blank'
setTimeout (-> iframe.src = 'http://sys.4chan.org/post'), 250 setTimeout (-> iframe.src = 'http://sys.4chan.org/robots.txt'), 250
$.on iframe, 'load', -> unless @src is 'about:blank' then setTimeout loadChecking, 250, @ $.on iframe, 'load', -> unless @src is 'about:blank' then setTimeout loadChecking, 500, @
$.add d.body, iframe $.add d.body, iframe
if conf['Persistent QR'] if conf['Persistent QR']
qr.dialog() qr.dialog()
qr.hide() if conf['Auto Hide QR'] qr.hide() if conf['Auto Hide QR']
$.on d, 'dragover', qr.fileDrop $.on d, 'dragover', qr.dragOver
$.on d, 'drop', qr.fileDrop $.on d, 'drop', qr.dropFile
$.on d, 'dragstart', qr.drag
$.on d, 'dragend', qr.drag
# prevent original captcha input from being focused on reload # prevent original captcha input from being focused on reload
window.location = 'javascript:void(Recaptcha.focus_response_field=function(){})' window.location = 'javascript:void(Recaptcha.focus_response_field=function(){})'
@ -931,6 +934,9 @@ qr =
el.textContent = err el.textContent = err
$.replace el.firstChild, node if node $.replace el.firstChild, node if node
qr.open() 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 alert err if d.hidden or d.oHidden or d.mozHidden or d.webkitHidden
cleanError: -> cleanError: ->
$('.warning', qr.el).textContent = null $('.warning', qr.el).textContent = null
@ -1004,16 +1010,21 @@ qr =
# Move the caret to the end of the new quote. # Move the caret to the end of the new quote.
ta.selectionEnd = ta.selectionStart = caretPos + text.length ta.selectionEnd = ta.selectionStart = caretPos + text.length
fileDrop: (e) -> drag: (e) ->
return if /TEXTAREA|INPUT/.test e.target.nodeName # 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.preventDefault()
e.stopPropagation()
e.dataTransfer.dropEffect = 'copy' # cursor feedback e.dataTransfer.dropEffect = 'copy' # cursor feedback
if e.type is 'drop' dropFile: (e) ->
return unless e.dataTransfer.files.length # let it only drop files # Let it only handle files from the desktop.
qr.open() return unless e.dataTransfer.files.length
qr.fileInput.call e.dataTransfer e.preventDefault()
$.addClass qr.el, 'dump' qr.open()
qr.fileInput.call e.dataTransfer
$.addClass qr.el, 'dump'
fileInput: -> fileInput: ->
qr.cleanError() qr.cleanError()
# Set or change current reply's file. # Set or change current reply's file.
@ -1144,7 +1155,7 @@ qr =
when 0 when 0
'Verification (Shift + Enter to cache)' 'Verification (Shift + Enter to cache)'
when 1 when 1
'Vertification (1 cached captcha)' 'Verification (1 cached captcha)'
else else
"Verification (#{count} cached captchas)" "Verification (#{count} cached captchas)"
@input.alt = count # For XTRM RICE. @input.alt = count # For XTRM RICE.
@ -1180,7 +1191,7 @@ qr =
</form>' </form>'
if conf['Remember QR size'] and engine is 'gecko' 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 $.set 'qr.size', @style.cssText
ta.style.cssText = $.get 'qr.size', '' ta.style.cssText = $.get 'qr.size', ''
@ -1229,7 +1240,7 @@ qr =
$.on input, 'change', -> qr.selected[@name] = @value $.on input, 'change', -> qr.selected[@name] = @value
# sync between tabs # sync between tabs
$.sync 'qr.persona', (persona) -> $.sync 'qr.persona', (persona) ->
return if qr.replies.length isnt 1 return unless qr.el.hidden
for key, val of persona for key, val of persona
qr.selected[key] = val qr.selected[key] = val
$("[name=#{key}]", qr.el).value = val $("[name=#{key}]", qr.el).value = val
@ -1395,18 +1406,15 @@ qr =
parent.postMessage data, '*' parent.postMessage data, '*'
script = $.el 'script', textContent: "window.addEventListener('message',#{code},false)" script = $.el 'script', textContent: "window.addEventListener('message',#{code},false)"
ready = -> ready = ->
$.add d.documentElement, script
if location.hostname is 'sys.4chan.org' if location.hostname is 'sys.4chan.org'
qr.message.send req: 'status', ready: true qr.message.send req: 'status', ready: true
$.rm script $.rm script
# Chrome can access the documentElement on document-start # Chrome can access the documentElement on document-start
if d.documentElement if d.documentElement
$.add d.documentElement, script
ready() ready()
return
# other browsers will have to wait # other browsers will have to wait
$.ready -> else $.ready ready
$.add d.head, script
ready()
send: (data) -> send: (data) ->
data.changeContext = true data.changeContext = true
data.qr = true data.qr = true
@ -1579,21 +1587,20 @@ options =
<li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li> <li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li>
<li>Minutes: %M</li> <li>Minutes: %M</li>
</ul> </ul>
<div class=warning><code>Unread Count</code> is disabled.</div> <div class=warning><code>Unread Favicon</code> is disabled.</div>
Unread favicons<br> Unread favicons<br>
<select name=favicon> <select name=favicon>
<option value=ferongr>ferongr</option> <option value=ferongr>ferongr</option>
<option value=xat->xat-</option> <option value=xat->xat-</option>
<option value=Mayhem>Mayhem</option> <option value=Mayhem>Mayhem</option>
<option value=Original>Original</option> <option value=Original>Original</option>
<option value=None>None</option>
</select> </select>
<span></span> <span></span>
</div> </div>
<input type=radio name=tab hidden id=keybinds_tab> <input type=radio name=tab hidden id=keybinds_tab>
<div> <div>
<div class=warning><code>Keybinds</code> are disabled.</div> <div class=warning><code>Keybinds</code> are disabled.</div>
<div>Allowed keys: Ctrl, Alt, a-z, A-Z, 0-1, Up, Down, Right, Left.</div> <div>Allowed keys: Ctrl, Alt, a-z, A-Z, 0-9, Up, Down, Right, Left.</div>
<table><tbody> <table><tbody>
<tr><th>Actions</th><th>Keybinds</th></tr> <tr><th>Actions</th><th>Keybinds</th></tr>
</tbody></table> </tbody></table>
@ -1656,15 +1663,20 @@ options =
indicators[@name].hidden = @checked indicators[@name].hidden = @checked
overlay = $.el 'div', id: 'overlay' overlay = $.el 'div', id: 'overlay'
$.on overlay, 'click', -> $.rm overlay $.on overlay, 'click', options.close
$.on dialog, 'click', (e) -> e.stopPropagation() $.on dialog, 'click', (e) -> e.stopPropagation()
$.add overlay, dialog $.add overlay, dialog
$.add d.body, overlay $.add d.body, overlay
d.body.style.setProperty 'overflow', 'hidden', null
options.backlink.call back options.backlink.call back
options.time.call time options.time.call time
options.favicon.call favicon options.favicon.call favicon
close: ->
$.rm this
d.body.style.removeProperty 'overflow'
clearHidden: -> clearHidden: ->
#'hidden' might be misleading; it's the number of IDs we're *looking* for, #'hidden' might be misleading; it's the number of IDs we're *looking* for,
# not the number of posts actually hidden on the page. # not the number of posts actually hidden on the page.
@ -1687,7 +1699,7 @@ options =
$.id('backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789' $.id('backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
favicon: -> favicon: ->
Favicon.switch() Favicon.switch()
Favicon.update() if g.REPLY and conf['Unread Count'] unread.update true
@nextElementSibling.innerHTML = "<img src=#{Favicon.unreadSFW}> <img src=#{Favicon.unreadNSFW}> <img src=#{Favicon.unreadDead}>" @nextElementSibling.innerHTML = "<img src=#{Favicon.unreadSFW}> <img src=#{Favicon.unreadNSFW}> <img src=#{Favicon.unreadDead}>"
threading = threading =
@ -1871,8 +1883,12 @@ updater =
updater.count.textContent = 404 updater.count.textContent = 404
updater.count.className = 'warning' updater.count.className = 'warning'
clearTimeout updater.timeoutID clearTimeout updater.timeoutID
d.title = d.title.match(/^.+-/)[0] + ' 404'
g.dead = true 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.message.send req: 'abort'
qr.status() qr.status()
Favicon.update() Favicon.update()
@ -2042,31 +2058,31 @@ anonymize =
sauce = sauce =
init: -> init: ->
# return unless
links = conf['sauces'].match /^[^#].+$/gm links = conf['sauces'].match /^[^#].+$/gm
return unless links.length
@links = [] @links = []
for link in 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 g.callbacks.push @node
node: (root) -> node: (root) ->
return if root.className is 'inline' or not span = $ '.filesize', root return if root.className is 'inline' or not span = $ '.filesize', root
img = $ 'img', root img = $ 'img', root
for link in sauce.links for link in sauce.links
a = $.el 'a', a = $.el 'a',
textContent: link[1] href: link[0] img
href: sauce.href link[0], img
target: '_blank' target: '_blank'
textContent: link[1]
$.add span, $.tn(' '), a $.add span, $.tn(' '), a
return 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 = revealSpoilers =
init: -> 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 root = if q.parentNode.nodeName is 'FONT' then q.parentNode else if q.nextSibling then q.nextSibling else q
if el = $.id id if el = $.id id
inline = quoteInline.table id, el.innerHTML 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.replies.splice i, 1
unread.updateTitle() unread.update()
Favicon.update()
if /\bbacklink\b/.test q.className if /\bbacklink\b/.test q.className
$.after q.parentNode, inline $.after q.parentNode, inline
$.addClass $.x('ancestor::table', el), 'forwarded' if conf['Forward Hiding'] $.addClass $.x('ancestor::table', el), 'forwarded' if conf['Forward Hiding']
@ -2398,7 +2413,8 @@ threadStats =
unread = unread =
init: -> init: ->
d.title = '(0) ' + d.title @title = d.title
unread.update()
$.on window, 'scroll', unread.scroll $.on window, 'scroll', unread.scroll
g.callbacks.push unread.node g.callbacks.push unread.node
@ -2407,9 +2423,7 @@ unread =
node: (root) -> node: (root) ->
return if root.hidden or root.className return if root.hidden or root.className
unread.replies.push root unread.replies.push root
unread.updateTitle() unread.update()
if unread.replies.length is 1
Favicon.update()
scroll: -> scroll: ->
height = d.body.clientHeight height = d.body.clientHeight
@ -2420,18 +2434,41 @@ unread =
return if i is 0 return if i is 0
unread.replies = unread.replies[i..] unread.replies = unread.replies[i..]
unread.updateTitle() unread.update()
if unread.replies.length is 0
Favicon.update()
updateTitle: -> update: (forceUpdate) ->
d.title = d.title.replace /\d+/, unread.replies.length 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 = Favicon =
init: -> init: ->
favicon = $ 'link[rel="shortcut icon"]', d.head @el = $ 'link[rel="shortcut icon"]', d.head
favicon.type = 'image/x-icon' @el.type = 'image/x-icon'
{href} = favicon {href} = @el
@SFW = /ws.ico$/.test href @SFW = /ws.ico$/.test href
@default = href @default = href
@switch() @switch()
@ -2454,50 +2491,28 @@ Favicon =
@unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=' @unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='
@unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=' @unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='
@unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAKECAAAAAGbMM////////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 @unread = if @SFW then @unreadSFW else @unreadNSFW
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==' empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==' 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 = redirect =
init: -> init: ->
url = url =
if location.hostname is 'images.4chan.org' 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 else if /^\d+$/.test g.THREAD_ID
redirect.thread() redirect.thread()
location.href = url if url location.href = url if url
image: (board, filename) -> #board must be given, the image can originate from a cross-quote image: (href) ->
switch board 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' 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: -> thread: ->
return unless conf['404 Redirect']
switch g.BOARD switch g.BOARD
when 'a', 'jp', 'm', 'tg', 'tv', 'u' when 'a', 'jp', 'm', 'tg', 'tv', 'u'
"http://archive.foolz.us/#{g.BOARD}/thread/#{g.THREAD_ID}/" "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' 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}" "http://archive.no-ip.org/#{g.BOARD}/thread/#{g.THREAD_ID}"
else else
"http://boards.4chan.org/#{g.BOARD}" "http://boards.4chan.org/#{g.BOARD}/"
imgHover = imgHover =
init: -> init: ->
@ -2599,16 +2614,15 @@ imgExpand =
a = thumb.parentNode a = thumb.parentNode
img = $.el 'img', img = $.el 'img',
src: url or a.href src: url or a.href
$.on img, 'error', imgExpand.error if conf['404 Redirect'] $.on img, 'error', imgExpand.error
$.add a, img $.add a, img
error: -> error: ->
href = @parentNode.href href = @parentNode.href
thumb = @previousSibling thumb = @previousSibling
src = href.split '/'
imgExpand.contract thumb imgExpand.contract thumb
$.rm @ $.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 return if g.dead
# CloudFlare may cache banned pages instead of images. # CloudFlare may cache banned pages instead of images.
# This will fool CloudFlare's cache. # This will fool CloudFlare's cache.
@ -2653,7 +2667,7 @@ Main =
$.on window, 'message', Main.message $.on window, 'message', Main.message
if location.hostname is 'sys.4chan.org' if location.hostname is 'sys.4chan.org'
if location.pathname is '/post' if location.pathname is '/robots.txt'
qr.message.init() qr.message.init()
else if /report/.test location.search else if /report/.test location.search
$.ready -> $.ready ->
@ -2736,17 +2750,21 @@ Main =
if conf['Indicate Cross-thread Quotes'] if conf['Indicate Cross-thread Quotes']
quoteDR.init() 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 Main.ready
ready: -> ready: ->
if conf['404 Redirect'] and d.title is '4chan - 404' if d.title is '4chan - 404'
redirect.init() redirect.init()
return return
if not $.id 'navtopr' if not $.id 'navtopr'
return return
$.addClass d.body, "chanx_#{VERSION.match(/\.(\d+)/)[1]}"
$.addClass d.body, engine $.addClass d.body, engine
$.addStyle Main.css
threading.init() threading.init()
Favicon.init() Favicon.init()
@ -2776,7 +2794,7 @@ Main =
if conf['Post in Title'] if conf['Post in Title']
titlePost.init() titlePost.init()
if conf['Unread Count'] if conf['Unread Count'] or conf['Unread Favicon']
unread.init() unread.init()
else #not reply else #not reply
@ -2803,6 +2821,13 @@ Main =
alert err alert err
$.on form, 'DOMNodeInserted', Main.node $.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) -> message: (e) ->
{data} = e {data} = e
{version} = data {version} = data
@ -3004,6 +3029,7 @@ textarea.field {
} }
#qr [type=submit] { #qr [type=submit] {
margin: 1px 0; margin: 1px 0;
padding: 1px; /* not Gecko */
padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */ padding: 0 -moz-calc(1px); /* Gecko does not respect box-sizing: border-box */
width: 30%; width: 30%;
} }
@ -3149,6 +3175,7 @@ img[md5], img[md5] + img {
} }
.filtered { .filtered {
text-decoration: line-through; text-decoration: line-through;
}' }
'
Main.init() Main.init()