Merge branch 'master' of git://github.com/MayhemYDG/4chan-x

This commit is contained in:
ahodesuka 2012-04-18 15:43:42 -05:00
commit b3d6539351
5 changed files with 151 additions and 446 deletions

View File

@ -1,6 +1,6 @@
// ==UserScript== // ==UserScript==
// @name 4chan x // @name 4chan x
// @version 2.29.1 // @version 2.29.3
// @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>
@ -9,7 +9,6 @@
// @include http*://boards.4chan.org/* // @include http*://boards.4chan.org/*
// @include http*://images.4chan.org/* // @include http*://images.4chan.org/*
// @include http*://sys.4chan.org/* // @include http*://sys.4chan.org/*
// @include http*://www.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
// @icon http://mayhemydg.github.com/4chan-x/favicon.gif // @icon http://mayhemydg.github.com/4chan-x/favicon.gif
@ -20,7 +19,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.29.1 * 4chan X 2.29.3
* *
* 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
@ -729,7 +728,7 @@
a.textContent = "" + req.status + " " + req.statusText; a.textContent = "" + req.status + " " + req.statusText;
return; return;
} }
doc = d.implementation.createHTMLDocument(null); doc = d.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = req.response; doc.documentElement.innerHTML = req.response;
Threading.op($('body > form', doc).firstChild); Threading.op($('body > form', doc).firstChild);
node = d.importNode(doc.getElementById(replyID)); node = d.importNode(doc.getElementById(replyID));
@ -834,7 +833,7 @@
return; return;
} }
a.textContent = a.textContent.replace('\u00d7 Loading...', '-'); a.textContent = a.textContent.replace('\u00d7 Loading...', '-');
doc = d.implementation.createHTMLDocument(null); doc = d.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = req.response; doc.documentElement.innerHTML = req.response;
nodes = []; nodes = [];
_ref = $$('.reply', doc); _ref = $$('.reply', doc);
@ -976,7 +975,7 @@
break; break;
case Conf.unreadCountTo0: case Conf.unreadCountTo0:
Unread.replies = []; Unread.replies = [];
Unread.update(); Unread.update(true);
break; break;
case Conf.expandImage: case Conf.expandImage:
Keybinds.img(thread); Keybinds.img(thread);
@ -1229,46 +1228,17 @@
return setTimeout(this.asyncInit); return setTimeout(this.asyncInit);
}, },
asyncInit: function() { asyncInit: function() {
var form, iframe, link, loadChecking, script, src; var link, script;
if (Conf['Hide Original Post Form']) { if (Conf['Hide Original Post Form']) {
link = $.el('h1', { link = $.el('h1', {
innerHTML: "<a href=javascript:;>" + (g.REPLY ? 'Quick Reply' : 'New Thread') + "</a>" innerHTML: "<a href=javascript:;>" + (g.REPLY ? 'Quick Reply' : 'New Thread') + "</a>"
}); });
$.on($('a', link), 'click', function() { $.on(link.firstChild, 'click', function() {
QR.open(); QR.open();
if (!g.REPLY) $('select', QR.el).value = 'new'; if (!g.REPLY) $('select', QR.el).value = 'new';
return $('textarea', QR.el).focus(); return $('textarea', QR.el).focus();
}); });
form = d.forms[0]; $.before($('form[name=post]'), link);
$.before(form, link);
}
if (/chrome/i.test(navigator.userAgent)) {
QR.status({
ready: true
});
} else {
src = "http" + (/^https/.test(form.action) ? 's' : '') + "://sys.4chan.org/robots.txt";
iframe = $.el('iframe', {
id: 'iframe',
src: src
});
$.on(iframe, 'error', function() {
return this.src = this.src;
});
loadChecking = function(iframe) {
if (!QR.status.ready) {
iframe.src = 'about:blank';
return setTimeout((function() {
return iframe.src = src;
}), 100);
}
};
$.on(iframe, 'load', function() {
if (this.src !== 'about:blank') {
return setTimeout(loadChecking, 500, this);
}
});
$.add(d.head, iframe);
} }
script = $.el('script', { script = $.el('script', {
textContent: 'Recaptcha.focus_response_field=function(){}' textContent: 'Recaptcha.focus_response_field=function(){}'
@ -1297,9 +1267,7 @@
close: function() { close: function() {
var i, spoiler, _i, _len, _ref; var i, spoiler, _i, _len, _ref;
QR.el.hidden = true; QR.el.hidden = true;
QR.message.send({ QR.abort();
req: 'abort'
});
d.activeElement.blur(); d.activeElement.blur();
$.removeClass(QR.el, 'dump'); $.removeClass(QR.el, 'dump');
_ref = QR.replies; _ref = QR.replies;
@ -1344,23 +1312,12 @@
status: function(data) { status: function(data) {
var disabled, input, value; var disabled, input, value;
if (data == null) data = {}; if (data == null) data = {};
if (data.ready) {
QR.status.ready = true;
QR.status.banned = data.banned;
} else if (!QR.status.ready) {
value = 'Loading';
disabled = true;
}
if (g.dead) { if (g.dead) {
value = 404; value = 404;
disabled = true; disabled = true;
QR.cooldown.auto = false; QR.cooldown.auto = false;
} else if (QR.status.banned) {
value = 'Banned';
disabled = true;
} else {
value = QR.cooldown.seconds || data.progress || value;
} }
value = QR.cooldown.seconds || data.progress || value;
if (!QR.el) return; if (!QR.el) return;
input = QR.status.input; input = QR.status.input;
input.value = QR.cooldown.auto && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit'; input.value = QR.cooldown.auto && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit';
@ -1826,16 +1783,14 @@
return QR.el.dispatchEvent(e); return QR.el.dispatchEvent(e);
}, },
submit: function(e) { submit: function(e) {
var captcha, captchas, challenge, err, file, m, post, reader, reply, response, threadID; var callbacks, captcha, captchas, challenge, err, form, m, name, opts, post, reply, response, threadID, val;
if (e != null) e.preventDefault(); if (e != null) e.preventDefault();
if (QR.cooldown.seconds) { if (QR.cooldown.seconds) {
QR.cooldown.auto = !QR.cooldown.auto; QR.cooldown.auto = !QR.cooldown.auto;
QR.status(); QR.status();
return; return;
} }
QR.message.send({ QR.abort();
req: 'abort'
});
reply = QR.replies[0]; reply = QR.replies[0];
if (!(reply.com || reply.file)) { if (!(reply.com || reply.file)) {
err = 'No file selected.'; err = 'No file selected.';
@ -1868,8 +1823,13 @@
if (Conf['Thread Watcher'] && Conf['Auto Watch Reply'] && threadID !== 'new') { if (Conf['Thread Watcher'] && Conf['Auto Watch Reply'] && threadID !== 'new') {
Watcher.watch(threadID); Watcher.watch(threadID);
} }
if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) {
d.activeElement.blur();
}
QR.status({
progress: '...'
});
post = { post = {
postURL: $('form[name=post]').action,
resto: threadID, resto: threadID,
name: reply.name, name: reply.name,
email: reply.email, email: reply.email,
@ -1882,37 +1842,51 @@
recaptcha_challenge_field: challenge, recaptcha_challenge_field: challenge,
recaptcha_response_field: response + ' ' recaptcha_response_field: response + ' '
}; };
QR.status({ form = new FormData();
progress: '...' for (name in post) {
}); val = post[name];
if ($.engine === 'gecko' && reply.file) { if (val) form.append(name, val);
file = {};
reader = new FileReader();
reader.onload = function() {
file.buffer = this.result;
file.name = reply.file.name;
file.type = reply.file.type;
post.upfile = file;
return QR.message.send(post);
};
reader.readAsBinaryString(reply.file);
return;
} }
if (/chrome/i.test(navigator.userAgent)) { callbacks = {
QR.message.post(post); onload: function() {
return; return QR.response(this.response);
} },
return QR.message.send(post); onerror: function() {
return QR.error('_', $.el('a', {
href: '//www.4chan.org/banned',
target: '_blank',
textContent: 'Connection error, or you are banned.'
}));
}
};
opts = {
form: form,
type: 'POST',
upCallbacks: {
onload: function() {
return QR.status({
progress: '...'
});
},
onprogress: function(e) {
return QR.status({
progress: "" + (Math.round(e.loaded / e.total * 100)) + "%"
});
}
}
};
return QR.ajax = $.ajax($('form[name=post]').action, callbacks, opts);
}, },
response: function(html) { response: function(html) {
var b, doc, err, node, persona, postNumber, reply, thread, _, _ref; var b, doc, err, node, persona, postNumber, reply, thread, _, _ref;
doc = $.el('a', { doc = d.implementation.createHTMLDocument('');
innerHTML: html doc.documentElement.innerHTML = html;
}); if (doc.title === '4chan - Banned') {
if ($('title', doc).textContent === '4chan - Banned') { QR.error('_', $.el('a', {
QR.message.receive({ href: '//www.4chan.org/banned',
req: 'banned' target: '_blank',
}); textContent: 'You are banned.'
}));
return; return;
} }
if (!(b = $('td b', doc))) { if (!(b = $('td b', doc))) {
@ -1970,140 +1944,10 @@
QR.status(); QR.status();
return QR.resetFileInput(); return QR.resetFileInput();
}, },
message: { abort: function() {
send: function(data) { var _ref;
var host, window; if ((_ref = QR.ajax) != null) _ref.abort();
if (/chrome/i.test(navigator.userAgent)) { return QR.status();
QR.message.receive(data);
return;
}
data.QR = true;
host = location.hostname;
window = host === 'boards.4chan.org' ? $.id('iframe').contentWindow : parent;
return window.postMessage(data, '*');
},
receive: function(data) {
var req, _ref;
req = data.req;
delete data.req;
delete data.QR;
switch (req) {
case 'abort':
if ((_ref = QR.ajax) != null) _ref.abort();
return QR.message.send({
req: 'status'
});
case 'response':
return QR.response(data.html);
case 'status':
return QR.status(data);
case 'banned':
QR.error('You are banned.', $.el('a', {
href: 'http://www.4chan.org/banned',
target: '_blank',
textContent: 'You are banned.'
}));
return QR.status({
ready: true,
banned: true
});
default:
return QR.message.post(data);
}
},
post: function(data) {
var boundary, callbacks, form, i, name, opts, parts, toBin, url, val;
url = data.postURL;
delete data.postURL;
if ($.engine === 'gecko' && data.upfile) {
if (!data.binary) {
toBin = function(data, name, val) {
var bb, r;
bb = new MozBlobBuilder();
bb.append(val);
r = new FileReader();
r.onload = function() {
data[name] = r.result;
if (!--i) return QR.message.post(data);
};
return r.readAsBinaryString(bb.getBlob('text/plain'));
};
i = Object.keys(data).length;
for (name in data) {
val = data[name];
if (typeof val === 'object') {
toBin(data.upfile, 'name', data.upfile.name);
} else if (typeof val === 'boolean') {
if (val) {
toBin(data, name, String(val));
} else {
i--;
}
} else {
toBin(data, name, val);
}
}
data.postURL = url;
data.binary = true;
return;
}
delete data.binary;
boundary = '-------------SMCD' + Date.now();
parts = [];
parts.push('Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n');
delete data.upfile;
for (name in data) {
val = data[name];
if (val) {
parts.push('Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n');
}
}
form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n';
} else {
form = new FormData();
for (name in data) {
val = data[name];
if (val) form.append(name, val);
}
}
callbacks = {
onload: function() {
return QR.message.send({
req: 'response',
html: this.response
});
},
onerror: function() {
return QR.message.send({
req: 'banned'
});
}
};
opts = {
form: form,
type: 'post',
upCallbacks: {
onload: function() {
return QR.message.send({
req: 'status',
progress: '...'
});
},
onprogress: function(e) {
return QR.message.send({
req: 'status',
progress: "" + (Math.round(e.loaded / e.total * 100)) + "%"
});
}
}
};
if (boundary) {
opts.headers = {
'Content-Type': 'multipart/form-data;boundary=' + boundary
};
}
return QR.ajax = $.ajax(url, callbacks, opts);
}
} }
}; };
@ -2121,6 +1965,7 @@
$.replace(home.firstElementChild, a); $.replace(home.firstElementChild, a);
} }
if (!$.get('firstrun')) { if (!$.get('firstrun')) {
if (!Favicon.el) Favicon.init();
$.set('firstrun', true); $.set('firstrun', true);
return Options.dialog(); return Options.dialog();
} }
@ -2563,10 +2408,7 @@
d.title = d.title.match(/^.+-/)[0] + ' 404'; d.title = d.title.match(/^.+-/)[0] + ' 404';
} }
Unread.update(true); Unread.update(true);
QR.message.send({ QR.abort();
req: 'abort'
});
QR.status();
return; return;
} }
Updater.retryCoef = 10; Updater.retryCoef = 10;
@ -2585,7 +2427,7 @@
return; return;
} }
Updater.lastModified = this.getResponseHeader('Last-Modified'); Updater.lastModified = this.getResponseHeader('Last-Modified');
doc = d.implementation.createHTMLDocument(null); doc = d.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = this.response; doc.documentElement.innerHTML = this.response;
id = $('input', Updater.br.previousElementSibling).name; id = $('input', Updater.br.previousElementSibling).name;
nodes = []; nodes = [];
@ -3139,7 +2981,7 @@
inline = QuoteInline.table(id, el.innerHTML); inline = QuoteInline.table(id, el.innerHTML);
if ((i = Unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) { if ((i = Unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) {
Unread.replies.splice(i, 1); Unread.replies.splice(i, 1);
Unread.update(); Unread.update(true);
} }
if (/\bbacklink\b/.test(q.className)) { if (/\bbacklink\b/.test(q.className)) {
$.after(q.parentNode, inline); $.after(q.parentNode, inline);
@ -3188,7 +3030,7 @@
inline.textContent = "" + req.status + " " + req.statusText; inline.textContent = "" + req.status + " " + req.statusText;
return; return;
} }
doc = d.implementation.createHTMLDocument(null); doc = d.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = req.response; doc.documentElement.innerHTML = req.response;
node = id === threadID ? Threading.op($('body > form', doc).firstChild) : doc.getElementById(id); node = id === threadID ? Threading.op($('body > form', doc).firstChild) : doc.getElementById(id);
newInline = QuoteInline.table(id, node.innerHTML); newInline = QuoteInline.table(id, node.innerHTML);
@ -3277,7 +3119,7 @@
qp.textContent = "" + req.status + " " + req.statusText; qp.textContent = "" + req.status + " " + req.statusText;
return; return;
} }
doc = d.implementation.createHTMLDocument(null); doc = d.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = req.response; doc.documentElement.innerHTML = req.response;
node = id === threadID ? Threading.op($('body > form', doc).firstChild) : doc.getElementById(id); node = id === threadID ? Threading.op($('body > form', doc).firstChild) : doc.getElementById(id);
qp.innerHTML = node.innerHTML; qp.innerHTML = node.innerHTML;
@ -3485,6 +3327,7 @@
Favicon = { Favicon = {
init: function() { init: function() {
var href; var href;
if (this.el) return;
this.el = $('link[rel="shortcut icon"]', d.head); this.el = $('link[rel="shortcut icon"]', d.head);
this.el.type = 'image/x-icon'; this.el.type = 'image/x-icon';
href = this.el.href; href = this.el.href;
@ -3629,6 +3472,7 @@
AutoGif = { AutoGif = {
init: function() { init: function() {
if (g.BOARD === 'gif') return;
return Main.callbacks.push(this.node); return Main.callbacks.push(this.node);
}, },
node: function(post) { node: function(post) {
@ -3808,12 +3652,7 @@
$.on(window, 'message', Main.message); $.on(window, 'message', Main.message);
switch (location.hostname) { switch (location.hostname) {
case 'sys.4chan.org': case 'sys.4chan.org':
if (path === '/robots.txt') { if (/report/.test(location.search)) {
QR.message.send({
req: 'status',
ready: true
});
} else if (/report/.test(location.search)) {
$.ready(function() { $.ready(function() {
return $.on($.id('recaptcha_response_field'), 'keydown', function(e) { return $.on($.id('recaptcha_response_field'), 'keydown', function(e) {
if (e.keyCode === 8 && !e.target.value) { if (e.keyCode === 8 && !e.target.value) {
@ -3823,13 +3662,6 @@
}); });
} }
return; return;
case 'www.4chan.org':
if (path === '/banned') {
QR.message.send({
req: 'banned'
});
}
return;
case 'images.4chan.org': case 'images.4chan.org':
$.ready(function() { $.ready(function() {
if (d.title === '4chan - 404') return Redirect.init(); if (d.title === '4chan - 404') return Redirect.init();
@ -3990,13 +3822,8 @@
} }
}, },
message: function(e) { message: function(e) {
var data, version; var version;
data = e.data; version = e.data.version;
if (data.QR) {
QR.message.receive(data);
return;
}
version = data.version;
if (version && version !== Main.version && confirm('An updated version of 4chan X is available, would you like to install it now?')) { if (version && version !== Main.version && confirm('An updated version of 4chan X is available, would you like to install it now?')) {
return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + version + "/4chan_x.user.js"; return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + version + "/4chan_x.user.js";
} }
@ -4054,7 +3881,7 @@
if (target.nodeName === 'TABLE') return Main.node([Main.preParse(target)]); if (target.nodeName === 'TABLE') return Main.node([Main.preParse(target)]);
}, },
namespace: '4chan_x.', namespace: '4chan_x.',
version: '2.29.1', version: '2.29.3',
callbacks: [], callbacks: [],
css: '\ css: '\
/* dialog styling */\ /* dialog styling */\

View File

@ -2,7 +2,7 @@
{exec} = require 'child_process' {exec} = require 'child_process'
fs = require 'fs' fs = require 'fs'
VERSION = '2.29.1' VERSION = '2.29.3'
HEADER = """ HEADER = """
// ==UserScript== // ==UserScript==
@ -16,7 +16,6 @@ HEADER = """
// @include http*://boards.4chan.org/* // @include http*://boards.4chan.org/*
// @include http*://images.4chan.org/* // @include http*://images.4chan.org/*
// @include http*://sys.4chan.org/* // @include http*://sys.4chan.org/*
// @include http*://www.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
// @icon http://mayhemydg.github.com/4chan-x/favicon.gif // @icon http://mayhemydg.github.com/4chan-x/favicon.gif

View File

@ -1,6 +1,16 @@
master master
- Mayhem - Mayhem
Now works when using https. Auto-GIF will not run in /gif/.
2.29.3
- Mayhem
Update Quick Reply posting method, this fixes compatibility for uncommon browsers such as
Opera Mobile 12 and Luakit for example.
2.29.2
- Mayhem
Add HTTPS support.
Ban support improvements and fixes.
2.29.1 2.29.1
- Mayhem - Mayhem

View File

@ -1 +1 @@
postMessage({version:'2.29.1'},'*') postMessage({version:'2.29.3'},'*')

View File

@ -627,7 +627,7 @@ ExpandComment =
a.textContent = "#{req.status} #{req.statusText}" a.textContent = "#{req.status} #{req.statusText}"
return return
doc = d.implementation.createHTMLDocument null doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = req.response doc.documentElement.innerHTML = req.response
Threading.op $('body > form', doc).firstChild Threading.op $('body > form', doc).firstChild
@ -712,7 +712,7 @@ ExpandThread =
a.textContent = a.textContent.replace '\u00d7 Loading...', '-' a.textContent = a.textContent.replace '\u00d7 Loading...', '-'
doc = d.implementation.createHTMLDocument null doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = req.response doc.documentElement.innerHTML = req.response
nodes = [] nodes = []
@ -833,7 +833,7 @@ Keybinds =
Updater.update() Updater.update()
when Conf.unreadCountTo0 when Conf.unreadCountTo0
Unread.replies = [] Unread.replies = []
Unread.update() Unread.update true
# Images # Images
when Conf.expandImage when Conf.expandImage
Keybinds.img thread Keybinds.img thread
@ -1014,32 +1014,15 @@ QR =
asyncInit: -> asyncInit: ->
if Conf['Hide Original Post Form'] if Conf['Hide Original Post Form']
link = $.el 'h1', innerHTML: "<a href=javascript:;>#{if g.REPLY then 'Quick Reply' else 'New Thread'}</a>" link = $.el 'h1', innerHTML: "<a href=javascript:;>#{if g.REPLY then 'Quick Reply' else 'New Thread'}</a>"
$.on $('a', link), 'click', -> $.on link.firstChild, 'click', ->
QR.open() QR.open()
$('select', QR.el).value = 'new' unless g.REPLY $('select', QR.el).value = 'new' unless g.REPLY
$('textarea', QR.el).focus() $('textarea', QR.el).focus()
form = d.forms[0] $.before $('form[name=post]'), link
$.before form, link
# CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox.
if /chrome/i.test navigator.userAgent
QR.status ready: true
else
src = "http#{if /^https/.test form.action then 's' else ''}://sys.4chan.org/robots.txt"
iframe = $.el 'iframe',
id: 'iframe'
src: src
$.on iframe, 'error', -> @src = @src
# Greasemonkey ghetto fix
loadChecking = (iframe) ->
unless QR.status.ready
iframe.src = 'about:blank'
setTimeout (-> iframe.src = src), 100
$.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. # Prevent original captcha input from being focused on reload.
script = $.el 'script', textContent: 'Recaptcha.focus_response_field=function(){}' script = $.el 'script',
textContent: 'Recaptcha.focus_response_field=function(){}'
$.add d.head, script $.add d.head, script
$.rm script $.rm script
@ -1061,7 +1044,7 @@ QR =
QR.dialog() QR.dialog()
close: -> close: ->
QR.el.hidden = true QR.el.hidden = true
QR.message.send req: 'abort' QR.abort()
d.activeElement.blur() d.activeElement.blur()
$.removeClass QR.el, 'dump' $.removeClass QR.el, 'dump'
for i in QR.replies for i in QR.replies
@ -1095,22 +1078,11 @@ QR =
$('.warning', QR.el).textContent = null $('.warning', QR.el).textContent = null
status: (data={}) -> status: (data={}) ->
if data.ready
QR.status.ready = true
QR.status.banned = data.banned
else unless QR.status.ready
value = 'Loading'
disabled = true
if g.dead if g.dead
value = 404 value = 404
disabled = true disabled = true
QR.cooldown.auto = false QR.cooldown.auto = false
else if QR.status.banned value = QR.cooldown.seconds or data.progress or value
value = 'Banned'
disabled = true
else
# do not cancel `value = 'Loading'` once the cooldown is over
value = QR.cooldown.seconds or data.progress or value
return unless QR.el return unless QR.el
{input} = QR.status {input} = QR.status
input.value = input.value =
@ -1511,7 +1483,7 @@ QR =
QR.cooldown.auto = !QR.cooldown.auto QR.cooldown.auto = !QR.cooldown.auto
QR.status() QR.status()
return return
QR.message.send req: 'abort' QR.abort()
reply = QR.replies[0] reply = QR.replies[0]
# prevent errors # prevent errors
@ -1550,9 +1522,15 @@ QR =
QR.hide() QR.hide()
if Conf['Thread Watcher'] and Conf['Auto Watch Reply'] and threadID isnt 'new' if Conf['Thread Watcher'] and Conf['Auto Watch Reply'] and threadID isnt 'new'
Watcher.watch threadID Watcher.watch threadID
if not QR.cooldown.auto and $.x 'ancestor::div[@id="qr"]', d.activeElement
# Unfocus the focused element if it is one within the QR and we're not auto-posting.
d.activeElement.blur()
# Starting to upload might take some time.
# Provide some feedback that we're starting to submit.
QR.status progress: '...'
post = post =
postURL: $('form[name=post]').action
resto: threadID resto: threadID
name: reply.name name: reply.name
email: reply.email email: reply.email
@ -1565,36 +1543,42 @@ QR =
recaptcha_challenge_field: challenge recaptcha_challenge_field: challenge
recaptcha_response_field: response + ' ' recaptcha_response_field: response + ' '
# Starting to upload might take some time. form = new FormData()
# Provide some feedback that we're starting to submit. for name, val of post
QR.status progress: '...' form.append name, val if val
if $.engine is 'gecko' and reply.file callbacks =
# https://bugzilla.mozilla.org/show_bug.cgi?id=673742 onload: ->
# We plan to allow postMessaging Files and FileLists across origins, QR.response @response
# that just needs a more in depth security review. onerror: ->
file = {} # Connection error, or
reader = new FileReader() # CORS disabled error on www.4chan.org/banned
reader.onload = -> QR.error '_', $.el 'a',
file.buffer = @result href: '//www.4chan.org/banned'
file.name = reply.file.name target: '_blank'
file.type = reply.file.type textContent: 'Connection error, or you are banned.'
post.upfile = file opts =
QR.message.send post form: form
reader.readAsBinaryString reply.file type: 'POST'
return upCallbacks:
onload: ->
# Upload done, waiting for response.
QR.status progress: '...'
onprogress: (e) ->
# Uploading...
QR.status progress: "#{Math.round e.loaded / e.total * 100}%"
# CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. QR.ajax = $.ajax $('form[name=post]').action, callbacks, opts
if /chrome/i.test navigator.userAgent
QR.message.post post
return
QR.message.send post
response: (html) -> response: (html) ->
doc = $.el 'a', innerHTML: html doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = html
# Check for ban. # Check for ban.
if $('title', doc).textContent is '4chan - Banned' if doc.title is '4chan - Banned'
QR.message.receive req: 'banned' QR.error '_', $.el 'a',
href: '//www.4chan.org/banned'
target: '_blank'
textContent: 'You are banned.'
return return
unless b = $ 'td b', doc unless b = $ 'td b', doc
err = 'Connection error with sys.4chan.org.' err = 'Connection error with sys.4chan.org.'
@ -1652,117 +1636,9 @@ QR =
QR.status() QR.status()
QR.resetFileInput() QR.resetFileInput()
message: abort: ->
send: (data) -> QR.ajax?.abort()
# CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. QR.status()
if /chrome/i.test navigator.userAgent
QR.message.receive data
return
data.QR = true
host = location.hostname
window =
if host is 'boards.4chan.org'
$.id('iframe').contentWindow
else
parent
window.postMessage data, '*'
receive: (data) ->
req = data.req
delete data.req
delete data.QR
switch req
when 'abort'
QR.ajax?.abort()
QR.message.send req: 'status'
when 'response' # xhr response
QR.response data.html
when 'status'
QR.status data
when 'banned'
QR.error 'You are banned.', $.el 'a',
href: 'http://www.4chan.org/banned'
target: '_blank'
textContent: 'You are banned.'
# Disable iframe reloading
QR.status ready: true, banned: true
else
QR.message.post data # Reply object: we're posting
post: (data) ->
url = data.postURL
# Do not append these values to the form.
delete data.postURL
# File with filename upload fix from desuwa
if $.engine is 'gecko' and data.upfile
# All of this is fucking retarded.
unless data.binary
toBin = (data, name, val) ->
bb = new MozBlobBuilder()
bb.append val
r = new FileReader()
r.onload = ->
data[name] = r.result
unless --i
QR.message.post data
r.readAsBinaryString bb.getBlob 'text/plain'
i = Object.keys(data).length
for name, val of data
if typeof val is 'object' # File. toBin the filename.
toBin data.upfile, 'name', data.upfile.name
else if typeof val is 'boolean'
if val
toBin data, name, String val
else
i--
else
toBin data, name, val
data.postURL = url
data.binary = true
return
delete data.binary
boundary = '-------------SMCD' + Date.now();
parts = []
parts.push 'Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n'
delete data.upfile
for name, val of data
parts.push 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n' if val
form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n'
else
form = new FormData()
for name, val of data
form.append name, val if val
callbacks =
onload: ->
QR.message.send
req: 'response'
html: @response
onerror: ->
# CORS disabled error: redirecting to banned page ;_;
QR.message.send req: 'banned'
opts =
form: form
type: 'post'
upCallbacks:
onload: ->
QR.message.send
req: 'status'
progress: '...'
onprogress: (e) ->
QR.message.send
req: 'status'
progress: "#{Math.round e.loaded / e.total * 100}%"
if boundary
opts.headers =
'Content-Type': 'multipart/form-data;boundary=' + boundary
QR.ajax = $.ajax url, callbacks, opts
Options = Options =
init: -> init: ->
@ -1773,6 +1649,8 @@ Options =
$.on a, 'click', Options.dialog $.on a, 'click', Options.dialog
$.replace home.firstElementChild, a $.replace home.firstElementChild, a
unless $.get 'firstrun' unless $.get 'firstrun'
# Prevent race conditions
Favicon.init() unless Favicon.el
$.set 'firstrun', true $.set 'firstrun', true
Options.dialog() Options.dialog()
@ -2175,8 +2053,7 @@ Updater =
else else
d.title = d.title.match(/^.+-/)[0] + ' 404' d.title = d.title.match(/^.+-/)[0] + ' 404'
Unread.update true Unread.update true
QR.message.send req: 'abort' QR.abort()
QR.status()
return return
Updater.retryCoef = 10 Updater.retryCoef = 10
@ -2195,7 +2072,7 @@ Updater =
return return
Updater.lastModified = @getResponseHeader 'Last-Modified' Updater.lastModified = @getResponseHeader 'Last-Modified'
doc = d.implementation.createHTMLDocument null doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = @response doc.documentElement.innerHTML = @response
id = $('input', Updater.br.previousElementSibling).name id = $('input', Updater.br.previousElementSibling).name
@ -2608,7 +2485,7 @@ QuoteInline =
inline = QuoteInline.table id, el.innerHTML inline = QuoteInline.table id, el.innerHTML
if (i = Unread.replies.indexOf el.parentNode.parentNode.parentNode) isnt -1 if (i = Unread.replies.indexOf el.parentNode.parentNode.parentNode) isnt -1
Unread.replies.splice i, 1 Unread.replies.splice i, 1
Unread.update() Unread.update true
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']
@ -2647,7 +2524,7 @@ QuoteInline =
inline.textContent = "#{req.status} #{req.statusText}" inline.textContent = "#{req.status} #{req.statusText}"
return return
doc = d.implementation.createHTMLDocument null doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = req.response doc.documentElement.innerHTML = req.response
node = node =
@ -2721,7 +2598,7 @@ QuotePreview =
qp.textContent = "#{req.status} #{req.statusText}" qp.textContent = "#{req.status} #{req.statusText}"
return return
doc = d.implementation.createHTMLDocument null doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = req.response doc.documentElement.innerHTML = req.response
node = node =
@ -2939,6 +2816,7 @@ Unread =
Favicon = Favicon =
init: -> init: ->
return if @el # Prevent race condition with options first run
@el = $ 'link[rel="shortcut icon"]', d.head @el = $ 'link[rel="shortcut icon"]', d.head
@el.type = 'image/x-icon' @el.type = 'image/x-icon'
{href} = @el {href} = @el
@ -3029,6 +2907,7 @@ ImageHover =
AutoGif = AutoGif =
init: -> init: ->
return if g.BOARD is 'gif'
Main.callbacks.push @node Main.callbacks.push @node
node: (post) -> node: (post) ->
return if post.root.hidden or not post.img return if post.root.hidden or not post.img
@ -3174,17 +3053,11 @@ Main =
switch location.hostname switch location.hostname
when 'sys.4chan.org' when 'sys.4chan.org'
if path is '/robots.txt' if /report/.test location.search
QR.message.send req: 'status', ready: true
else if /report/.test location.search
$.ready -> $.ready ->
$.on $.id('recaptcha_response_field'), 'keydown', (e) -> $.on $.id('recaptcha_response_field'), 'keydown', (e) ->
window.location = 'javascript:Recaptcha.reload()' if e.keyCode is 8 and not e.target.value window.location = 'javascript:Recaptcha.reload()' if e.keyCode is 8 and not e.target.value
return return
when 'www.4chan.org'
if path is '/banned'
QR.message.send req: 'banned'
return
when 'images.4chan.org' when 'images.4chan.org'
$.ready -> Redirect.init() if d.title is '4chan - 404' $.ready -> Redirect.init() if d.title is '4chan - 404'
return return
@ -3361,11 +3234,7 @@ Main =
$.on d, 'DOMNodeInserted', Main.addStyle $.on d, 'DOMNodeInserted', Main.addStyle
message: (e) -> message: (e) ->
{data} = e {version} = e.data
if data.QR
QR.message.receive data
return
{version} = data
if version and version isnt Main.version and confirm 'An updated version of 4chan X is available, would you like to install it now?' if version and version isnt Main.version and confirm 'An updated version of 4chan X is available, would you like to install it now?'
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"
@ -3401,7 +3270,7 @@ Main =
Main.node [Main.preParse target] if target.nodeName is 'TABLE' Main.node [Main.preParse target] if target.nodeName is 'TABLE'
namespace: '4chan_x.' namespace: '4chan_x.'
version: '2.29.1' version: '2.29.3'
callbacks: [] callbacks: []
css: ' css: '
/* dialog styling */ /* dialog styling */