This commit is contained in:
No Face 2012-03-03 08:51:10 +01:00
commit 92714d702c
5 changed files with 387 additions and 294 deletions

View File

@ -1,6 +1,6 @@
// ==UserScript== // ==UserScript==
// @name 4chan x // @name 4chan x
// @version 2.27.1 // @version 2.28.0
// @namespace aeosynth // @namespace aeosynth
// @description Adds various features. // @description Adds various features.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com> // @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
@ -20,7 +20,7 @@
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
* Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com> * Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
* http://mayhemydg.github.com/4chan-x/ * http://mayhemydg.github.com/4chan-x/
* 4chan X 2.27.1 * 4chan X 2.28.0
* *
* Permission is hereby granted, free of charge, to any person * Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation * obtaining a copy of this software and associated documentation
@ -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: {
@ -209,7 +209,7 @@
NAMESPACE = '4chan_x.'; NAMESPACE = '4chan_x.';
VERSION = '2.27.1'; VERSION = '2.28.0';
SECOND = 1000; SECOND = 1000;
@ -575,109 +575,115 @@
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) { var test;
var firstThread, thisThread; test = typeof regexp === 'string' ? function(value) {
return regexp === value;
} : function(value) {
return regexp.test(value);
};
return function(value, isOP) {
if (isOP && op === 'no' || !isOP && op === 'only') return false; if (isOP && op === 'no' || !isOP && op === 'only') return false;
if (typeof regexp === 'string') { if (!test(value)) return false;
if (regexp !== value) return false; if (hl) return [hl, top];
} else if (!regexp.test(value)) {
return false;
}
if (hl) {
if (isOP) {
$.addClass(root, hl);
} else {
$.addClass(root.parentNode, hl);
}
if (isOP && top && !g.REPLY) {
thisThread = root.parentNode;
if (firstThread = $('div[class=op]')) {
$.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]);
}
}
return false;
}
if (isOP) {
if (!g.REPLY) threadHiding.hideHide(root.parentNode);
} else {
replyHiding.hideHide(root);
}
return true; return true;
}; };
}, },
node: function(root) { node: function(post) {
var Filter, isOP, key, klass, value, _i, _len, _ref; var Filter, el, firstThread, isOP, key, result, thisThread, value, _i, _len, _ref;
klass = root.className; if (post.isInlined) return;
if (/\binlined\b/.test(klass)) return; isOP = post.isOP, el = post.el;
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 (!(result = Filter(value, isOP))) continue;
if (result === true) {
if (isOP) {
if (!g.REPLY) {
threadHiding.hideHide(post.el.parentNode);
} else {
continue;
}
} else {
replyHiding.hideHide(post.el);
}
return;
}
if (isOP) {
$.addClass(el, result[0]);
} else {
$.addClass(el.parentNode, result[0]);
}
if (isOP && result[1] && !g.REPLY) {
thisThread = el.parentNode;
if (firstThread = $('div[class=op]')) {
$.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]);
}
}
} }
} }
}, },
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.el.lastChild, 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 +692,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;
} }
} }
} }
@ -753,7 +759,8 @@
$.replace(a.parentNode.parentNode, bq); $.replace(a.parentNode.parentNode, bq);
if (conf['Quote Preview']) quotePreview.node(bq); if (conf['Quote Preview']) quotePreview.node(bq);
if (conf['Quote Inline']) quoteInline.node(bq); if (conf['Quote Inline']) quoteInline.node(bq);
return quoteIndicators.node(bq); if (conf['Indicate OP quote']) quoteOP.node(bq);
if (conf['Indicate Cross-thread Quotes']) return quoteDR.node(bq);
} }
}; };
@ -866,9 +873,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: '[ - ]',
@ -1257,7 +1265,7 @@
qr = { qr = {
init: function() { init: function() {
var form, iframe, link, loadChecking, script; var form, iframe, link, loadChecking;
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', {
@ -1298,11 +1306,14 @@
}); });
$.add(d.head, iframe); $.add(d.head, iframe);
} }
script = $.el('script', { setTimeout(function() {
textContent: 'Recaptcha.focus_response_field=function(){}' var script;
script = $.el('script', {
textContent: 'Recaptcha.focus_response_field=function(){}'
});
$.add(d.head, script);
return $.rm(script);
}); });
$.add(d.head, script);
$.rm(script);
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();
@ -1312,8 +1323,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 +2756,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 +2808,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 +2827,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 +2857,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 +2950,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 +3072,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']) {
@ -3085,10 +3097,12 @@
container = $.el('span', { container = $.el('span', {
className: 'container' className: 'container'
}); });
$.add(container, [$.tn(' '), link]);
root = $('.reportbutton', el) || $('span[id]', el); root = $('.reportbutton', el) || $('span[id]', el);
$.after(root, container); $.after(root, container);
} else {
$.add(container, [$.tn(' '), link]);
} }
$.add(container, [$.tn(' '), link]);
} }
} }
}; };
@ -3097,9 +3111,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 +3237,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 +3315,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 +3355,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 +3397,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 +3418,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 +3555,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 +3589,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 +3608,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,20 +3838,24 @@
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() {
var MutationObserver, form, nodes, observer; var MutationObserver, form, nav, nodes, observer, _i, _len, _ref;
if (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, "chanx_" + (VERSION.split('.')[1]));
$.addClass(d.body, engine); $.addClass(d.body, engine);
_ref = ['navtop', 'navbot'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
nav = _ref[_i];
$.addClass($("a[href$='/" + g.BOARD + "/']", $.id(nav)), 'current');
}
form = $('form[name=delform]'); form = $('form[name=delform]');
threading.thread(form.firstElementChild); threading.thread(form.firstElementChild);
Favicon.init(); Favicon.init();
@ -3880,17 +3908,37 @@
} }
}, },
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: /\binline\b/.test(klass),
filesize: $('.filesize', node),
img: $('img[md5]', node),
quotes: $$('.quotelink', node),
backlinks: $$('.backlink', node)
});
}
_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) alert(err.message); if (notify) {
alert("4chan X error: " + err.message + "\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n" + err.stack);
}
} }
} }
}, },

View File

@ -2,7 +2,7 @@
{exec} = require 'child_process' {exec} = require 'child_process'
fs = require 'fs' fs = require 'fs'
VERSION = '2.27.1' VERSION = '2.28.0'
HEADER = """ HEADER = """
// ==UserScript== // ==UserScript==

View File

@ -1,4 +1,8 @@
master master
- Mayhem
General performance improvements.
2.28.0
- ahodesuka - ahodesuka
Reply/Thread File Info Formatting: Reply/Thread File Info Formatting:
- Link: %l, %L (Original file names are shown inside threads). - Link: %l, %L (Original file names are shown inside threads).
@ -6,7 +10,7 @@ master
- Resolution/PDF: %r - Resolution/PDF: %r
- Original filename: %n, %N. - Original filename: %n, %N.
- noface - noface
Update imagelimit for mlp. Update imagelimit for /mlp/.
Fix stubs if poster has unique ID. Fix stubs if poster has unique ID.
- Mayhem - Mayhem
You can now filter or highlight admin/mod posts. You can now filter or highlight admin/mod posts.

View File

@ -1 +1 @@
postMessage({version:'2.27.1'},'*'); postMessage({version:'2.28.0'},'*');

View File

@ -161,7 +161,7 @@ conf = {}
) null, config ) null, config
NAMESPACE = '4chan_x.' NAMESPACE = '4chan_x.'
VERSION = '2.27.1' VERSION = '2.28.0'
SECOND = 1000 SECOND = 1000
MINUTE = 60*SECOND MINUTE = 60*SECOND
HOUR = 60*MINUTE HOUR = 60*MINUTE
@ -499,102 +499,110 @@ filter =
g.callbacks.push @node g.callbacks.push @node
createFilter: (regexp, op, hl, top) -> createFilter: (regexp, op, hl, top) ->
(root, value, isOP) -> test =
if isOP and op is 'no' or !isOP and op is 'only'
return false
if typeof regexp is 'string' if typeof regexp is 'string'
# MD5 checking # MD5 checking
if regexp isnt value (value) -> regexp is value
return false else
else unless regexp.test value (value) -> regexp.test value
(value, isOP) ->
if isOP and op is 'no' or !isOP and op is 'only'
return false
unless test value
return false return false
if hl if hl
if isOP return [hl, top]
$.addClass root, hl
else
$.addClass root.parentNode, hl
if isOP and top and not g.REPLY
# Put the highlighted OPs' threads on top of the board pages...
thisThread = root.parentNode
# ...before the first non highlighted thread.
if firstThread = $ 'div[class=op]'
$.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling]
# Continue the filter lookup to add more classes or hide it.
return false
if isOP
unless g.REPLY
threadHiding.hideHide root.parentNode
else
replyHiding.hideHide root
true true
node: (root) -> node: (post) ->
klass = root.className return if post.isInlined
return if /\binlined\b/.test klass {isOP, el} = post
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 unless result = Filter value, isOP
continue
# Hide
if result is true
if isOP
unless g.REPLY
threadHiding.hideHide post.el.parentNode
else
continue
else
replyHiding.hideHide post.el
return return
name: (root, isOP) -> # Highlight
name = if isOP then $ '.postername', root else $ '.commentpostername', root if isOP
$.addClass el, result[0]
else
$.addClass el.parentNode, result[0]
if isOP and result[1] and not g.REPLY
# Put the highlighted OPs' threads on top of the board pages...
thisThread = el.parentNode
# ...before the first non highlighted thread.
if firstThread = $ 'div[class=op]'
$.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling]
name: (post) ->
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.el.lastChild, 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 =
@ -633,7 +641,10 @@ expandComment =
quotePreview.node bq quotePreview.node bq
if conf['Quote Inline'] if conf['Quote Inline']
quoteInline.node bq quoteInline.node bq
quoteIndicators.node bq if conf['Indicate OP quote']
quoteOP.node bq
if conf['Indicate Cross-thread Quotes']
quoteDR.node bq
expandThread = expandThread =
init: -> init: ->
@ -711,8 +722,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 +767,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'
@ -1024,10 +1036,12 @@ qr =
$.on iframe, 'load', -> if @src isnt 'about:blank' then setTimeout loadChecking, 500, @ $.on iframe, 'load', -> if @src isnt 'about:blank' then setTimeout loadChecking, 500, @
$.add d.head, iframe $.add d.head, iframe
# Prevent original captcha input from being focused on reload. # This is extemely slow, execute is asynchronously.
script = $.el 'script', textContent: 'Recaptcha.focus_response_field=function(){}' setTimeout ->
$.add d.head, script # Prevent original captcha input from being focused on reload.
$.rm script script = $.el 'script', textContent: 'Recaptcha.focus_response_field=function(){}'
$.add d.head, script
$.rm script
if conf['Persistent QR'] if conf['Persistent QR']
qr.dialog() qr.dialog()
@ -1037,8 +1051,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 +2278,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 +2316,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 +2358,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 +2419,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 +2501,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']
@ -2508,16 +2523,18 @@ quoteBacklink =
$.on link, 'click', quoteInline.toggle $.on link, 'click', quoteInline.toggle
unless (container = $ '.container', el) and container.parentNode is el unless (container = $ '.container', el) and container.parentNode is el
container = $.el 'span', className: 'container' container = $.el 'span', className: 'container'
$.add container, [$.tn(' '), link]
root = $('.reportbutton', el) or $('span[id]', el) root = $('.reportbutton', el) or $('span[id]', el)
$.after root, container $.after root, container
$.add container, [$.tn(' '), link] else
$.add container, [$.tn(' '), link]
return return
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 +2625,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 +2685,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 +2715,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 +2744,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 +2762,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 +2870,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 +2896,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 not 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 +2911,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 +3125,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
@ -3114,8 +3139,10 @@ Main =
return return
if not $.id 'navtopr' if not $.id 'navtopr'
return return
$.addClass d.body, "chanx_#{VERSION.match(/\.(\d+)/)[1]}" $.addClass d.body, "chanx_#{VERSION.split('.')[1]}"
$.addClass d.body, engine $.addClass d.body, engine
for nav in ['navtop', 'navbot']
$.addClass $("a[href$='/#{g.BOARD}/']", $.id nav), 'current'
form = $ 'form[name=delform]' form = $ 'form[name=delform]'
threading.thread form.firstElementChild threading.thread form.firstElementChild
Favicon.init() Favicon.init()
@ -3162,7 +3189,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,11 +3217,26 @@ 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: /\binline\b/.test klass
filesize: $ '.filesize', node
img: $ 'img[md5]', node
quotes: $$ '.quotelink', node
backlinks: $$ '.backlink', node
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 err.message if notify alert "4chan X error: #{err.message}\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n#{err.stack}" if notify
return return
observer: (mutations) -> observer: (mutations) ->
nodes = [] nodes = []