Preparse posts; global performance improvements. This is mostly efficient when parsing multiple posts at once (when opening a thread, or expanding a thread if mutation observers are working).

This commit is contained in:
Nicolas Stepien 2012-03-03 02:35:21 +01:00
parent 0f6d607902
commit 63f48aeba6
2 changed files with 287 additions and 233 deletions

View File

@ -72,7 +72,7 @@
*/ */
(function() { (function() {
var $, $$, DAY, Favicon, FileInfo, HOUR, MINUTE, Main, NAMESPACE, SECOND, Time, VERSION, anonymize, conf, config, d, engine, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, key, keybinds, log, nav, options, qr, quoteBacklink, quoteIndicators, quoteInline, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _base; var $, $$, DAY, Favicon, FileInfo, HOUR, MINUTE, Main, NAMESPACE, SECOND, Time, VERSION, anonymize, conf, config, d, engine, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, key, keybinds, log, nav, options, qr, quoteBacklink, quoteDR, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _base;
config = { config = {
main: { main: {
@ -575,8 +575,9 @@
if (Object.keys(this.filters).length) return g.callbacks.push(this.node); if (Object.keys(this.filters).length) return g.callbacks.push(this.node);
}, },
createFilter: function(regexp, op, hl, top) { createFilter: function(regexp, op, hl, top) {
return function(root, value, isOP) { return function(post, value) {
var firstThread, thisThread; var el, firstThread, isOP, thisThread;
el = post.el, isOP = post.isOP;
if (isOP && op === 'no' || !isOP && op === 'only') return false; if (isOP && op === 'no' || !isOP && op === 'only') return false;
if (typeof regexp === 'string') { if (typeof regexp === 'string') {
if (regexp !== value) return false; if (regexp !== value) return false;
@ -585,12 +586,12 @@
} }
if (hl) { if (hl) {
if (isOP) { if (isOP) {
$.addClass(root, hl); $.addClass(el, hl);
} else { } else {
$.addClass(root.parentNode, hl); $.addClass(el.parentNode, hl);
} }
if (isOP && top && !g.REPLY) { if (isOP && top && !g.REPLY) {
thisThread = root.parentNode; thisThread = el.parentNode;
if (firstThread = $('div[class=op]')) { if (firstThread = $('div[class=op]')) {
$.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]); $.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]);
} }
@ -598,86 +599,85 @@
return false; return false;
} }
if (isOP) { if (isOP) {
if (!g.REPLY) threadHiding.hideHide(root.parentNode); if (!g.REPLY) threadHiding.hideHide(el.parentNode);
} else { } else {
replyHiding.hideHide(root); replyHiding.hideHide(el);
} }
return true; return true;
}; };
}, },
node: function(root) { node: function(post) {
var Filter, isOP, key, klass, value, _i, _len, _ref; var Filter, key, value, _i, _len, _ref;
klass = root.className; if (post.isInlined) return;
if (/\binlined\b/.test(klass)) return;
if (!(isOP = klass === 'op')) root = $('td[id]', root);
for (key in filter.filters) { for (key in filter.filters) {
value = filter[key](root, isOP); value = filter[key](post);
if (value === false) continue; if (value === false) continue;
_ref = filter.filters[key]; _ref = filter.filters[key];
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
Filter = _ref[_i]; Filter = _ref[_i];
if (Filter(root, value, isOP)) return; if (Filter(post, value)) return;
} }
} }
}, },
name: function(root, isOP) { name: function(post) {
var name; var name;
name = isOP ? $('.postername', root) : $('.commentpostername', root); name = post.isOP ? $('.postername', post.el) : $('.commentpostername', post.el);
return name.textContent; return name.textContent;
}, },
tripcode: function(root) { tripcode: function(post) {
var trip; var trip;
if (trip = $('.postertrip', root)) return trip.textContent; if (trip = $('.postertrip', post.el)) return trip.textContent;
return false; return false;
}, },
mod: function(root, isOP) { mod: function(post) {
var mod; var mod;
if (mod = (isOP ? $('.commentpostername', root) : $('.commentpostername ~ .commentpostername', root))) { if (mod = (post.isOP ? $('.commentpostername', post.el) : $('.commentpostername ~ .commentpostername', post.el))) {
return mod.textContent; return mod.textContent;
} }
return false; return false;
}, },
email: function(root) { email: function(post) {
var mail; var mail;
if (mail = $('.linkmail', root)) return mail.href; if (mail = $('.linkmail', post.el)) return mail.href;
return false; return false;
}, },
subject: function(root, isOP) { subject: function(post) {
var sub; var sub;
sub = isOP ? $('.filetitle', root) : $('.replytitle', root); sub = post.isOP ? $('.filetitle', post.el) : $('.replytitle', post.el);
return sub.textContent; return sub.textContent;
}, },
comment: function(root) { comment: function(post) {
var data, i, len, nodes, text; var data, i, nodes, text, _ref;
text = []; text = [];
nodes = d.evaluate('.//br|.//text()', root.lastChild, null, 7, null); nodes = d.evaluate('.//br|.//text()', post.bq, null, 7, null);
i = 0; for (i = 0, _ref = nodes.snapshotLength; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
len = nodes.snapshotLength; text.push((data = nodes.snapshotItem(i).data) ? data : '\n');
while (i < len) {
text.push((data = nodes.snapshotItem(i++).data) ? data : '\n');
} }
return text.join(''); return text.join('');
}, },
filename: function(root) { filename: function(post) {
var file; var file;
if (file = $('.filesize > span', root)) return file.title; if (file = $('span', post.filesize)) return file.title;
return false; return false;
}, },
dimensions: function(root) { dimensions: function(post) {
var match, span; var filesize, match;
if ((span = $('.filesize', root)) && (match = span.textContent.match(/\d+x\d+/))) { filesize = post.filesize;
if (filesize && (match = filesize.textContent.match(/\d+x\d+/))) {
return match[0]; return match[0];
} }
return false; return false;
}, },
filesize: function(root) { filesize: function(post) {
var img; var img;
if (img = $('img[md5]', root)) return img.alt; img = post.img;
if (img) return img.alt;
return false; return false;
}, },
md5: function(root) { md5: function(post) {
var img; var img;
if (img = $('img[md5]', root)) return img.getAttribute('md5'); img = post.img;
if (img) return img.getAttribute('md5');
return false; return false;
} }
}; };
@ -686,15 +686,15 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var el, quote, _i, _len, _ref; var el, quote, _i, _len, _ref;
if (root.className === 'inline') return; if (post.isInlined) return;
_ref = $$('.quotelink', root); _ref = post.quotes;
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))) && el.parentNode.parentNode.parentNode.hidden) { if ((el = $.id(quote.hash.slice(1))) && el.parentNode.parentNode.parentNode.hidden) {
$.addClass(quote, 'filtered'); $.addClass(quote, 'filtered');
if (conf['Recursive Filtering']) root.hidden = true; if (conf['Recursive Filtering']) post.root.hidden = true;
} }
} }
} }
@ -866,9 +866,10 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var a, dd, id, reply; var a, dd, id, reply;
if (!(dd = $('.doubledash', root))) return; if (post["class"]) return;
dd = $('.doubledash', post.root);
dd.className = 'replyhider'; dd.className = 'replyhider';
a = $.el('a', { a = $.el('a', {
textContent: '[ - ]', textContent: '[ - ]',
@ -1312,8 +1313,8 @@
$.on(d, 'dragstart', qr.drag); $.on(d, 'dragstart', qr.drag);
return $.on(d, 'dragend', qr.drag); return $.on(d, 'dragend', qr.drag);
}, },
node: function(root) { node: function(post) {
return $.on($('.quotejs + .quotejs', root), 'click', qr.quote); return $.on($('.quotejs + .quotejs', post.el), 'click', qr.quote);
}, },
open: function() { open: function() {
if (qr.el) { if (qr.el) {
@ -2745,11 +2746,11 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var name, trip; var name, trip;
name = $('.commentpostername, .postername', root); name = $('.commentpostername, .postername', post.el);
name.textContent = 'Anonymous'; name.textContent = 'Anonymous';
if (trip = $('.postertrip', root)) { if (trip = $('.postertrip', post.el)) {
if (trip.parentNode.nodeName === 'A') { if (trip.parentNode.nodeName === 'A') {
return $.rm(trip.parentNode); return $.rm(trip.parentNode);
} else { } else {
@ -2797,17 +2798,18 @@
}); });
}; };
}, },
node: function(root) { node: function(post) {
var img, link, nodes, span, _i, _len, _ref; var img, link, nodes, _i, _len, _ref;
if (root.className === 'inline' || !(span = $('.filesize', root))) return; img = post.img;
img = span.nextElementSibling.nextElementSibling; if (post["class"] === 'inline' || !img) return;
img = img.parentNode;
nodes = []; nodes = [];
_ref = sauce.links; _ref = sauce.links;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
link = _ref[_i]; link = _ref[_i];
nodes.push($.tn(' '), link(img)); nodes.push($.tn(' '), link(img));
} }
return $.add(span, nodes); return $.add(post.filesize, nodes);
} }
}; };
@ -2815,11 +2817,12 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var img; var img;
if (!(img = $('img[alt^=Spoil]', root)) || root.className === 'inline') { img = {
return; post: post
} };
if (!(img && /^Spoil/.test(img.alt)) || post["class"] === 'inline') return;
img.removeAttribute('height'); img.removeAttribute('height');
img.removeAttribute('width'); img.removeAttribute('width');
return img.src = "http://thumbs.4chan.org" + (img.parentNode.pathname.replace(/src(\/\d+).+$/, 'thumb$1s.jpg')); return img.src = "http://thumbs.4chan.org" + (img.parentNode.pathname.replace(/src(\/\d+).+$/, 'thumb$1s.jpg'));
@ -2844,10 +2847,10 @@
}; };
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var node, time; var node, time;
if (root.className === 'inline') return; if (post["class"] === 'inline') return;
node = $('.posttime', root) || $('span[id]', root).previousSibling; node = $('.posttime', post.el) || $('span[id]', post.el).previousSibling;
Time.date = Time.parse(node); Time.date = Time.parse(node);
time = $.el('time', { time = $.el('time', {
textContent: ' ' + Time.funk(Time) + ' ' textContent: ' ' + Time.funk(Time) + ' '
@ -2937,9 +2940,9 @@
this.setFormats(); this.setFormats();
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var fullname, link, node, regexp, resolution, shortname, size, type, unit, _, _ref; var fullname, link, node, regexp, resolution, shortname, size, type, unit, _, _ref;
if (root.className === 'inline' || !(node = $('.filesize', root))) return; if (post["class"] === 'inline' || !(node = post.filesize)) return;
type = node.childElementCount === 2 ? 0 : 1; type = node.childElementCount === 2 ? 0 : 1;
regexp = type ? /^File: (<.+>)-\((?:Spoiler Image, )?([\d\.]+) (\w+), (\d+x\d+|PDF)/ : /^File: (<.+>)-\((?:Spoiler Image, )?([\d\.]+) (\w+), (\d+x\d+|PDF), <span title="(.+)">([^<]+)/; regexp = type ? /^File: (<.+>)-\((?:Spoiler Image, )?([\d\.]+) (\w+), (\d+x\d+|PDF)/ : /^File: (<.+>)-\((?:Spoiler Image, )?([\d\.]+) (\w+), (\d+x\d+|PDF), <span title="(.+)">([^<]+)/;
_ref = node.innerHTML.match(regexp), _ = _ref[0], link = _ref[1], size = _ref[2], unit = _ref[3], resolution = _ref[4], fullname = _ref[5], shortname = _ref[6]; _ref = node.innerHTML.match(regexp), _ = _ref[0], link = _ref[1], size = _ref[2], unit = _ref[3], resolution = _ref[4], fullname = _ref[5], shortname = _ref[6];
@ -3059,20 +3062,19 @@
quoteBacklink.funk = Function('id', "return '" + format + "'"); quoteBacklink.funk = Function('id', "return '" + format + "'");
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var a, container, el, id, link, qid, quote, quotes, _i, _len, _ref; var a, container, el, link, qid, quote, quotes, root, _i, _len, _ref;
if (/\binline\b/.test(root.className)) return; if (post.isInlined) return;
quotes = {}; quotes = {};
_ref = $$('.quotelink', root); _ref = post.quotes;
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 (qid = quote.hash.slice(1)) quotes[qid] = true; if (qid = quote.hash.slice(1)) quotes[qid] = true;
} }
id = $('input', root).name;
a = $.el('a', { a = $.el('a', {
href: "#" + id, href: "#" + post.id,
className: root.hidden ? 'filtered backlink' : 'backlink', className: post.root.hidden ? 'filtered backlink' : 'backlink',
textContent: quoteBacklink.funk(id) textContent: quoteBacklink.funk(post.id)
}); });
for (qid in quotes) { for (qid in quotes) {
if (!(el = $.id(qid)) || el.className === 'op' && !conf['OP Backlinks']) { if (!(el = $.id(qid)) || el.className === 'op' && !conf['OP Backlinks']) {
@ -3097,9 +3099,9 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var quote, _i, _len, _ref; var quote, _i, _len, _ref;
_ref = $$('.quotelink, .backlink', root); _ref = post.quotes.concat(post.backlinks);
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 (!quote.hash) continue; if (!quote.hash) continue;
@ -3223,9 +3225,9 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var quote, _i, _len, _ref; var quote, _i, _len, _ref;
_ref = $$('.quotelink, .backlink', root); _ref = post.quotes.concat(post.backlinks);
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 (quote.hash) $.on(quote, 'mouseover', quotePreview.mouseover); if (quote.hash) $.on(quote, 'mouseover', quotePreview.mouseover);
@ -3301,24 +3303,36 @@
} }
}; };
quoteIndicators = { quoteOP = {
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var hash, path, quote, tid, _i, _len, _ref; var quote, _i, _len, _ref;
if (root.className === 'inline') return; if (post["class"] === 'inline') return;
tid = g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]', root).firstChild.id; _ref = post.quotes;
_ref = $$('.quotelink', root);
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 (!(hash = quote.hash.slice(1))) continue; if (quote.hash.slice(1) === post.threadId) {
if (conf['Indicate OP quote'] && hash === tid) {
$.add(quote, $.tn('\u00A0(OP)')); $.add(quote, $.tn('\u00A0(OP)'));
continue;
} }
path = quote.pathname; }
if (conf['Indicate Cross-thread Quotes'] && path.lastIndexOf("/" + tid) === -1 && path.indexOf("/" + g.BOARD + "/") === 0) { }
};
quoteDR = {
init: function() {
return g.callbacks.push(this.node);
},
node: function(post) {
var path, quote, _i, _len, _ref;
if (post["class"] === 'inline') return;
_ref = post.quotes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i];
if (!quote.hash) continue;
path = quote.pathname.split('/');
if (path[1] === g.BOARD && path[3] !== post.threadId) {
$.add(quote, $.tn('\u00A0(Cross-thread)')); $.add(quote, $.tn('\u00A0(Cross-thread)'));
} }
} }
@ -3329,16 +3343,15 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var a, span; var a;
if (!(a = $('.reportbutton', root))) { if (!(a = $('.reportbutton', post.el))) {
span = $('span[id]', root);
a = $.el('a', { a = $.el('a', {
className: 'reportbutton', className: 'reportbutton',
innerHTML: '[&nbsp;!&nbsp;]', innerHTML: '[&nbsp;!&nbsp;]',
href: 'javascript:;' href: 'javascript:;'
}); });
$.after(span, [$.tn(' '), a]); $.after($('span[id]', post.el), [$.tn(' '), a]);
} }
return $.on(a, 'click', reportButton.report); return $.on(a, 'click', reportButton.report);
}, },
@ -3372,11 +3385,11 @@
})(); })();
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var imgcount; var imgcount;
if (/\binline\b/.test(root.className)) return; if (post.isInlined) return;
$.id('postcount').textContent = ++threadStats.posts; $.id('postcount').textContent = ++threadStats.posts;
if (!$('img[md5]', root)) return; if (!post.img) return;
imgcount = $.id('imagecount'); imgcount = $.id('imagecount');
imgcount.textContent = ++threadStats.images; imgcount.textContent = ++threadStats.images;
if (threadStats.images > threadStats.imgLimit) { if (threadStats.images > threadStats.imgLimit) {
@ -3393,9 +3406,9 @@
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
replies: [], replies: [],
node: function(root) { node: function(post) {
if (root.hidden || root.className) return; if (post.root.hidden || post["class"]) return;
unread.replies.push(root); unread.replies.push(post.root);
return unread.update(); return unread.update();
}, },
scroll: function() { scroll: function() {
@ -3530,10 +3543,9 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var thumb; if (!post.img) return;
if (!(thumb = $('img[md5]', root))) return; return $.on(post.img, 'mouseover', imgHover.mouseover);
return $.on(thumb, 'mouseover', imgHover.mouseover);
}, },
mouseover: function() { mouseover: function() {
ui.el = $.el('img', { ui.el = $.el('img', {
@ -3565,14 +3577,14 @@
init: function() { init: function() {
return g.callbacks.push(this.node); return g.callbacks.push(this.node);
}, },
node: function(root) { node: function(post) {
var img, src, thumb; var img, src;
if (root.hidden || !(thumb = $('img[md5]', root))) return; if (post.root.hidden || post.img) return;
src = thumb.parentNode.href; src = post.img.parentNode.href;
if (/gif$/.test(src) && !/spoiler/.test(src)) { if (/gif$/.test(src) && !/spoiler/.test(src)) {
img = $.el('img'); img = $.el('img');
$.on(img, 'load', function() { $.on(img, 'load', function() {
return thumb.src = src; return post.img.src = src;
}); });
return img.src = src; return img.src = src;
} }
@ -3584,12 +3596,12 @@
g.callbacks.push(this.node); g.callbacks.push(this.node);
return imgExpand.dialog(); return imgExpand.dialog();
}, },
node: function(root) { node: function(post) {
var a, thumb; var a;
if (!(thumb = $('img[md5]', root))) return; if (!post.img) return;
a = thumb.parentNode; a = post.img.parentNode;
$.on(a, 'click', imgExpand.cb.toggle); $.on(a, 'click', imgExpand.cb.toggle);
if (imgExpand.on && !root.hidden && root.className !== 'inline') { if (imgExpand.on && !post.root.hidden && post["class"] !== 'inline') {
return imgExpand.expand(a.firstChild); return imgExpand.expand(a.firstChild);
} }
}, },
@ -3814,9 +3826,8 @@
if (conf['Quote Inline']) quoteInline.init(); if (conf['Quote Inline']) quoteInline.init();
if (conf['Quote Preview']) quotePreview.init(); if (conf['Quote Preview']) quotePreview.init();
if (conf['Quote Backlinks']) quoteBacklink.init(); if (conf['Quote Backlinks']) quoteBacklink.init();
if (conf['Indicate OP quote'] || conf['Indicate Cross-thread Quotes']) { if (conf['Indicate OP quote']) quoteOP.init();
quoteIndicators.init(); if (conf['Indicate Cross-thread Quotes']) quoteDR.init();
}
return $.ready(Main.ready); return $.ready(Main.ready);
}, },
ready: function() { ready: function() {
@ -3880,14 +3891,33 @@
} }
}, },
node: function(nodes, notify) { node: function(nodes, notify) {
var callback, node, _i, _j, _len, _len2, _ref; var callback, klass, node, post, posts, _i, _j, _k, _len, _len2, _len3, _ref;
posts = [];
for (_i = 0, _len = nodes.length; _i < _len; _i++) {
node = nodes[_i];
klass = node.className;
posts.push({
root: node,
el: klass === 'op' ? node : $('td[id]', node),
"class": klass,
id: $('input', node).name,
threadId: g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]', node).firstChild.id,
isOP: klass === 'op',
isInlined: /\binlined\b/.test(klass),
filesize: $('.filesize', node),
img: $('img[md5]', node),
quotes: $$('.quotelink', node),
backlinks: $$('.backlink', node),
bq: node.lastChild
});
}
_ref = g.callbacks; _ref = g.callbacks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
callback = _ref[_i]; callback = _ref[_j];
try { try {
for (_j = 0, _len2 = nodes.length; _j < _len2; _j++) { for (_k = 0, _len3 = posts.length; _k < _len3; _k++) {
node = nodes[_j]; post = posts[_k];
callback(node); callback(post);
} }
} catch (err) { } catch (err) {
if (notify) { if (notify) {

View File

@ -499,7 +499,8 @@ filter =
g.callbacks.push @node g.callbacks.push @node
createFilter: (regexp, op, hl, top) -> createFilter: (regexp, op, hl, top) ->
(root, value, isOP) -> (post, value) ->
{el, isOP} = post
if isOP and op is 'no' or !isOP and op is 'only' if isOP and op is 'no' or !isOP and op is 'only'
return false return false
if typeof regexp is 'string' if typeof regexp is 'string'
@ -510,12 +511,12 @@ filter =
return false return false
if hl if hl
if isOP if isOP
$.addClass root, hl $.addClass el, hl
else else
$.addClass root.parentNode, hl $.addClass el.parentNode, hl
if isOP and top and not g.REPLY if isOP and top and not g.REPLY
# Put the highlighted OPs' threads on top of the board pages... # Put the highlighted OPs' threads on top of the board pages...
thisThread = root.parentNode thisThread = el.parentNode
# ...before the first non highlighted thread. # ...before the first non highlighted thread.
if firstThread = $ 'div[class=op]' if firstThread = $ 'div[class=op]'
$.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling] $.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling]
@ -523,78 +524,76 @@ filter =
return false return false
if isOP if isOP
unless g.REPLY unless g.REPLY
threadHiding.hideHide root.parentNode threadHiding.hideHide el.parentNode
else else
replyHiding.hideHide root replyHiding.hideHide el
true true
node: (root) -> node: (post) ->
klass = root.className return if post.isInlined
return if /\binlined\b/.test klass
unless isOP = klass is 'op'
root = $ 'td[id]', root
for key of filter.filters for key of filter.filters
value = filter[key] root, isOP value = filter[key] post
if value is false if value is false
# Continue if there's nothing to filter (no tripcode for example). # Continue if there's nothing to filter (no tripcode for example).
continue continue
for Filter in filter.filters[key] for Filter in filter.filters[key]
if Filter root, value, isOP if Filter post, value
return return
name: (root, isOP) -> name: (post) ->
name = if isOP then $ '.postername', root else $ '.commentpostername', root name = if post.isOP then $ '.postername', post.el else $ '.commentpostername', post.el
name.textContent name.textContent
tripcode: (root) -> tripcode: (post) ->
if trip = $ '.postertrip', root if trip = $ '.postertrip', post.el
return trip.textContent return trip.textContent
false false
mod: (root, isOP) -> mod: (post) ->
if mod = (if isOP then $ '.commentpostername', root else $ '.commentpostername ~ .commentpostername', root) if mod = (if post.isOP then $ '.commentpostername', post.el else $ '.commentpostername ~ .commentpostername', post.el)
return mod.textContent return mod.textContent
false false
email: (root) -> email: (post) ->
if mail = $ '.linkmail', root if mail = $ '.linkmail', post.el
return mail.href return mail.href
false false
subject: (root, isOP) -> subject: (post) ->
sub = if isOP then $ '.filetitle', root else $ '.replytitle', root sub = if post.isOP then $ '.filetitle', post.el else $ '.replytitle', post.el
sub.textContent sub.textContent
comment: (root) -> comment: (post) ->
text = [] text = []
# XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE is 7 # XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE is 7
nodes = d.evaluate './/br|.//text()', root.lastChild, null, 7, null nodes = d.evaluate './/br|.//text()', post.bq, null, 7, null
i = 0 for i in [0...nodes.snapshotLength]
len = nodes.snapshotLength text.push if data = nodes.snapshotItem(i).data then data else '\n'
while i < len
text.push if data = nodes.snapshotItem(i++).data then data else '\n'
text.join '' text.join ''
filename: (root) -> filename: (post) ->
if file = $ '.filesize > span', root if file = $ 'span', post.filesize
return file.title return file.title
false false
dimensions: (root) -> dimensions: (post) ->
if (span = $ '.filesize', root) and match = span.textContent.match /\d+x\d+/ {filesize} = post
if filesize and match = filesize.textContent.match /\d+x\d+/
return match[0] return match[0]
return false return false
filesize: (root) -> filesize: (post) ->
if img = $ 'img[md5]', root {img} = post
if img
return img.alt return img.alt
false false
md5: (root) -> md5: (post) ->
if img = $ 'img[md5]', root {img} = post
if img
return img.getAttribute 'md5' return img.getAttribute 'md5'
false false
strikethroughQuotes = strikethroughQuotes =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if root.className is 'inline' return if post.isInlined
for quote in $$ '.quotelink', root for quote in post.quotes
if (el = $.id quote.hash[1..]) and el.parentNode.parentNode.parentNode.hidden if (el = $.id quote.hash[1..]) and el.parentNode.parentNode.parentNode.hidden
$.addClass quote, 'filtered' $.addClass quote, 'filtered'
root.hidden = true if conf['Recursive Filtering'] post.root.hidden = true if conf['Recursive Filtering']
return return
expandComment = expandComment =
@ -711,8 +710,9 @@ replyHiding =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return unless dd = $ '.doubledash', root return if post.class
dd = $ '.doubledash', post.root
dd.className = 'replyhider' dd.className = 'replyhider'
a = $.el 'a', a = $.el 'a',
textContent: '[ - ]' textContent: '[ - ]'
@ -755,8 +755,8 @@ replyHiding =
if conf['Show Stubs'] if conf['Show Stubs']
name = $('.commentpostername', reply).textContent name = $('.commentpostername', reply).textContent
uid = $('.posteruid', reply)?.textContent or '' uid = $('.posteruid', reply)?.textContent or ''
trip = $('.postertrip', reply)?.textContent or '' trip = $('.postertrip', reply)?.textContent or ''
div = $.el 'div', div = $.el 'div',
className: 'stub' className: 'stub'
@ -1037,8 +1037,8 @@ qr =
$.on d, 'dragstart', qr.drag $.on d, 'dragstart', qr.drag
$.on d, 'dragend', qr.drag $.on d, 'dragend', qr.drag
node: (root) -> node: (post) ->
$.on $('.quotejs + .quotejs', root), 'click', qr.quote $.on $('.quotejs + .quotejs', post.el), 'click', qr.quote
open: -> open: ->
if qr.el if qr.el
@ -2264,10 +2264,10 @@ watcher =
anonymize = anonymize =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
name = $ '.commentpostername, .postername', root name = $ '.commentpostername, .postername', post.el
name.textContent = 'Anonymous' name.textContent = 'Anonymous'
if trip = $ '.postertrip', root if trip = $ '.postertrip', post.el
if trip.parentNode.nodeName is 'A' if trip.parentNode.nodeName is 'A'
$.rm trip.parentNode $.rm trip.parentNode
else else
@ -2302,19 +2302,22 @@ sauce =
target: '_blank' target: '_blank'
textContent: domain textContent: domain
node: (root) -> node: (post) ->
return if root.className is 'inline' or not span = $ '.filesize', root {img} = post
img = span.nextElementSibling.nextElementSibling return if post.class is 'inline' or not img
img = img.parentNode
nodes = [] nodes = []
for link in sauce.links for link in sauce.links
nodes.push $.tn(' '), link img nodes.push $.tn(' '), link img
$.add span, nodes $.add post.filesize, nodes
revealSpoilers = revealSpoilers =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if not (img = $ 'img[alt^=Spoil]', root) or root.className is 'inline' img = {post}
if not (img and /^Spoil/.test img.alt) or post.class is 'inline'
return
img.removeAttribute 'height' img.removeAttribute 'height'
img.removeAttribute 'width' img.removeAttribute 'width'
img.src = "http://thumbs.4chan.org#{img.parentNode.pathname.replace(/src(\/\d+).+$/, 'thumb$1s.jpg')}" img.src = "http://thumbs.4chan.org#{img.parentNode.pathname.replace(/src(\/\d+).+$/, 'thumb$1s.jpg')}"
@ -2341,10 +2344,10 @@ Time =
new Date year, month, day, hour, min new Date year, month, day, hour, min
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if root.className is 'inline' return if post.class is 'inline'
# .posttime exists on every board except /f/ # .posttime exists on every board except /f/
node = $('.posttime', root) or $('span[id]', root).previousSibling node = $('.posttime', post.el) or $('span[id]', post.el).previousSibling
Time.date = Time.parse node Time.date = Time.parse node
time = $.el 'time', time = $.el 'time',
textContent: ' ' + Time.funk(Time) + ' ' textContent: ' ' + Time.funk(Time) + ' '
@ -2402,8 +2405,8 @@ FileInfo =
return if g.BOARD is 'f' return if g.BOARD is 'f'
@setFormats() @setFormats()
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if root.className is 'inline' or not node = $ '.filesize', root return if post.class is 'inline' or not node = post.filesize
type = if node.childElementCount is 2 then 0 else 1 type = if node.childElementCount is 2 then 0 else 1
regexp = regexp =
if type if type
@ -2484,20 +2487,18 @@ quoteBacklink =
format = conf['backlink'].replace /%id/g, "' + id + '" format = conf['backlink'].replace /%id/g, "' + id + '"
quoteBacklink.funk = Function 'id', "return '#{format}'" quoteBacklink.funk = Function 'id', "return '#{format}'"
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if /\binline\b/.test root.className return if post.isInlined
quotes = {} quotes = {}
for quote in $$ '.quotelink', root for quote in post.quotes
# Don't process >>>/b/. # Don't process >>>/b/.
if qid = quote.hash[1..] if qid = quote.hash[1..]
# Duplicate quotes get overwritten. # Duplicate quotes get overwritten.
quotes[qid] = true quotes[qid] = true
# OP or reply id.
id = $('input', root).name
a = $.el 'a', a = $.el 'a',
href: "##{id}" href: "##{post.id}"
className: if root.hidden then 'filtered backlink' else 'backlink' className: if post.root.hidden then 'filtered backlink' else 'backlink'
textContent: quoteBacklink.funk id textContent: quoteBacklink.funk post.id
for qid of quotes for qid of quotes
# Don't backlink the OP. # Don't backlink the OP.
continue if !(el = $.id qid) or el.className is 'op' and !conf['OP Backlinks'] continue if !(el = $.id qid) or el.className is 'op' and !conf['OP Backlinks']
@ -2516,8 +2517,8 @@ quoteBacklink =
quoteInline = quoteInline =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
for quote in $$ '.quotelink, .backlink', root for quote in post.quotes.concat post.backlinks
continue unless quote.hash continue unless quote.hash
quote.removeAttribute 'onclick' quote.removeAttribute 'onclick'
$.on quote, 'click', quoteInline.toggle $.on quote, 'click', quoteInline.toggle
@ -2608,8 +2609,8 @@ quoteInline =
quotePreview = quotePreview =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
for quote in $$ '.quotelink, .backlink', root for quote in post.quotes.concat post.backlinks
$.on quote, 'mouseover', quotePreview.mouseover if quote.hash $.on quote, 'mouseover', quotePreview.mouseover if quote.hash
return return
mouseover: (e) -> mouseover: (e) ->
@ -2668,23 +2669,29 @@ quotePreview =
if conf['File Info Formatting'] if conf['File Info Formatting']
FileInfo.node qp FileInfo.node qp
quoteIndicators = quoteOP =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if root.className is 'inline' return if post.class is 'inline'
# We use contains() so that it works with hidden threads for quote in post.quotes
tid = g.THREAD_ID or $.x('ancestor::div[contains(@class,"thread")]', root).firstChild.id if quote.hash[1..] is post.threadId
for quote in $$ '.quotelink', root
unless hash = quote.hash[1..]
continue
if conf['Indicate OP quote'] and hash is tid
# \u00A0 is nbsp # \u00A0 is nbsp
$.add quote, $.tn '\u00A0(OP)' $.add quote, $.tn '\u00A0(OP)'
return
quoteDR =
init: ->
g.callbacks.push @node
node: (post) ->
return if post.class is 'inline'
for quote in post.quotes
unless quote.hash
# Make sure this isn't a link to the board we're on.
continue continue
path = quote.pathname path = quote.pathname.split '/'
#if quote leads to a different thread id and is located on the same board (index 0) # If quote leads to a different thread id and is located on the same board.
if conf['Indicate Cross-thread Quotes'] and path.lastIndexOf("/#{tid}") is -1 and path.indexOf("/#{g.BOARD}/") is 0 if path[1] is g.BOARD and path[3] isnt post.threadId
# \u00A0 is nbsp # \u00A0 is nbsp
$.add quote, $.tn '\u00A0(Cross-thread)' $.add quote, $.tn '\u00A0(Cross-thread)'
return return
@ -2692,14 +2699,13 @@ quoteIndicators =
reportButton = reportButton =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
if not a = $ '.reportbutton', root if not a = $ '.reportbutton', post.el
span = $ 'span[id]', root
a = $.el 'a', a = $.el 'a',
className: 'reportbutton' className: 'reportbutton'
innerHTML: '[&nbsp;!&nbsp;]' innerHTML: '[&nbsp;!&nbsp;]'
href: 'javascript:;' href: 'javascript:;'
$.after span, [$.tn(' '), a] $.after $('span[id]', post.el), [$.tn(' '), a]
$.on a, 'click', reportButton.report $.on a, 'click', reportButton.report
report: -> report: ->
url = "http://sys.4chan.org/#{g.BOARD}/imgboard.php?mode=report&no=#{$.x('preceding-sibling::input', @).name}" url = "http://sys.4chan.org/#{g.BOARD}/imgboard.php?mode=report&no=#{$.x('preceding-sibling::input', @).name}"
@ -2722,10 +2728,10 @@ threadStats =
else else
151 151
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if /\binline\b/.test root.className return if post.isInlined
$.id('postcount').textContent = ++threadStats.posts $.id('postcount').textContent = ++threadStats.posts
return unless $ 'img[md5]', root return unless post.img
imgcount = $.id 'imagecount' imgcount = $.id 'imagecount'
imgcount.textContent = ++threadStats.images imgcount.textContent = ++threadStats.images
if threadStats.images > threadStats.imgLimit if threadStats.images > threadStats.imgLimit
@ -2740,9 +2746,9 @@ unread =
replies: [] replies: []
node: (root) -> node: (post) ->
return if root.hidden or root.className return if post.root.hidden or post.class
unread.replies.push root unread.replies.push post.root
unread.update() unread.update()
scroll: -> scroll: ->
@ -2848,9 +2854,9 @@ redirect =
imgHover = imgHover =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return unless thumb = $ 'img[md5]', root return unless post.img
$.on thumb, 'mouseover', imgHover.mouseover $.on post.img, 'mouseover', imgHover.mouseover
mouseover: -> mouseover: ->
ui.el = $.el 'img' ui.el = $.el 'img'
id: 'ihover' id: 'ihover'
@ -2874,14 +2880,14 @@ imgHover =
imgGif = imgGif =
init: -> init: ->
g.callbacks.push @node g.callbacks.push @node
node: (root) -> node: (post) ->
return if root.hidden or !thumb = $ 'img[md5]', root return if post.root.hidden or post.img
src = thumb.parentNode.href src = post.img.parentNode.href
if /gif$/.test(src) and !/spoiler/.test src if /gif$/.test(src) and !/spoiler/.test src
img = $.el 'img' img = $.el 'img'
$.on img, 'load', -> $.on img, 'load', ->
# Replace the thumbnail once the GIF has finished loading. # Replace the thumbnail once the GIF has finished loading.
thumb.src = src post.img.src = src
img.src = src img.src = src
imgExpand = imgExpand =
@ -2889,11 +2895,11 @@ imgExpand =
g.callbacks.push @node g.callbacks.push @node
imgExpand.dialog() imgExpand.dialog()
node: (root) -> node: (post) ->
return unless thumb = $ 'img[md5]', root return unless post.img
a = thumb.parentNode a = post.img.parentNode
$.on a, 'click', imgExpand.cb.toggle $.on a, 'click', imgExpand.cb.toggle
if imgExpand.on and !root.hidden and root.className isnt 'inline' if imgExpand.on and !post.root.hidden and post.class isnt 'inline'
imgExpand.expand a.firstChild imgExpand.expand a.firstChild
cb: cb:
toggle: (e) -> toggle: (e) ->
@ -3103,8 +3109,11 @@ Main =
if conf['Quote Backlinks'] if conf['Quote Backlinks']
quoteBacklink.init() quoteBacklink.init()
if conf['Indicate OP quote'] or conf['Indicate Cross-thread Quotes'] if conf['Indicate OP quote']
quoteIndicators.init() quoteOP.init()
if conf['Indicate Cross-thread Quotes']
quoteDR.init()
$.ready Main.ready $.ready Main.ready
@ -3162,7 +3171,6 @@ Main =
if conf['Index Navigation'] if conf['Index Navigation']
nav.init() nav.init()
nodes = $$ '.op, a + table', form nodes = $$ '.op, a + table', form
Main.node nodes, true Main.node nodes, true
@ -3191,9 +3199,25 @@ Main =
window.location = "https://raw.github.com/mayhemydg/4chan-x/#{version}/4chan_x.user.js" window.location = "https://raw.github.com/mayhemydg/4chan-x/#{version}/4chan_x.user.js"
node: (nodes, notify) -> node: (nodes, notify) ->
posts = []
for node in nodes
klass = node.className
posts.push
root: node
el: if klass is 'op' then node else $ 'td[id]', node
class: klass
id: $('input', node).name
threadId: g.THREAD_ID or $.x('ancestor::div[contains(@class,"thread")]', node).firstChild.id
isOP: klass is 'op'
isInlined: /\binlined\b/.test klass
filesize: $ '.filesize', node
img: $ 'img[md5]', node
quotes: $$ '.quotelink', node
backlinks: $$ '.backlink', node
bq: node.lastChild
for callback in g.callbacks for callback in g.callbacks
try try
callback node for node in nodes callback post for post in posts
catch err catch err
alert "4chan X error: #{err.message}\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n#{err.stack}" if notify alert "4chan X error: #{err.message}\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n#{err.stack}" if notify
return return