Skip to content

Commit 4358336

Browse files
committed
updated to Eli Grey's latest FileSaver.js
1 parent e97a206 commit 4358336

File tree

2 files changed

+133
-146
lines changed

2 files changed

+133
-146
lines changed

FileSaver.js

Lines changed: 132 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,185 +1,172 @@
11
/*
2-
* FileSaver.js
3-
* A saveAs() FileSaver implementation.
4-
*
5-
* By Eli Grey, http://eligrey.com
6-
*
7-
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
8-
* source : http://purl.eligrey.com/github/FileSaver.js
9-
*/
10-
11-
(function (global, factory) {
12-
if (typeof define === "function" && define.amd) {
13-
define([], factory);
14-
} else if (typeof exports !== "undefined") {
15-
factory();
16-
} else {
17-
var mod = {
18-
exports: {}
19-
};
20-
factory();
21-
global.FileSaver = mod.exports;
2+
* FileSaver.js
3+
* A saveAs() FileSaver implementation.
4+
*
5+
* By Eli Grey, http://eligrey.com
6+
*
7+
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
8+
* source : http://purl.eligrey.com/github/FileSaver.js
9+
*/
10+
11+
// The one and only way of getting global scope in all environments
12+
// https://stackoverflow.com/q/3277182/1008999
13+
var _global = typeof window === 'object' && window.window === window
14+
? window : typeof self === 'object' && self.self === self
15+
? self : typeof global === 'object' && global.global === global
16+
? global
17+
: this
18+
19+
function bom (blob, opts) {
20+
if (typeof opts === 'undefined') opts = { autoBom: false }
21+
else if (typeof opts !== 'object') {
22+
console.warn('Deprecated: Expected third argument to be a object')
23+
opts = { autoBom: !opts }
2224
}
23-
})(this, function () {
24-
"use strict";
25-
26-
// The one and only way of getting global scope in all environments
27-
// https://stackoverflow.com/q/3277182/1008999
28-
var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : void 0;
29-
30-
function bom(blob, opts) {
31-
if (typeof opts === 'undefined') opts = {
32-
autoBom: false
33-
};else if (typeof opts !== 'object') {
34-
console.warn('Deprecated: Expected third argument to be a object');
35-
opts = {
36-
autoBom: !opts
37-
};
38-
} // prepend BOM for UTF-8 XML and text/* types (including HTML)
39-
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
40-
41-
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
42-
return new Blob([String.fromCharCode(0xFEFF), blob], {
43-
type: blob.type
44-
});
45-
}
4625

47-
return blob;
26+
// prepend BOM for UTF-8 XML and text/* types (including HTML)
27+
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
28+
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
29+
return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })
4830
}
49-
50-
function download(url, name, opts) {
51-
var xhr = new XMLHttpRequest();
52-
xhr.open('GET', url);
53-
xhr.responseType = 'blob';
54-
55-
xhr.onload = function () {
56-
saveAs(xhr.response, name, opts);
57-
};
58-
59-
xhr.onerror = function () {
60-
console.error('could not download file');
61-
};
62-
63-
xhr.send();
31+
return blob
32+
}
33+
34+
function download (url, name, opts) {
35+
var xhr = new XMLHttpRequest()
36+
xhr.open('GET', url)
37+
xhr.responseType = 'blob'
38+
xhr.onload = function () {
39+
saveAs(xhr.response, name, opts)
6440
}
41+
xhr.onerror = function () {
42+
console.error('could not download file')
43+
}
44+
xhr.send()
45+
}
46+
47+
function corsEnabled (url) {
48+
var xhr = new XMLHttpRequest()
49+
// use sync to avoid popup blocker
50+
xhr.open('HEAD', url, false)
51+
try {
52+
xhr.send()
53+
} catch (e) {}
54+
return xhr.status >= 200 && xhr.status <= 299
55+
}
56+
57+
// `a.click()` doesn't work for all browsers (#465)
58+
function click (node) {
59+
try {
60+
node.dispatchEvent(new MouseEvent('click'))
61+
} catch (e) {
62+
var evt = document.createEvent('MouseEvents')
63+
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
64+
20, false, false, false, false, 0, null)
65+
node.dispatchEvent(evt)
66+
}
67+
}
6568

66-
function corsEnabled(url) {
67-
var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker
68-
69-
xhr.open('HEAD', url, false);
70-
71-
try {
72-
xhr.send();
73-
} catch (e) {}
69+
// Detect WebView inside a native macOS app by ruling out all browsers
70+
// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
71+
// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
72+
var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)
7473

75-
return xhr.status >= 200 && xhr.status <= 299;
76-
} // `a.click()` doesn't work for all browsers (#465)
74+
var saveAs = _global.saveAs || (
75+
// probably in some web worker
76+
(typeof window !== 'object' || window !== _global)
77+
? function saveAs () { /* noop */ }
7778

79+
// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
80+
: ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)
81+
? function saveAs (blob, name, opts) {
82+
var URL = _global.URL || _global.webkitURL
83+
// Namespace is used to prevent conflict w/ Chrome Poper Blocker extension (Issue #561)
84+
var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')
85+
name = name || blob.name || 'download'
7886

79-
function click(node) {
80-
try {
81-
node.dispatchEvent(new MouseEvent('click'));
82-
} catch (e) {
83-
var evt = document.createEvent('MouseEvents');
84-
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
85-
node.dispatchEvent(evt);
86-
}
87-
}
87+
a.download = name
88+
a.rel = 'noopener' // tabnabbing
8889

89-
var saveAs = _global.saveAs || ( // probably in some web worker
90-
typeof window !== 'object' || window !== _global ? function saveAs() {}
91-
/* noop */
92-
// Use download attribute first if possible (#193 Lumia mobile)
93-
: 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) {
94-
var URL = _global.URL || _global.webkitURL;
95-
var a = document.createElement('a');
96-
name = name || blob.name || 'download';
97-
a.download = name;
98-
a.rel = 'noopener'; // tabnabbing
9990
// TODO: detect chrome extensions & packaged apps
10091
// a.target = '_blank'
10192

10293
if (typeof blob === 'string') {
10394
// Support regular links
104-
a.href = blob;
105-
95+
a.href = blob
10696
if (a.origin !== location.origin) {
107-
corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
97+
corsEnabled(a.href)
98+
? download(blob, name, opts)
99+
: click(a, a.target = '_blank')
108100
} else {
109-
click(a);
101+
click(a)
110102
}
111103
} else {
112104
// Support blobs
113-
a.href = URL.createObjectURL(blob);
114-
setTimeout(function () {
115-
URL.revokeObjectURL(a.href);
116-
}, 4E4); // 40s
117-
118-
setTimeout(function () {
119-
click(a);
120-
}, 0);
105+
a.href = URL.createObjectURL(blob)
106+
setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
107+
setTimeout(function () { click(a) }, 0)
121108
}
122-
} // Use msSaveOrOpenBlob as a second approach
123-
: 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
124-
name = name || blob.name || 'download';
109+
}
110+
111+
// Use msSaveOrOpenBlob as a second approach
112+
: 'msSaveOrOpenBlob' in navigator
113+
? function saveAs (blob, name, opts) {
114+
name = name || blob.name || 'download'
125115

126116
if (typeof blob === 'string') {
127117
if (corsEnabled(blob)) {
128-
download(blob, name, opts);
118+
download(blob, name, opts)
129119
} else {
130-
var a = document.createElement('a');
131-
a.href = blob;
132-
a.target = '_blank';
133-
setTimeout(function () {
134-
click(a);
135-
});
120+
var a = document.createElement('a')
121+
a.href = blob
122+
a.target = '_blank'
123+
setTimeout(function () { click(a) })
136124
}
137125
} else {
138-
navigator.msSaveOrOpenBlob(bom(blob, opts), name);
126+
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
139127
}
140-
} // Fallback to using FileReader and a popup
141-
: function saveAs(blob, name, opts, popup) {
128+
}
129+
130+
// Fallback to using FileReader and a popup
131+
: function saveAs (blob, name, opts, popup) {
142132
// Open a popup immediately do go around popup blocker
143133
// Mostly only available on user interaction and the fileReader is async so...
144-
popup = popup || open('', '_blank');
145-
134+
popup = popup || open('', '_blank')
146135
if (popup) {
147-
popup.document.title = popup.document.body.innerText = 'downloading...';
136+
popup.document.title =
137+
popup.document.body.innerText = 'downloading...'
148138
}
149139

150-
if (typeof blob === 'string') return download(blob, name, opts);
151-
var force = blob.type === 'application/octet-stream';
152-
153-
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
140+
if (typeof blob === 'string') return download(blob, name, opts)
154141

155-
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
142+
var force = blob.type === 'application/octet-stream'
143+
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
144+
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)
156145

157-
if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') {
146+
if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {
158147
// Safari doesn't allow downloading of blob URLs
159-
var reader = new FileReader();
160-
148+
var reader = new FileReader()
161149
reader.onloadend = function () {
162-
var url = reader.result;
163-
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
164-
if (popup) popup.location.href = url;else location = url;
165-
popup = null; // reverse-tabnabbing #460
166-
};
167-
168-
reader.readAsDataURL(blob);
150+
var url = reader.result
151+
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
152+
if (popup) popup.location.href = url
153+
else location = url
154+
popup = null // reverse-tabnabbing #460
155+
}
156+
reader.readAsDataURL(blob)
169157
} else {
170-
var URL = _global.URL || _global.webkitURL;
171-
var url = URL.createObjectURL(blob);
172-
if (popup) popup.location = url;else location.href = url;
173-
popup = null; // reverse-tabnabbing #460
174-
175-
setTimeout(function () {
176-
URL.revokeObjectURL(url);
177-
}, 4E4); // 40s
158+
var URL = _global.URL || _global.webkitURL
159+
var url = URL.createObjectURL(blob)
160+
if (popup) popup.location = url
161+
else location.href = url
162+
popup = null // reverse-tabnabbing #460
163+
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s
178164
}
179-
});
180-
_global.saveAs = saveAs.saveAs = saveAs;
181-
182-
if (typeof module !== 'undefined') {
183-
module.exports = saveAs;
184165
}
185-
});
166+
)
167+
168+
_global.saveAs = saveAs.saveAs = saveAs
169+
170+
if (typeof module !== 'undefined') {
171+
module.exports = saveAs;
172+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Export your Spotify playlists for analysis or just safekeeping: [exportify.net](
66

77
<a href="https://pavelkomarov.com/exportify/app"><img src="screenshot.png"/></a>
88

9-
This is a hard fork of [the original Exportify repo](https://github.com/watsonbox/exportify). I simplified and updated the code, instituted rate limiting (because back when I forked, the original didn't work for large playlists or all playlists at once), got rid of tests I deemed confusing, made the table sortable, modified to display all playlists at once (which simplified the code), included playlist images, fixed null object bugs in the event of missing data, set up automatic deployment to github pages, fixed a parsing bug when album or artists names contain commas, enhanced the set of features, added logout functionality, and provided an ipython notebook to do something interesting with the data.
9+
This is a hard fork of [the original Exportify repo](https://github.com/watsonbox/exportify). I simplified and updated the code, instituted rate limiting (because back when I forked, the original didn't work for large playlists or all playlists at once), got rid of tests I deemed confusing, made the table sortable, modified to display all playlists at once (which simplified the code), included playlist images, fixed null object bugs in the event of missing data, set up automatic deployment to github pages, fixed a parsing bug when album or artists names contain commas, enhanced the set of features, added logout functionality, and provided an interactive python notebook to do something interesting with the data.
1010

1111
### Export Format
1212

0 commit comments

Comments
 (0)