Merge @ccd0's capcha changes up to ccd0/4chan-x@bb806b453

This commit is contained in:
Zixaphir 2014-12-12 03:03:27 -07:00
parent 29da4807e4
commit 168aa98096
7 changed files with 596 additions and 446 deletions

View File

@ -1,5 +1,5 @@
/* /*
* appchan x - Version 2.9.40 - 2014-12-09 * appchan x - Version 2.9.40 - 2014-12-12
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -28,7 +28,7 @@
// ==/UserScript== // ==/UserScript==
/* /*
* appchan x - Version 2.9.40 - 2014-12-09 * appchan x - Version 2.9.40 - 2014-12-12
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -8819,6 +8819,9 @@
return QR.captcha.destroy(); return QR.captcha.destroy();
}, },
focusin: function() { focusin: function() {
if ($.hasClass(QR.nodes.el, 'autohide') && !$.hasClass(QR.nodes.el, 'focus')) {
QR.captcha.setup();
}
return $.addClass(QR.nodes.el, 'focus'); return $.addClass(QR.nodes.el, 'focus');
}, },
focusout: function() { focusout: function() {
@ -8850,7 +8853,7 @@
el.removeAttribute('style'); el.removeAttribute('style');
} }
if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) { if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) {
QR.captcha.setup(); QR.captcha.setup(true);
} }
QR.notify(el); QR.notify(el);
if (d.hidden) { if (d.hidden) {
@ -9007,6 +9010,9 @@
}, },
paste: function(e) { paste: function(e) {
var blob, files, item, _i, _len, _ref; var blob, files, item, _i, _len, _ref;
if (!e.clipboardData.items) {
return;
}
files = []; files = [];
_ref = e.clipboardData.items; _ref = e.clipboardData.items;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -9028,45 +9034,16 @@
QR.handleFiles(files); QR.handleFiles(files);
return $.addClass(QR.nodes.el, 'dump'); return $.addClass(QR.nodes.el, 'dump');
}, },
handleBlob: function(urlBlob, contentType, contentDisposition, url) {
var blob, match, mime, name, _ref, _ref1, _ref2;
name = (_ref = url.match(/([^\/]+)\/*$/)) != null ? _ref[1] : void 0;
mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream';
match = (contentDisposition != null ? (_ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref1[1] : void 0 : void 0) || (contentType != null ? (_ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref2[1] : void 0 : void 0);
if (match) {
name = match.replace(/\\"/g, '"');
}
blob = new Blob([urlBlob], {
type: mime
});
blob.name = name;
return QR.handleFiles([blob]);
},
handleUrl: function() { handleUrl: function() {
var url; var url;
url = prompt("Enter a URL:"); url = prompt('Enter a URL:');
if (url === null) { if (url === null) {
return; return;
} }
return GM_xmlhttpRequest({ return CrossOrigin.file(url, function(blob) {
method: "GET", if (blob) {
url: url, return QR.handleFiles([blob]);
overrideMimeType: "text/plain; charset=x-user-defined", } else {
onload: function(xhr) {
var contentDisposition, contentType, data, h, i, r;
r = xhr.responseText;
h = xhr.responseHeaders;
data = new Uint8Array(r.length);
i = 0;
while (i < r.length) {
data[i] = r.charCodeAt(i);
i++;
}
contentType = (h.match(/Content-Type:\s*(.*)/i) || [])[1];
contentDisposition = (h.match(/Content-Disposition:\s*(.*)/i) || [])[1];
return QR.handleBlob(data, contentType, contentDisposition, url);
},
onerror: function(xhr) {
return QR.error("Can't load image."); return QR.error("Can't load image.");
} }
}); });
@ -9090,12 +9067,12 @@
} }
}, },
handleFile: function(file, index, nfiles) { handleFile: function(file, index, nfiles) {
var isSingle, max, post, _ref; var err, isSingle, max, post, _ref;
isSingle = nfiles === 1; isSingle = nfiles === 1;
if (/^text\//.test(file.type)) { if (/^text\//.test(file.type)) {
if (isSingle) { if (isSingle) {
post = QR.selected; post = QR.selected;
} else if ((post = QR.posts[QR.posts.length - 1]).com) { } else if (index !== 0 || (post = QR.posts[QR.posts.length - 1]).com) {
post = new QR.post(); post = new QR.post();
} }
post.pasteText(file); post.pasteText(file);
@ -9118,7 +9095,12 @@
} else if ((post = QR.posts[QR.posts.length - 1]).file) { } else if ((post = QR.posts[QR.posts.length - 1]).file) {
post = new QR.post(); post = new QR.post();
} }
return post.setFile(file); try {
return post.setFile(file);
} catch (_error) {
err = _error;
return console.log(err);
}
}, },
openFileInput: function(e) { openFileInput: function(e) {
var _ref; var _ref;
@ -9126,10 +9108,12 @@
if (e.shiftKey && e.type === 'click') { if (e.shiftKey && e.type === 'click') {
return QR.selected.rmFile(); return QR.selected.rmFile();
} }
if (e.ctrlKey && e.type === 'click') { if ((e.ctrlKey || e.metaKey) && e.type === 'click') {
$.addClass(QR.nodes.filename, 'edit'); $.addClass(QR.nodes.filename, 'edit');
QR.nodes.filename.focus(); QR.nodes.filename.focus();
return; return $.on(QR.nodes.filename, 'blur', function() {
return $.rmClass(QR.nodes.filename, 'edit');
});
} }
if (e.target.nodeName === 'INPUT' || (e.keyCode && ((_ref = e.keyCode) !== 32 && _ref !== 13)) || e.ctrlKey) { if (e.target.nodeName === 'INPUT' || (e.keyCode && ((_ref = e.keyCode) !== 32 && _ref !== 13)) || e.ctrlKey) {
return; return;
@ -9165,7 +9149,7 @@
} }
}, },
dialog: function() { dialog: function() {
var dialog, elm, event, i, items, name, node, nodes, prop, rules, save, setNode, _, _i, _len, _ref, _ref1, _ref2; var dialog, elm, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode;
QR.nodes = nodes = { QR.nodes = nodes = {
el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab class=move><input type=checkbox id=autohide title=Auto-hide><div id=qr-thread-select><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><a href=javascript:; class='close fa' title=Close>\uf00d</a></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1><input name=email data-name=email list=\"list-email\" placeholder=Options class=field size=1><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1> </div><div class=textarea><textarea data-name=com placeholder=Comment class=field></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\">+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=0><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image'></label><span class=description>Spoiler</span><a id=url-button><i class=\"fa\">\uf0c1</i></a><span class=description>Post from URL</span><a id=dump-button title='Dump list'>+</a><span class=description>Dump</span><a id=qr-filerm href=javascript:; title='Remove file' class=fa>\uf00d</a><span class=description>Remove File</span></span></span><input type=submit></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist>") el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab class=move><input type=checkbox id=autohide title=Auto-hide><div id=qr-thread-select><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><a href=javascript:; class='close fa' title=Close>\uf00d</a></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1><input name=email data-name=email list=\"list-email\" placeholder=Options class=field size=1><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1> </div><div class=textarea><textarea data-name=com placeholder=Comment class=field></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\">+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=0><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image'></label><span class=description>Spoiler</span><a id=url-button><i class=\"fa\">\uf0c1</i></a><span class=description>Post from URL</span><a id=dump-button title='Dump list'>+</a><span class=description>Dump</span><a id=qr-filerm href=javascript:; title='Remove file' class=fa>\uf00d</a><span class=description>Remove File</span></span></span><input type=submit></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist>")
}; };
@ -9197,23 +9181,20 @@
setNode('status', '[type=submit]'); setNode('status', '[type=submit]');
setNode('fileInput', '[type=file]'); setNode('fileInput', '[type=file]');
rules = $('ul.rules').textContent.trim(); rules = $('ul.rules').textContent.trim();
QR.min_width = QR.min_height = 1; match_min = rules.match(/.+smaller than (\d+)x(\d+).+/);
QR.max_width = QR.max_height = 10000; match_max = rules.match(/.+greater than (\d+)x(\d+).+/);
try { QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1;
_ref = rules.match(/.+smaller than (\d+)x(\d+).+/), _ = _ref[0], QR.min_width = _ref[1], QR.min_height = _ref[2]; QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1;
_ref1 = rules.match(/.+greater than (\d+)x(\d+).+/), _ = _ref1[0], QR.max_width = _ref1[1], QR.max_height = _ref1[2]; QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000;
_ref2 = ['min_width', 'min_height', 'max_width', 'max_height']; QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
prop = _ref2[_i];
QR[prop] = parseInt(QR[prop], 10);
}
} catch (_error) {
null;
}
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value; nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
QR.max_size_video = 3145728; QR.max_size_video = 3145728;
QR.max_width_video = QR.max_height_video = 2048; QR.max_width_video = QR.max_height_video = 2048;
QR.max_duration_video = 120; QR.max_duration_video = 120;
QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]');
if (QR.forcedAnon) {
$.addClass(QR.nodes.el, 'forced-anon');
}
QR.spoiler = !!$('.postForm input[name=spoiler]'); QR.spoiler = !!$('.postForm input[name=spoiler]');
if (QR.spoiler) { if (QR.spoiler) {
$.addClass(QR.nodes.el, 'has-spoiler'); $.addClass(QR.nodes.el, 'has-spoiler');
@ -9224,9 +9205,10 @@
$.after(nodes.name.parentElement, nodes.dumpList.parentElement); $.after(nodes.name.parentElement, nodes.dumpList.parentElement);
nodes.addPost.tabIndex = 35; nodes.addPost.tabIndex = 35;
} }
if (g.BOARD.ID === 'f') { if (g.BOARD.ID === 'f' && g.VIEW !== 'thread') {
nodes.flashTag = $.el('select', { nodes.flashTag = $.el('select', {
name: 'filetag', name: 'filetag'
}, {
innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>" innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>"
}); });
nodes.flashTag.dataset["default"] = '4'; nodes.flashTag.dataset["default"] = '4';
@ -9413,9 +9395,9 @@
post.lock(); post.lock();
formData = { formData = {
resto: threadID, resto: threadID,
name: post.name, name: !QR.forcedAnon ? post.name : void 0,
email: post.email, email: post.email,
sub: post.sub, sub: !(QR.forcedAnon || threadID) ? post.sub : void 0,
com: post.com, com: post.com,
upfile: post.file, upfile: post.file,
filetag: filetag, filetag: filetag,
@ -9502,9 +9484,6 @@
} }
QR.status(); QR.status();
QR.error(err); QR.error(err);
if (QR.captcha.isEnabled) {
QR.captcha.setup();
}
return; return;
} }
h1 = $('h1', resDoc); h1 = $('h1', resDoc);
@ -9555,7 +9534,7 @@
QR.close(); QR.close();
} else { } else {
post.rm(); post.rm();
QR.captcha.setup(); QR.captcha.setup(true);
} }
QR.cooldown.set({ QR.cooldown.set({
req: req, req: req,
@ -9607,7 +9586,7 @@
QR.captcha = { QR.captcha = {
init: function() { init: function() {
var container, counter, section; var counter, root;
if (d.cookie.indexOf('pass_enabled=1') >= 0) { if (d.cookie.indexOf('pass_enabled=1') >= 0) {
return; return;
} }
@ -9621,80 +9600,101 @@
return QR.captcha.sync(captchas); return QR.captcha.sync(captchas);
}); });
$.sync('captchas', this.sync.bind(this)); $.sync('captchas', this.sync.bind(this));
section = $.el('div', { root = $.el('div', {
className: 'captcha-section' className: 'captcha-root'
}); });
$.extend(section, { $.extend(root, {
innerHTML: "<div class=\"captcha-container\"></div><div class=\"captcha-counter\"><a href=\"javascript:;\"></a></div>" innerHTML: "<div class=\"captcha-counter\"><a href=\"javascript:;\"></a></div>"
}); });
container = $('.captcha-container', section); counter = $('.captcha-counter > a', root);
counter = $('.captcha-counter > a', section);
this.nodes = { this.nodes = {
container: container, root: root,
counter: counter counter: counter
}; };
this.count(); this.count();
$.addClass(QR.nodes.el, 'has-captcha'); $.addClass(QR.nodes.el, 'has-captcha');
$.after(QR.nodes.com.parentNode, section); $.after(QR.nodes.com.parentNode, root);
new MutationObserver(this.afterSetup.bind(this)).observe(container, {
childList: true,
subtree: true
});
$.on(counter, 'click', this.toggle.bind(this)); $.on(counter, 'click', this.toggle.bind(this));
return $.on(window, 'captcha:success', this.save.bind(this)); return $.on(window, 'captcha:success', (function(_this) {
return function() {
return _this.save(false);
};
})(this));
}, },
shouldFocus: false, shouldFocus: false,
timeouts: {}, timeouts: {},
postsCount: 0,
needed: function() { needed: function() {
var captchaCount, postsCount; var captchaCount;
captchaCount = this.captchas.length; captchaCount = this.captchas.length;
if (this.nodes.container.dataset.widgetID && !this.timeouts.destroy) { if (this.nodes.container && !this.timeouts.destroy) {
captchaCount++; captchaCount++;
} }
postsCount = QR.posts.length; this.postsCount = QR.posts.length;
if (postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) {
postsCount = 0; this.postsCount = 0;
}
return captchaCount < this.postsCount;
},
onPostChange: function() {
if (this.postsCount === 0) {
this.setup();
}
if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) {
return this.postsCount = 0;
} }
return captchaCount < postsCount;
}, },
toggle: function() { toggle: function() {
if (this.nodes.container.dataset.widgetID && !this.timeouts.destroy) { if (this.nodes.container && !this.timeouts.destroy) {
return this.destroy(); return this.destroy();
} else { } else {
this.shouldFocus = true; return this.setup(true, true);
return this.setup(true);
} }
}, },
setup: function(force) { setup: function(focus, force) {
if (!(this.isEnabled && (this.needed() || force))) { if (!(this.isEnabled && (this.needed() || force))) {
return; return;
} }
$.addClass(QR.nodes.el, 'captcha-open'); $.addClass(QR.nodes.el, 'captcha-open');
if (focus) {
this.shouldFocus = true;
}
if (this.timeouts.destroy) { if (this.timeouts.destroy) {
clearTimeout(this.timeouts.destroy); clearTimeout(this.timeouts.destroy);
delete this.timeouts.destroy; delete this.timeouts.destroy;
return this.reload(); return this.reload();
} }
if (this.nodes.container.dataset.widgetID) { if (this.nodes.container) {
return; return;
} }
return $.globalEval('(function() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n})();'); this.nodes.container = $.el('div', {
className: 'captcha-container'
});
$.prepend(this.nodes.root, this.nodes.container);
new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, {
childList: true,
subtree: true
});
return $.globalEval('(function() {\n function render() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n }\n if (window.grecaptcha) {\n render();\n } else {\n var cbNative = window.onRecaptchaLoaded;\n window.onRecaptchaLoaded = function() {\n render();\n cbNative();\n }\n }\n})();');
}, },
afterSetup: function(mutations) { afterSetup: function(mutations) {
var iframe, mutation, node, _i, _j, _len, _len1, _ref; var iframe, mutation, node, textarea, _i, _j, _len, _len1, _ref;
for (_i = 0, _len = mutations.length; _i < _len; _i++) { for (_i = 0, _len = mutations.length; _i < _len; _i++) {
mutation = mutations[_i]; mutation = mutations[_i];
_ref = mutation.addedNodes; _ref = mutation.addedNodes;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
node = _ref[_j]; node = _ref[_j];
if (node.nodeName === 'IFRAME') { if (iframe = $.x('./descendant-or-self::iframe', node)) {
iframe = node; this.setupIFrame(iframe);
}
if (textarea = $.x('./descendant-or-self::textarea', node)) {
this.setupTextArea(textarea);
} }
} }
} }
if (!iframe) { },
return; setupIFrame: function(iframe) {
} this.setupTime = Date.now();
if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) {
QR.nodes.el.style.top = null; QR.nodes.el.style.top = null;
QR.nodes.el.style.bottom = '0px'; QR.nodes.el.style.bottom = '0px';
@ -9704,16 +9704,28 @@
} }
return this.shouldFocus = false; return this.shouldFocus = false;
}, },
setupTextArea: function(textarea) {
return $.one(textarea, 'input', (function(_this) {
return function() {
return _this.save(true);
};
})(this));
},
destroy: function() { destroy: function() {
if (!this.isEnabled) { if (!this.isEnabled) {
return; return;
} }
delete this.timeouts.destroy; delete this.timeouts.destroy;
$.rmClass(QR.nodes.el, 'captcha-open'); $.rmClass(QR.nodes.el, 'captcha-open');
$.rmAll(this.nodes.container); if (this.nodes.container) {
return this.nodes.container.removeAttribute('data-widget-i-d'); $.rm(this.nodes.container);
}
return delete this.nodes.container;
}, },
sync: function(captchas) { sync: function(captchas) {
if (captchas == null) {
captchas = [];
}
this.captchas = captchas; this.captchas = captchas;
this.clear(); this.clear();
return this.count(); return this.count();
@ -9729,30 +9741,39 @@
return null; return null;
} }
}, },
save: function(e) { save: function(pasted) {
var _base; var reload, _base;
if (this.needed()) { $.forceSync('captchas');
reload = (QR.cooldown.auto || Conf['Post on Captcha Completion']) && this.needed();
this.captchas.push({
response: $('textarea', this.nodes.container).value,
timeout: (pasted ? this.setupTime : Date.now()) + 2 * $.MINUTE
});
this.count();
$.set('captchas', this.captchas);
if (reload) {
this.shouldFocus = true; this.shouldFocus = true;
this.reload(); this.reload();
} else { } else {
this.nodes.counter.focus(); if (pasted) {
if ((_base = this.timeouts).destroy == null) { this.destroy();
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); } else {
if ((_base = this.timeouts).destroy == null) {
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
}
} }
QR.nodes.status.focus();
}
if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) {
return QR.submit();
} }
$.forceSync('captchas');
this.captchas.push({
response: e.detail,
timeout: Date.now() + 2 * $.MINUTE
});
this.count();
return $.set('captchas', this.captchas);
}, },
clear: function() { clear: function() {
var captcha, i, now, _i, _len, _ref; var captcha, i, now, _i, _len, _ref;
if (!this.captchas.length) { if (!this.captchas.length) {
return; return;
} }
$.forceSync('captchas');
now = Date.now(); now = Date.now();
_ref = this.captchas; _ref = this.captchas;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
@ -9767,7 +9788,7 @@
this.captchas = this.captchas.slice(i); this.captchas = this.captchas.slice(i);
this.count(); this.count();
$.set('captchas', this.captchas); $.set('captchas', this.captchas);
return this.setup(); return this.setup(true);
}, },
count: function() { count: function() {
this.nodes.counter.textContent = "Captchas: " + this.captchas.length; this.nodes.counter.textContent = "Captchas: " + this.captchas.length;
@ -10004,7 +10025,7 @@
QR.post = (function() { QR.post = (function() {
function _Class(select) { function _Class(select) {
this.select = __bind(this.select, this); this.select = __bind(this.select, this);
var el, elm, event, prev, _i, _j, _len, _len1, _ref, _ref1, _ref2; var el, elm, event, prev, _i, _j, _len, _len1, _ref, _ref1;
el = $.el('a', { el = $.el('a', {
className: 'qr-preview', className: 'qr-preview',
draggable: true, draggable: true,
@ -10051,9 +10072,8 @@
$.on(el, event.toLowerCase(), this[event]); $.on(el, event.toLowerCase(), this[event]);
} }
this.thread = g.VIEW === 'thread' ? g.THREADID : 'new'; this.thread = g.VIEW === 'thread' ? g.THREADID : 'new';
_ref2 = QR.posts, prev = _ref2[_ref2.length - 1]; prev = QR.posts[QR.posts.length - 1];
QR.posts.push(this); QR.posts.push(this);
QR.captcha.setup();
this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false; this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
QR.persona.get((function(_this) { QR.persona.get((function(_this) {
return function(persona) { return function(persona) {
@ -10072,6 +10092,9 @@
this.select(); this.select();
} }
this.unlock(); this.unlock();
$.queueTask(function() {
return QR.captcha.setup();
});
} }
_Class.prototype.rm = function() { _Class.prototype.rm = function() {
@ -10162,7 +10185,7 @@
return QR.status(); return QR.status();
case 'com': case 'com':
this.nodes.span.textContent = this.com; this.nodes.span.textContent = this.com;
QR.captcha.setup(); QR.captcha.onPostChange();
QR.characterCount(); QR.characterCount();
if (QR.cooldown.auto && this === QR.posts[0] && (0 < (_ref = QR.cooldown.seconds) && _ref <= 5)) { if (QR.cooldown.auto && this === QR.posts[0] && (0 < (_ref = QR.cooldown.seconds) && _ref <= 5)) {
return QR.cooldown.auto = false; return QR.cooldown.auto = false;
@ -10202,7 +10225,7 @@
if (QR.spoiler) { if (QR.spoiler) {
this.nodes.label.hidden = false; this.nodes.label.hidden = false;
} }
QR.captcha.setup(); QR.captcha.onPostChange();
URL.revokeObjectURL(this.URL); URL.revokeObjectURL(this.URL);
if (this === QR.selected) { if (this === QR.selected) {
this.showFileData(); this.showFileData();
@ -10220,55 +10243,55 @@
var el, fileURL, isVideo; var el, fileURL, isVideo;
isVideo = /^video\//.test(this.file.type); isVideo = /^video\//.test(this.file.type);
el = $.el((isVideo ? 'video' : 'img')); el = $.el((isVideo ? 'video' : 'img'));
$.on(el, (isVideo ? 'loadeddata' : 'load'), (function(_this) { $.on(el, (isVideo ? 'loadeddata' : 'load'), function() {
return function() { var cv, error, errors, height, s, width, _i, _len;
var cv, error, errors, height, s, width, _i, _len; errors = this.checkDimensions(el, isVideo);
errors = _this.checkDimensions(el, isVideo); if (errors.length) {
if (errors.length) { for (_i = 0, _len = errors.length; _i < _len; _i++) {
for (_i = 0, _len = errors.length; _i < _len; _i++) { error = errors[_i];
error = errors[_i]; QR.error(error);
QR.error(error);
}
_this.URL = fileURL;
if ((QR.posts.length === 1) || (_this.com && _this.com.length)) {
return _this.rmFile();
} else {
return _this.rm();
}
} }
s = 90 * 2 * window.devicePixelRatio; this.URL = fileURL;
if (_this.file.type === 'image/gif') { if ((QR.posts.length === 1) || (this.com && this.com.length)) {
s *= 3; return this.rmFile();
}
if (isVideo) {
height = el.videoHeight;
width = el.videoWidth;
} else { } else {
height = el.height, width = el.width; return this.rm();
if (height < s || width < s) {
_this.URL = fileURL;
_this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")";
return;
}
} }
if (height <= width) { }
width = s / height * width; s = 90 * 2 * window.devicePixelRatio;
height = s; if (this.file.type === 'image/gif') {
} else { s *= 3;
height = s / width * height; }
width = s; if (isVideo) {
height = el.videoHeight;
width = el.videoWidth;
} else {
height = el.height, width = el.width;
if (height < s || width < s) {
this.URL = fileURL;
this.nodes.el.style.backgroundImage = "url(" + this.URL + ")";
return;
} }
cv = $.el('canvas'); }
cv.height = el.height = height; if (height <= width) {
cv.width = el.width = width; width = s / height * width;
cv.getContext('2d').drawImage(el, 0, 0, width, height); height = s;
URL.revokeObjectURL(fileURL); } else {
return cv.toBlob(function(blob) { height = s / width * height;
width = s;
}
cv = $.el('canvas');
cv.height = el.height = height;
cv.width = el.width = width;
cv.getContext('2d').drawImage(el, 0, 0, width, height);
URL.revokeObjectURL(fileURL);
return cv.toBlob((function(_this) {
return function(blob) {
_this.URL = URL.createObjectURL(blob); _this.URL = URL.createObjectURL(blob);
return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")"; return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")";
}); };
}; })(this));
})(this)); });
fileURL = URL.createObjectURL(this.file); fileURL = URL.createObjectURL(this.file);
return el.src = fileURL; return el.src = fileURL;
}; };
@ -10326,7 +10349,7 @@
_Class.prototype.updateFilename = function() { _Class.prototype.updateFilename = function() {
var title; var title;
title = "" + this.filename + " (" + this.filesize + ")\nCtrl+click to edit filename. Shift+click to clear."; title = "" + this.filename + " (" + this.filesize + ")\nCtrl/\u2318+click to edit filename. Shift+click to clear.";
this.nodes.el.title = title; this.nodes.el.title = title;
if (this !== QR.selected) { if (this !== QR.selected) {
return; return;

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript // Generated by CoffeeScript
/* /*
* appchan x - Version 2.9.40 - 2014-12-09 * appchan x - Version 2.9.40 - 2014-12-12
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -8841,6 +8841,9 @@
return QR.captcha.destroy(); return QR.captcha.destroy();
}, },
focusin: function() { focusin: function() {
if ($.hasClass(QR.nodes.el, 'autohide') && !$.hasClass(QR.nodes.el, 'focus')) {
QR.captcha.setup();
}
return $.addClass(QR.nodes.el, 'focus'); return $.addClass(QR.nodes.el, 'focus');
}, },
focusout: function() { focusout: function() {
@ -8872,7 +8875,7 @@
el.removeAttribute('style'); el.removeAttribute('style');
} }
if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) { if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) {
QR.captcha.setup(); QR.captcha.setup(true);
} }
QR.notify(el); QR.notify(el);
if (d.hidden) { if (d.hidden) {
@ -9038,6 +9041,9 @@
}, },
paste: function(e) { paste: function(e) {
var blob, files, item, _i, _len, _ref; var blob, files, item, _i, _len, _ref;
if (!e.clipboardData.items) {
return;
}
files = []; files = [];
_ref = e.clipboardData.items; _ref = e.clipboardData.items;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -9059,42 +9065,19 @@
QR.handleFiles(files); QR.handleFiles(files);
return $.addClass(QR.nodes.el, 'dump'); return $.addClass(QR.nodes.el, 'dump');
}, },
handleBlob: function(urlBlob, contentType, contentDisposition, url) {
var blob, match, mime, name, _ref, _ref1, _ref2;
name = (_ref = url.match(/([^\/]+)\/*$/)) != null ? _ref[1] : void 0;
mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream';
match = (contentDisposition != null ? (_ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref1[1] : void 0 : void 0) || (contentType != null ? (_ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref2[1] : void 0 : void 0);
if (match) {
name = match.replace(/\\"/g, '"');
}
blob = new Blob([urlBlob], {
type: mime
});
blob.name = name;
return QR.handleFiles([blob]);
},
handleUrl: function() { handleUrl: function() {
var url, xhr; var url;
url = prompt("Enter a URL:"); url = prompt('Enter a URL:');
if (url === null) { if (url === null) {
return; return;
} }
xhr = new XMLHttpRequest(); return CrossOrigin.file(url, function(blob) {
xhr.open('GET', url, true); if (blob) {
xhr.responseType = 'blob'; return QR.handleFiles([blob]);
xhr.onload = function(e) { } else {
var contentDisposition, contentType;
if (!(this.readyState === this.DONE && xhr.status === 200)) {
return QR.error("Can't load image."); return QR.error("Can't load image.");
} }
contentType = this.getResponseHeader('Content-Type'); });
contentDisposition = this.getResponseHeader('Content-Disposition');
return QR.handleBlob(this.response, contentType, contentDisposition, url);
};
xhr.onerror = function(e) {
return QR.error("Can't load image.");
};
return xhr.send();
}, },
handleFiles: function(files) { handleFiles: function(files) {
var file, i, _i, _len; var file, i, _i, _len;
@ -9115,12 +9098,12 @@
} }
}, },
handleFile: function(file, index, nfiles) { handleFile: function(file, index, nfiles) {
var isSingle, max, post, _ref; var err, isSingle, max, post, _ref;
isSingle = nfiles === 1; isSingle = nfiles === 1;
if (/^text\//.test(file.type)) { if (/^text\//.test(file.type)) {
if (isSingle) { if (isSingle) {
post = QR.selected; post = QR.selected;
} else if ((post = QR.posts[QR.posts.length - 1]).com) { } else if (index !== 0 || (post = QR.posts[QR.posts.length - 1]).com) {
post = new QR.post(); post = new QR.post();
} }
post.pasteText(file); post.pasteText(file);
@ -9143,7 +9126,12 @@
} else if ((post = QR.posts[QR.posts.length - 1]).file) { } else if ((post = QR.posts[QR.posts.length - 1]).file) {
post = new QR.post(); post = new QR.post();
} }
return post.setFile(file); try {
return post.setFile(file);
} catch (_error) {
err = _error;
return console.log(err);
}
}, },
openFileInput: function(e) { openFileInput: function(e) {
var _ref; var _ref;
@ -9151,10 +9139,12 @@
if (e.shiftKey && e.type === 'click') { if (e.shiftKey && e.type === 'click') {
return QR.selected.rmFile(); return QR.selected.rmFile();
} }
if (e.ctrlKey && e.type === 'click') { if ((e.ctrlKey || e.metaKey) && e.type === 'click') {
$.addClass(QR.nodes.filename, 'edit'); $.addClass(QR.nodes.filename, 'edit');
QR.nodes.filename.focus(); QR.nodes.filename.focus();
return; return $.on(QR.nodes.filename, 'blur', function() {
return $.rmClass(QR.nodes.filename, 'edit');
});
} }
if (e.target.nodeName === 'INPUT' || (e.keyCode && ((_ref = e.keyCode) !== 32 && _ref !== 13)) || e.ctrlKey) { if (e.target.nodeName === 'INPUT' || (e.keyCode && ((_ref = e.keyCode) !== 32 && _ref !== 13)) || e.ctrlKey) {
return; return;
@ -9190,7 +9180,7 @@
} }
}, },
dialog: function() { dialog: function() {
var dialog, elm, event, i, items, name, node, nodes, prop, rules, save, setNode, _, _i, _len, _ref, _ref1, _ref2; var dialog, elm, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode;
QR.nodes = nodes = { QR.nodes = nodes = {
el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab class=move><input type=checkbox id=autohide title=Auto-hide><div id=qr-thread-select><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><a href=javascript:; class='close fa' title=Close>\uf00d</a></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1><input name=email data-name=email list=\"list-email\" placeholder=Options class=field size=1><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1> </div><div class=textarea><textarea data-name=com placeholder=Comment class=field></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\">+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=0><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image'></label><span class=description>Spoiler</span><a id=url-button><i class=\"fa\">\uf0c1</i></a><span class=description>Post from URL</span><a id=dump-button title='Dump list'>+</a><span class=description>Dump</span><a id=qr-filerm href=javascript:; title='Remove file' class=fa>\uf00d</a><span class=description>Remove File</span></span></span><input type=submit></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist>") el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab class=move><input type=checkbox id=autohide title=Auto-hide><div id=qr-thread-select><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><a href=javascript:; class='close fa' title=Close>\uf00d</a></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1><input name=email data-name=email list=\"list-email\" placeholder=Options class=field size=1><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1> </div><div class=textarea><textarea data-name=com placeholder=Comment class=field></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\">+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=0><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image'></label><span class=description>Spoiler</span><a id=url-button><i class=\"fa\">\uf0c1</i></a><span class=description>Post from URL</span><a id=dump-button title='Dump list'>+</a><span class=description>Dump</span><a id=qr-filerm href=javascript:; title='Remove file' class=fa>\uf00d</a><span class=description>Remove File</span></span></span><input type=submit></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist>")
}; };
@ -9222,23 +9212,20 @@
setNode('status', '[type=submit]'); setNode('status', '[type=submit]');
setNode('fileInput', '[type=file]'); setNode('fileInput', '[type=file]');
rules = $('ul.rules').textContent.trim(); rules = $('ul.rules').textContent.trim();
QR.min_width = QR.min_height = 1; match_min = rules.match(/.+smaller than (\d+)x(\d+).+/);
QR.max_width = QR.max_height = 10000; match_max = rules.match(/.+greater than (\d+)x(\d+).+/);
try { QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1;
_ref = rules.match(/.+smaller than (\d+)x(\d+).+/), _ = _ref[0], QR.min_width = _ref[1], QR.min_height = _ref[2]; QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1;
_ref1 = rules.match(/.+greater than (\d+)x(\d+).+/), _ = _ref1[0], QR.max_width = _ref1[1], QR.max_height = _ref1[2]; QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000;
_ref2 = ['min_width', 'min_height', 'max_width', 'max_height']; QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
prop = _ref2[_i];
QR[prop] = parseInt(QR[prop], 10);
}
} catch (_error) {
null;
}
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value; nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value;
QR.max_size_video = 3145728; QR.max_size_video = 3145728;
QR.max_width_video = QR.max_height_video = 2048; QR.max_width_video = QR.max_height_video = 2048;
QR.max_duration_video = 120; QR.max_duration_video = 120;
QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]');
if (QR.forcedAnon) {
$.addClass(QR.nodes.el, 'forced-anon');
}
QR.spoiler = !!$('.postForm input[name=spoiler]'); QR.spoiler = !!$('.postForm input[name=spoiler]');
if (QR.spoiler) { if (QR.spoiler) {
$.addClass(QR.nodes.el, 'has-spoiler'); $.addClass(QR.nodes.el, 'has-spoiler');
@ -9249,9 +9236,10 @@
$.after(nodes.name.parentElement, nodes.dumpList.parentElement); $.after(nodes.name.parentElement, nodes.dumpList.parentElement);
nodes.addPost.tabIndex = 35; nodes.addPost.tabIndex = 35;
} }
if (g.BOARD.ID === 'f') { if (g.BOARD.ID === 'f' && g.VIEW !== 'thread') {
nodes.flashTag = $.el('select', { nodes.flashTag = $.el('select', {
name: 'filetag', name: 'filetag'
}, {
innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>" innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>"
}); });
nodes.flashTag.dataset["default"] = '4'; nodes.flashTag.dataset["default"] = '4';
@ -9427,9 +9415,9 @@
post.lock(); post.lock();
formData = { formData = {
resto: threadID, resto: threadID,
name: post.name, name: !QR.forcedAnon ? post.name : void 0,
email: post.email, email: post.email,
sub: post.sub, sub: !(QR.forcedAnon || threadID) ? post.sub : void 0,
com: post.com, com: post.com,
upfile: post.file, upfile: post.file,
filetag: filetag, filetag: filetag,
@ -9516,9 +9504,6 @@
} }
QR.status(); QR.status();
QR.error(err); QR.error(err);
if (QR.captcha.isEnabled) {
QR.captcha.setup();
}
return; return;
} }
h1 = $('h1', resDoc); h1 = $('h1', resDoc);
@ -9569,7 +9554,7 @@
QR.close(); QR.close();
} else { } else {
post.rm(); post.rm();
QR.captcha.setup(); QR.captcha.setup(true);
} }
QR.cooldown.set({ QR.cooldown.set({
req: req, req: req,
@ -9621,7 +9606,7 @@
QR.captcha = { QR.captcha = {
init: function() { init: function() {
var container, counter, section; var counter, root;
if (d.cookie.indexOf('pass_enabled=1') >= 0) { if (d.cookie.indexOf('pass_enabled=1') >= 0) {
return; return;
} }
@ -9635,80 +9620,101 @@
return QR.captcha.sync(captchas); return QR.captcha.sync(captchas);
}); });
$.sync('captchas', this.sync.bind(this)); $.sync('captchas', this.sync.bind(this));
section = $.el('div', { root = $.el('div', {
className: 'captcha-section' className: 'captcha-root'
}); });
$.extend(section, { $.extend(root, {
innerHTML: "<div class=\"captcha-container\"></div><div class=\"captcha-counter\"><a href=\"javascript:;\"></a></div>" innerHTML: "<div class=\"captcha-counter\"><a href=\"javascript:;\"></a></div>"
}); });
container = $('.captcha-container', section); counter = $('.captcha-counter > a', root);
counter = $('.captcha-counter > a', section);
this.nodes = { this.nodes = {
container: container, root: root,
counter: counter counter: counter
}; };
this.count(); this.count();
$.addClass(QR.nodes.el, 'has-captcha'); $.addClass(QR.nodes.el, 'has-captcha');
$.after(QR.nodes.com.parentNode, section); $.after(QR.nodes.com.parentNode, root);
new MutationObserver(this.afterSetup.bind(this)).observe(container, {
childList: true,
subtree: true
});
$.on(counter, 'click', this.toggle.bind(this)); $.on(counter, 'click', this.toggle.bind(this));
return $.on(window, 'captcha:success', this.save.bind(this)); return $.on(window, 'captcha:success', (function(_this) {
return function() {
return _this.save(false);
};
})(this));
}, },
shouldFocus: false, shouldFocus: false,
timeouts: {}, timeouts: {},
postsCount: 0,
needed: function() { needed: function() {
var captchaCount, postsCount; var captchaCount;
captchaCount = this.captchas.length; captchaCount = this.captchas.length;
if (this.nodes.container.dataset.widgetID && !this.timeouts.destroy) { if (this.nodes.container && !this.timeouts.destroy) {
captchaCount++; captchaCount++;
} }
postsCount = QR.posts.length; this.postsCount = QR.posts.length;
if (postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) {
postsCount = 0; this.postsCount = 0;
}
return captchaCount < this.postsCount;
},
onPostChange: function() {
if (this.postsCount === 0) {
this.setup();
}
if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) {
return this.postsCount = 0;
} }
return captchaCount < postsCount;
}, },
toggle: function() { toggle: function() {
if (this.nodes.container.dataset.widgetID && !this.timeouts.destroy) { if (this.nodes.container && !this.timeouts.destroy) {
return this.destroy(); return this.destroy();
} else { } else {
this.shouldFocus = true; return this.setup(true, true);
return this.setup(true);
} }
}, },
setup: function(force) { setup: function(focus, force) {
if (!(this.isEnabled && (this.needed() || force))) { if (!(this.isEnabled && (this.needed() || force))) {
return; return;
} }
$.addClass(QR.nodes.el, 'captcha-open'); $.addClass(QR.nodes.el, 'captcha-open');
if (focus) {
this.shouldFocus = true;
}
if (this.timeouts.destroy) { if (this.timeouts.destroy) {
clearTimeout(this.timeouts.destroy); clearTimeout(this.timeouts.destroy);
delete this.timeouts.destroy; delete this.timeouts.destroy;
return this.reload(); return this.reload();
} }
if (this.nodes.container.dataset.widgetID) { if (this.nodes.container) {
return; return;
} }
return $.globalEval('(function() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n})();'); this.nodes.container = $.el('div', {
className: 'captcha-container'
});
$.prepend(this.nodes.root, this.nodes.container);
new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, {
childList: true,
subtree: true
});
return $.globalEval('(function() {\n function render() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n }\n if (window.grecaptcha) {\n render();\n } else {\n var cbNative = window.onRecaptchaLoaded;\n window.onRecaptchaLoaded = function() {\n render();\n cbNative();\n }\n }\n})();');
}, },
afterSetup: function(mutations) { afterSetup: function(mutations) {
var iframe, mutation, node, _i, _j, _len, _len1, _ref; var iframe, mutation, node, textarea, _i, _j, _len, _len1, _ref;
for (_i = 0, _len = mutations.length; _i < _len; _i++) { for (_i = 0, _len = mutations.length; _i < _len; _i++) {
mutation = mutations[_i]; mutation = mutations[_i];
_ref = mutation.addedNodes; _ref = mutation.addedNodes;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
node = _ref[_j]; node = _ref[_j];
if (node.nodeName === 'IFRAME') { if (iframe = $.x('./descendant-or-self::iframe', node)) {
iframe = node; this.setupIFrame(iframe);
}
if (textarea = $.x('./descendant-or-self::textarea', node)) {
this.setupTextArea(textarea);
} }
} }
} }
if (!iframe) { },
return; setupIFrame: function(iframe) {
} this.setupTime = Date.now();
if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) {
QR.nodes.el.style.top = null; QR.nodes.el.style.top = null;
QR.nodes.el.style.bottom = '0px'; QR.nodes.el.style.bottom = '0px';
@ -9718,16 +9724,28 @@
} }
return this.shouldFocus = false; return this.shouldFocus = false;
}, },
setupTextArea: function(textarea) {
return $.one(textarea, 'input', (function(_this) {
return function() {
return _this.save(true);
};
})(this));
},
destroy: function() { destroy: function() {
if (!this.isEnabled) { if (!this.isEnabled) {
return; return;
} }
delete this.timeouts.destroy; delete this.timeouts.destroy;
$.rmClass(QR.nodes.el, 'captcha-open'); $.rmClass(QR.nodes.el, 'captcha-open');
$.rmAll(this.nodes.container); if (this.nodes.container) {
return this.nodes.container.removeAttribute('data-widget-i-d'); $.rm(this.nodes.container);
}
return delete this.nodes.container;
}, },
sync: function(captchas) { sync: function(captchas) {
if (captchas == null) {
captchas = [];
}
this.captchas = captchas; this.captchas = captchas;
this.clear(); this.clear();
return this.count(); return this.count();
@ -9743,30 +9761,39 @@
return null; return null;
} }
}, },
save: function(e) { save: function(pasted) {
var _base; var reload, _base;
if (this.needed()) { $.forceSync('captchas');
reload = (QR.cooldown.auto || Conf['Post on Captcha Completion']) && this.needed();
this.captchas.push({
response: $('textarea', this.nodes.container).value,
timeout: (pasted ? this.setupTime : Date.now()) + 2 * $.MINUTE
});
this.count();
$.set('captchas', this.captchas);
if (reload) {
this.shouldFocus = true; this.shouldFocus = true;
this.reload(); this.reload();
} else { } else {
this.nodes.counter.focus(); if (pasted) {
if ((_base = this.timeouts).destroy == null) { this.destroy();
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); } else {
if ((_base = this.timeouts).destroy == null) {
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
}
} }
QR.nodes.status.focus();
}
if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) {
return QR.submit();
} }
$.forceSync('captchas');
this.captchas.push({
response: e.detail,
timeout: Date.now() + 2 * $.MINUTE
});
this.count();
return $.set('captchas', this.captchas);
}, },
clear: function() { clear: function() {
var captcha, i, now, _i, _len, _ref; var captcha, i, now, _i, _len, _ref;
if (!this.captchas.length) { if (!this.captchas.length) {
return; return;
} }
$.forceSync('captchas');
now = Date.now(); now = Date.now();
_ref = this.captchas; _ref = this.captchas;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
@ -9781,7 +9808,7 @@
this.captchas = this.captchas.slice(i); this.captchas = this.captchas.slice(i);
this.count(); this.count();
$.set('captchas', this.captchas); $.set('captchas', this.captchas);
return this.setup(); return this.setup(true);
}, },
count: function() { count: function() {
this.nodes.counter.textContent = "Captchas: " + this.captchas.length; this.nodes.counter.textContent = "Captchas: " + this.captchas.length;
@ -10018,7 +10045,7 @@
QR.post = (function() { QR.post = (function() {
function _Class(select) { function _Class(select) {
this.select = __bind(this.select, this); this.select = __bind(this.select, this);
var el, event, prev, _i, _len, _ref, _ref1; var el, event, prev, _i, _len, _ref;
el = $.el('a', { el = $.el('a', {
className: 'qr-preview', className: 'qr-preview',
draggable: true, draggable: true,
@ -10059,9 +10086,8 @@
$.on(el, event.toLowerCase(), this[event]); $.on(el, event.toLowerCase(), this[event]);
} }
this.thread = g.VIEW === 'thread' ? g.THREADID : 'new'; this.thread = g.VIEW === 'thread' ? g.THREADID : 'new';
_ref1 = QR.posts, prev = _ref1[_ref1.length - 1]; prev = QR.posts[QR.posts.length - 1];
QR.posts.push(this); QR.posts.push(this);
QR.captcha.setup();
this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false; this.nodes.spoiler.checked = this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false;
QR.persona.get((function(_this) { QR.persona.get((function(_this) {
return function(persona) { return function(persona) {
@ -10080,6 +10106,9 @@
this.select(); this.select();
} }
this.unlock(); this.unlock();
$.queueTask(function() {
return QR.captcha.setup();
});
} }
_Class.prototype.rm = function() { _Class.prototype.rm = function() {
@ -10170,7 +10199,7 @@
return QR.status(); return QR.status();
case 'com': case 'com':
this.nodes.span.textContent = this.com; this.nodes.span.textContent = this.com;
QR.captcha.setup(); QR.captcha.onPostChange();
QR.characterCount(); QR.characterCount();
if (QR.cooldown.auto && this === QR.posts[0] && (0 < (_ref = QR.cooldown.seconds) && _ref <= 5)) { if (QR.cooldown.auto && this === QR.posts[0] && (0 < (_ref = QR.cooldown.seconds) && _ref <= 5)) {
return QR.cooldown.auto = false; return QR.cooldown.auto = false;
@ -10210,7 +10239,7 @@
if (QR.spoiler) { if (QR.spoiler) {
this.nodes.label.hidden = false; this.nodes.label.hidden = false;
} }
QR.captcha.setup(); QR.captcha.onPostChange();
URL.revokeObjectURL(this.URL); URL.revokeObjectURL(this.URL);
if (this === QR.selected) { if (this === QR.selected) {
this.showFileData(); this.showFileData();
@ -10228,55 +10257,55 @@
var el, fileURL, isVideo; var el, fileURL, isVideo;
isVideo = /^video\//.test(this.file.type); isVideo = /^video\//.test(this.file.type);
el = $.el((isVideo ? 'video' : 'img')); el = $.el((isVideo ? 'video' : 'img'));
$.on(el, (isVideo ? 'loadeddata' : 'load'), (function(_this) { $.on(el, (isVideo ? 'loadeddata' : 'load'), function() {
return function() { var cv, error, errors, height, s, width, _i, _len;
var cv, error, errors, height, s, width, _i, _len; errors = this.checkDimensions(el, isVideo);
errors = _this.checkDimensions(el, isVideo); if (errors.length) {
if (errors.length) { for (_i = 0, _len = errors.length; _i < _len; _i++) {
for (_i = 0, _len = errors.length; _i < _len; _i++) { error = errors[_i];
error = errors[_i]; QR.error(error);
QR.error(error);
}
_this.URL = fileURL;
if ((QR.posts.length === 1) || (_this.com && _this.com.length)) {
return _this.rmFile();
} else {
return _this.rm();
}
} }
s = 90 * 2 * window.devicePixelRatio; this.URL = fileURL;
if (_this.file.type === 'image/gif') { if ((QR.posts.length === 1) || (this.com && this.com.length)) {
s *= 3; return this.rmFile();
}
if (isVideo) {
height = el.videoHeight;
width = el.videoWidth;
} else { } else {
height = el.height, width = el.width; return this.rm();
if (height < s || width < s) {
_this.URL = fileURL;
_this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")";
return;
}
} }
if (height <= width) { }
width = s / height * width; s = 90 * 2 * window.devicePixelRatio;
height = s; if (this.file.type === 'image/gif') {
} else { s *= 3;
height = s / width * height; }
width = s; if (isVideo) {
height = el.videoHeight;
width = el.videoWidth;
} else {
height = el.height, width = el.width;
if (height < s || width < s) {
this.URL = fileURL;
this.nodes.el.style.backgroundImage = "url(" + this.URL + ")";
return;
} }
cv = $.el('canvas'); }
cv.height = el.height = height; if (height <= width) {
cv.width = el.width = width; width = s / height * width;
cv.getContext('2d').drawImage(el, 0, 0, width, height); height = s;
URL.revokeObjectURL(fileURL); } else {
return cv.toBlob(function(blob) { height = s / width * height;
width = s;
}
cv = $.el('canvas');
cv.height = el.height = height;
cv.width = el.width = width;
cv.getContext('2d').drawImage(el, 0, 0, width, height);
URL.revokeObjectURL(fileURL);
return cv.toBlob((function(_this) {
return function(blob) {
_this.URL = URL.createObjectURL(blob); _this.URL = URL.createObjectURL(blob);
return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")"; return _this.nodes.el.style.backgroundImage = "url(" + _this.URL + ")";
}); };
}; })(this));
})(this)); });
fileURL = URL.createObjectURL(this.file); fileURL = URL.createObjectURL(this.file);
return el.src = fileURL; return el.src = fileURL;
}; };
@ -10331,7 +10360,7 @@
_Class.prototype.updateFilename = function() { _Class.prototype.updateFilename = function() {
var title; var title;
title = "" + this.filename + " (" + this.filesize + ")\nCtrl+click to edit filename. Shift+click to clear."; title = "" + this.filename + " (" + this.filesize + ")\nCtrl/\u2318+click to edit filename. Shift+click to clear.";
this.nodes.el.title = title; this.nodes.el.title = title;
if (this !== QR.selected) { if (this !== QR.selected) {
return; return;

View File

@ -0,0 +1,99 @@
CrossOrigin = do ->
<% if (type === 'crx') { %>
eventPageRequest = do ->
callbacks = []
chrome.runtime.onMessage.addListener (data) ->
callbacks[data.id] data
delete callbacks[data.id]
(url, responseType, cb) ->
chrome.runtime.sendMessage {url, responseType}, (id) ->
callbacks[id] = cb
<% } %>
file: do ->
makeBlob = (urlBlob, contentType, contentDisposition, url) ->
name = url.match(/([^\/]+)\/*$/)?[1]
mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream'
match =
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1]
if match
name = match.replace /\\"/g, '"'
blob = new Blob([urlBlob], {type: mime})
blob.name = name
blob
(url, cb) ->
<% if (type === 'crx') { %>
if /^https:\/\//.test(url) or location.protocol is 'http:'
$.ajax url,
responseType: 'blob'
onload: ->
return cb null unless @readyState is @DONE and @status is 200
contentType = @getResponseHeader 'Content-Type'
contentDisposition = @getResponseHeader 'Content-Disposition'
cb (makeBlob @response, contentType, contentDisposition, url)
onerror: ->
cb null
else
eventPageRequest url, 'arraybuffer', ({response, contentType, contentDisposition, error}) ->
return cb null if error
cb (makeBlob new Uint8Array(response), contentType, contentDisposition, url)
<% } %>
<% if (type === 'userscript') { %>
GM_xmlhttpRequest
method: "GET"
url: url
overrideMimeType: "text/plain; charset=x-user-defined"
onload: (xhr) ->
r = xhr.responseText
data = new Uint8Array r.length
i = 0
while i < r.length
data[i] = r.charCodeAt i
i++
contentType = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)?[1]
contentDisposition = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)?[1]
cb (makeBlob data, contentType, contentDisposition, url)
onerror: ->
cb null
<% } %>
json: do ->
callbacks = {}
responses = {}
(url, cb) ->
<% if (type === 'crx') { %>
if /^https:\/\//.test(url) or location.protocol is 'http:'
return $.cache url, (-> cb @response), responseType: 'json'
<% } %>
if responses[url]
cb responses[url]
return
if callbacks[url]
callbacks[url].push cb
return
callbacks[url] = [cb]
<% if (type === 'userscript') { %>
GM_xmlhttpRequest
method: "GET"
url: url+''
onload: (xhr) ->
response = JSON.parse xhr.responseText
cb response for cb in callbacks[url]
delete callbacks[url]
responses[url] = response
onerror: ->
delete callbacks[url]
onabort: ->
delete callbacks[url]
<% } %>
<% if (type === 'crx') { %>
eventPageRequest url, 'json', ({response, error}) ->
if error
delete callbacks[url]
else
cb response for cb in callbacks[url]
delete callbacks[url]
responses[url] = response
<% } %>

View File

@ -8,83 +8,108 @@ QR.captcha =
QR.captcha.sync captchas QR.captcha.sync captchas
$.sync 'captchas', @sync.bind @ $.sync 'captchas', @sync.bind @
section = $.el 'div', className: 'captcha-section' root = $.el 'div', className: 'captcha-root'
$.extend section, <%= html( $.extend root, <%= html(
'<div class="captcha-container"></div>' +
'<div class="captcha-counter"><a href="javascript:;"></a></div>' '<div class="captcha-counter"><a href="javascript:;"></a></div>'
) %> ) %>
container = $ '.captcha-container', section counter = $ '.captcha-counter > a', root
counter = $ '.captcha-counter > a', section @nodes = {root, counter}
@nodes = {container, counter}
@count() @count()
$.addClass QR.nodes.el, 'has-captcha' $.addClass QR.nodes.el, 'has-captcha'
$.after QR.nodes.com.parentNode, section $.after QR.nodes.com.parentNode, root
new MutationObserver(@afterSetup.bind @).observe container,
childList: true
subtree: true
$.on counter, 'click', @toggle.bind @ $.on counter, 'click', @toggle.bind @
$.on window, 'captcha:success', @save.bind @ $.on window, 'captcha:success', => @save false
shouldFocus: false shouldFocus: false
timeouts: {} timeouts: {}
postsCount: 0
needed: -> needed: ->
captchaCount = @captchas.length captchaCount = @captchas.length
captchaCount++ if @nodes.container.dataset.widgetID and !@timeouts.destroy captchaCount++ if @nodes.container and !@timeouts.destroy
postsCount = QR.posts.length @postsCount = QR.posts.length
postsCount = 0 if postsCount is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file @postsCount = 0 if @postsCount is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file
captchaCount < postsCount captchaCount < @postsCount
onPostChange: ->
@setup() if @postsCount is 0
@postsCount = 0 if QR.posts.length is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file
toggle: -> toggle: ->
if @nodes.container.dataset.widgetID and !@timeouts.destroy if @nodes.container and !@timeouts.destroy
@destroy() @destroy()
else else
@shouldFocus = true @setup true, true
@setup true
setup: (force) -> setup: (focus, force) ->
return unless @isEnabled and (@needed() or force) return unless @isEnabled and (@needed() or force)
$.addClass QR.nodes.el, 'captcha-open' $.addClass QR.nodes.el, 'captcha-open'
@shouldFocus = true if focus
if @timeouts.destroy if @timeouts.destroy
clearTimeout @timeouts.destroy clearTimeout @timeouts.destroy
delete @timeouts.destroy delete @timeouts.destroy
return @reload() return @reload()
return if @nodes.container.dataset.widgetID
return if @nodes.container
@nodes.container = $.el 'div', className: 'captcha-container'
$.prepend @nodes.root, @nodes.container
new MutationObserver(@afterSetup.bind @).observe @nodes.container,
childList: true
subtree: true
$.globalEval ''' $.globalEval '''
(function() { (function() {
var container = document.querySelector("#qr .captcha-container"); function render() {
container.dataset.widgetID = window.grecaptcha.render(container, { var container = document.querySelector("#qr .captcha-container");
sitekey: '<%= meta.recaptchaKey %>', container.dataset.widgetID = window.grecaptcha.render(container, {
theme: document.documentElement.classList.contains('tomorrow') ? 'dark' : 'light', sitekey: '<%= meta.recaptchaKey %>',
callback: function(response) { theme: document.documentElement.classList.contains('tomorrow') ? 'dark' : 'light',
window.dispatchEvent(new CustomEvent("captcha:success", {detail: response})); callback: function(response) {
window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));
}
});
}
if (window.grecaptcha) {
render();
} else {
var cbNative = window.onRecaptchaLoaded;
window.onRecaptchaLoaded = function() {
render();
cbNative();
} }
}); }
})(); })();
''' '''
afterSetup: (mutations) -> afterSetup: (mutations) ->
for mutation in mutations for mutation in mutations
for node in mutation.addedNodes for node in mutation.addedNodes
iframe = node if node.nodeName is 'IFRAME' @setupIFrame iframe if iframe = $.x './descendant-or-self::iframe', node
return unless iframe @setupTextArea textarea if textarea = $.x './descendant-or-self::textarea', node
return
setupIFrame: (iframe) ->
@setupTime = Date.now()
if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight
QR.nodes.el.style.top = null QR.nodes.el.style.top = null
QR.nodes.el.style.bottom = '0px' QR.nodes.el.style.bottom = '0px'
iframe.focus() if @shouldFocus iframe.focus() if @shouldFocus
@shouldFocus = false @shouldFocus = false
setupTextArea: (textarea) ->
$.one textarea, 'input', => @save true
destroy: -> destroy: ->
return unless @isEnabled return unless @isEnabled
delete @timeouts.destroy delete @timeouts.destroy
$.rmClass QR.nodes.el, 'captcha-open' $.rmClass QR.nodes.el, 'captcha-open'
$.rmAll @nodes.container $.rm @nodes.container if @nodes.container
# XXX https://github.com/greasemonkey/greasemonkey/issues/1571 delete @nodes.container
@nodes.container.removeAttribute 'data-widget-i-d'
sync: (captchas) -> sync: (captchas=[]) ->
@captchas = captchas @captchas = captchas
@clear() @clear()
@count() @count()
@ -98,22 +123,30 @@ QR.captcha =
else else
null null
save: (e) -> save: (pasted) ->
if @needed()
@shouldFocus = true
@reload()
else
@nodes.counter.focus()
@timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND
$.forceSync 'captchas' $.forceSync 'captchas'
reload = (QR.cooldown.auto or Conf['Post on Captcha Completion']) and @needed()
@captchas.push @captchas.push
response: e.detail response: $('textarea', @nodes.container).value
timeout: Date.now() + 2 * $.MINUTE timeout: (if pasted then @setupTime else Date.now()) + 2 * $.MINUTE
@count() @count()
$.set 'captchas', @captchas $.set 'captchas', @captchas
if reload
@shouldFocus = true
@reload()
else
if pasted
@destroy()
else
@timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND
QR.nodes.status.focus()
QR.submit() if Conf['Post on Captcha Completion'] and !QR.cooldown.auto
clear: -> clear: ->
return unless @captchas.length return unless @captchas.length
$.forceSync 'captchas'
now = Date.now() now = Date.now()
for captcha, i in @captchas for captcha, i in @captchas
break if captcha.timeout > now break if captcha.timeout > now
@ -121,7 +154,7 @@ QR.captcha =
@captchas = @captchas[i..] @captchas = @captchas[i..]
@count() @count()
$.set 'captchas', @captchas $.set 'captchas', @captchas
@setup() @setup true
count: -> count: ->
@nodes.counter.textContent = "Captchas: #{@captchas.length}" @nodes.counter.textContent = "Captchas: #{@captchas.length}"

View File

@ -108,6 +108,7 @@ QR =
QR.captcha.destroy() QR.captcha.destroy()
focusin: -> focusin: ->
QR.captcha.setup() if $.hasClass(QR.nodes.el, 'autohide') and !$.hasClass(QR.nodes.el, 'focus')
$.addClass QR.nodes.el, 'focus' $.addClass QR.nodes.el, 'focus'
focusout: -> focusout: ->
@ -136,7 +137,7 @@ QR =
el = err el = err
el.removeAttribute 'style' el.removeAttribute 'style'
if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent
QR.captcha.setup() QR.captcha.setup true
QR.notify el QR.notify el
alert el.textContent if d.hidden alert el.textContent if d.hidden
@ -270,6 +271,7 @@ QR =
QR.handleFiles e.dataTransfer.files QR.handleFiles e.dataTransfer.files
paste: (e) -> paste: (e) ->
return unless e.clipboardData.items
files = [] files = []
for item in e.clipboardData.items when item.kind is 'file' for item in e.clipboardData.items when item.kind is 'file'
blob = item.getAsFile() blob = item.getAsFile()
@ -280,66 +282,15 @@ QR =
QR.open() QR.open()
QR.handleFiles files QR.handleFiles files
$.addClass QR.nodes.el, 'dump' $.addClass QR.nodes.el, 'dump'
handleBlob: (urlBlob, contentType, contentDisposition, url) ->
name = url.match(/([^\/]+)\/*$/)?[1]
mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream'
match =
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1]
if match
name = match.replace /\\"/g, '"'
blob = new Blob([urlBlob], {type: mime})
blob.name = name
QR.handleFiles([blob])
handleUrl: -> handleUrl: ->
url = prompt("Enter a URL:") url = prompt 'Enter a URL:'
return if url is null return if url is null
CrossOrigin.file url, (blob) ->
<% if (type === 'crx') { %> if blob
xhr = new XMLHttpRequest(); QR.handleFiles([blob])
xhr.open('GET', url, true) else
xhr.responseType = 'blob'
xhr.onload = (e) ->
return QR.error "Can't load image." unless @readyState is @DONE and xhr.status is 200
contentType = @getResponseHeader('Content-Type')
contentDisposition = @getResponseHeader('Content-Disposition')
QR.handleBlob @response, contentType, contentDisposition, url
xhr.onerror = (e) ->
QR.error "Can't load image."
xhr.send()
<% } else { %>
GM_xmlhttpRequest
method: "GET"
url: url
# FIXME: responseType: 'blob'
# Could do it now, but don't wanna kill off legacy GM versions yet
overrideMimeType: "text/plain; charset=x-user-defined"
onload: (xhr) ->
r = xhr.responseText
h = xhr.responseHeaders
data = new Uint8Array r.length
i = 0
while i < r.length
data[i] = r.charCodeAt i
i++
contentType = (h.match(/Content-Type:\s*(.*)/i) or [])[1]
contentDisposition = (h.match(/Content-Disposition:\s*(.*)/i) or [])[1]
QR.handleBlob data, contentType, contentDisposition, url
onerror: (xhr) ->
QR.error "Can't load image." QR.error "Can't load image."
<% } %>
handleFiles: (files) -> handleFiles: (files) ->
if @ isnt QR # file input if @ isnt QR # file input
files = [@files...] files = [@files...]
@ -355,7 +306,7 @@ QR =
if /^text\//.test file.type if /^text\//.test file.type
if isSingle if isSingle
post = QR.selected post = QR.selected
else if (post = QR.posts[QR.posts.length - 1]).com else if index isnt 0 or (post = QR.posts[QR.posts.length - 1]).com
post = new QR.post() post = new QR.post()
post.pasteText file post.pasteText file
return return
@ -371,16 +322,19 @@ QR =
post = QR.selected post = QR.selected
else if (post = QR.posts[QR.posts.length - 1]).file else if (post = QR.posts[QR.posts.length - 1]).file
post = new QR.post() post = new QR.post()
post.setFile file try
post.setFile file
catch err
console.log err
openFileInput: (e) -> openFileInput: (e) ->
e.stopPropagation() e.stopPropagation()
if e.shiftKey and e.type is 'click' if e.shiftKey and e.type is 'click'
return QR.selected.rmFile() return QR.selected.rmFile()
if e.ctrlKey and e.type is 'click' if (e.ctrlKey or e.metaKey) and e.type is 'click'
$.addClass QR.nodes.filename, 'edit' $.addClass QR.nodes.filename, 'edit'
QR.nodes.filename.focus() QR.nodes.filename.focus()
return return $.on QR.nodes.filename, 'blur', -> $.rmClass QR.nodes.filename, 'edit'
return if e.target.nodeName is 'INPUT' or (e.keyCode and e.keyCode not in [32, 13]) or e.ctrlKey return if e.target.nodeName is 'INPUT' or (e.keyCode and e.keyCode not in [32, 13]) or e.ctrlKey
e.preventDefault() e.preventDefault()
QR.nodes.fileInput.click() QR.nodes.fileInput.click()
@ -439,15 +393,13 @@ QR =
setNode 'fileInput', '[type=file]' setNode 'fileInput', '[type=file]'
rules = $('ul.rules').textContent.trim() rules = $('ul.rules').textContent.trim()
QR.min_width = QR.min_height = 1
QR.max_width = QR.max_height = 10000 match_min = rules.match(/.+smaller than (\d+)x(\d+).+/)
try match_max = rules.match(/.+greater than (\d+)x(\d+).+/)
[_, QR.min_width, QR.min_height] = rules.match(/.+smaller than (\d+)x(\d+).+/) QR.min_width = +match_min?[1] or 1
[_, QR.max_width, QR.max_height] = rules.match(/.+greater than (\d+)x(\d+).+/) QR.min_height = +match_min?[2] or 1
for prop in ['min_width', 'min_height', 'max_width', 'max_height'] QR.max_width = +match_max?[1] or 10000
QR[prop] = parseInt QR[prop], 10 QR.max_height = +match_max?[2] or 10000
catch
null
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
@ -455,6 +407,10 @@ QR =
QR.max_width_video = QR.max_height_video = 2048 QR.max_width_video = QR.max_height_video = 2048
QR.max_duration_video = 120 QR.max_duration_video = 120
QR.forcedAnon = !!$ 'form[name="post"] input[name="name"][type="hidden"]'
if QR.forcedAnon
$.addClass QR.nodes.el, 'forced-anon'
QR.spoiler = !!$ '.postForm input[name=spoiler]' QR.spoiler = !!$ '.postForm input[name=spoiler]'
if QR.spoiler if QR.spoiler
$.addClass QR.nodes.el, 'has-spoiler' $.addClass QR.nodes.el, 'has-spoiler'
@ -465,9 +421,8 @@ QR =
$.after nodes.name.parentElement, nodes.dumpList.parentElement $.after nodes.name.parentElement, nodes.dumpList.parentElement
nodes.addPost.tabIndex = 35 nodes.addPost.tabIndex = 35
if g.BOARD.ID is 'f' if g.BOARD.ID is 'f' and g.VIEW isnt 'thread'
nodes.flashTag = $.el 'select', nodes.flashTag = $.el 'select', name: 'filetag',
name: 'filetag'
innerHTML: """ innerHTML: """
<option value=0>Hentai</option> <option value=0>Hentai</option>
<option value=6>Porn</option> <option value=6>Porn</option>
@ -660,9 +615,11 @@ QR =
formData = formData =
resto: threadID resto: threadID
name: post.name
name: post.name unless QR.forcedAnon
email: post.email email: post.email
sub: post.sub
sub: post.sub unless QR.forcedAnon or threadID
com: post.com com: post.com
upfile: post.file upfile: post.file
filetag: filetag filetag: filetag
@ -766,7 +723,6 @@ QR =
QR.cooldown.auto = false QR.cooldown.auto = false
QR.status() QR.status()
QR.error err QR.error err
QR.captcha.setup() if QR.captcha.isEnabled
return return
h1 = $ 'h1', resDoc h1 = $ 'h1', resDoc
@ -817,7 +773,7 @@ QR =
QR.close() QR.close()
else else
post.rm() post.rm()
QR.captcha.setup() QR.captcha.setup true
QR.cooldown.set {req, post, isReply, threadID} QR.cooldown.set {req, post, isReply, threadID}

View File

@ -35,9 +35,8 @@ QR.post = class
else else
'new' 'new'
[..., prev] = QR.posts prev = QR.posts[QR.posts.length - 1]
QR.posts.push @ QR.posts.push @
QR.captcha.setup()
@nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler'] @nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler']
prev.spoiler prev.spoiler
else else
@ -72,6 +71,8 @@ QR.post = class
@load() if QR.selected is @ # load persona @load() if QR.selected is @ # load persona
@select() if select @select() if select
@unlock() @unlock()
# Post count temporarily off by 1 when called from QR.post.rm
$.queueTask -> QR.captcha.setup()
rm: -> rm: ->
@delete() @delete()
@ -136,7 +137,7 @@ QR.post = class
QR.status() QR.status()
when 'com' when 'com'
@nodes.span.textContent = @com @nodes.span.textContent = @com
QR.captcha.setup() QR.captcha.onPostChange()
QR.characterCount() QR.characterCount()
# Disable auto-posting if you're typing in the first post # Disable auto-posting if you're typing in the first post
# during the last 5 seconds of the cooldown. # during the last 5 seconds of the cooldown.
@ -165,7 +166,7 @@ QR.post = class
@filename = file.name @filename = file.name
@filesize = $.bytesToString file.size @filesize = $.bytesToString file.size
@nodes.label.hidden = false if QR.spoiler @nodes.label.hidden = false if QR.spoiler
QR.captcha.setup() QR.captcha.onPostChange()
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
if @ is QR.selected if @ is QR.selected
@showFileData() @showFileData()
@ -181,7 +182,7 @@ QR.post = class
isVideo = /^video\//.test @file.type isVideo = /^video\//.test @file.type
el = $.el (if isVideo then 'video' else 'img') el = $.el (if isVideo then 'video' else 'img')
$.on el, (if isVideo then 'loadeddata' else 'load'), => $.on el, (if isVideo then 'loadeddata' else 'load'), ->
# Verify element dimensions. # Verify element dimensions.
errors = @checkDimensions el, isVideo errors = @checkDimensions el, isVideo
if errors.length if errors.length
@ -195,6 +196,7 @@ QR.post = class
# to avoid crappy resized quality. # to avoid crappy resized quality.
s = 90 * 2 * window.devicePixelRatio s = 90 * 2 * window.devicePixelRatio
s *= 3 if @file.type is 'image/gif' # let them animate s *= 3 if @file.type is 'image/gif' # let them animate
if isVideo if isVideo
height = el.videoHeight height = el.videoHeight
width = el.videoWidth width = el.videoWidth
@ -204,12 +206,14 @@ QR.post = class
@URL = fileURL @URL = fileURL
@nodes.el.style.backgroundImage = "url(#{@URL})" @nodes.el.style.backgroundImage = "url(#{@URL})"
return return
if height <= width if height <= width
width = s / height * width width = s / height * width
height = s height = s
else else
height = s / width * height height = s / width * height
width = s width = s
cv = $.el 'canvas' cv = $.el 'canvas'
cv.height = el.height = height cv.height = el.height = height
cv.width = el.width = width cv.width = el.width = width
@ -226,8 +230,14 @@ QR.post = class
err = [] err = []
if video if video
{videoHeight, videoWidth, duration} = el {videoHeight, videoWidth, duration} = el
max_height = if QR.max_height < QR.max_height_video then QR.max_height else QR.max_height_video max_height = if QR.max_height < QR.max_height_video
max_width = if QR.max_width < QR.max_width_video then QR.max_width else QR.max_width_video QR.max_height
else
QR.max_height_video
max_width = if QR.max_width < QR.max_width_video
QR.max_width
else
QR.max_width_video
if videoHeight > max_height or videoWidth > max_width if videoHeight > max_height or videoWidth > max_width
err.push "#{@file.name}: Video too large (video: #{videoHeight}x#{videoWidth}px, max: #{max_height}x#{max_width}px)" err.push "#{@file.name}: Video too large (video: #{videoHeight}x#{videoWidth}px, max: #{max_height}x#{max_width}px)"
if videoHeight < QR.min_height or videoWidth < QR.min_width if videoHeight < QR.min_height or videoWidth < QR.min_width
@ -261,7 +271,7 @@ QR.post = class
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
updateFilename: -> updateFilename: ->
title = "#{@filename} (#{@filesize})\nCtrl+click to edit filename. Shift+click to clear." title = "#{@filename} (#{@filesize})\nCtrl/\u2318+click to edit filename. Shift+click to clear."
@nodes.el.title = title @nodes.el.title = title
return unless @ is QR.selected return unless @ is QR.selected
QR.nodes.fileContainer.title = title QR.nodes.fileContainer.title = title