Merge branch 'master' into Fx10

This commit is contained in:
Nicolas Stepien 2012-01-04 22:48:24 +01:00
commit cc7be74f54
5 changed files with 286 additions and 218 deletions

View File

@ -1,11 +1,12 @@
// ==UserScript== // ==UserScript==
// @name 4chan x // @name 4chan x
// @version 2.23.7 // @version 2.24.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>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license // @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @include http://boards.4chan.org/* // @include http://boards.4chan.org/*
// @include http://images.4chan.org/*
// @include http://sys.4chan.org/* // @include http://sys.4chan.org/*
// @run-at document-start // @run-at document-start
// @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js // @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js
@ -15,8 +16,9 @@
/* LICENSE /* LICENSE
* *
* 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>
* http://mayhemydg.github.com/4chan-x/ * http://mayhemydg.github.com/4chan-x/
* 4chan X 2.23.7 * 4chan X 2.24.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
@ -70,7 +72,7 @@
config = { config = {
main: { main: {
Enhancing: { Enhancing: {
'404 Redirect': [true, 'Redirect dead threads'], '404 Redirect': [true, 'Redirect dead threads and images'],
'Keybinds': [true, 'Binds actions to keys'], 'Keybinds': [true, 'Binds actions to keys'],
'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'], 'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'],
'Report Button': [true, 'Add report buttons'], 'Report Button': [true, 'Add report buttons'],
@ -133,11 +135,12 @@
filesize: '', filesize: '',
md5: '' md5: ''
}, },
flavors: ['http://iqdb.org/?url=', 'http://google.com/searchbyimage?image_url=', '#http://tineye.com/search?url=', '#http://saucenao.com/search.php?db=999&url=', '#http://3d.iqdb.org/?url=', '#http://regex.info/exif.cgi?imgurl=', '#http://imgur.com/upload?url='].join('\n'), flavors: ['http://iqdb.org/?url=', 'http://google.com/searchbyimage?image_url=', '#http://tineye.com/search?url=', '#http://saucenao.com/search.php?db=999&url=', '#http://3d.iqdb.org/?url=', '#http://regex.info/exif.cgi?imgurl=', '#http://imgur.com/upload?url=', '#http://ompldr.org/upload?url1='].join('\n'),
time: '%m/%d/%y(%a)%H:%M', time: '%m/%d/%y(%a)%H:%M',
backlink: '>>%id', backlink: '>>%id',
favicon: 'ferongr', favicon: 'ferongr',
hotkeys: { hotkeys: {
openOptions: 'ctrl+o',
close: 'Esc', close: 'Esc',
spoiler: 'ctrl+s', spoiler: 'ctrl+s',
openQR: 'i', openQR: 'i',
@ -197,7 +200,7 @@
NAMESPACE = '4chan_x.'; NAMESPACE = '4chan_x.';
VERSION = '2.23.7'; VERSION = '2.24.0';
SECOND = 1000; SECOND = 1000;
@ -304,6 +307,15 @@
}; };
$.extend($, { $.extend($, {
onLoad: function(fc) {
var cb;
if (/interactive|complete/.test(d.readyState)) return fc();
cb = function() {
$.off(d, 'DOMContentLoaded', cb);
return fc();
};
return $.on(d, 'DOMContentLoaded', cb);
},
id: function(id) { id: function(id) {
return d.getElementById(id); return d.getElementById(id);
}, },
@ -463,54 +475,44 @@
$.cache.requests = {}; $.cache.requests = {};
if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { $.extend($, typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null ? {
$.extend($, { "delete": function(name) {
"delete": function(name) { name = NAMESPACE + name;
name = NAMESPACE + name; return GM_deleteValue(name);
return GM_deleteValue(name); },
}, get: function(name, defaultValue) {
get: function(name, defaultValue) { var value;
var value; name = NAMESPACE + name;
name = NAMESPACE + name; if (value = GM_getValue(name)) {
if (value = GM_getValue(name)) { return JSON.parse(value);
return JSON.parse(value); } else {
} else { return defaultValue;
return defaultValue;
}
},
openInTab: function(url) {
return GM_openInTab(url);
},
set: function(name, value) {
name = NAMESPACE + name;
localStorage[name] = JSON.stringify(value);
return GM_setValue(name, JSON.stringify(value));
} }
}); },
} else { set: function(name, value) {
$.extend($, { name = NAMESPACE + name;
"delete": function(name) { localStorage[name] = JSON.stringify(value);
name = NAMESPACE + name; return GM_setValue(name, JSON.stringify(value));
return delete localStorage[name]; }
}, } : {
get: function(name, defaultValue) { "delete": function(name) {
var value; name = NAMESPACE + name;
name = NAMESPACE + name; return delete localStorage[name];
if (value = localStorage[name]) { },
return JSON.parse(value); get: function(name, defaultValue) {
} else { var value;
return defaultValue; name = NAMESPACE + name;
} if (value = localStorage[name]) {
}, return JSON.parse(value);
openInTab: function(url) { } else {
return window.open(url, "_blank"); return defaultValue;
},
set: function(name, value) {
name = NAMESPACE + name;
return localStorage[name] = JSON.stringify(value);
} }
}); },
} set: function(name, value) {
name = NAMESPACE + name;
return localStorage[name] = JSON.stringify(value);
}
});
for (key in conf) { for (key in conf) {
val = conf[key]; val = conf[key];
@ -891,8 +893,11 @@
if (!(key = keybinds.keyCode(e))) return; if (!(key = keybinds.keyCode(e))) return;
thread = nav.getThread(); thread = nav.getThread();
switch (key) { switch (key) {
case conf.openOptions:
if (!$.id('overlay')) options.dialog();
break;
case conf.close: case conf.close:
if (o = $('#overlay')) { if (o = $.id('overlay')) {
$.rm(o); $.rm(o);
} else if (qr.el) { } else if (qr.el) {
qr.close(); qr.close();
@ -970,7 +975,7 @@
} }
break; break;
case conf.unreadCountTo0: case conf.unreadCountTo0:
unread.replies.length = 0; unread.replies = [];
unread.updateTitle(); unread.updateTitle();
Favicon.update(); Favicon.update();
break; break;
@ -1067,11 +1072,12 @@
} }
}, },
open: function(thread, tab) { open: function(thread, tab) {
var id, url; var id, open, url;
id = thread.firstChild.id; id = thread.firstChild.id;
url = "http://boards.4chan.org/" + g.BOARD + "/res/" + id; url = "http://boards.4chan.org/" + g.BOARD + "/res/" + id;
if (tab) { if (tab) {
return $.openInTab(url); open = GM_openInTab || window.open;
return open(url, "_blank");
} else { } else {
return location.href = url; return location.href = url;
} }
@ -1303,6 +1309,7 @@
<div class=error><code>Keybinds</code> are disabled.</div>\ <div class=error><code>Keybinds</code> are disabled.</div>\
<table><tbody>\ <table><tbody>\
<tr><th>Actions</th><th>Keybinds</th></tr>\ <tr><th>Actions</th><th>Keybinds</th></tr>\
<tr><td>Open Options</td><td><input name=openOptions></td></tr>\
<tr><td>Close Options or QR</td><td><input name=close></td></tr>\ <tr><td>Close Options or QR</td><td><input name=close></td></tr>\
<tr><td>Quick spoiler</td><td><input name=spoiler></td></tr>\ <tr><td>Quick spoiler</td><td><input name=spoiler></td></tr>\
<tr><td>Open QR with post number inserted</td><td><input name=openQR></td></tr>\ <tr><td>Open QR with post number inserted</td><td><input name=openQR></td></tr>\
@ -1425,10 +1432,10 @@
time: function() { time: function() {
Time.foo(); Time.foo();
Time.date = new Date(); Time.date = new Date();
return $('#timePreview').textContent = Time.funk(Time); return $.id('timePreview').textContent = Time.funk(Time);
}, },
backlink: function() { backlink: function() {
return $('#backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789'); return $.id('backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789');
}, },
favicon: function() { favicon: function() {
Favicon["switch"](); Favicon["switch"]();
@ -2027,7 +2034,7 @@
input.disabled = true; input.disabled = true;
input.value = 404; input.value = 404;
} }
d.title = d.title.match(/.+-/)[0] + ' 404'; d.title = d.title.match(/^.+-/)[0] + ' 404';
g.dead = true; g.dead = true;
Favicon.update(); Favicon.update();
return; return;
@ -2040,7 +2047,6 @@
This saves bandwidth for both the user and the servers, avoid unnecessary computation, This saves bandwidth for both the user and the servers, avoid unnecessary computation,
and won't load images and scripts when parsing the response. and won't load images and scripts when parsing the response.
*/ */
updater.lastModified = this.getResponseHeader('Last-Modified');
if (this.status === 304) { if (this.status === 304) {
if (conf['Verbose']) { if (conf['Verbose']) {
updater.count.textContent = '+0'; updater.count.textContent = '+0';
@ -2048,6 +2054,7 @@
} }
return; return;
} }
updater.lastModified = this.getResponseHeader('Last-Modified');
body = $.el('body', { body = $.el('body', {
innerHTML: this.responseText innerHTML: this.responseText
}); });
@ -2462,7 +2469,9 @@
}, },
toggle: function(e) { toggle: function(e) {
var id; var id;
if (e.shiftKey || e.altKey || e.ctrlKey || e.button !== 0) return; if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return;
}
e.preventDefault(); e.preventDefault();
id = this.hash.slice(1); id = this.hash.slice(1);
if (/\binlined\b/.test(this.className)) { if (/\binlined\b/.test(this.className)) {
@ -2474,10 +2483,15 @@
return this.classList.toggle('inlined'); return this.classList.toggle('inlined');
}, },
add: function(q, id) { add: function(q, id) {
var el, inline, pathname, root, threadID; var el, i, inline, pathname, root, threadID;
root = q.parentNode.nodeName === 'FONT' ? q.parentNode : q.nextSibling ? q.nextSibling : q; root = q.parentNode.nodeName === 'FONT' ? q.parentNode : q.nextSibling ? q.nextSibling : q;
if (el = $.id(id)) { if (el = $.id(id)) {
inline = quoteInline.table(id, el.innerHTML); inline = quoteInline.table(id, el.innerHTML);
if (g.REPLY && conf['Unread Count'] && (i = unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) {
unread.replies.splice(i, 1);
unread.updateTitle();
Favicon.update();
}
if (/\bbacklink\b/.test(q.className)) { if (/\bbacklink\b/.test(q.className)) {
$.after(q.parentNode, inline); $.after(q.parentNode, inline);
if (conf['Forward Hiding']) { if (conf['Forward Hiding']) {
@ -2707,7 +2721,7 @@
report: function() { report: function() {
var id, set, url; var id, set, url;
url = "http://sys.4chan.org/" + g.BOARD + "/imgboard.php?mode=report&no=" + ($.x('preceding-sibling::input', this).name); url = "http://sys.4chan.org/" + g.BOARD + "/imgboard.php?mode=report&no=" + ($.x('preceding-sibling::input', this).name);
id = "" + NAMESPACE + "popup"; id = Date.now();
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"; set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200";
return window.open(url, id, set); return window.open(url, id, set);
} }
@ -2824,49 +2838,66 @@
} }
}; };
redirect = function() { redirect = {
var url; init: function() {
switch (g.BOARD) { var url;
case 'a': url = location.hostname === 'images.4chan.org' ? redirect.image(g.BOARD, location.pathname.split('/')[3]) : /^\d+$/.test(g.THREAD_ID) ? redirect.thread() : void 0;
case 'jp': if (url) return location.href = url;
case 'm': },
case 'tg': image: function(board, filename) {
case 'tv': switch (board) {
url = "http://oldarchive.foolz.us/" + g.BOARD + "/thread/" + g.THREAD_ID; case 'a':
break; case 'jp':
case 'diy': case 'm':
case 'g': case 'tg':
case 'sci': case 'tv':
url = "http://archive.installgentoo.net/" + g.BOARD + "/thread/" + g.THREAD_ID; case 'u':
break; return "http://archive.foolz.us/" + board + "/full_image/" + filename;
case '3': }
case 'adv': },
case 'an': thread: function() {
case 'ck': switch (g.BOARD) {
case 'co': case 'a':
case 'fa': case 'jp':
case 'fit': case 'm':
case 'int': case 'tg':
case 'k': case 'tv':
case 'mu': case 'u':
case 'n': return "http://archive.foolz.us/" + g.BOARD + "/thread/" + g.THREAD_ID + "/";
case 'o': case 'lit':
case 'p': return "http://fuuka.warosu.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
case 'po': case 'diy':
case 'pol': case 'g':
case 'soc': case 'sci':
case 'sp': return "http://archive.installgentoo.net/" + g.BOARD + "/thread/" + g.THREAD_ID;
case 'toy': case '3':
case 'trv': case 'adv':
case 'v': case 'an':
case 'vp': case 'ck':
case 'x': case 'co':
url = "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID; case 'fa':
break; case 'fit':
default: case 'int':
url = "http://boards.4chan.org/" + g.BOARD; case 'k':
case 'mu':
case 'n':
case 'o':
case 'p':
case 'po':
case 'pol':
case 'r9k':
case 'soc':
case 'sp':
case 'toy':
case 'trv':
case 'v':
case 'vp':
case 'x':
return "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
default:
return "http://boards.4chan.org/" + g.BOARD;
}
} }
return location.href = url;
}; };
imgHover = { imgHover = {
@ -2881,7 +2912,7 @@
}, },
mouseover: function() { mouseover: function() {
ui.el = $.el('img', { ui.el = $.el('img', {
id: 'iHover', id: 'ihover',
src: this.parentNode.href src: this.parentNode.href
}); });
return $.add(d.body, ui.el); return $.add(d.body, ui.el);
@ -2915,7 +2946,9 @@
}, },
cb: { cb: {
toggle: function(e) { toggle: function(e) {
if (e.shiftKey || e.altKey || e.ctrlKey || e.button !== 0) return; if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return;
}
e.preventDefault(); e.preventDefault();
return imgExpand.toggle(this); return imgExpand.toggle(this);
}, },
@ -2978,26 +3011,29 @@
thumb.hidden = false; thumb.hidden = false;
return $.rm(thumb.nextSibling); return $.rm(thumb.nextSibling);
}, },
expand: function(thumb) { expand: function(thumb, url) {
var a, filesize, img, max; var a, filesize, img, max;
a = thumb.parentNode; a = thumb.parentNode;
img = $.el('img', { img = $.el('img', {
src: a.href src: url ? url : a.href
}); });
if (engine === 'gecko' && a.parentNode.className !== 'op') { if (engine === 'gecko' && a.parentNode.className !== 'op') {
filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent; filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent;
max = filesize.match(/(\d+)x/); max = filesize.match(/(\d+)x/);
img.style.maxWidth = "" + max[1] + "px"; img.style.maxWidth = "" + max[1] + "px";
} }
$.on(img, 'error', imgExpand.error); if (conf['404 Redirect']) $.on(img, 'error', imgExpand.error);
thumb.hidden = true; thumb.hidden = true;
return $.add(a, img); return $.add(a, img);
}, },
error: function() { error: function() {
var req, thumb; var req, src, thumb, url;
thumb = this.previousSibling; thumb = this.previousSibling;
imgExpand.contract(thumb); imgExpand.contract(thumb);
if (engine === 'webkit') { src = this.src.split('/');
if (url = redirect.image(src[3], src[5])) {
return imgExpand.expand(thumb, url);
} else if (engine === 'webkit') {
return req = $.ajax(this.src, (function() { return req = $.ajax(this.src, (function() {
if (this.status !== 404) { if (this.status !== 404) {
return setTimeout(imgExpand.retry, 10000, thumb); return setTimeout(imgExpand.retry, 10000, thumb);
@ -3037,43 +3073,42 @@
return $.prepend(form, controls); return $.prepend(form, controls);
}, },
resize: function() { resize: function() {
return imgExpand.style.innerHTML = ".fitheight [md5] + img {max-height:" + d.body.clientHeight + "px;}"; return imgExpand.style.innerHTML = ".fitheight img[md5] + img {max-height:" + d.body.clientHeight + "px;}";
} }
}; };
Main = { Main = {
init: function() { init: function() {
var cutoff, hiddenThreads, id, now, pathname, temp, timestamp, update, _ref; var cutoff, hiddenThreads, id, now, pathname, temp, timestamp, _ref;
pathname = location.pathname.slice(1).split('/'); pathname = location.pathname.slice(1).split('/');
g.BOARD = pathname[0], temp = pathname[1]; g.BOARD = pathname[0], temp = pathname[1];
if (temp === 'res') { if (temp === 'res') {
g.REPLY = temp; g.REPLY = true;
g.THREAD_ID = pathname[2]; g.THREAD_ID = pathname[2];
} else { } else {
g.PAGENUM = parseInt(temp) || 0; g.PAGENUM = parseInt(temp) || 0;
} }
if (location.hostname === 'sys.4chan.org') { if (location.hostname === 'sys.4chan.org') {
if (/interactive|complete/.test(d.readyState)) { $.onLoad(qr.sys);
qr.sys(); return;
} else { }
$.on(d, 'DOMContentLoaded', qr.sys); if (location.hostname === 'images.4chan.org') {
if (conf['404 Redirect']) {
$.onLoad(function() {
if (d.title === '4chan - 404') return redirect.init();
});
} }
return; return;
} }
$.onLoad(options.init);
$.on(window, 'message', Main.message); $.on(window, 'message', Main.message);
now = Date.now(); now = Date.now();
if (conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 6 * HOUR) { if (conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 6 * HOUR) {
update = function() { $.onLoad(function() {
$.off(d, 'DOMContentLoaded', update);
return $.add(d.head, $.el('script', { return $.add(d.head, $.el('script', {
src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js' src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js'
})); }));
}; });
if (/interactive|complete/.test(d.readyState)) {
update();
} else {
$.on(d, 'DOMContentLoaded', update);
}
$.set('lastUpdate', now); $.set('lastUpdate', now);
} }
g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {}); g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {});
@ -3107,25 +3142,20 @@
if (conf['Quote Backlinks']) quoteBacklink.init(); if (conf['Quote Backlinks']) quoteBacklink.init();
if (conf['Indicate OP quote']) quoteOP.init(); if (conf['Indicate OP quote']) quoteOP.init();
if (conf['Indicate Cross-thread Quotes']) quoteDR.init(); if (conf['Indicate Cross-thread Quotes']) quoteDR.init();
if (/interactive|complete/.test(d.readyState)) { return $.onLoad(Main.onLoad);
return Main.onLoad();
} else {
return $.on(d, 'DOMContentLoaded', Main.onLoad);
}
}, },
onLoad: function() { onLoad: function() {
var callback, canPost, form, node, nodes, _i, _j, _len, _len2, _ref; var callback, canPost, form, node, nodes, _i, _j, _len, _len2, _ref;
$.off(d, 'DOMContentLoaded', Main.onLoad); if (conf['404 Redirect'] && d.title === '4chan - 404') {
if (conf['404 Redirect'] && d.title === '4chan - 404' && /^\d+$/.test(g.THREAD_ID)) { redirect.init();
redirect();
return; return;
} }
if (!$('#navtopr')) return; if (!$.id('navtopr')) return;
$.addClass(d.body, engine); $.addClass(d.body, engine);
$.addStyle(Main.css); $.addStyle(Main.css);
threading.init(); threading.init();
Favicon.init(); Favicon.init();
if ((form = $('form[name=post]')) && (canPost = !!$('#recaptcha_response_field'))) { if ((form = $('form[name=post]')) && (canPost = !!$.id('recaptcha_response_field'))) {
Recaptcha.init(); Recaptcha.init();
if (g.REPLY && conf['Auto Watch Reply'] && conf['Thread Watcher']) { if (g.REPLY && conf['Auto Watch Reply'] && conf['Thread Watcher']) {
$.on(form, 'submit', function() { $.on(form, 'submit', function() {
@ -3142,10 +3172,10 @@
if (conf['Quick Reply']) qr.init(); if (conf['Quick Reply']) qr.init();
if (conf['Thread Watcher']) watcher.init(); if (conf['Thread Watcher']) watcher.init();
if (conf['Keybinds']) keybinds.init(); if (conf['Keybinds']) keybinds.init();
if (conf['Reply Navigation'] || conf['Index Navigation']) nav.init();
if (g.REPLY) { if (g.REPLY) {
if (conf['Thread Updater']) updater.init(); if (conf['Thread Updater']) updater.init();
if (conf['Thread Stats']) threadStats.init(); if (conf['Thread Stats']) threadStats.init();
if (conf['Reply Navigation']) nav.init();
if (conf['Post in Title']) titlePost.init(); if (conf['Post in Title']) titlePost.init();
if (conf['Unread Count']) unread.init(); if (conf['Unread Count']) unread.init();
if (conf['Quick Reply'] && conf['Persistent QR'] && canPost) { if (conf['Quick Reply'] && conf['Persistent QR'] && canPost) {
@ -3156,6 +3186,7 @@
if (conf['Thread Hiding']) threadHiding.init(); if (conf['Thread Hiding']) threadHiding.init();
if (conf['Thread Expansion']) expandThread.init(); if (conf['Thread Expansion']) expandThread.init();
if (conf['Comment Expansion']) expandComment.init(); if (conf['Comment Expansion']) expandComment.init();
if (conf['Index Navigation']) nav.init();
} }
nodes = $$('.op, a + table'); nodes = $$('.op, a + table');
_ref = g.callbacks; _ref = g.callbacks;
@ -3170,8 +3201,7 @@
alert(err); alert(err);
} }
} }
$.on($('form[name=delform]'), 'DOMNodeInserted', Main.node); return $.on($('form[name=delform]'), 'DOMNodeInserted', Main.node);
return options.init();
}, },
message: function(e) { message: function(e) {
var data, origin; var data, origin;
@ -3213,7 +3243,6 @@
text-decoration: none;\ text-decoration: none;\
}\ }\
\ \
[hidden], /* Firefox bug: hidden tables are not hidden. fixed in 9.0 */\
.thread.stub > :not(.block),\ .thread.stub > :not(.block),\
#content > [name=tab]:not(:checked) + div,\ #content > [name=tab]:not(:checked) + div,\
#updater:not(:hover) > :not(.move),\ #updater:not(:hover) > :not(.move),\
@ -3241,23 +3270,24 @@
float: left;\ float: left;\
pointer-events: none;\ pointer-events: none;\
}\ }\
[md5], [md5] + img {\ img[md5], img[md5] + img {\
pointer-events: all;\ pointer-events: all;\
}\ }\
.fitwidth [md5] + img {\ .fitwidth img[md5] + img {\
max-width: 100%;\ max-width: 100%;\
}\ }\
.gecko > .fitwidth [md5] + img,\ .gecko > .fitwidth img[md5] + img,\
.presto > .fitwidth [md5] + img {\ .presto > .fitwidth img[md5] + img {\
width: 100%;\ width: 100%;\
}\ }\
\ \
#qp, #iHover {\ #qp, #ihover {\
position: fixed;\ position: fixed;\
}\ }\
\ \
#iHover {\ #ihover {\
max-height: 100%;\ max-height: 100%;\
max-width: 75%;\
}\ }\
\ \
#navlinks {\ #navlinks {\

View File

@ -2,7 +2,7 @@
{exec} = require 'child_process' {exec} = require 'child_process'
fs = require 'fs' fs = require 'fs'
VERSION = '2.23.7' VERSION = '2.24.0'
HEADER = """ HEADER = """
// ==UserScript== // ==UserScript==
@ -13,6 +13,7 @@ HEADER = """
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com> // @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license // @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @include http://boards.4chan.org/* // @include http://boards.4chan.org/*
// @include http://images.4chan.org/*
// @include http://sys.4chan.org/* // @include http://sys.4chan.org/*
// @run-at document-start // @run-at document-start
// @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js // @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js
@ -22,6 +23,7 @@ HEADER = """
/* LICENSE /* LICENSE
* *
* 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>
* http://mayhemydg.github.com/4chan-x/ * http://mayhemydg.github.com/4chan-x/
* 4chan X #{VERSION} * 4chan X #{VERSION}
* *

View File

@ -1,4 +1,17 @@
master master
- mayhem
fix Open thread in new tab keybind for Safari with Ninjakit
fix Index/Reply Navigation working in both cases when only one is enabled
2.24.0
- mayhem
redirect 404'd pictures to archives when possible
new keybind to open the options: ctrl+o
the unread count will decrease when inlining quotes of unread posts
the report button can open multiple popups again
add omploader to the list of optional flavors (http://ompldr.org/upload?url1=)
update archive redirections, add /lit/ and /u/
fit horizontally for Image Hover
2.23.7 2.23.7
- mayhem - mayhem

View File

@ -1 +1 @@
postMessage({version:'2.23.7'},'*'); postMessage({version:'2.24.0'},'*');

View File

@ -1,7 +1,7 @@
config = config =
main: main:
Enhancing: Enhancing:
'404 Redirect': [true, 'Redirect dead threads'] '404 Redirect': [true, 'Redirect dead threads and images']
'Keybinds': [true, 'Binds actions to keys'] 'Keybinds': [true, 'Binds actions to keys']
'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'] 'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time']
'Report Button': [true, 'Add report buttons'] 'Report Button': [true, 'Add report buttons']
@ -64,11 +64,13 @@ config =
'#http://3d.iqdb.org/?url=' '#http://3d.iqdb.org/?url='
'#http://regex.info/exif.cgi?imgurl=' '#http://regex.info/exif.cgi?imgurl='
'#http://imgur.com/upload?url=' '#http://imgur.com/upload?url='
'#http://ompldr.org/upload?url1='
].join '\n' ].join '\n'
time: '%m/%d/%y(%a)%H:%M' time: '%m/%d/%y(%a)%H:%M'
backlink: '>>%id' backlink: '>>%id'
favicon: 'ferongr' favicon: 'ferongr'
hotkeys: hotkeys:
openOptions: 'ctrl+o'
close: 'Esc' close: 'Esc'
spoiler: 'ctrl+s' spoiler: 'ctrl+s'
openQR: 'i' openQR: 'i'
@ -119,7 +121,7 @@ conf = {}
) null, config ) null, config
NAMESPACE = '4chan_x.' NAMESPACE = '4chan_x.'
VERSION = '2.23.7' VERSION = '2.24.0'
SECOND = 1000 SECOND = 1000
MINUTE = 60*SECOND MINUTE = 60*SECOND
HOUR = 60*MINUTE HOUR = 60*MINUTE
@ -219,6 +221,13 @@ $.extend = (object, properties) ->
object object
$.extend $, $.extend $,
onLoad: (fc) ->
if /interactive|complete/.test d.readyState
return fc()
cb = ->
$.off d, 'DOMContentLoaded', cb
fc()
$.on d, 'DOMContentLoaded', cb
id: (id) -> id: (id) ->
d.getElementById id d.getElementById id
globalEval: (code) -> globalEval: (code) ->
@ -349,8 +358,8 @@ $.extend $,
$.cache.requests = {} $.cache.requests = {}
if GM_deleteValue? $.extend $,
$.extend $, if GM_deleteValue?
delete: (name) -> delete: (name) ->
name = NAMESPACE + name name = NAMESPACE + name
GM_deleteValue name GM_deleteValue name
@ -360,15 +369,12 @@ if GM_deleteValue?
JSON.parse value JSON.parse value
else else
defaultValue defaultValue
openInTab: (url) ->
GM_openInTab url
set: (name, value) -> set: (name, value) ->
name = NAMESPACE + name name = NAMESPACE + name
# for `storage` events # for `storage` events
localStorage[name] = JSON.stringify value localStorage[name] = JSON.stringify value
GM_setValue name, JSON.stringify value GM_setValue name, JSON.stringify value
else else
$.extend $,
delete: (name) -> delete: (name) ->
name = NAMESPACE + name name = NAMESPACE + name
delete localStorage[name] delete localStorage[name]
@ -378,8 +384,6 @@ else
JSON.parse value JSON.parse value
else else
defaultValue defaultValue
openInTab: (url) ->
window.open url, "_blank"
set: (name, value) -> set: (name, value) ->
name = NAMESPACE + name name = NAMESPACE + name
localStorage[name] = JSON.stringify value localStorage[name] = JSON.stringify value
@ -647,8 +651,10 @@ keybinds =
thread = nav.getThread() thread = nav.getThread()
switch key switch key
when conf.openOptions
options.dialog() unless $.id 'overlay'
when conf.close when conf.close
if o = $ '#overlay' if o = $.id 'overlay'
$.rm o $.rm o
else if qr.el else if qr.el
qr.close() qr.close()
@ -707,7 +713,7 @@ keybinds =
else else
$('.postarea form').submit() $('.postarea form').submit()
when conf.unreadCountTo0 when conf.unreadCountTo0
unread.replies.length = 0 unread.replies = []
unread.updateTitle() unread.updateTitle()
Favicon.update() Favicon.update()
else else
@ -758,7 +764,8 @@ keybinds =
id = thread.firstChild.id id = thread.firstChild.id
url = "http://boards.4chan.org/#{g.BOARD}/res/#{id}" url = "http://boards.4chan.org/#{g.BOARD}/res/#{id}"
if tab if tab
$.openInTab url open = GM_openInTab or window.open
open url, "_blank"
else else
location.href = url location.href = url
@ -964,6 +971,7 @@ options =
<div class=error><code>Keybinds</code> are disabled.</div> <div class=error><code>Keybinds</code> are disabled.</div>
<table><tbody> <table><tbody>
<tr><th>Actions</th><th>Keybinds</th></tr> <tr><th>Actions</th><th>Keybinds</th></tr>
<tr><td>Open Options</td><td><input name=openOptions></td></tr>
<tr><td>Close Options or QR</td><td><input name=close></td></tr> <tr><td>Close Options or QR</td><td><input name=close></td></tr>
<tr><td>Quick spoiler</td><td><input name=spoiler></td></tr> <tr><td>Quick spoiler</td><td><input name=spoiler></td></tr>
<tr><td>Open QR with post number inserted</td><td><input name=openQR></td></tr> <tr><td>Open QR with post number inserted</td><td><input name=openQR></td></tr>
@ -1071,9 +1079,9 @@ options =
time: -> time: ->
Time.foo() Time.foo()
Time.date = new Date() Time.date = new Date()
$('#timePreview').textContent = Time.funk Time $.id('timePreview').textContent = Time.funk Time
backlink: -> backlink: ->
$('#backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789' $.id('backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
favicon: -> favicon: ->
Favicon.switch() Favicon.switch()
Favicon.update() if g.REPLY and conf['Unread Count'] Favicon.update() if g.REPLY and conf['Unread Count']
@ -1626,8 +1634,7 @@ updater =
for input in $$ '#com_submit' for input in $$ '#com_submit'
input.disabled = true input.disabled = true
input.value = 404 input.value = 404
# XXX trailing spaces are trimmed d.title = d.title.match(/^.+-/)[0] + ' 404'
d.title = d.title.match(/.+-/)[0] + ' 404'
g.dead = true g.dead = true
Favicon.update() Favicon.update()
return return
@ -1641,12 +1648,12 @@ updater =
This saves bandwidth for both the user and the servers, avoid unnecessary computation, This saves bandwidth for both the user and the servers, avoid unnecessary computation,
and won't load images and scripts when parsing the response. and won't load images and scripts when parsing the response.
### ###
updater.lastModified = @getResponseHeader('Last-Modified')
if @status is 304 if @status is 304
if conf['Verbose'] if conf['Verbose']
updater.count.textContent = '+0' updater.count.textContent = '+0'
updater.count.className = null updater.count.className = null
return return
updater.lastModified = @getResponseHeader 'Last-Modified'
body = $.el 'body', body = $.el 'body',
innerHTML: @responseText innerHTML: @responseText
@ -1956,7 +1963,7 @@ quoteInline =
quote.removeAttribute 'onclick' quote.removeAttribute 'onclick'
$.on quote, 'click', quoteInline.toggle $.on quote, 'click', quoteInline.toggle
toggle: (e) -> toggle: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.button isnt 0 return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
e.preventDefault() e.preventDefault()
id = @hash[1..] id = @hash[1..]
if /\binlined\b/.test @className if /\binlined\b/.test @className
@ -1970,6 +1977,10 @@ quoteInline =
root = if q.parentNode.nodeName is 'FONT' then q.parentNode else if q.nextSibling then q.nextSibling else q root = if q.parentNode.nodeName is 'FONT' then q.parentNode else if q.nextSibling then q.nextSibling else q
if el = $.id id if el = $.id id
inline = quoteInline.table id, el.innerHTML inline = quoteInline.table id, el.innerHTML
if g.REPLY and conf['Unread Count'] and (i = unread.replies.indexOf el.parentNode.parentNode.parentNode) isnt -1
unread.replies.splice i, 1
unread.updateTitle()
Favicon.update()
if /\bbacklink\b/.test q.className if /\bbacklink\b/.test q.className
$.after q.parentNode, inline $.after q.parentNode, inline
$.addClass $.x('ancestor::table', el), 'forwarded' if conf['Forward Hiding'] $.addClass $.x('ancestor::table', el), 'forwarded' if conf['Forward Hiding']
@ -2113,7 +2124,7 @@ reportButton =
$.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}"
id = "#{NAMESPACE}popup" id = Date.now()
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200" set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"
window.open url, id, set window.open url, id, set
@ -2226,17 +2237,31 @@ Favicon =
favicon.href = null favicon.href = null
$.replace favicon, clone $.replace favicon, clone
redirect = -> redirect =
switch g.BOARD init: ->
when 'a', 'jp', 'm', 'tg', 'tv' url =
url = "http://oldarchive.foolz.us/#{g.BOARD}/thread/#{g.THREAD_ID}" # waiting for https://github.com/FoOlRulez/FoOlFuuka/issues/11
when 'diy', 'g', 'sci' if location.hostname is 'images.4chan.org'
url = "http://archive.installgentoo.net/#{g.BOARD}/thread/#{g.THREAD_ID}" redirect.image g.BOARD, location.pathname.split('/')[3]
when '3', 'adv', 'an', 'ck', 'co', 'fa', 'fit', 'int', 'k', 'mu', 'n', 'o', 'p', 'po', 'pol', 'soc', 'sp', 'toy', 'trv', 'v', 'vp', 'x' else if /^\d+$/.test g.THREAD_ID
url = "http://archive.no-ip.org/#{g.BOARD}/thread/#{g.THREAD_ID}" redirect.thread()
else location.href = url if url
url = "http://boards.4chan.org/#{g.BOARD}" image: (board, filename) -> #board must be given, the image can originate from a cross-quote
location.href = url switch board
when 'a', 'jp', 'm', 'tg', 'tv', 'u'
"http://archive.foolz.us/#{board}/full_image/#{filename}"
thread: ->
switch g.BOARD
when 'a', 'jp', 'm', 'tg', 'tv', 'u'
"http://archive.foolz.us/#{g.BOARD}/thread/#{g.THREAD_ID}/"
when 'lit'
"http://fuuka.warosu.org/#{g.BOARD}/thread/#{g.THREAD_ID}"
when 'diy', 'g', 'sci'
"http://archive.installgentoo.net/#{g.BOARD}/thread/#{g.THREAD_ID}"
when '3', 'adv', 'an', 'ck', 'co', 'fa', 'fit', 'int', 'k', 'mu', 'n', 'o', 'p', 'po', 'pol', 'r9k', 'soc', 'sp', 'toy', 'trv', 'v', 'vp', 'x'
"http://archive.no-ip.org/#{g.BOARD}/thread/#{g.THREAD_ID}"
else
"http://boards.4chan.org/#{g.BOARD}"
imgHover = imgHover =
init: -> init: ->
@ -2247,7 +2272,7 @@ imgHover =
$.on thumb, 'mouseout', ui.hoverend $.on thumb, 'mouseout', ui.hoverend
mouseover: -> mouseover: ->
ui.el = $.el 'img' ui.el = $.el 'img'
id: 'iHover' id: 'ihover'
src: @parentNode.href src: @parentNode.href
$.add d.body, ui.el $.add d.body, ui.el
@ -2272,7 +2297,7 @@ imgExpand =
imgExpand.expand a.firstChild imgExpand.expand a.firstChild
cb: cb:
toggle: (e) -> toggle: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.button isnt 0 return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
e.preventDefault() e.preventDefault()
imgExpand.toggle @ imgExpand.toggle @
all: -> all: ->
@ -2313,23 +2338,26 @@ imgExpand =
thumb.hidden = false thumb.hidden = false
$.rm thumb.nextSibling $.rm thumb.nextSibling
expand: (thumb) -> expand: (thumb, url) ->
a = thumb.parentNode a = thumb.parentNode
img = $.el 'img', img = $.el 'img',
src: a.href src: if url then url else a.href
if engine is 'gecko' and a.parentNode.className isnt 'op' if engine is 'gecko' and a.parentNode.className isnt 'op'
filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent
max = filesize.match /(\d+)x/ max = filesize.match /(\d+)x/
img.style.maxWidth = "#{max[1]}px" img.style.maxWidth = "#{max[1]}px"
$.on img, 'error', imgExpand.error $.on img, 'error', imgExpand.error if conf['404 Redirect']
thumb.hidden = true thumb.hidden = true
$.add a, img $.add a, img
error: -> error: ->
thumb = @previousSibling thumb = @previousSibling
imgExpand.contract thumb imgExpand.contract thumb
src = @src.split '/'
if url = redirect.image src[3], src[5]
imgExpand.expand thumb, url
#navigator.online is not x-browser/os yet #navigator.online is not x-browser/os yet
if engine is 'webkit' else if engine is 'webkit'
req = $.ajax @src, (-> req = $.ajax @src, (->
setTimeout imgExpand.retry, 10000, thumb if @status isnt 404 setTimeout imgExpand.retry, 10000, thumb if @status isnt 404
), type: 'head', event: 'onreadystatechange' ), type: 'head', event: 'onreadystatechange'
@ -2361,36 +2389,33 @@ imgExpand =
$.prepend form, controls $.prepend form, controls
resize: -> resize: ->
imgExpand.style.innerHTML = ".fitheight [md5] + img {max-height:#{d.body.clientHeight}px;}" imgExpand.style.innerHTML = ".fitheight img[md5] + img {max-height:#{d.body.clientHeight}px;}"
Main = Main =
init: -> init: ->
pathname = location.pathname[1..].split('/') pathname = location.pathname[1..].split('/')
[g.BOARD, temp] = pathname [g.BOARD, temp] = pathname
if temp is 'res' if temp is 'res'
g.REPLY = temp g.REPLY = true
g.THREAD_ID = pathname[2] g.THREAD_ID = pathname[2]
else else
g.PAGENUM = parseInt(temp) or 0 g.PAGENUM = parseInt(temp) or 0
if location.hostname is 'sys.4chan.org' if location.hostname is 'sys.4chan.org'
if /interactive|complete/.test d.readyState $.onLoad qr.sys
qr.sys()
else
$.on d, 'DOMContentLoaded', qr.sys
return return
if location.hostname is 'images.4chan.org'
if conf['404 Redirect']
$.onLoad -> redirect.init() if d.title is '4chan - 404'
return
$.onLoad options.init
$.on window, 'message', Main.message $.on window, 'message', Main.message
now = Date.now() now = Date.now()
if conf['Check for Updates'] and $.get('lastUpdate', 0) < now - 6*HOUR if conf['Check for Updates'] and $.get('lastUpdate', 0) < now - 6*HOUR
update = -> $.onLoad -> $.add d.head, $.el 'script', src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js'
$.off d, 'DOMContentLoaded', update
$.add d.head, $.el 'script', src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js'
if /interactive|complete/.test d.readyState
update()
else
$.on d, 'DOMContentLoaded', update
$.set 'lastUpdate', now $.set 'lastUpdate', now
g.hiddenReplies = $.get "hiddenReplies/#{g.BOARD}/", {} g.hiddenReplies = $.get "hiddenReplies/#{g.BOARD}/", {}
@ -2456,17 +2481,13 @@ Main =
quoteDR.init() quoteDR.init()
if /interactive|complete/.test d.readyState $.onLoad Main.onLoad
Main.onLoad()
else
$.on d, 'DOMContentLoaded', Main.onLoad
onLoad: -> onLoad: ->
$.off d, 'DOMContentLoaded', Main.onLoad if conf['404 Redirect'] and d.title is '4chan - 404'
if conf['404 Redirect'] and d.title is '4chan - 404' and /^\d+$/.test g.THREAD_ID redirect.init()
redirect()
return return
if not $ '#navtopr' if not $.id 'navtopr'
return return
$.addClass d.body, engine $.addClass d.body, engine
$.addStyle Main.css $.addStyle Main.css
@ -2474,7 +2495,7 @@ Main =
Favicon.init() Favicon.init()
#recaptcha may be blocked, eg by noscript #recaptcha may be blocked, eg by noscript
if (form = $ 'form[name=post]') and (canPost = !!$ '#recaptcha_response_field') if (form = $ 'form[name=post]') and (canPost = !!$.id 'recaptcha_response_field')
Recaptcha.init() Recaptcha.init()
if g.REPLY and conf['Auto Watch Reply'] and conf['Thread Watcher'] if g.REPLY and conf['Auto Watch Reply'] and conf['Thread Watcher']
$.on form, 'submit', -> if $('img.favicon').src is Favicon.empty $.on form, 'submit', -> if $('img.favicon').src is Favicon.empty
@ -2502,9 +2523,6 @@ Main =
if conf['Keybinds'] if conf['Keybinds']
keybinds.init() keybinds.init()
if conf['Reply Navigation'] or conf['Index Navigation']
nav.init()
if g.REPLY if g.REPLY
if conf['Thread Updater'] if conf['Thread Updater']
updater.init() updater.init()
@ -2512,6 +2530,9 @@ Main =
if conf['Thread Stats'] if conf['Thread Stats']
threadStats.init() threadStats.init()
if conf['Reply Navigation']
nav.init()
if conf['Post in Title'] if conf['Post in Title']
titlePost.init() titlePost.init()
@ -2533,6 +2554,9 @@ Main =
if conf['Comment Expansion'] if conf['Comment Expansion']
expandComment.init() expandComment.init()
if conf['Index Navigation']
nav.init()
nodes = $$ '.op, a + table' nodes = $$ '.op, a + table'
for callback in g.callbacks for callback in g.callbacks
@ -2542,7 +2566,6 @@ Main =
catch err catch err
alert err alert err
$.on $('form[name=delform]'), 'DOMNodeInserted', Main.node $.on $('form[name=delform]'), 'DOMNodeInserted', Main.node
options.init()
message: (e) -> message: (e) ->
{origin, data} = e {origin, data} = e
@ -2575,7 +2598,6 @@ Main =
text-decoration: none; text-decoration: none;
} }
[hidden], /* Firefox bug: hidden tables are not hidden. fixed in 9.0 */
.thread.stub > :not(.block), .thread.stub > :not(.block),
#content > [name=tab]:not(:checked) + div, #content > [name=tab]:not(:checked) + div,
#updater:not(:hover) > :not(.move), #updater:not(:hover) > :not(.move),
@ -2603,23 +2625,24 @@ Main =
float: left; float: left;
pointer-events: none; pointer-events: none;
} }
[md5], [md5] + img { img[md5], img[md5] + img {
pointer-events: all; pointer-events: all;
} }
.fitwidth [md5] + img { .fitwidth img[md5] + img {
max-width: 100%; max-width: 100%;
} }
.gecko > .fitwidth [md5] + img, .gecko > .fitwidth img[md5] + img,
.presto > .fitwidth [md5] + img { .presto > .fitwidth img[md5] + img {
width: 100%; width: 100%;
} }
#qp, #iHover { #qp, #ihover {
position: fixed; position: fixed;
} }
#iHover { #ihover {
max-height: 100%; max-height: 100%;
max-width: 75%;
} }
#navlinks { #navlinks {