From 70c261d0268e4d02154c12e8ffd6069f82275ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20W=C3=A4rting?= Date: Wed, 26 Sep 2018 17:04:34 +0200 Subject: [PATCH] rc.2 Dispatch event instead of using a.click() closes #465 added a changelog closes #466 Made third argument as a object instead for easier transition to v2 --- README.md | 4 +-- bower.json | 2 +- changelog.md | 30 ++++++++++++++++++++++ dist/FileSaver.js | 52 ++++++++++++++++++++++++++++----------- dist/FileSaver.min.js | 2 +- dist/FileSaver.min.js.map | 2 +- package.json | 2 +- src/FileSaver.js | 49 ++++++++++++++++++++++++------------ 8 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 changelog.md diff --git a/README.md b/README.md index cd45e4d..f655756 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,10 @@ import { saveAs } from 'file-saver/FileSaver'; ``` ```js -FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Boolean autoBOM) +FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBOM }) ``` -Pass `true` for `autoBOM` if you want FileSaver.js to automatically provide Unicode text encoding hints (see: [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark)). +Pass `{ autoBOM: true }` if you want FileSaver.js to automatically provide Unicode text encoding hints (see: [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark)). Examples -------- diff --git a/bower.json b/bower.json index 1c5e33f..7d29c4f 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "file-saver", "main": "dist/FileSaver.min.js", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.2", "homepage": "https://github.com/eligrey/FileSaver.js", "authors": [ "Eli Grey " diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..1e24ad2 --- /dev/null +++ b/changelog.md @@ -0,0 +1,30 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0-rc.2] - 2018-09-26 + +- Added a changelog.md +- Reverted `a.click()` to use dispatch with a try-catch (#382) +- Made third argument to an object where you have to pass `{ autoBom: true }` + - boolean are depricated but still works + +## [2.0.0-rc.1] - 2018-09-26 + +- saveAs don't return anything + - The object that dispatched `writestart progress write writeend` are gone + - detecting such features was never possible and nobody seems to use it. +- Removed the demo folder +- Removed date/version from top of the file +- Dosen't crash in web workers (#449) +- Support saving urls (#260 with workarounds for cross origin) +- Uses babel universal module pattern (UMD) to export the package +- Provides source map now as well. +- use a[download] before msSaveAs (#193, #294) +- removed dist from .gitignore (npm uses it if it don't find a .npmignore) +- autoBom is now reversed so you have to tell when you want to use autoBom (#432) +- `a.click()` since there are new and depricated event constructors that works differently (#382) +- opens up a new popup (tab) directly for the fallback method since the FileReader is async +- removed the explicitly MSIE [1-9] check diff --git a/dist/FileSaver.js b/dist/FileSaver.js index 96e8d5d..1e1e2b3 100644 --- a/dist/FileSaver.js +++ b/dist/FileSaver.js @@ -40,11 +40,18 @@ } }(); - function bom(blob, autoBom) { - if (!autoBom) return blob; // prepend BOM for UTF-8 XML and text/* types (including HTML) + function bom(blob, opts) { + if (typeof opts === 'undefined') opts = { + autoBom: false + };else if (typeof opts !== 'object') { + console.warn('Depricated: Expected third argument to be a object'); + opts = { + autoBom: !opts + }; + } // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF - if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type }); @@ -53,13 +60,13 @@ return blob; } - function download(url, name, autoBom) { + function download(url, name, opts) { var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'blob'; xhr.onload = function () { - saveAs(xhr.response, name, autoBom); + saveAs(xhr.response, name, opts); }; xhr.onerror = function () { @@ -75,13 +82,24 @@ xhr.open('HEAD', url, false); xhr.send(); return xhr.status >= 200 && xhr.status <= 299; + } // `a.click()` don't work for all browsers (#465) + + + function click(node) { + try { + node.dispatchEvent(new MouseEvent('click')); + } catch (e) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); + node.dispatchEvent(evt); + } } var saveAs = _global.saveAs || // probably in some web worker typeof window !== 'object' || window !== _global ? function saveAs() {} /* noop */ // Use download attribute first if possible (#193 Lumia mobile) - : 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, autoBom) { + : 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) { var URL = _global.URL || _global.webkitURL; var a = document.createElement('a'); name = name || blob.name || 'download'; @@ -95,9 +113,9 @@ a.href = blob; if (a.origin !== location.origin) { - corsEnabled(a.href) ? download(blob, name, autoBom) : a.click(a.target = '_blank'); + corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); } else { - a.click(); + click(a); } } else { // Support blobs @@ -106,26 +124,30 @@ URL.revokeObjectURL(a.href); }, 4E4); // 40s - setTimeout(a.click.bind(a), 0); + setTimeout(function () { + click(a); + }, 0); } } // Use msSaveOrOpenBlob as a second approch - : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, autoBom) { + : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { name = name || blob.name || 'download'; if (typeof blob === 'string') { if (corsEnabled(blob)) { - download(blob, name, autoBom); + download(blob, name, opts); } else { var a = document.createElement('a'); a.href = blob; a.target = '_blank'; - setTimeout(a.click.bind(a)); + setTimeout(function () { + clikc(a); + }); } } else { - navigator.msSaveOrOpenBlob(bom(blob, autoBom), name); + navigator.msSaveOrOpenBlob(bom(blob, opts), name); } } // Fallback to using FileReader and a popup - : function saveAs(blob, name, autoBom, popup) { + : function saveAs(blob, name, opts, popup) { // Open a popup immediately do go around popup blocker // Mostly only avalible on user interaction and the fileReader is async so... popup = popup || open('', '_blank'); @@ -134,7 +156,7 @@ popup.document.title = popup.document.body.innerText = 'downloading...'; } - if (typeof blob === 'string') return download(blob, name, autoBom); + if (typeof blob === 'string') return download(blob, name, opts); var force = blob.type === 'application/octet-stream'; var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; diff --git a/dist/FileSaver.min.js b/dist/FileSaver.min.js index 4cea842..b56d8b3 100644 --- a/dist/FileSaver.min.js +++ b/dist/FileSaver.min.js @@ -1,3 +1,3 @@ -(function(a,b){if("function"==typeof define&&define.amd)define(["exports"],b);else if("undefined"!=typeof exports)b(exports);else{var c={exports:{}};b(c.exports),a.FileSaver=c.exports}})(this,function(a){"use strict";function b(a,b){return b?/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a:a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){f(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;return b.open("HEAD",a,!1),b.send(),200<=b.status&&299>=b.status}Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var e=function(){try{return Function("return this")()||(42,eval)("this")}catch(a){return"object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:this}}(),f=e.saveAs||"object"!=typeof window||window!==e?function(){}:"download"in HTMLAnchorElement.prototype?function(b,f,g){var h=e.URL||e.webkitURL,i=document.createElement("a");f=f||b.name||"download",i.download=f,i.rel="noopener","string"==typeof b?(i.href=b,i.origin===location.origin?i.click():d(i.href)?c(b,f,g):i.click(i.target="_blank")):(i.href=h.createObjectURL(b),setTimeout(function(){h.revokeObjectURL(i.href)},4E4),setTimeout(i.click.bind(i),0))}:"msSaveOrOpenBlob"in navigator?function(e,f,g){if(f=f||e.name||"download","string"!=typeof e)navigator.msSaveOrOpenBlob(b(e,g),f);else if(d(e))c(e,f,g);else{var h=document.createElement("a");h.href=e,h.target="_blank",setTimeout(h.click.bind(h))}}:function(a,b,d,f){if(f=f||open("","_blank"),f&&(f.document.title=f.document.body.innerText="downloading..."),"string"==typeof a)return c(a,b,d);var g="application/octet-stream"===a.type,h=/constructor/i.test(e.HTMLElement)||e.safari,i=/CriOS\/[\d]+/.test(navigator.userAgent);if((i||g&&h)&&"object"==typeof FileReader){var j=new FileReader;j.onloadend=function(){var a=j.result;a=i?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),f?f.location.href=a:location=a,f=null},j.readAsDataURL(a)}else{var k=e.URL||e.webkitURL,l=k.createObjectURL(a);f?f.location=l:location.href=l,f=null,setTimeout(function(){k.revokeObjectURL(l)},4E4)}};e.saveAs=f;a.default=f}); +(function(a,b){if("function"==typeof define&&define.amd)define(["exports"],b);else if("undefined"!=typeof exports)b(exports);else{var c={exports:{}};b(c.exports),a.FileSaver=c.exports}})(this,function(a){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Depricated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;return b.open("HEAD",a,!1),b.send(),200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var f=function(){try{return Function("return this")()||(42,eval)("this")}catch(a){return"object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:this}}(),g=f.saveAs||"object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(e,f,g){if(f=f||e.name||"download","string"!=typeof e)navigator.msSaveOrOpenBlob(b(e,g),f);else if(d(e))c(e,f,g);else{var h=document.createElement("a");h.href=e,h.target="_blank",setTimeout(function(){clikc(h)})}}:function(a,b,d,e){if(e=e||open("","_blank"),e&&(e.document.title=e.document.body.innerText="downloading..."),"string"==typeof a)return c(a,b,d);var g="application/octet-stream"===a.type,h=/constructor/i.test(f.HTMLElement)||f.safari,i=/CriOS\/[\d]+/.test(navigator.userAgent);if((i||g&&h)&&"object"==typeof FileReader){var j=new FileReader;j.onloadend=function(){var a=j.result;a=i?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),e?e.location.href=a:location=a,e=null},j.readAsDataURL(a)}else{var k=f.URL||f.webkitURL,l=k.createObjectURL(a);e?e.location=l:location.href=l,e=null,setTimeout(function(){k.revokeObjectURL(l)},4E4)}};f.saveAs=g;a.default=g}); //# sourceMappingURL=FileSaver.min.js.map \ No newline at end of file diff --git a/dist/FileSaver.min.js.map b/dist/FileSaver.min.js.map index c082b31..006a697 100644 --- a/dist/FileSaver.min.js.map +++ b/dist/FileSaver.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/FileSaver.js"],"names":[],"mappings":"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","file":"FileSaver.min.js","sourcesContent":["/*\n* FileSaver.js\n* A saveAs() FileSaver implementation.\n*\n* By Eli Grey, http://eligrey.com\n*\n* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)\n* source : http://purl.eligrey.com/github/FileSaver.js\n*/\n\n\n// The one and only way of getting global scope in all enviorment\n// https://stackoverflow.com/q/3277182/1008999\nvar _global = (function () {\n// some use content security policy to disable eval\n try {\n return Function('return this')() || (42, eval)('this')\n } catch (e) {\n // every global should have circular reference\n // used for checking if someone writes var window = {}; var self = {}\n return typeof window === 'object' && window.window === window ? window\n : typeof self === 'object' && self.self === self ? self\n : typeof global === 'object' && global.global === global ? global : this\n }\n})()\n\nfunction bom (blob, autoBom) {\n if (!autoBom) return blob\n // prepend BOM for UTF-8 XML and text/* types (including HTML)\n // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF\n if (/^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(blob.type)) {\n return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })\n }\n return blob\n}\n\nfunction download (url, name, autoBom) {\n var xhr = new XMLHttpRequest()\n xhr.open('GET', url)\n xhr.responseType = 'blob'\n xhr.onload = function () {\n saveAs(xhr.response, name, autoBom)\n }\n xhr.onerror = function () {\n console.error('could not download file')\n }\n xhr.send()\n}\n\nfunction corsEnabled (url) {\n var xhr = new XMLHttpRequest()\n // use sync to avoid popup blocker\n xhr.open('HEAD', url, false)\n xhr.send()\n return xhr.status >= 200 && xhr.status <= 299\n}\n\nvar saveAs = _global.saveAs ||\n// probably in some web worker\n(typeof window !== 'object' || window !== _global)\n ? function saveAs () { /* noop */ }\n\n// Use download attribute first if possible (#193 Lumia mobile)\n: 'download' in HTMLAnchorElement.prototype\n? function saveAs (blob, name, autoBom) {\n var URL = _global.URL || _global.webkitURL\n var a = document.createElement('a')\n name = name || blob.name || 'download'\n\n a.download = name\n a.rel = 'noopener' // tabnabbing\n\n // TODO: detect chrome extensions & packaged apps\n // a.target = '_blank'\n\n if (typeof blob === 'string') {\n // Support regular links\n a.href = blob\n if (a.origin !== location.origin) {\n corsEnabled(a.href)\n ? download(blob, name, autoBom)\n : a.click(a.target = '_blank')\n } else {\n a.click()\n }\n } else {\n // Support blobs\n a.href = URL.createObjectURL(blob)\n setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s\n setTimeout(a.click.bind(a), 0)\n }\n}\n\n// Use msSaveOrOpenBlob as a second approch\n: 'msSaveOrOpenBlob' in navigator\n? function saveAs (blob, name, autoBom) {\n name = name || blob.name || 'download'\n\n if (typeof blob === 'string') {\n if (corsEnabled(blob)) {\n download(blob, name, autoBom)\n } else {\n var a = document.createElement('a')\n a.href = blob\n a.target = '_blank'\n setTimeout(a.click.bind(a))\n }\n } else {\n navigator.msSaveOrOpenBlob(bom(blob, autoBom), name)\n }\n}\n\n// Fallback to using FileReader and a popup\n: function saveAs (blob, name, autoBom, popup) {\n // Open a popup immediately do go around popup blocker\n // Mostly only avalible on user interaction and the fileReader is async so...\n popup = popup || open('', '_blank')\n if (popup) {\n popup.document.title =\n popup.document.body.innerText = 'downloading...'\n }\n\n if (typeof blob === 'string') return download(blob, name, autoBom)\n\n var force = blob.type === 'application/octet-stream'\n var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari\n var isChromeIOS = /CriOS\\/[\\d]+/.test(navigator.userAgent)\n\n if ((isChromeIOS || (force && isSafari)) && typeof FileReader === 'object') {\n // Safari doesn't allow downloading of blob urls\n var reader = new FileReader()\n reader.onloadend = function () {\n var url = reader.result\n url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')\n if (popup) popup.location.href = url\n else location = url\n popup = null // reverse-tabnabbing #460\n }\n reader.readAsDataURL(blob)\n } else {\n var URL = _global.URL || _global.webkitURL\n var url = URL.createObjectURL(blob)\n if (popup) popup.location = url\n else location.href = url\n popup = null // reverse-tabnabbing #460\n setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s\n }\n}\n\n_global.saveAs = saveAs\n\nexport default saveAs\n"]} \ No newline at end of file +{"version":3,"sources":["../src/FileSaver.js"],"names":[],"mappings":"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","file":"FileSaver.min.js","sourcesContent":["/*\n* FileSaver.js\n* A saveAs() FileSaver implementation.\n*\n* By Eli Grey, http://eligrey.com\n*\n* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)\n* source : http://purl.eligrey.com/github/FileSaver.js\n*/\n\n\n// The one and only way of getting global scope in all enviorment\n// https://stackoverflow.com/q/3277182/1008999\nvar _global = (function () {\n// some use content security policy to disable eval\n try {\n return Function('return this')() || (42, eval)('this')\n } catch (e) {\n // every global should have circular reference\n // used for checking if someone writes var window = {}; var self = {}\n return typeof window === 'object' && window.window === window ? window\n : typeof self === 'object' && self.self === self ? self\n : typeof global === 'object' && global.global === global ? global : this\n }\n})()\n\nfunction bom (blob, opts) {\n if (typeof opts === 'undefined') opts = { autoBom: false }\n else if (typeof opts !== 'object') {\n console.warn('Depricated: Expected third argument to be a object')\n opts = { autoBom: !opts }\n }\n\n // prepend BOM for UTF-8 XML and text/* types (including HTML)\n // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF\n if (opts.autoBom && /^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(blob.type)) {\n return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })\n }\n return blob\n}\n\nfunction download (url, name, opts) {\n var xhr = new XMLHttpRequest()\n xhr.open('GET', url)\n xhr.responseType = 'blob'\n xhr.onload = function () {\n saveAs(xhr.response, name, opts)\n }\n xhr.onerror = function () {\n console.error('could not download file')\n }\n xhr.send()\n}\n\nfunction corsEnabled (url) {\n var xhr = new XMLHttpRequest()\n // use sync to avoid popup blocker\n xhr.open('HEAD', url, false)\n xhr.send()\n return xhr.status >= 200 && xhr.status <= 299\n}\n\n// `a.click()` don't work for all browsers (#465)\nfunction click(node) {\n try {\n node.dispatchEvent(new MouseEvent('click'))\n } catch (e) {\n var evt = document.createEvent('MouseEvents')\n evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,\n 20, false, false, false, false, 0, null)\n node.dispatchEvent(evt)\n }\n}\n\nvar saveAs = _global.saveAs ||\n// probably in some web worker\n(typeof window !== 'object' || window !== _global)\n ? function saveAs () { /* noop */ }\n\n// Use download attribute first if possible (#193 Lumia mobile)\n: 'download' in HTMLAnchorElement.prototype\n? function saveAs (blob, name, opts) {\n var URL = _global.URL || _global.webkitURL\n var a = document.createElement('a')\n name = name || blob.name || 'download'\n\n a.download = name\n a.rel = 'noopener' // tabnabbing\n\n // TODO: detect chrome extensions & packaged apps\n // a.target = '_blank'\n\n if (typeof blob === 'string') {\n // Support regular links\n a.href = blob\n if (a.origin !== location.origin) {\n corsEnabled(a.href)\n ? download(blob, name, opts)\n : click(a, a.target = '_blank')\n } else {\n click(a)\n }\n } else {\n // Support blobs\n a.href = URL.createObjectURL(blob)\n setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s\n setTimeout(function () { click(a) }, 0)\n }\n}\n\n// Use msSaveOrOpenBlob as a second approch\n: 'msSaveOrOpenBlob' in navigator\n? function saveAs (blob, name, opts) {\n name = name || blob.name || 'download'\n\n if (typeof blob === 'string') {\n if (corsEnabled(blob)) {\n download(blob, name, opts)\n } else {\n var a = document.createElement('a')\n a.href = blob\n a.target = '_blank'\n setTimeout(function () { clikc(a) })\n }\n } else {\n navigator.msSaveOrOpenBlob(bom(blob, opts), name)\n }\n}\n\n// Fallback to using FileReader and a popup\n: function saveAs (blob, name, opts, popup) {\n // Open a popup immediately do go around popup blocker\n // Mostly only avalible on user interaction and the fileReader is async so...\n popup = popup || open('', '_blank')\n if (popup) {\n popup.document.title =\n popup.document.body.innerText = 'downloading...'\n }\n\n if (typeof blob === 'string') return download(blob, name, opts)\n\n var force = blob.type === 'application/octet-stream'\n var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari\n var isChromeIOS = /CriOS\\/[\\d]+/.test(navigator.userAgent)\n\n if ((isChromeIOS || (force && isSafari)) && typeof FileReader === 'object') {\n // Safari doesn't allow downloading of blob urls\n var reader = new FileReader()\n reader.onloadend = function () {\n var url = reader.result\n url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')\n if (popup) popup.location.href = url\n else location = url\n popup = null // reverse-tabnabbing #460\n }\n reader.readAsDataURL(blob)\n } else {\n var URL = _global.URL || _global.webkitURL\n var url = URL.createObjectURL(blob)\n if (popup) popup.location = url\n else location.href = url\n popup = null // reverse-tabnabbing #460\n setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s\n }\n}\n\n_global.saveAs = saveAs\n\nexport default saveAs\n"]} \ No newline at end of file diff --git a/package.json b/package.json index f1d4abf..40f78e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "file-saver", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.2", "description": "An HTML5 saveAs() FileSaver implementation", "main": "dist/FileSaver.min.js", "module": "src/FileSaver.js", diff --git a/src/FileSaver.js b/src/FileSaver.js index e5c9676..ab4c6eb 100644 --- a/src/FileSaver.js +++ b/src/FileSaver.js @@ -24,22 +24,27 @@ var _global = (function () { } })() -function bom (blob, autoBom) { - if (!autoBom) return blob +function bom (blob, opts) { + if (typeof opts === 'undefined') opts = { autoBom: false } + else if (typeof opts !== 'object') { + console.warn('Depricated: Expected third argument to be a object') + opts = { autoBom: !opts } + } + // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF - if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type }) } return blob } -function download (url, name, autoBom) { +function download (url, name, opts) { var xhr = new XMLHttpRequest() xhr.open('GET', url) xhr.responseType = 'blob' xhr.onload = function () { - saveAs(xhr.response, name, autoBom) + saveAs(xhr.response, name, opts) } xhr.onerror = function () { console.error('could not download file') @@ -55,6 +60,18 @@ function corsEnabled (url) { return xhr.status >= 200 && xhr.status <= 299 } +// `a.click()` don't work for all browsers (#465) +function click(node) { + try { + node.dispatchEvent(new MouseEvent('click')) + } catch (e) { + var evt = document.createEvent('MouseEvents') + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, + 20, false, false, false, false, 0, null) + node.dispatchEvent(evt) + } +} + var saveAs = _global.saveAs || // probably in some web worker (typeof window !== 'object' || window !== _global) @@ -62,7 +79,7 @@ var saveAs = _global.saveAs || // Use download attribute first if possible (#193 Lumia mobile) : 'download' in HTMLAnchorElement.prototype -? function saveAs (blob, name, autoBom) { +? function saveAs (blob, name, opts) { var URL = _global.URL || _global.webkitURL var a = document.createElement('a') name = name || blob.name || 'download' @@ -78,40 +95,40 @@ var saveAs = _global.saveAs || a.href = blob if (a.origin !== location.origin) { corsEnabled(a.href) - ? download(blob, name, autoBom) - : a.click(a.target = '_blank') + ? download(blob, name, opts) + : click(a, a.target = '_blank') } else { - a.click() + click(a) } } else { // Support blobs a.href = URL.createObjectURL(blob) setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s - setTimeout(a.click.bind(a), 0) + setTimeout(function () { click(a) }, 0) } } // Use msSaveOrOpenBlob as a second approch : 'msSaveOrOpenBlob' in navigator -? function saveAs (blob, name, autoBom) { +? function saveAs (blob, name, opts) { name = name || blob.name || 'download' if (typeof blob === 'string') { if (corsEnabled(blob)) { - download(blob, name, autoBom) + download(blob, name, opts) } else { var a = document.createElement('a') a.href = blob a.target = '_blank' - setTimeout(a.click.bind(a)) + setTimeout(function () { clikc(a) }) } } else { - navigator.msSaveOrOpenBlob(bom(blob, autoBom), name) + navigator.msSaveOrOpenBlob(bom(blob, opts), name) } } // Fallback to using FileReader and a popup -: function saveAs (blob, name, autoBom, popup) { +: function saveAs (blob, name, opts, popup) { // Open a popup immediately do go around popup blocker // Mostly only avalible on user interaction and the fileReader is async so... popup = popup || open('', '_blank') @@ -120,7 +137,7 @@ var saveAs = _global.saveAs || popup.document.body.innerText = 'downloading...' } - if (typeof blob === 'string') return download(blob, name, autoBom) + if (typeof blob === 'string') return download(blob, name, opts) var force = blob.type === 'application/octet-stream' var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari