diff --git a/CHANGELOG.md b/CHANGELOG.md
index db3114f7e..953f9c49f 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+### v1.7.52
+*2014-06-02*
+
+**ccd0**
+- Add workaround for downloading with the original filename in Firefox.
+
### v1.7.51
*2014-06-02*
diff --git a/Gruntfile.coffee b/Gruntfile.coffee
index 62aa4eaf4..9fcd25d93 100755
--- a/Gruntfile.coffee
+++ b/Gruntfile.coffee
@@ -27,6 +27,7 @@ module.exports = (grunt) ->
'src/General/Get.coffee'
'src/General/UI.coffee'
'src/General/Notice.coffee'
+ 'src/General/CrossOrigin.coffee'
'src/Filtering/**/*.coffee'
'src/Quotelinks/**/*.coffee'
'src/Posting/QR.coffee'
diff --git a/LICENSE b/LICENSE
index 3081293f9..cac8eb81a 100755
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,5 @@
/*
-* 4chan X - Version 1.7.51 - 2014-06-02
+* 4chan X - Version 1.7.52 - 2014-06-02
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js
index 404368fc2..3515df658 100755
--- a/builds/4chan-X.meta.js
+++ b/builds/4chan-X.meta.js
@@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
-// @version 1.7.51
+// @version 1.7.52
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js
index 4f5c93cf4..d80b1a06c 100644
--- a/builds/4chan-X.user.js
+++ b/builds/4chan-X.user.js
@@ -1,7 +1,7 @@
// Generated by CoffeeScript
// ==UserScript==
// @name 4chan X
-// @version 1.7.51
+// @version 1.7.52
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@@ -24,7 +24,7 @@
// ==/UserScript==
/*
-* 4chan X - Version 1.7.51 - 2014-06-02
+* 4chan X - Version 1.7.52 - 2014-06-02
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
@@ -106,7 +106,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, g,
+ var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, g,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
@@ -194,6 +194,7 @@
'Thread Hiding Link': [true, 'Add a link to hide entire threads.'],
'Reply Hiding Link': [true, 'Add a link to hide single replies.'],
'Delete Link': [true, 'Add post and image deletion links to the menu.'],
+ 'Download Link': [true, 'Add a download with original filename link to the menu.'],
'Archive Link': [true, 'Add an archive link to the menu.']
},
'Monitoring': {
@@ -372,7 +373,7 @@
doc = d.documentElement;
g = {
- VERSION: '1.7.51',
+ VERSION: '1.7.52',
NAMESPACE: '4chan X.',
boards: {}
};
@@ -4016,6 +4017,50 @@
};
})();
+ CrossOrigin = (function() {
+ var handleBlob, handleUrl;
+ handleBlob = function(urlBlob, contentType, contentDisposition, url, cb) {
+ 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 cb(blob);
+ };
+ handleUrl = function(url, cb) {
+ return GM_xmlhttpRequest({
+ method: "GET",
+ url: url,
+ overrideMimeType: "text/plain; charset=x-user-defined",
+ onload: function(xhr) {
+ var contentDisposition, contentType, data, i, r, _ref, _ref1;
+ r = xhr.responseText;
+ data = new Uint8Array(r.length);
+ i = 0;
+ while (i < r.length) {
+ data[i] = r.charCodeAt(i);
+ i++;
+ }
+ contentType = (_ref = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? _ref[1] : void 0;
+ contentDisposition = (_ref1 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? _ref1[1] : void 0;
+ return handleBlob(data, contentType, contentDisposition, url, cb);
+ },
+ onerror: function(xhr) {
+ return cb(null);
+ }
+ });
+ };
+ return {
+ request: handleUrl
+ };
+ })();
+
Anonymize = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Anonymize']) {
@@ -6052,44 +6097,16 @@
QR.handleFiles(files);
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() {
var url;
url = prompt("Insert an url:");
if (url === null) {
return;
}
- return GM_xmlhttpRequest({
- method: "GET",
- url: url,
- overrideMimeType: "text/plain; charset=x-user-defined",
- onload: function(xhr) {
- var contentDisposition, contentType, data, i, r, _ref, _ref1;
- r = xhr.responseText;
- data = new Uint8Array(r.length);
- i = 0;
- while (i < r.length) {
- data[i] = r.charCodeAt(i);
- i++;
- }
- contentType = (_ref = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? _ref[1] : void 0;
- contentDisposition = (_ref1 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? _ref1[1] : void 0;
- return QR.handleBlob(data, contentType, contentDisposition, url);
- },
- onerror: function(xhr) {
+ return CrossOrigin.request(url, function(blob) {
+ if (blob) {
+ return QR.handleFiles([blob]);
+ } else {
return QR.error("Can't load image.");
}
});
@@ -9320,6 +9337,24 @@
className: 'download-link',
textContent: 'Download file'
});
+ if (typeof chrome === "undefined" || chrome === null) {
+ $.on(a, 'click', function(e) {
+ if (this.protocol === 'blob:') {
+ return true;
+ }
+ e.preventDefault();
+ return CrossOrigin.request(this.href, (function(_this) {
+ return function(blob) {
+ if (blob) {
+ _this.href = URL.createObjectURL(blob);
+ return _this.click();
+ } else {
+ return new Notice('error', "Could not download " + file.URL, 30);
+ }
+ };
+ })(this));
+ });
+ }
return $.event('AddMenuEntry', {
type: 'post',
el: a,
@@ -12444,7 +12479,7 @@
Settings.dialog = dialog = $.el('div', {
id: 'fourchanx-settings',
className: 'dialog',
- innerHTML: '
'
+ innerHTML: ''
});
$.on($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]);
@@ -13289,7 +13324,7 @@
}
if (previousversion) {
el = $.el('span', {
- innerHTML: '4chan X has been updated to version 1.7.51.'
+ innerHTML: '4chan X has been updated to version 1.7.52.'
});
new Notice('info', el, 15);
} else {
diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip
index ab9aee252..77a5cef89 100644
Binary files a/builds/4chan-X.zip and b/builds/4chan-X.zip differ
diff --git a/builds/crx.crx b/builds/crx.crx
index aade6da8a..87f8e8e62 100644
Binary files a/builds/crx.crx and b/builds/crx.crx differ
diff --git a/builds/crx/manifest.json b/builds/crx/manifest.json
index 4d0e7d248..a7f853f0a 100755
--- a/builds/crx/manifest.json
+++ b/builds/crx/manifest.json
@@ -1,6 +1,6 @@
{
"name": "4chan X",
- "version": "1.7.51",
+ "version": "1.7.52",
"manifest_version": 2,
"description": "Cross-browser userscript for maximum lurking on 4chan.",
"icons": {
diff --git a/builds/crx/script.js b/builds/crx/script.js
index 14457ad3c..91c21a80f 100644
--- a/builds/crx/script.js
+++ b/builds/crx/script.js
@@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
-* 4chan X - Version 1.7.51 - 2014-06-02
+* 4chan X - Version 1.7.52 - 2014-06-02
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
@@ -82,7 +82,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, g,
+ var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, g,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
@@ -170,7 +170,7 @@
'Thread Hiding Link': [true, 'Add a link to hide entire threads.'],
'Reply Hiding Link': [true, 'Add a link to hide single replies.'],
'Delete Link': [true, 'Add post and image deletion links to the menu.'],
- 'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'],
+ 'Download Link': [true, 'Add a download with original filename link to the menu.'],
'Archive Link': [true, 'Add an archive link to the menu.']
},
'Monitoring': {
@@ -348,7 +348,7 @@
doc = d.documentElement;
g = {
- VERSION: '1.7.51',
+ VERSION: '1.7.52',
NAMESPACE: '4chan X.',
boards: {}
};
@@ -4045,6 +4045,47 @@
};
})();
+ CrossOrigin = (function() {
+ var handleBlob, handleUrl;
+ handleBlob = function(urlBlob, contentType, contentDisposition, url, cb) {
+ 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 cb(blob);
+ };
+ handleUrl = function(url, cb) {
+ var xhr;
+ xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.responseType = 'blob';
+ xhr.onload = function(e) {
+ var contentDisposition, contentType;
+ if (this.readyState === this.DONE && xhr.status === 200) {
+ contentType = this.getResponseHeader('Content-Type');
+ contentDisposition = this.getResponseHeader('Content-Disposition');
+ return handleBlob(this.response, contentType, contentDisposition, url, cb);
+ } else {
+ return cb(null);
+ }
+ };
+ xhr.onerror = function(e) {
+ return cb(null);
+ };
+ return xhr.send();
+ };
+ return {
+ request: handleUrl
+ };
+ })();
+
Anonymize = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Anonymize']) {
@@ -6090,43 +6131,19 @@
QR.handleFiles(files);
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() {
- var url, xhr;
+ var url;
url = prompt("Insert an url:");
if (url === null) {
return;
}
- xhr = new XMLHttpRequest();
- xhr.open('GET', url, true);
- xhr.responseType = 'blob';
- xhr.onload = function(e) {
- var contentDisposition, contentType;
- if (this.readyState === this.DONE && xhr.status === 200) {
- contentType = this.getResponseHeader('Content-Type');
- contentDisposition = this.getResponseHeader('Content-Disposition');
- return QR.handleBlob(this.response, contentType, contentDisposition, url);
+ return CrossOrigin.request(url, function(blob) {
+ if (blob) {
+ return QR.handleFiles([blob]);
} else {
return QR.error("Can't load image.");
}
- };
- xhr.onerror = function(e) {
- return QR.error("Can't load image.");
- };
- return xhr.send();
+ });
},
handleFiles: function(files) {
var file, i, _i, _len;
@@ -12438,7 +12455,7 @@
Settings.dialog = dialog = $.el('div', {
id: 'fourchanx-settings',
className: 'dialog',
- innerHTML: ''
+ innerHTML: ''
});
$.on($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]);
@@ -13270,7 +13287,7 @@
}
if (previousversion) {
el = $.el('span', {
- innerHTML: '4chan X has been updated to version 1.7.51.'
+ innerHTML: '4chan X has been updated to version 1.7.52.'
});
new Notice('info', el, 15);
} else {
diff --git a/builds/updates.xml b/builds/updates.xml
index 272a860f6..8e776a68d 100644
--- a/builds/updates.xml
+++ b/builds/updates.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/builds/wcrx/manifest.json b/builds/wcrx/manifest.json
index 1034923ab..636735ed6 100644
--- a/builds/wcrx/manifest.json
+++ b/builds/wcrx/manifest.json
@@ -1,6 +1,6 @@
{
"name": "4chan X",
- "version": "1.7.51",
+ "version": "1.7.52",
"manifest_version": 2,
"description": "Cross-browser userscript for maximum lurking on 4chan.",
"icons": {
diff --git a/builds/wcrx/script.js b/builds/wcrx/script.js
index 14457ad3c..91c21a80f 100644
--- a/builds/wcrx/script.js
+++ b/builds/wcrx/script.js
@@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
-* 4chan X - Version 1.7.51 - 2014-06-02
+* 4chan X - Version 1.7.52 - 2014-06-02
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
@@ -82,7 +82,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, g,
+ var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, g,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
@@ -170,7 +170,7 @@
'Thread Hiding Link': [true, 'Add a link to hide entire threads.'],
'Reply Hiding Link': [true, 'Add a link to hide single replies.'],
'Delete Link': [true, 'Add post and image deletion links to the menu.'],
- 'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'],
+ 'Download Link': [true, 'Add a download with original filename link to the menu.'],
'Archive Link': [true, 'Add an archive link to the menu.']
},
'Monitoring': {
@@ -348,7 +348,7 @@
doc = d.documentElement;
g = {
- VERSION: '1.7.51',
+ VERSION: '1.7.52',
NAMESPACE: '4chan X.',
boards: {}
};
@@ -4045,6 +4045,47 @@
};
})();
+ CrossOrigin = (function() {
+ var handleBlob, handleUrl;
+ handleBlob = function(urlBlob, contentType, contentDisposition, url, cb) {
+ 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 cb(blob);
+ };
+ handleUrl = function(url, cb) {
+ var xhr;
+ xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.responseType = 'blob';
+ xhr.onload = function(e) {
+ var contentDisposition, contentType;
+ if (this.readyState === this.DONE && xhr.status === 200) {
+ contentType = this.getResponseHeader('Content-Type');
+ contentDisposition = this.getResponseHeader('Content-Disposition');
+ return handleBlob(this.response, contentType, contentDisposition, url, cb);
+ } else {
+ return cb(null);
+ }
+ };
+ xhr.onerror = function(e) {
+ return cb(null);
+ };
+ return xhr.send();
+ };
+ return {
+ request: handleUrl
+ };
+ })();
+
Anonymize = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Anonymize']) {
@@ -6090,43 +6131,19 @@
QR.handleFiles(files);
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() {
- var url, xhr;
+ var url;
url = prompt("Insert an url:");
if (url === null) {
return;
}
- xhr = new XMLHttpRequest();
- xhr.open('GET', url, true);
- xhr.responseType = 'blob';
- xhr.onload = function(e) {
- var contentDisposition, contentType;
- if (this.readyState === this.DONE && xhr.status === 200) {
- contentType = this.getResponseHeader('Content-Type');
- contentDisposition = this.getResponseHeader('Content-Disposition');
- return QR.handleBlob(this.response, contentType, contentDisposition, url);
+ return CrossOrigin.request(url, function(blob) {
+ if (blob) {
+ return QR.handleFiles([blob]);
} else {
return QR.error("Can't load image.");
}
- };
- xhr.onerror = function(e) {
- return QR.error("Can't load image.");
- };
- return xhr.send();
+ });
},
handleFiles: function(files) {
var file, i, _i, _len;
@@ -12438,7 +12455,7 @@
Settings.dialog = dialog = $.el('div', {
id: 'fourchanx-settings',
className: 'dialog',
- innerHTML: ''
+ innerHTML: ''
});
$.on($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]);
@@ -13270,7 +13287,7 @@
}
if (previousversion) {
el = $.el('span', {
- innerHTML: '4chan X has been updated to version 1.7.51.'
+ innerHTML: '4chan X has been updated to version 1.7.52.'
});
new Notice('info', el, 15);
} else {
diff --git a/package.json b/package.json
index a696651ae..d82ead39b 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "4chan-X",
- "version": "1.7.51",
+ "version": "1.7.52",
"description": "Cross-browser userscript for maximum lurking on 4chan.",
"meta": {
"name": "4chan X",
diff --git a/src/General/Config.coffee b/src/General/Config.coffee
index a5b4f206a..8086abf7d 100755
--- a/src/General/Config.coffee
+++ b/src/General/Config.coffee
@@ -229,12 +229,10 @@ Config =
true
'Add post and image deletion links to the menu.'
]
- <% if (type === 'crx') { %>
'Download Link': [
true
- 'Add a download with original filename link to the menu. Chrome-only currently.'
+ 'Add a download with original filename link to the menu.'
]
- <% } %>
'Archive Link': [
true
'Add an archive link to the menu.'
diff --git a/src/General/CrossOrigin.coffee b/src/General/CrossOrigin.coffee
new file mode 100644
index 000000000..a712cc9f5
--- /dev/null
+++ b/src/General/CrossOrigin.coffee
@@ -0,0 +1,52 @@
+CrossOrigin = do ->
+
+ handleBlob = (urlBlob, contentType, contentDisposition, url, cb) ->
+ 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
+ cb blob
+
+ handleUrl = (url, cb) ->
+ <% if (type === 'crx') { %>
+ xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true)
+ xhr.responseType = 'blob'
+ xhr.onload = (e) ->
+ if @readyState is @DONE && xhr.status is 200
+ contentType = @getResponseHeader('Content-Type')
+ contentDisposition = @getResponseHeader('Content-Disposition')
+ handleBlob @response, contentType, contentDisposition, url, cb
+ else
+ cb null
+ xhr.onerror = (e) ->
+ cb null
+ xhr.send()
+ <% } %>
+
+ <% 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]
+ handleBlob data, contentType, contentDisposition, url, cb
+ onerror: (xhr) ->
+ cb null
+ <% } %>
+
+ return {request: handleUrl}
+
diff --git a/src/Menu/DownloadLink.coffee b/src/Menu/DownloadLink.coffee
index c3eda1620..8da1cb882 100755
--- a/src/Menu/DownloadLink.coffee
+++ b/src/Menu/DownloadLink.coffee
@@ -5,6 +5,21 @@ DownloadLink =
a = $.el 'a',
className: 'download-link'
textContent: 'Download file'
+
+ <% if (type === 'userscript') { %>
+ unless chrome?
+ # Firefox places same-origin restrictions on links with the download attribute.
+ $.on a, 'click', (e) ->
+ return true if @protocol is 'blob:'
+ e.preventDefault()
+ CrossOrigin.request @href, (blob) =>
+ if blob
+ @href = URL.createObjectURL blob
+ @click()
+ else
+ new Notice 'error', "Could not download #{file.URL}", 30
+ <% } %>
+
$.event 'AddMenuEntry',
type: 'post'
el: a
diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee
index cef172f12..9447ab7c4 100644
--- a/src/Posting/QR.coffee
+++ b/src/Posting/QR.coffee
@@ -293,56 +293,14 @@ QR =
QR.handleFiles files
$.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: ->
url = prompt("Insert an url:")
return if url is null
-
- <% if (type === 'crx') { %>
- xhr = new XMLHttpRequest();
- xhr.open('GET', url, true)
- xhr.responseType = 'blob'
- xhr.onload = (e) ->
- if @readyState is @DONE && xhr.status is 200
- contentType = @getResponseHeader('Content-Type')
- contentDisposition = @getResponseHeader('Content-Disposition')
- QR.handleBlob @response, contentType, contentDisposition, url
+ CrossOrigin.request url, (blob) ->
+ if blob
+ QR.handleFiles([blob])
else
QR.error "Can't load image."
- xhr.onerror = (e) ->
- QR.error "Can't load image."
- xhr.send()
- <% } %>
-
- <% 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]
- QR.handleBlob data, contentType, contentDisposition, url
- onerror: (xhr) ->
- QR.error "Can't load image."
- <% } %>
handleFiles: (files) ->
if @ isnt QR # file input