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==
// @name 4chan x
// @version 2.27.1
// @version 2.28.0
// @namespace aeosynth
// @description Adds various features.
// @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) 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
* 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
* obtaining a copy of this software and associated documentation
@ -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: {
@ -209,7 +209,7 @@
NAMESPACE = '4chan_x.';
VERSION = '2.27.1';
VERSION = '2.28.0';
SECOND = 1000;
@ -575,109 +575,115 @@
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;
var test;
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 (typeof regexp === 'string') {
if (regexp !== value) return false;
} 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);
}
if (!test(value)) return false;
if (hl) return [hl, top];
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, el, firstThread, isOP, key, result, thisThread, value, _i, _len, _ref;
if (post.isInlined) return;
isOP = post.isOP, el = post.el;
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 (!(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;
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.el.lastChild, 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 +692,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;
}
}
}
@ -753,7 +759,8 @@
$.replace(a.parentNode.parentNode, bq);
if (conf['Quote Preview']) quotePreview.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() {
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: '[ - ]',
@ -1257,7 +1265,7 @@
qr = {
init: function() {
var form, iframe, link, loadChecking, script;
var form, iframe, link, loadChecking;
if (!$.id('recaptcha_challenge_field_holder')) return;
if (conf['Hide Original Post Form']) {
link = $.el('h1', {
@ -1298,11 +1306,14 @@
});
$.add(d.head, iframe);
}
script = $.el('script', {
textContent: 'Recaptcha.focus_response_field=function(){}'
setTimeout(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']) {
qr.dialog();
if (conf['Auto Hide QR']) qr.hide();
@ -1312,8 +1323,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 +2756,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 +2808,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 +2827,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 +2857,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 +2950,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 +3072,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']) {
@ -3085,10 +3097,12 @@
container = $.el('span', {
className: 'container'
});
$.add(container, [$.tn(' '), link]);
root = $('.reportbutton', el) || $('span[id]', el);
$.after(root, container);
} else {
$.add(container, [$.tn(' '), link]);
}
$.add(container, [$.tn(' '), link]);
}
}
};
@ -3097,9 +3111,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 +3237,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 +3315,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 +3355,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 +3397,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 +3418,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 +3555,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 +3589,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 +3608,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,20 +3838,24 @@
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() {
var MutationObserver, form, nodes, observer;
var MutationObserver, form, nav, nodes, observer, _i, _len, _ref;
if (d.title === '4chan - 404') {
redirect.init();
return;
}
if (!$.id('navtopr')) return;
$.addClass(d.body, "chanx_" + (VERSION.match(/\.(\d+)/)[1]));
$.addClass(d.body, "chanx_" + (VERSION.split('.')[1]));
$.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]');
threading.thread(form.firstElementChild);
Favicon.init();
@ -3880,17 +3908,37 @@
}
},
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;
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) 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'
fs = require 'fs'
VERSION = '2.27.1'
VERSION = '2.28.0'
HEADER = """
// ==UserScript==

View File

@ -1,4 +1,8 @@
master
- Mayhem
General performance improvements.
2.28.0
- ahodesuka
Reply/Thread File Info Formatting:
- Link: %l, %L (Original file names are shown inside threads).
@ -6,7 +10,7 @@ master
- Resolution/PDF: %r
- Original filename: %n, %N.
- noface
Update imagelimit for mlp.
Update imagelimit for /mlp/.
Fix stubs if poster has unique ID.
- Mayhem
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
NAMESPACE = '4chan_x.'
VERSION = '2.27.1'
VERSION = '2.28.0'
SECOND = 1000
MINUTE = 60*SECOND
HOUR = 60*MINUTE
@ -499,102 +499,110 @@ filter =
g.callbacks.push @node
createFilter: (regexp, op, hl, top) ->
(root, value, isOP) ->
if isOP and op is 'no' or !isOP and op is 'only'
return false
test =
if typeof regexp is 'string'
# MD5 checking
if regexp isnt value
return false
else unless regexp.test value
(value) -> regexp is value
else
(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
if hl
if isOP
$.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
return [hl, top]
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
{isOP, el} = post
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
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
name: (root, isOP) ->
name = if isOP then $ '.postername', root else $ '.commentpostername', root
# Highlight
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
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.el.lastChild, 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 =
@ -633,7 +641,10 @@ expandComment =
quotePreview.node bq
if conf['Quote Inline']
quoteInline.node bq
quoteIndicators.node bq
if conf['Indicate OP quote']
quoteOP.node bq
if conf['Indicate Cross-thread Quotes']
quoteDR.node bq
expandThread =
init: ->
@ -711,8 +722,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 +767,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'
@ -1024,10 +1036,12 @@ qr =
$.on iframe, 'load', -> if @src isnt 'about:blank' then setTimeout loadChecking, 500, @
$.add d.head, iframe
# Prevent original captcha input from being focused on reload.
script = $.el 'script', textContent: 'Recaptcha.focus_response_field=function(){}'
$.add d.head, script
$.rm script
# This is extemely slow, execute is asynchronously.
setTimeout ->
# Prevent original captcha input from being focused on reload.
script = $.el 'script', textContent: 'Recaptcha.focus_response_field=function(){}'
$.add d.head, script
$.rm script
if conf['Persistent QR']
qr.dialog()
@ -1037,8 +1051,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 +2278,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 +2316,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 +2358,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 +2419,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 +2501,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']
@ -2508,16 +2523,18 @@ quoteBacklink =
$.on link, 'click', quoteInline.toggle
unless (container = $ '.container', el) and container.parentNode is el
container = $.el 'span', className: 'container'
$.add container, [$.tn(' '), link]
root = $('.reportbutton', el) or $('span[id]', el)
$.after root, container
$.add container, [$.tn(' '), link]
else
$.add container, [$.tn(' '), link]
return
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 +2625,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 +2685,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 +2715,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 +2744,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 +2762,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 +2870,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 +2896,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 not 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 +2911,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 +3125,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
@ -3114,8 +3139,10 @@ Main =
return
if not $.id 'navtopr'
return
$.addClass d.body, "chanx_#{VERSION.match(/\.(\d+)/)[1]}"
$.addClass d.body, "chanx_#{VERSION.split('.')[1]}"
$.addClass d.body, engine
for nav in ['navtop', 'navbot']
$.addClass $("a[href$='/#{g.BOARD}/']", $.id nav), 'current'
form = $ 'form[name=delform]'
threading.thread form.firstElementChild
Favicon.init()
@ -3162,7 +3189,6 @@ Main =
if conf['Index Navigation']
nav.init()
nodes = $$ '.op, a + table', form
Main.node nodes, true
@ -3191,11 +3217,26 @@ 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: /\binline\b/.test klass
filesize: $ '.filesize', node
img: $ 'img[md5]', node
quotes: $$ '.quotelink', node
backlinks: $$ '.backlink', node
for callback in g.callbacks
try
callback node for node in nodes
callback post for post in posts
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
observer: (mutations) ->
nodes = []