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

View File

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