diff --git a/.gitignore b/.gitignore
index 2c40e63..faacab1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.vscode/settings.json
autorename-workspace.code-workspace
+/lib/TwitterAPI/TwitterAPIKey.js
\ No newline at end of file
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index d922a98..d52f20d 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -94,6 +94,10 @@
"message": "About"
},
+ "common_section_downloads": {
+ "message": "Downloads"
+ },
+
"common_section_file_name": {
"message": "File Name"
},
@@ -250,6 +254,10 @@
"message": "The Date/Time Format you entered is either empty or invalid"
},
+ "error_download_queue_invalid_file_name": {
+ "message": "You have entered an invalid file name"
+ },
+
"error_validation_prefix": {
"message": "The Prefix you entered contains special characters"
},
@@ -339,6 +347,10 @@
"message": "Enable notification when extension is updated"
},
+ "general_settings_store_images_to_custom_folder": {
+ "message": "Store Images to AutoRename Folder"
+ },
+
"twitter_settings_desciption": {
"message": "Customize File Name Format for Twitter"
},
@@ -359,6 +371,10 @@
"message": "Include Website Title [Twitter]"
},
+ "twitter_settings_save_image_to_folder_based_on_username": {
+ "message": "Save image to folder based on username"
+ },
+
"lineblog_settings_description": {
"message": "Customize File Name Format for LINE BLOG"
},
@@ -397,5 +413,75 @@
"reddit_settings_include_date": {
"message": "Include Date (Based on System Date)"
+ },
+
+ "downloads_section_description": {
+ "message": "View files that are pending for download"
+ },
+
+ "downloads_section_dialog_empty_queue_title": {
+ "message": "Download Queue is Empty"
+ },
+
+ "downloads_section_empty_queue": {
+ "message": "Download queue is empty. To get started, right click on an image > AutoRename > Add to Download Queue"
+ },
+
+ "downloads_section_dialog_remove_from_queue_title": {
+ "message": "Remove from Queue"
+ },
+
+ "downloads_section_dialog_enter_zip_name": {
+ "message": "Enter name for ZIP file"
+ },
+
+ "downloads_section_dialog_queue_cleared": {
+ "message": "Download Queue Cleared"
+ },
+
+ "downloads_section_dialog_enter_zip_name_button_create": {
+ "message": "Create ZIP"
+ },
+
+ "downloads_section_dialog_remove_from_queue_body": {
+ "message": "Are you sure you want to remove this?"
+ },
+
+ "downloads_section_count_label": {
+ "message": "Download Queue -"
+ },
+
+ "downloads_section_button_remove": {
+ "message": "Remove"
+ },
+
+ "downloads_section_button_download": {
+ "message": "Download"
+ },
+
+ "downloads_section_button_clear_queue": {
+ "message": "Clear Queue"
+ },
+
+ "downloads_section_saving_file": {
+ "message": "Saving file:"
+ },
+
+ "downloads_section_saving_file_2": {
+ "message": "to ZIP -",
+ "description": "Dash is required since the percentage is after that"
+ },
+
+ "downloads_section_saving_file_3": {
+ "message": "Your files are currently downloading. Please wait..."
+ },
+
+ "downloads_section_downlaod_in_progress": {
+ "message": "Download in progress"
+ },
+
+ "downloads_section_downlaod_complete_title": {
+ "message": "Download Complete"
}
+
}
\ No newline at end of file
diff --git a/assets/icons8-clock-48.png b/assets/icons8-clock-48.png
new file mode 100644
index 0000000..500da44
Binary files /dev/null and b/assets/icons8-clock-48.png differ
diff --git a/assets/icons8-data-pending-48.png b/assets/icons8-data-pending-48.png
new file mode 100644
index 0000000..dfe28b5
Binary files /dev/null and b/assets/icons8-data-pending-48.png differ
diff --git a/assets/icons8-download-48.png b/assets/icons8-download-48.png
new file mode 100644
index 0000000..31a8275
Binary files /dev/null and b/assets/icons8-download-48.png differ
diff --git a/css/stylesheet.css b/css/stylesheet.css
index 42592bc..11e730a 100644
--- a/css/stylesheet.css
+++ b/css/stylesheet.css
@@ -1,6 +1,6 @@
body {
- background: #fff;
- font-family: 'Segoe UI', 'Roboto', '-apple-system', 'BlinkMacSystemFont', 'Arial', sans-serif !important;
+ background: #eeeeee3b;
+ font-family: 'Segoe UI Variable Display', 'Segoe UI', 'Roboto', '-apple-system', 'BlinkMacSystemFont', 'Arial', sans-serif !important;
font-size: 1.1em;
margin: 0;
padding: 0;
@@ -364,12 +364,75 @@ a:-webkit-any-link {
grid-template-columns: auto auto;
margin-top: 20px;
}
+
+.download-card {
+ align-items: center;
+ background-color: #fff;
+ box-shadow: 1px 1px 4px #e5e5e5;
+ border-radius: 5px;
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 5px;
+ padding: 3px;
+}
+
+.download-card .download-card-actions {
+ visibility: hidden;
+}
+
+.download-card:hover .download-card-actions {
+ visibility: visible;
+}
+
+.image-thumbnail {
+ width: 40px;
+ height: 40px;
+ margin: 5px 5px 5px 5px;
+ object-fit: cover;
+}
+
+.download-card-site {
+ background-color: #000;
+ border-radius: 5px;
+ color: #fff;
+ display: inline-flex;
+ margin-top: 5px;
+ padding: 2px;
+ font-size: 12px;
+}
+
+.download-card-actions {
+ margin-left: auto;
+}
+
+.download-card-actions-button-primary {
+ background-color: #03B4D3;
+ color: #fff;
+ margin-right: 10px;
+
+}
+
+
+.download-card-actions-button-secondary {
+ background-color: #fff0;
+ margin-right: 1px;
+}
+
+.beta_tag {
+ padding: 4px;
+ background-color: #FFCD03;
+ color: black;
+ border-radius: 6px;
+ margin-left: 5px;
+ font-weight: 600;
+}
+
/** EVERYTHING BELOW IS FOR DARK MODE ONLY **/
@media (prefers-color-scheme: dark) {
body {
- background-color: #121212;
+ background-color: #161616;
}
hr {
@@ -377,7 +440,7 @@ a:-webkit-any-link {
}
.navbar {
- background-color: #121212;
+ background-color: #181818;
border-right: 1px solid #1f1f1f;
color: #fff;
}
@@ -414,6 +477,46 @@ a:-webkit-any-link {
background: #242424;
box-shadow: 0 0 28px #00000059;
}
+
+ .download_card_container {
+ color: #B0B0B0;
+ }
+
+ .download-card {
+ background-color: #222222;
+ box-shadow: 1px 1px 4px #000;
+ }
+
+ .download-card-site {
+ color: #fff;
+ margin-top: 5px;
+ font-size: 12px;
+ }
+
+ .download-card-actions {
+ margin-left: auto;
+ }
+
+ .download-card-actions-button-primary {
+ background-color: #03B4D3;
+ color: #fff;
+ margin-right: 10px;
+
+ }
+
+ .download-card-actions-button-secondary {
+ background-color: #fff0;
+ color: #B0B0B0;
+ margin-right: 1px;
+ }
+
+ .download-card-file {
+ color: #B0B0B0;
+ }
+
+ .download-card-info {
+ color: #B0B0B0;
+ }
}
/** ANIMATION **/
diff --git a/js/BackgroundService.js b/js/BackgroundService.js
new file mode 100644
index 0000000..5f8b9a0
--- /dev/null
+++ b/js/BackgroundService.js
@@ -0,0 +1,43 @@
+/** MIT License
+ *
+ * Copyright (c) 2023 Dasutein
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ * is furnished to do so, subject to the following conditions:
+ *
+ * See more: https://github.com/ddasutein/AutoRename/blob/master/LICENSE
+ *
+ */
+
+const GLOBAL_VARIABLES = {
+
+ UA_HEADER: `${navigator.userAgent} ${chrome.runtime.getManifest().name}/${chrome.runtime.getManifest().version}`
+
+}
+
+const Backgroundscripts = [
+ "/js/Settings.js",
+ "/js/Runtime.js",
+ "/lib/Moment/moment.js",
+ "/lib/Moment/moment-with-locales.js",
+ "/js/Common/Utility.js",
+ "/js/Common/Debugger.js",
+ "/js/Common/SetTimeDate.js",
+ "/js/Twitter/TwitterContent.js",
+ "/js/LINE BLOG/LineBlogContent.js",
+ "/js/Reddit/RedditContent.js",
+ "/js/DownloadManager.js",
+ "/js/SaveAsEventHandle.js"
+]
+
+try {
+ Backgroundscripts.forEach((x)=>{
+ importScripts(x);
+ });
+
+}catch(e){
+ console.error(e);
+}
\ No newline at end of file
diff --git a/js/Common/Utility.js b/js/Common/Utility.js
index fa8a5b8..37573a9 100644
--- a/js/Common/Utility.js
+++ b/js/Common/Utility.js
@@ -75,6 +75,27 @@ let Utility = {
}
break;
}
+ }),
+
+ SetBadgeText : ((str)=>{
+ if (str == 0 || str == null){
+ chrome.action.setBadgeText({
+ text: ""
+ });
+ } else if (str > 0){
+ chrome.action.setBadgeText({
+ text: str.toString()
+ });
+ }
+
+ }),
+
+ CreateNewTab : ((url)=>{
+ return chrome.tabs.create({
+ url: url
+ });
})
+
+
}
\ No newline at end of file
diff --git a/js/DownloadManager.js b/js/DownloadManager.js
index 932b92d..4e24b51 100644
--- a/js/DownloadManager.js
+++ b/js/DownloadManager.js
@@ -1,6 +1,6 @@
/** MIT License
*
- * Copyright (c) 2021 Dasutein
+ * Copyright (c) 2023 Dasutein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
@@ -12,64 +12,111 @@
*
*/
-/**
- * Start downloading files
- *
- * @param {object} downloadQueue Images that are in queue to download
- */
-function StartDownload(downloadQueue){
-
- Object.values(Settings.Load().General.map((key, index)=>{
- switch (index){
-
- // global_enable_save_as_window
- case 1:
- if (key.value == true){
- downloadQueue.forEach((dq)=>{
- chrome.downloads.download({
- url: dq.url,
- filename: dq.filename,
- saveAs: true
- });
- });
- } else {
- downloadQueue.forEach((dq)=>{
- chrome.downloads.download({
- url: dq.url,
- filename: dq.filename,
- saveAs: false
- });
- });
- }
- break;
+var DownloadManager = {
+
+ UpdateBadge: (async ()=>{
+ generalSettings = Settings.Load().General.map((data) => {
+ return {
+ "key": data.key,
+ "value": data.value
+ }
+ }).reduce((obj, data) => {
+ obj[data.key] = data;
+ return obj;
+ }, {});
+
+ let downloadData = generalSettings["global_download_queue_data"].value;
+ if (downloadData.length > 0) {
+ downloadData = JSON.parse(downloadData)
+ } else {
+ downloadData = [];
}
- }));
-}
-chrome.downloads.onChanged.addListener(function (downloadDelta) {
- chrome.storage.local.get({
- showDownloadFolderCheckbox: false
- }, function (items) {
- if (downloadDelta.state && downloadDelta.state.current == "complete") {
+ Utility.SetBadgeText(downloadData.length);
+ }),
+
+ AddDownloadQueue: ((data) => {
- if (DevMode){
- const DEBUG_TAG = "downloadsOnChangedListener => ";
- console.log(DEBUG_TAG + downloadDelta.state.current);
+ generalSettings = Settings.Load().General.map((data) => {
+ return {
+ "key": data.key,
+ "value": data.value
}
+ }).reduce((obj, data) => {
+ obj[data.key] = data;
+ return obj;
+ }, {});
- let generalSettings = Settings.Load().General;
+ let downloadData = generalSettings["global_download_queue_data"].value;
+ if (downloadData.length > 0) {
+ downloadData = JSON.parse(downloadData)
+ } else {
+ downloadData = [];
+ }
- generalSettings.map((key, index) => {
- switch (index){
- case 0:
- if (key.value){
- chrome.downloads.showDefaultFolder();
- }
- break;
- }
+ data.forEach((x) => {
+ downloadData.push({
+ filename: x.filename,
+ url: x.url,
+ website: x.website
});
+ });
+
+ Utility.SetBadgeText(downloadData.length);
+ Settings.Save("global_download_queue_data", JSON.stringify(downloadData));
+
+ }),
+
+ ClearDownloadQueue: (() => {
+ Settings.Save("global_download_queue_data", "");
+ Utility.SetBadgeText(null);
+ }),
+
+ StartDownload: ((data) => {
+
+ let fileData = {};
+
+ generalSettings = Settings.Load().General.map((data) => {
+ return {
+ "key": data.key,
+ "value": data.value
+ }
+ }).reduce((obj, data) => {
+ obj[data.key] = data;
+ return obj;
+ }, {});
+
+ data.forEach((x) => {
+ chrome.downloads.download({
+ url: x.url,
+ filename: `${x.filename}`,
+ saveAs: generalSettings["global_enable_save_as_window"].value
+ }, ((id) => {
+
+ chrome.downloads.onChanged.addListener((delta) => {
+
+ if (id == delta.id) {
+
+ if (delta.state && delta.state.current == "in_progress") {
+ fileData["size"] = delta.fileSize.current;
+ fileData["url"] = delta.url.current;
+ fileData["id"] = delta.id;
+ fileData["name"] = x.filename;
+ }
+
+ if (delta.state && delta.state.current == "complete") {
+ generalSettings["global_show_download_folder"].value ? chrome.downloads.showDefaultFolder() : null;
+ tmp = [];
+ tmp.push(fileData);
+ Settings.Save("global_download_history_data", JSON.stringify(tmp));
+ DownloadManager.UpdateBadge();
+ }
+ }
+ });
+
+ }));
+ });
+ }),
+
+}
- return;
- }
- });
-});
\ No newline at end of file
diff --git a/js/ExtensionInfo.js b/js/ExtensionInfo.js
index 15dfe58..26e22f7 100644
--- a/js/ExtensionInfo.js
+++ b/js/ExtensionInfo.js
@@ -18,37 +18,37 @@
/**
* Loads extension data from Manifest
*/
-function LoadExtensionData(){
- let request = new XMLHttpRequest();
-
- request.onload = function (e) {
- if (request.readyState === 4) {
- if (request.readyState === 200) {
- console.log(request.responseText);
- } else {
- let jsonConfig = this.responseText;
- let jsonParse = JSON.parse(jsonConfig);
- document.getElementById("main_extension_name").textContent = jsonParse.short_name;
-
- if ((jsonParse.version).split(".")[3] >= 1000) {
- document.getElementById("main_extension_version").textContent = `${jsonParse.version} (dev-build)`;
-
- } else if ((jsonParse.version).split(".")[3] == undefined) {
- document.getElementById("main_extension_version").textContent = `${jsonParse.version}`;
+function LoadExtensionData() {
+ let request = new XMLHttpRequest();
+
+ request.onload = function (e) {
+ if (request.readyState === 4) {
+ if (request.readyState === 200) {
+ console.log(request.responseText);
+ } else {
+ let jsonConfig = this.responseText;
+ let jsonParse = JSON.parse(jsonConfig);
+ document.getElementById("main_extension_name").textContent = jsonParse.short_name;
+
+ if ((jsonParse.version).split(".")[3] >= 1000) {
+ document.getElementById("main_extension_version").textContent = `${jsonParse.version} (dev-build)`;
+
+ } else if ((jsonParse.version).split(".")[3] == undefined) {
+ document.getElementById("main_extension_version").textContent = `${jsonParse.version}`;
+ }
+
+ // document.getElementById("extension_description").innerHTML = jsonParse.description;
+ console.log("AND IT SHALL BE BESTOWNED UPON YOU, THE STAR WHICH YOU HAVE LONGED FOR—");
+ console.log("Status: " + request.statusText + "👍");
+ }
}
-
- // document.getElementById("extension_description").innerHTML = jsonParse.description;
- console.log("AND IT SHALL BE BESTOWNED UPON YOU, THE STAR WHICH YOU HAVE LONGED FOR—");
- console.log("Status: " + request.statusText + "👍");
- }
}
- }
- request.open('GET', chrome.extension.getURL('manifest.json'), true);
- request.onerror = function (e) {
- console.error(request.statusText);
- };
-
- request.send(null);
+ request.open('GET', chrome.runtime.getURL('manifest.json'), true);
+ request.onerror = function (e) {
+ console.error(request.statusText);
+ };
+
+ request.send(null);
}
@@ -57,124 +57,139 @@ function LoadExtensionData(){
*/
function GetSystemInfo() {
- let appVersion = navigator.appVersion;
- let platformData = {};
-
- function isChromium() {
- for (brand_version_pair of navigator.userAgentData.brands) {
- if (brand_version_pair.brand == "Chromium") {
- return true;
- }
- }
- return false;
- }
-
- function queryOtherOS(av){
- console.log(av)
- function getMacOS(av){
- let macVersion = av.substring(31, av.lastIndexOf("OS X") + 12);
- return `macOS ${macVersion.replace(/_/gi, ".")}`;
- }
-
- function getChromeOS(av){
- let chromeOSVersion = av.substring(31, av.lastIndexOf("CrOS") + 5);
- return `Chrome OS ${chromeOSVersion}`
- }
-
- if (av.includes("Windows NT 6.1")){
- return "Windows 7";
- } else if (av.includes("Windows NT 6.2")){
- return "Windows 8";
- } else if (av.includes("Windows NT 6.3")){
- return "Windows 8.1";
- } else if (av.includes("Macintosh")){
- return getMacOS(av);
- } else if (av.includes("CrOS")){
- return getChromeOS(av);
- } else {
- return null;
- }
-
- }
-
- if (isChromium() == true) {
- navigator.userAgentData.getHighEntropyValues(
- ["architecture", "model", "platform", "platformVersion", "uaFullVersion"])
- .then(ua => {
-
- if (!!ua.uaFullVersion){
- platformData["browserVersion"] = `(${ua.uaFullVersion})`;
- }
-
- if (ua.platform == "Windows"){
- switch (ua.platformVersion){
-
- case "0.0.0":
- // Fallback to legacy OS query
- queryOtherOS(appVersion);
- break;
-
- case "1.0.0":
- platformData["os"] = "Windows 10 version 1507";
- break;
-
- case "2.0.0":
- platformData["os"] = "Windows 10 version 1511";
- break;
-
- case "3.0.0":
- platformData["os"] = "Windows 10 version 1607";
- break;
-
- case "4.0.0":
- platformData["os"] = "Windows 10 version 1703";
- break;
-
- case "5.0.0":
- platformData["os"] = "Windows 10 version 1709";
- break;
-
- case "6.0.0":
- platformData["os"] = "Windows 10 version 1803";
- break;
-
- case "7.0.0":
- platformData["os"] = "Windows 10 version 1809";
- break;
-
- case "8.0.0":
- platformData["os"] = "Windows 10 version 1903/1909";
- break;
-
- case "10.0.0":
- platformData["os"] = "Windows 10 version 2004/20H2/21H1";
- break;
-
- case "14.0.0":
- platformData["os"] = "Windows 11 version 21H2";
- break;
-
- default:
- platformData["os"] = "-";
- break;
-
- }
- } else {
- platformData["os"] = queryOtherOS(appVersion);
- }
-
-
- if (ua.architecture == "x86"){
- if (ua.platform == "Windows"){
- platformData["architecture"] = `${ua.platform} - Intel/AMD x86-64`;
- }
- }
- document.getElementById("system_information_platform_value").innerHTML = platformData.architecture;
- document.getElementById("system_information_os_value").innerHTML = platformData.os;
- document.getElementById("system_information_browser_value").innerHTML = `${ua.brands[1].brand} ${platformData.browserVersion}`;
-
- });
- }
+ let appVersion = navigator.appVersion;
+ let platformData = {};
+
+ function isChromium() {
+ for (brand_version_pair of navigator.userAgentData.brands) {
+ if (brand_version_pair.brand == "Chromium") {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function queryOtherOS(av) {
+ console.log(av)
+
+ function getMacOS(av) {
+ let macVersion = av.substring(31, av.lastIndexOf("OS X") + 12);
+ return `macOS ${macVersion.replace(/_/gi, ".")}`;
+ }
+
+ function getChromeOS(av) {
+ let chromeOSVersion = av.substring(31, av.lastIndexOf("CrOS") + 5);
+ return `Chrome OS ${chromeOSVersion}`
+ }
+
+ if (av.includes("Windows NT 6.1")) {
+ return "Windows 7";
+ } else if (av.includes("Windows NT 6.2")) {
+ return "Windows 8";
+ } else if (av.includes("Windows NT 6.3")) {
+ return "Windows 8.1";
+ } else if (av.includes("Macintosh")) {
+ return getMacOS(av);
+ } else if (av.includes("CrOS")) {
+ return getChromeOS(av);
+ } else {
+ return null;
+ }
+
+ }
+
+ if (isChromium() == true) {
+ navigator.userAgentData.getHighEntropyValues(
+ ["architecture", "model", "platform", "platformVersion", "uaFullVersion"])
+ .then(ua => {
+
+ if (!!ua.uaFullVersion) {
+ platformData["browserVersion"] = `(${ua.uaFullVersion})`;
+ }
+
+ if (ua.platform == "Windows") {
+ switch (ua.platformVersion) {
+
+ case "0.0.0":
+ // Fallback to legacy OS query
+ queryOtherOS(appVersion);
+ break;
+
+ case "1.0.0":
+ platformData["os"] = "Windows 10 version 1507";
+ break;
+
+ case "2.0.0":
+ platformData["os"] = "Windows 10 version 1511";
+ break;
+
+ case "3.0.0":
+ platformData["os"] = "Windows 10 version 1607";
+ break;
+
+ case "4.0.0":
+ platformData["os"] = "Windows 10 version 1703";
+ break;
+
+ case "5.0.0":
+ platformData["os"] = "Windows 10 version 1709";
+ break;
+
+ case "6.0.0":
+ platformData["os"] = "Windows 10 version 1803";
+ break;
+
+ case "7.0.0":
+ platformData["os"] = "Windows 10 version 1809";
+ break;
+
+ case "8.0.0":
+ platformData["os"] = "Windows 10 version 1903/1909";
+ break;
+
+ case "10.0.0":
+ platformData["os"] = "Windows 10 version 2004/20H2/21H1";
+ break;
+
+ case "14.0.0":
+ platformData["os"] = "Windows 11 version 21H2";
+ break;
+
+ case "15.0.0":
+ platformData["os"] = "Windows 11 version 22H2";
+ break;
+
+ default:
+ platformData["os"] = "-";
+ break;
+
+ }
+ } else {
+ platformData["os"] = queryOtherOS(appVersion);
+ }
+
+
+ if (ua.architecture == "x86") {
+ if (ua.platform == "Windows") {
+ platformData["architecture"] = `${ua.platform} - Intel/AMD x86-64`;
+ } else if (ua.platform == "macOS"){
+ platformData["architecture"] = `${ua.platform} - Intel/AMD x86-64`;
+ }
+ }
+
+ if (ua.architecture == "arm"){
+ if (ua.platform == "macOS"){
+ platformData["architecture"] = `${ua.platform} - Apple Silicon`;
+ } else if (ua.platform == "Windows"){
+ platformData["architecture"] = `${ua.platform} - ARM`;
+ }
+ }
+ document.getElementById("system_information_platform_value").innerHTML = platformData.architecture;
+ document.getElementById("system_information_os_value").innerHTML = platformData.os;
+ document.getElementById("system_information_browser_value").innerHTML = `${ua.brands[1].brand} ${platformData.browserVersion}`;
+
+ });
+ }
}
diff --git a/js/LINE BLOG/LineBlogContent.js b/js/LINE BLOG/LineBlogContent.js
index 46e9bb5..200b187 100644
--- a/js/LINE BLOG/LineBlogContent.js
+++ b/js/LINE BLOG/LineBlogContent.js
@@ -12,7 +12,7 @@
*
*/
-function SaveLINEBlogMedia(tabUrl, url, customObj){
+function SaveLINEBlogMedia(tabUrl, url, linkUrl, customObj){
if (BrowserTabInfo.URL.match(Website.LINE_BLOG)){
lineblogTitle = BrowserTabInfo.Title.split("-")[1] != undefined ? BrowserTabInfo.Title.split("-")[1].trim().toString() : "";
@@ -114,7 +114,11 @@ function SaveLINEBlogMedia(tabUrl, url, customObj){
filename: buildFileName(fileNameObj) + ".jpg",
url: url
});
-
- StartDownload(lineBlogImageFile);
+ console.log(customObj);
+ if (customObj.download_queue == true){
+ AddToDownloadQueue(lineBlogImageFile[0].url, lineBlogImageFile[0].filename, "LINE BLOG")
+ } else {
+ StartDownload(lineBlogImageFile);
+ }
}
\ No newline at end of file
diff --git a/js/OptionsPage.js b/js/OptionsPage.js
index cc1e4b7..7c31083 100644
--- a/js/OptionsPage.js
+++ b/js/OptionsPage.js
@@ -1,6 +1,6 @@
/** MIT License
*
- * Copyright (c) 2022 Dasutein
+ * Copyright (c) 2023 Dasutein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
@@ -21,7 +21,7 @@ const messageBox = {
Save: (() => {
- return swal ({
+ return swal({
title: "",
text: chrome.i18n.getMessage("context_save_success"),
icon: "success",
@@ -31,7 +31,7 @@ const messageBox = {
});
}),
- Warning : ((title, message)=>{
+ Warning: ((title, message) => {
return swal({
title: title,
text: message,
@@ -54,18 +54,18 @@ const specialCharacters = new RegExp(/[?!@#$%^&*(),';\:*"<>|/]/g);
* @param {*} dateString Date string
* @returns
*/
-function ValidateDateTimeFormat(settingsKey, dateString){
+function ValidateDateTimeFormat(settingsKey, dateString) {
let output = {};
- if (dateString == "" || dateString == null || dateString == undefined){
+ if (dateString == "" || dateString == null || dateString == undefined) {
output["title"] = chrome.i18n.getMessage("error_title_invalid");
output["message"] = chrome.i18n.getMessage("error_validation_date_time_format");
output["is_error"] = true;
} else {
// Check for special characters that are not allowed by the operating system
- if (dateString.match(specialCharacters)){
+ if (dateString.match(specialCharacters)) {
isValidDateFormat = false;
output["title"] = chrome.i18n.getMessage("error_title_invalid");
@@ -76,9 +76,9 @@ function ValidateDateTimeFormat(settingsKey, dateString){
output["is_error"] = false;
}
- }
+ }
- if (output.is_error){
+ if (output.is_error) {
return output;
} else {
Settings.Save(settingsKey, dateString);
@@ -93,10 +93,10 @@ function ValidateDateTimeFormat(settingsKey, dateString){
* @param {*} prefixString
* @returns
*/
-function ValidatePrefix(settingsKey, prefixString){
+function ValidatePrefix(settingsKey, prefixString) {
let output = {};
- if (prefixString.match(specialCharacters)){
+ if (prefixString.match(specialCharacters)) {
output["title"] = chrome.i18n.getMessage("error_title_invalid");
output["message"] = chrome.i18n.getMessage("error_validation_prefix");
output["is_error"] = true;
@@ -104,7 +104,7 @@ function ValidatePrefix(settingsKey, prefixString){
output["is_error"] = false;
}
- if (output.is_error){
+ if (output.is_error) {
return output;
} else {
Settings.Save(settingsKey, prefixString);
@@ -128,6 +128,9 @@ document.onreadystatechange = () => {
case "global_notifications_updated":
document.getElementById("general_settings_notification_updated").checked = x.value
break;
+ case "global_use_autorename_folder":
+ document.getElementById("general_settings_use_autorename_folder").checked = x.value;
+ break;
}
});
@@ -167,6 +170,10 @@ document.onreadystatechange = () => {
case "twitter_settings_custom_prefix":
document.getElementById("twitter_settings_custom_prefix").value = x.value;
break;
+
+ case "twitter_save_image_to_folder_based_on_username":
+ document.getElementById("twitter_settings_save_to_folder_by_username").checked = x.value;
+ break;
}
});
@@ -249,7 +256,7 @@ document.onreadystatechange = () => {
}
- });
+ });
}
}
@@ -260,19 +267,44 @@ document.onreadystatechange = () => {
* Perform execution after page has loaded
*
*/
-document.addEventListener("DOMContentLoaded", (()=>{
+document.addEventListener("DOMContentLoaded", (() => {
//#region Tab Switching Logic
const tabs = document.querySelectorAll("[data-tab-target]");
const tabContents = document.querySelectorAll("[data-tab-content]");
- tabs.forEach((tab) => {
- tab.addEventListener("click", () => {
- const target = document.querySelector(tab.dataset.tabTarget);
- tabContents.forEach(tabContent => tabContent.classList.remove("active"));
- tabs.forEach(tabContent => tabContent.classList.remove("active"));
- tab.classList.add("active");
- target.classList.add("active");
+ const optionsConfig = Settings.Load().OptionsUI.map((data)=>{
+ return {
+ "key": data.key,
+ "value": data.value
+ }
+ }).reduce((obj, data)=>{
+ obj[data.key] = data;
+ return obj;
+ }, {});
+
+ createDownloadCardItem();
+
+ tabs.forEach((tab, idx) => {
+
+ if (idx == optionsConfig["optionsUITabIndexNumber"].value){
+ const target = document.querySelector(optionsConfig["optionsUITabName"].value);
+ tabContents.forEach(tabContent => tabContent.classList.remove("active"));
+ tabs.forEach(tabContent => tabContent.classList.remove("active"));
+ tab.classList.add("active");
+ target.classList.add("active");
+
+ }
+
+ tab.addEventListener("click", () => {
+ const target = document.querySelector(tab.dataset.tabTarget);
+ tabContents.forEach(tabContent => tabContent.classList.remove("active"));
+ tabs.forEach(tabContent => tabContent.classList.remove("active"));
+ tab.classList.add("active");
+ target.classList.add("active");
+
+ Settings.Save("optionsUITabName", tab.dataset.tabTarget);
+ Settings.Save("optionsUITabIndexNumber", idx);
});
});
@@ -304,65 +336,71 @@ document.addEventListener("DOMContentLoaded", (()=>{
*/
switch (buttons.id) {
-
+
+ // case "download-primary":
+ // buttons.addEventListener("click", (() => {
+ // alert("TEST " + buttons.id)
+ // }));
+ // break;
+
case "button_help_twitter":
- buttons.addEventListener("click", (()=>{
+ buttons.addEventListener("click", (() => {
chrome.tabs.create({
url: "https://github.com/ddasutein/AutoRename/wiki/%E2%9A%99-Settings#twitter"
})
}));
break;
-
+
case "button_help_lineblog":
- buttons.addEventListener("click", (()=>{
+ buttons.addEventListener("click", (() => {
chrome.tabs.create({
url: "https://github.com/ddasutein/AutoRename/wiki/%E2%9A%99-Settings#line-blog"
})
}));
break;
-
+
case "button_help_reddit":
- buttons.addEventListener("click", (()=>{
+ buttons.addEventListener("click", (() => {
chrome.tabs.create({
url: "https://github.com/ddasutein/AutoRename/wiki/%E2%9A%99-Settings#reddit"
})
}));
break;
-
case "button_save_general":
buttons.addEventListener("click", (() => {
Settings.Save("global_show_download_folder", document.getElementById("general_settings_auto_show_download_folder").checked);
Settings.Save("global_enable_save_as_window", document.getElementById("general_settings_enable_save_as_window").checked);
Settings.Save("global_notifications_updated", document.getElementById("general_settings_notification_updated").checked);
+ Settings.Save("global_use_autorename_folder", document.getElementById("general_settings_use_autorename_folder").checked)
messageBox.Save();
}));
-
+
break;
-
+
case "button_save_twitter":
-
+
buttons.addEventListener("click", (() => {
try {
-
- if (temp.length > 0){
+
+ if (temp.length > 0) {
temp = [];
errorCode = {};
}
- if (document.getElementById("twitter_settings_include_date_checkbox").checked && !document.getElementById("twitter_settings_prefer_locale_format").checked && document.getElementById("twitter_settings_select_date_format").value == "custom"){
+ if (document.getElementById("twitter_settings_include_date_checkbox").checked && !document.getElementById("twitter_settings_prefer_locale_format").checked && document.getElementById("twitter_settings_select_date_format").value == "custom") {
temp.push(ValidateDateTimeFormat("twitter_settings_custom_date_format", document.getElementById("twitter_settings_custom_date_format").value));
}
temp.push(ValidatePrefix("twitter_settings_custom_prefix", document.getElementById("twitter_settings_custom_prefix").value));
- temp.forEach((x)=>{
- if (x.is_error == true){
+ temp.forEach((x) => {
+ if (x.is_error == true) {
errorCode["title"] = x.title;
errorCode["message"] = x.message;
throw errorCode;
- }
+ }
});
-
+
Settings.Save("twitter_include_mention_symbol", document.getElementById("twitter_settings_include_mention_symbol").checked);
Settings.Save("twitter_include_tweet_id", document.getElementById("twitter_settings_include_tweet_id").checked);
Settings.Save("twitter_include_date", document.getElementById("twitter_settings_include_date_checkbox").checked);
@@ -370,46 +408,46 @@ document.addEventListener("DOMContentLoaded", (()=>{
Settings.Save("twitter_random_string_length", document.getElementById("twitter_settings_string_length").value);
Settings.Save("twitter_include_website_title", document.getElementById("twitter_settings_site_title").checked);
Settings.Save("twitter_prefer_locale_format", document.getElementById("twitter_settings_prefer_locale_format").checked);
+ Settings.Save("twitter_save_image_to_folder_based_on_username", document.getElementById("twitter_settings_save_to_folder_by_username").checked);
// See dev note #1
Settings.Save("twitter_settings_custom_date_format", document.getElementById("twitter_settings_custom_date_format").value);
messageBox.Save();
-
- } catch(e){
+
+ } catch (e) {
console.error(e);
messageBox.Warning(e.title, e.message);
}
-
+
}));
-
+
break;
-
+
case "button_save_lineblog":
buttons.addEventListener("click", (() => {
-
+
try {
- if (temp.length > 0){
+ if (temp.length > 0) {
temp = [];
errorCode = {};
}
- if (document.getElementById("lineblog_settings_include_date").checked && !document.getElementById("lineblog_settings_prefer_locale_format").checked && document.getElementById("lineblog_settings_select_date_format").value == "custom"){
+ if (document.getElementById("lineblog_settings_include_date").checked && !document.getElementById("lineblog_settings_prefer_locale_format").checked && document.getElementById("lineblog_settings_select_date_format").value == "custom") {
temp.push(ValidateDateTimeFormat("lineblogCustomDateFormat", document.getElementById("lineblog_settings_custom_date_format").value));
}
-
+
temp.push(ValidatePrefix("lineblogCustomPrefix", document.getElementById("lineblog_settings_custom_prefix").value));
- temp.forEach((x)=>{
- if (x.is_error == true){
+ temp.forEach((x) => {
+ if (x.is_error == true) {
errorCode["title"] = x.title;
errorCode["message"] = x.message;
throw errorCode;
- }
+ }
});
- console.log( document.getElementById("lineblog_settings_custom_date_format").value)
Settings.Save("lbPrefIncludeWebsiteTitle", document.getElementById("lineblog_settings_include_site_title").checked);
Settings.Save("lbPrefIncludeBlogTitle", document.getElementById("lineblog_include_blog_title").checked);
Settings.Save("lbPrefUseDate", document.getElementById("lineblog_settings_include_date").checked);
@@ -417,45 +455,45 @@ document.addEventListener("DOMContentLoaded", (()=>{
Settings.Save("lineblog_convert_title_romaji", document.getElementById("lineblog_settings_convert_title_romaji").checked);
Settings.Save("lineblogDateFormat", document.getElementById("lineblog_settings_select_date_format").value);
Settings.Save("lineblogCustomPrefix", document.getElementById("lineblog_settings_custom_prefix").value);
-
+
// See dev note #1
Settings.Save("lineblogCustomDateFormat", document.getElementById("lineblog_settings_custom_date_format").value);
messageBox.Save();
-
- } catch(e){
+
+ } catch (e) {
console.error(e)
messageBox.Warning(e.title, e.message);
}
-
+
}));
-
+
break;
-
+
case "button_save_reddit":
buttons.addEventListener("click", (() => {
try {
- if (temp.length > 0){
+ if (temp.length > 0) {
temp = [];
errorCode = {};
}
- if (document.getElementById("reddit_settings_include_date").checked && !document.getElementById("reddit_settings_prefer_locale_format").checked &&document.getElementById("reddit_settings_custom_date_format").value == "custom"){
+ if (document.getElementById("reddit_settings_include_date").checked && !document.getElementById("reddit_settings_prefer_locale_format").checked && document.getElementById("reddit_settings_custom_date_format").value == "custom") {
ValidateDateTimeFormat("redditCustomDateFormat", document.getElementById("reddit_settings_custom_date_format").value);
}
temp.push(ValidatePrefix("redditCustomPrefix", document.getElementById("reddit_settings_custom_prefix").value));
- temp.forEach((x)=>{
- if (x.is_error == true){
+ temp.forEach((x) => {
+ if (x.is_error == true) {
errorCode["title"] = x.title;
errorCode["message"] = x.message;
throw errorCode;
- }
+ }
});
-
+
Settings.Save("redditIncludeWebsite", document.getElementById("reddit_settings_site_title").checked);
Settings.Save("redditIncludePostID", document.getElementById("reddit_settings_subreddit_post_id").checked);
Settings.Save("redditIncludeDate", document.getElementById("reddit_settings_include_date").checked);
@@ -467,15 +505,15 @@ document.addEventListener("DOMContentLoaded", (()=>{
Settings.Save("redditCustomDateFormat", document.getElementById("reddit_settings_custom_date_format").value);
messageBox.Save();
- } catch(e){
+ } catch (e) {
console.error(e);
messageBox.Warning(e.title, e.message);
}
}));
-
+
break;
-
+
case "button_help_general":
buttons.addEventListener("click", (() => {
chrome.tabs.create({
@@ -483,7 +521,6 @@ document.addEventListener("DOMContentLoaded", (()=>{
});
}));
break;
-
case "button_help_twitter":
buttons.addEventListener("click", (() => {
chrome.tabs.create({
@@ -491,7 +528,6 @@ document.addEventListener("DOMContentLoaded", (()=>{
});
}));
break;
-
case "button_help_lineblog":
buttons.addEventListener("click", (() => {
chrome.tabs.create({
@@ -499,7 +535,6 @@ document.addEventListener("DOMContentLoaded", (()=>{
});
}));
break;
-
case "button_help_reddit":
buttons.addEventListener("click", (() => {
chrome.tabs.create({
@@ -507,16 +542,120 @@ document.addEventListener("DOMContentLoaded", (()=>{
});
}));
break;
-
-
+
+ case "button_download_all":
+ buttons.addEventListener("click", (()=>{
+ let isDone = false;
+ function createZip(zipName){
+
+ let downloadJSONData = Settings.Load().General;
+ downloadJSONData = downloadJSONData.filter((x) => x.key == "global_download_queue_data").map((x) => x.value)[0];
+ if (typeof downloadJSONData == "string" && downloadJSONData.length == 0) {
+ return swal({
+ title: chrome.i18n.getMessage("downloads_section_dialog_empty_queue_title"),
+ text: chrome.i18n.getMessage("downloads_section_empty_queue"),
+ icon: "error",
+ buttons: false,
+ dangerMode: false
+ });
+ }
+
+ downloadJSONData = JSON.parse(downloadJSONData);
+
+ let zip = new JSZip();
+ downloadJSONData.forEach((x)=>{
+ zip.file(x.filename, urlToPromise(x.url), { binary: true});
+ });
+ zip.generateAsync({
+ type: "blob",streamFiles: true
+ }, function updateCallback(metaData){
+
+ if(metaData.currentFile) {
+
+ if (metaData.percent > 0){
+ msg = `${chrome.i18n.getMessage("downloads_section_saving_file")} ${metaData.currentFile} ${chrome.i18n.getMessage("downloads_section_saving_file_2")} ${metaData.percent.toFixed(2)}%`;
+ } else if (metaData.percent == 0){
+ msg = `${chrome.i18n.getMessage("downloads_section_saving_file_3")}`
+ }
+
+ swal({
+ title: chrome.i18n.getMessage("downloads_section_downlaod_in_progress"),
+ text: `${msg}`,
+ closeOnClickOutside: false,
+ buttons: false
+ })
+
+ }
+
+ }).then(function callback(blob){
+ saveAs(blob, zipName)
+ swal({
+ title: chrome.i18n.getMessage("downloads_section_downlaod_complete_title"),
+ text: zipName,
+ icon: "success"
+ }).then(()=>{
+ Settings.Save("global_download_queue_data", "");
+ window.location.reload();
+ DownloadManager.UpdateBadge();
+ Utility.SetBadgeText(0);
+ });
+
+ });
+ return isDone;
+ }
+
+
+ swal({
+ text: chrome.i18n.getMessage("downloads_section_dialog_enter_zip_name"),
+ content: "input",
+ button: {
+ text: chrome.i18n.getMessage("downloads_section_dialog_enter_zip_name_button_create"),
+ closeModal: false,
+ },
+ })
+ .then(name => {
+ if (!name) throw null;
+
+ return createZip(name);
+ }).catch(error =>{
+ if (error == null){
+ swal({
+ title: chrome.i18n.getMessage("error_title"),
+ text: chrome.i18n.getMessage("error_download_queue_invalid_file_name"),
+ icon: "warning",
+ })
+ }
+ console.error(error);
+ })
+
+
+ }));
+ break;
+
+ case "button_clear_queue":
+ buttons.addEventListener("click", (()=>{
+ swal({
+ title: "",
+ text: chrome.i18n.getMessage("downloads_section_dialog_queue_cleared"),
+ icon: "success",
+ buttons: false,
+ dangerMode: false,
+ timer: messageBox.autoCloseTimer
+ });
+ DownloadManager.ClearDownloadQueue();
+ window.location.reload();
+ }));
+ break;
+
+
}
});
- document.querySelectorAll("input[type=checkbox]").forEach((input) => {
-
- switch (input.id){
+ document.querySelectorAll("input[type=checkbox]").forEach((input) => {
+
+ switch (input.id) {
case "twitter_settings_include_date_checkbox":
- input.addEventListener("change", (()=>{
+ input.addEventListener("change", (() => {
Utility.UpdateFieldDisplay("checkbox", input.checked, "twitter_settings_prefer_locale_format");
Utility.UpdateFieldDisplay("checkbox", input.checked, "twitter_settings_select_date_format");
@@ -526,7 +665,7 @@ document.addEventListener("DOMContentLoaded", (()=>{
break;
case "lineblog_settings_include_date":
- input.addEventListener("change", (()=>{
+ input.addEventListener("change", (() => {
Utility.UpdateFieldDisplay("checkbox", input.checked, "lineblog_settings_prefer_locale_format");
Utility.UpdateFieldDisplay("checkbox", input.checked, "lineblog_settings_select_date_format");
Utility.UpdateFieldDisplay("checkbox", input.checked, "lineblog_settings_custom_date_format");
@@ -534,7 +673,7 @@ document.addEventListener("DOMContentLoaded", (()=>{
break;
case "reddit_settings_include_date":
- input.addEventListener("change", (()=>{
+ input.addEventListener("change", (() => {
Utility.UpdateFieldDisplay("checkbox", input.checked, "reddit_settings_prefer_locale_format");
Utility.UpdateFieldDisplay("checkbox", input.checked, "reddit_settings_select_date_format");
Utility.UpdateFieldDisplay("checkbox", input.checked, "reddit_settings_custom_date_format");
@@ -543,4 +682,180 @@ document.addEventListener("DOMContentLoaded", (()=>{
}
});
-}));
\ No newline at end of file
+}));
+
+function urlToPromise(url) {
+ return new Promise(function (resolve, reject) {
+ JSZipUtils.getBinaryContent(url, function (err, data) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
+ });
+}
+
+function createDownloadCardItem(indexNumber, objData){
+
+ let download_card_container = document.getElementById("download_card_container");
+ let download_card_container_history = document.getElementById("download_card_container_history");
+ let download_queue_label = document.getElementById("download-queue-label");
+ download_queue_label.textContent = `${chrome.i18n.getMessage("downloads_section_count_label")} (0)`;
+
+ let downloadJSONData = Settings.Load().General;
+ downloadJSONData = downloadJSONData.filter((x) => x.key == "global_download_queue_data").map((x) => x.value)[0];
+
+ /**
+ * This scenario triggers on a fresh installation as by default, it is a string value.
+ */
+ if (typeof downloadJSONData == "string" && downloadJSONData.length == 0){
+ download_card_container.innerHTML += `
${chrome.i18n.getMessage("downloads_section_empty_queue")}
`;
+ return;
+ }
+
+ downloadJSONData = JSON.parse(downloadJSONData);
+ if (downloadJSONData.length == 0){
+ download_card_container.innerHTML += `${chrome.i18n.getMessage("downloads_section_empty_queue")}
`;
+ return;
+ }
+
+
+ download_queue_label.textContent = `${chrome.i18n.getMessage("downloads_section_count_label")} (${downloadJSONData.length})`;
+
+ let buttonIds = [];
+ downloadJSONData.forEach((x, idx)=>{
+
+ buttonIds.push({
+ primary: `download-primary-${idx}`,
+ secondary: `download-secondary-${idx}`,
+ download_card_ids: `download-card-${idx}`,
+ index_count: idx
+ });
+ download_card_container.innerHTML += `
+
+
+
+
${x.website}
+
${x.filename}
+
+
+
+
+
+
+ `;
+ });
+ updateDownloadButtonListeners(buttonIds);
+ buttonIds = []; // Clear when done
+
+}
+
+function createDownloadHistoryCardItem(indexNumber, objData){
+
+ let download_card_container = document.getElementById("download_card_container_history");
+ let download_card_container_history = document.getElementById("download_card_container_history");
+ let download_queue_label = document.getElementById("download-queue-label");
+ download_queue_label.textContent = `${chrome.i18n.getMessage("downloads_section_count_label")} (0)`;
+
+ let downloadJSONData = Settings.Load().General;
+ downloadJSONData = downloadJSONData.filter((x) => x.key == "global_download_history_data").map((x) => x.value)[0];
+
+ /**
+ * This scenario triggers on a fresh installation as by default, it is a string value.
+ */
+ if (typeof downloadJSONData == "string" && downloadJSONData.length == 0){
+ download_card_container.innerHTML += `${chrome.i18n.getMessage("downloads_section_empty_queue")}
`;
+ return;
+ }
+
+ downloadJSONData = JSON.parse(downloadJSONData);
+ if (downloadJSONData.length == 0){
+ download_card_container.innerHTML += `${chrome.i18n.getMessage("downloads_section_empty_queue")}
`;
+ return;
+ }
+
+
+ download_queue_label.textContent = `${chrome.i18n.getMessage("downloads_section_count_label")} (${downloadJSONData.length})`;
+
+ let buttonIds = [];
+ downloadJSONData.forEach((x, idx)=>{
+
+ buttonIds.push({
+ primary: `download-primary-${idx}`,
+ secondary: `download-secondary-${idx}`,
+ download_card_ids: `download-card-${idx}`,
+ index_count: idx
+ });
+ download_card_container.innerHTML += `
+
+
+
+
${x.website}
+
${x.filename}
+
+
+
+
+
+
+ `;
+ });
+ updateDownloadButtonListeners(buttonIds);
+ buttonIds = []; // Clear when done
+
+}
+
+function updateDownloadButtonListeners(downloadBtns){
+
+ let downloadJSONData = Settings.Load().General;
+ downloadJSONData = downloadJSONData.filter((x) => x.key == "global_download_queue_data").map((x) => x.value)[0];
+ downloadJSONData = JSON.parse(downloadJSONData);
+ if (downloadJSONData.length == 0 || typeof downloadJSONData != "object") return;
+
+ downloadBtns.forEach((x)=>{
+
+ document.querySelectorAll("button").forEach((buttons) => {
+
+ if (x.secondary == buttons.id){
+ buttons.addEventListener("click", ((e) => {
+
+ swal({
+ title: "Remove from Queue",
+ text: "Are you sure you want to remove this file?",
+ icon: "warning",
+ buttons: {
+ cancel: true,
+ confirm: true
+ }
+ }).then((result)=>{
+ if (result){
+ let id = e.target.id;
+ id = id.split("-")[2]; // Temp workaround as for some reason, there is no value despite it being entered in the for-loop
+
+ downloadJSONData = downloadJSONData.filter((v,idx)=>idx != +id);
+ Settings.Save("global_download_queue_data", JSON.stringify(downloadJSONData));
+ Utility.SetBadgeText(downloadJSONData.length);
+ window.location.reload();
+ }
+ }).catch((error)=> console.error(error));
+ }));
+ }
+
+ if (x.primary == buttons.id){
+ buttons.addEventListener("click", ((e) => {
+ data = downloadJSONData.filter((v,idx)=>idx == e.target.value)[0];
+ DownloadManager.StartDownload([{filename: data.filename, url: data.url}]);
+
+ // Remove once downloaded
+ data = downloadJSONData.filter((v, idx) => idx != e.target.value);
+ Settings.Save("global_download_queue_data", JSON.stringify(data));
+
+ }));
+ }
+
+ });
+
+ });
+
+}
diff --git a/js/Runtime.js b/js/Runtime.js
index e7b8a46..b08e62a 100644
--- a/js/Runtime.js
+++ b/js/Runtime.js
@@ -30,7 +30,7 @@ function ShowUpdateMsg(){
title: chrome.i18n.getMessage("message_toast_button_ok"),
}
],
- "iconUrl": chrome.extension.getURL("assets/autorename-128px.png"),
+ "iconUrl": chrome.runtime.getURL("assets/autorename-128px.png"),
"type": "basic",
"title": getExtensionName,
"message": chrome.i18n.getMessage("message_toast_new_version") + " " + getExtensionVersion
@@ -46,7 +46,7 @@ function ShowWelcomeMsg(){
}, {
title: chrome.i18n.getMessage("message_toast_button_ok")
}],
- "iconUrl": chrome.extension.getURL("assets/autorename-128px.png"),
+ "iconUrl": chrome.runtime.getURL("assets/autorename-128px.png"),
"type": "basic",
"title": `${chrome.i18n.getMessage("message_toast_welcome_title")} ${getExtensionName}`,
"message": `${chrome.i18n.getMessage("message_toast_welcome_body")}`
diff --git a/js/SaveAsEventHandle.js b/js/SaveAsEventHandle.js
index d784a02..0e6e863 100644
--- a/js/SaveAsEventHandle.js
+++ b/js/SaveAsEventHandle.js
@@ -20,7 +20,8 @@
const contextMenuId = {
saveImage: "saveImage",
saveImageWithCustomPrefix: "saveImageWithCustomPrefix",
- viewOriginalImage: "viewOriginalImage"
+ viewOriginalImage: "viewOriginalImage",
+ addDownloadQueue: "downloadqueue"
}
chrome.runtime.onInstalled.addListener(()=>{
@@ -30,13 +31,19 @@ chrome.runtime.onInstalled.addListener(()=>{
id: contextMenuId.saveImage,
title: chrome.i18n.getMessage("context_menu_save_image_as"),
contexts: ["image"]
- });
+ }, () => chrome.runtime.lastError );
chrome.contextMenus.create({
id: contextMenuId.saveImageWithCustomPrefix,
title: "Save image as (AutoRename) with Prefix",
contexts: ["image"]
- });
+ }, () => chrome.runtime.lastError );
+
+ chrome.contextMenus.create({
+ id: contextMenuId.addDownloadQueue,
+ title: "Add to Download Queue",
+ contexts: ["image"]
+ }, () => chrome.runtime.lastError );
//#endregion
@@ -45,8 +52,12 @@ chrome.runtime.onInstalled.addListener(()=>{
id: contextMenuId.viewOriginalImage,
title: chrome.i18n.getMessage("context_menu_view_original_image"),
contexts: ["image"]
- });
+ }, () => chrome.runtime.lastError );
+
//#endregion
+
+ DownloadManager.UpdateBadge();
+
});
//#endregion
@@ -80,7 +91,9 @@ let BrowserTabInfo = {
chrome.tabs.onActivated.addListener((activeInfo) => {
DevMode ? console.log("tabs onActivated") : "";
- QueryTab();
+ console.log(activeInfo.tabId)
+
+ QueryTab(activeInfo);
});
@@ -97,38 +110,41 @@ chrome.tabs.onUpdated.addListener((tabId, selectInfo) => {
});
-/**
- * Queries tab data
- */
-function QueryTab() {
- setTimeout(() => {
+chrome.action.setBadgeBackgroundColor({
+ color: "#181818"
+});
- chrome.tabs.query({
- active: true,
- currentWindow: true
- }, ((tabs)=>{
- let url;
- let title;
+let count = 0;
+/**
+ * Queries tab data
+ */
+function QueryTab(tabData) {
- if (tabs[0] != undefined){
- DevMode ? console.log(BrowserTabInfo) : "";
- url = tabs[0].url;
- title = tabs[0].title;
- }
+ chrome.tabs.query({
+ active: true,
+ currentWindow: true
+ }, ((tabs)=>{
- url = url.split("/");
- url = url[2];
- DevMode ? console.log(url) : "";
- UpdateContextMenus(url);
+ let url;
+ let title;
- BrowserTabInfo.Title = title;
- BrowserTabInfo.URL = url;
+ if (tabs[0] != undefined){
DevMode ? console.log(BrowserTabInfo) : "";
- }));
+ url = tabs[0].url;
+ title = tabs[0].title;
+ }
- }, 500);
+ url = url.split("/");
+ url = url[2];
+ DevMode ? console.log(url) : "";
+ UpdateContextMenus(url, tabs[0].url);
+
+ BrowserTabInfo.Title = title;
+ BrowserTabInfo.URL = url;
+ DevMode ? console.log(BrowserTabInfo) : "";
+ }));
};
@@ -139,10 +155,25 @@ function QueryTab() {
*
* @param {string} url URL of the website
*/
-function UpdateContextMenus(url) {
+function UpdateContextMenus(url, urlFull) {
switch(url){
case Website.Twitter:
+
+ if (urlFull.includes("messages")){
+ chrome.contextMenus.update(contextMenuId.saveImage, {
+ visible: false
+ });
+
+ chrome.contextMenus.update(contextMenuId.viewOriginalImage, {
+ visible: false
+ });
+
+ chrome.contextMenus.update(contextMenuId.saveImageWithCustomPrefix, {
+ visible: false
+ });
+ return;
+ }
chrome.contextMenus.update(contextMenuId.saveImage, {
visible: true
@@ -159,6 +190,21 @@ function UpdateContextMenus(url) {
case Website.Mobile_Twitter:
+ if (urlFull.includes("messages")){
+ chrome.contextMenus.update(contextMenuId.saveImage, {
+ visible: false
+ });
+
+ chrome.contextMenus.update(contextMenuId.viewOriginalImage, {
+ visible: false
+ });
+
+ chrome.contextMenus.update(contextMenuId.saveImageWithCustomPrefix, {
+ visible: false
+ });
+ return;
+ }
+
chrome.contextMenus.update(contextMenuId.saveImage, {
visible: true
});
@@ -270,20 +316,28 @@ chrome.contextMenus.onClicked.addListener(function (info, tab) {
currentUrl = currentUrl.split("/");
currentUrl = currentUrl[2];
+ let data = {};
+ data["tab_url"] = tab.url;
+ data["info_url"] = info.srcUrl;
+ data["link_url"] = info.linkUrl;
+ data["use_prefix"] = false;
+ data["download_queue"] = false;
+ console.log(data);
switch (currentUrl) {
case Website.Twitter:
if (info.menuItemId === contextMenuId.viewOriginalImage){
- ViewOriginalMedia(info.srcUrl);
+ Twitter.ViewOriginalImage(data);
return;
}
- else if (info.menuItemId === contextMenuId.saveImage){
- temp["use_prefix"] = false;
- SaveTwitterMedia(tab.url, info.srcUrl, info.linkUrl, temp);
-
+ else if (info.menuItemId === contextMenuId.saveImage) {
+ Twitter.SaveMedia(data);
} else if (info.menuItemId == contextMenuId.saveImageWithCustomPrefix){
- temp["use_prefix"] = true;
- SaveTwitterMedia(tab.url, info.srcUrl, info.linkUrl, temp)
+ data.use_prefix = true;
+ Twitter.SaveMedia(data);
+ } else if ( info.menuItemId === contextMenuId.addDownloadQueue){
+ data.download_queue = true;
+ Twitter.SaveMedia(data);
}
break;
@@ -305,6 +359,10 @@ chrome.contextMenus.onClicked.addListener(function (info, tab) {
} else if (info.menuItemId == contextMenuId.saveImageWithCustomPrefix){
temp["use_prefix"] = true;
SaveLINEBlogMedia(tab.url, info.srcUrl, temp);
+ } else if (info.menuItemId == contextMenuId.addDownloadQueue){
+ temp["use_prefix"] = false;
+ temp["download_queue"] = true;
+ SaveLINEBlogMedia(tab.url, info.srcUrl, info.linkUrl, temp);
}
break;
diff --git a/js/Settings.js b/js/Settings.js
index 8f19034..3607962 100644
--- a/js/Settings.js
+++ b/js/Settings.js
@@ -1,6 +1,6 @@
/** MIT License
*
- * Copyright (c) 2022 Dasutein
+ * Copyright (c) 2023 Dasutein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
@@ -22,7 +22,8 @@
*/
let Settings = {};
-function StartSettingsService() {
+(()=>{
+ console.log("Settings service started")
let SettingsMap = [];
@@ -30,7 +31,8 @@ function StartSettingsService() {
General: "General",
LINE_BLOG: "LINE BLOG",
Twitter: "Twitter",
- Reddit: "Reddit"
+ Reddit: "Reddit",
+ Options: "OptionsUI"
}
chrome.storage.local.get({
@@ -47,6 +49,9 @@ function StartSettingsService() {
global_show_download_folder: false,
global_enable_save_as_window: true,
global_notifications_updated: true,
+ global_use_autorename_folder: false,
+ global_download_queue_data: "",
+ global_download_history_data: "",
//#endregion
//#region Twitter Settings
@@ -59,6 +64,7 @@ function StartSettingsService() {
twitter_date_format: "custom",
twitter_settings_custom_date_format: "",
twitter_settings_custom_prefix: "",
+ twitter_save_image_to_folder_based_on_username: false,
//#endregion
//#region LINE BLOG Settings
@@ -82,6 +88,10 @@ function StartSettingsService() {
redditDateFormat: "custom",
redditCustomDateFormat: "",
redditCustomPrefix: "",
+
+ //#region Options UI settings
+ optionsUITabName: "#general",
+ optionsUITabIndexNumber: 2 // Default
//#endregion
@@ -107,6 +117,21 @@ function StartSettingsService() {
name: chrome.i18n.getMessage("general_settings_enable_notifications_on_update"),
value: items.global_notifications_updated,
key: "global_notifications_updated"
+ }, {
+ category: Category.General,
+ name: "Save image to AutoRename folder",
+ value: items.global_use_autorename_folder,
+ key: "global_use_autorename_folder"
+ }, {
+ category: Category.General,
+ name: "Download Queue",
+ value: items.global_download_queue_data,
+ key: "global_download_queue_data"
+ }, {
+ category: Category.General,
+ name: "Download History",
+ value: items.global_download_history_data,
+ key: "global_download_history_data"
},
//#endregion
@@ -157,6 +182,11 @@ function StartSettingsService() {
name: chrome.i18n.getMessage("common_section_custom_prefix"),
value: items.twitter_settings_custom_prefix,
key: "twitter_settings_custom_prefix"
+ }, {
+ category: Category.Twitter,
+ name: "Save image to folder based on username",
+ value: items.twitter_save_image_to_folder_based_on_username,
+ key: "twitter_save_image_to_folder_based_on_username"
},
//#endregion
@@ -252,6 +282,20 @@ function StartSettingsService() {
key: "redditCustomPrefix"
},
//#endregion
+
+ //#region Options UI
+ {
+ category: Category.Options,
+ name: "Options Tab",
+ value: items.optionsUITabName,
+ key: "optionsUITabName"
+ },
+ {
+ category: Category.Options,
+ name: "Options Tab Index",
+ value: items.optionsUITabIndexNumber,
+ key: "optionsUITabIndexNumber"
+ }
);
});
@@ -296,9 +340,20 @@ function StartSettingsService() {
}),
Reddit: SettingsMap.filter((key)=>{
return key.category == Category.Reddit;
+ }),
+ OptionsUI: SettingsMap.filter((key)=>{
+ return key.category == Category.Options;
+ }),
+ DownloadQueue: SettingsMap.filter((key)=>{
+ return key.category == Category.General && key.key == "global_download_queue_data";
+ }).map((x,idx,arr)=>{
+ console.log(arr.length)
+ if (arr.length > 1){
+ return JSON.parse(arr[0].value);
+ } else {
+ return null
+ }
})
}
});
-
-};
-StartSettingsService();
\ No newline at end of file
+})();
diff --git a/js/Twitter/TwitterContent.js b/js/Twitter/TwitterContent.js
index c5c0228..83ad5e8 100644
--- a/js/Twitter/TwitterContent.js
+++ b/js/Twitter/TwitterContent.js
@@ -1,6 +1,6 @@
/** MIT License
*
- * Copyright (c) 2022 Dasutein
+ * Copyright (c) 2023 Dasutein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
@@ -12,73 +12,13 @@
*
*/
-/**
- * Twitter supports various image sizes. When using `original`,
- * the extension can save images up to 4096x4096 resolution
- */
- const size = {
- small: "&name=small",
- medium: "&name=medium",
- large: "&name=large",
- original: "&name=orig"
-}
-
-/**
- * Return if the image is either a JPG or PNG
- *
- * @param {string} url
- */
-let getImageFormat = ((url) => "." + url.split("?format=")[1].substring(0, 3));
-
-/**
- * View the Twitter image in it's original size
- *
- * @param {string} url
- */
-function ViewOriginalMedia(url) {
-
- let updatedUrl = null;
-
- const twitterImageSize = url.substring(0, url.lastIndexOf("&name=") + 0);
+var Twitter = {
- if (url.includes("&name=")) {
- updatedUrl = twitterImageSize + size.original
- } else {
- updatedUrl = url;
- }
-
- chrome.tabs.create({
- url: updatedUrl
- });
-}
-
-function SaveTwitterMedia(tabUrl, url, linkUrl, customObj){
-
- if (Utility.SplitURL(tabUrl, 5) == null || Utility.SplitURL(tabUrl, 5).length == 0){
-
- if (!linkUrl){
- alert(chrome.i18n.getMessage("error_tweet_detail_alert_prompt"));
- return;
- }
-
- }
-
- function buildFileName(fileNameObj){
+ BuildFileName: ((twitterConfig, fileNameObj)=>{
let temp;
temp = `{prefix}-{website_title}-{username}-{tweetId}-{date}-{randomstring}`;
temp = temp.split("-");
- const twitterConfig = Settings.Load().Twitter.map((data)=>{
- return {
- "key": data.key,
- "value": data.value
- }
- }).reduce((obj, data)=>{
- obj[data.key] = data;
- return obj;
- }, {});
-
-
if (!twitterConfig["twitter_include_mention_symbol"].value){
temp[temp.indexOf("{username}")] = fileNameObj.username;
} else {
@@ -124,12 +64,13 @@ function SaveTwitterMedia(tabUrl, url, linkUrl, customObj){
}
if (twitterConfig["twitter_random_string_length"].value == "0"){
- Utility.RemoveUnusedParameter(temp, "{randomstring}");
+ temp[temp.indexOf("{randomstring}")] = fileNameObj.photo_count;
+
} else {
temp[temp.indexOf("{randomstring}")] = Utility.GenerateRandomString(twitterConfig["twitter_random_string_length"].value);
}
- if (customObj.use_prefix == true){
+ if (fileNameObj.use_prefix == true){
if (twitterConfig["twitter_settings_custom_prefix"].value == ""){
Utility.RemoveUnusedParameter(temp, "{prefix}");
@@ -142,42 +83,108 @@ function SaveTwitterMedia(tabUrl, url, linkUrl, customObj){
}
return temp.toString().replace(/,/g, "-");
- }
-
- let twitterImageFile = [];
- let fileNameObj = {};
- let twitterMediaSrc;
- let tweetId;
- const specialCharacters = /[?!@#$%^&*(),';:*-.]/g;
-
- // Rule 1: When full tweet is clicked then it should be prioritized first
- if (!!tabUrl){
- if (specialCharacters.test(Utility.SplitURL(tabUrl, 5))){
- tweetId = Utility.SplitURL(tabUrl, 5).split(specialCharacters)[0];
+ }),
+
+ ImageFormatType : ((url) => "." + url.split("?format=")[1].substring(0, 3)),
+
+ ImageSizeType : (()=> {
+ const size = [{
+ small: "&name=small",
+ medium: "&name=medium",
+ large: "&name=large",
+ original: "&name=orig"
+ }];
+ return size.map((x => x))[0];
+ }),
+
+ MediaSrc : ((linkUrl)=>{
+ let src = "";
+ src = linkUrl.substring(0, linkUrl.lastIndexOf("&name=") + 0) + Twitter.ImageSizeType().original
+ return src;
+ }),
+
+ ViewOriginalImage : (async (url)=>{
+
+ if (url.info_url.includes("&name=")) {
+ updatedUrl = url.info_url.substring(0, url.info_url.lastIndexOf("&name=") + 0) + Twitter.ImageSizeType().original
} else {
- tweetId = Utility.SplitURL(tabUrl, 5);
+ updatedUrl = url.info_url;
}
- }
-
- // Rule 2: If Tweet ID is still empty then retrieve it from linkUrl
- if (tweetId == "" || tweetId == undefined || tweetId == null){
- if (!!linkUrl){
- if (specialCharacters.test(Utility.SplitURL(linkUrl, 5))){
- tweetId = Utility.SplitURL(linkUrl, 5).split(specialCharacters)[0];
+ Utility.CreateNewTab(updatedUrl)
+
+ }),
+
+ SaveMedia : ( (data)=>{
+
+ let filename = "";
+ let tweetId;
+ let fileNameObj = {};
+
+ twitterConfig = Settings.Load().Twitter.map((data)=>{
+ return {
+ "key": data.key,
+ "value": data.value
+ }
+ }).reduce((obj, data)=>{
+ obj[data.key] = data;
+ return obj;
+ }, {});
+
+ generalSettings = Settings.Load().General.map((data)=>{
+ return {
+ "key": data.key,
+ "value": data.value
+ }
+ }).reduce((obj, data)=>{
+ obj[data.key] = data;
+ return obj;
+ }, {});
+
+
+ const specialCharacters = /[?!@#$%^&*(),';:*-.]/g;
+
+ // Rule 1: When full tweet is clicked then it should be prioritized first
+ if (!!data.tab_url){
+ if (specialCharacters.test(Utility.SplitURL(data.tab_url, 5))){
+ tweetId = Utility.SplitURL(data.tab_url, 5).split(specialCharacters)[0];
} else {
- tweetId = Utility.SplitURL(linkUrl, 5);
+ tweetId = Utility.SplitURL(data.tab_url, 5);
+ }
+ }
+
+ // Rule 2: If Tweet ID is still empty then retrieve it from linkUrl
+ if (tweetId == "" || tweetId == undefined || tweetId == null){
+ if (!!data.link_url){
+ if (specialCharacters.test(Utility.SplitURL(data.link_url, 5))){
+ tweetId = Utility.SplitURL(data.link_url, 5).split(specialCharacters)[0];
+ } else {
+ tweetId = Utility.SplitURL(data.link_url, 5);
+ }
}
}
- }
- fileNameObj["username"] = linkUrl != undefined ? Utility.SplitURL(linkUrl, 3) : Utility.SplitURL(tabUrl, 3);
- fileNameObj["tweetId"] = tweetId;
- twitterMediaSrc = url.substring(0, url.lastIndexOf("&name=") + 0) + size.original;
- twitterImageFile.push({
- filename: buildFileName(fileNameObj) + getImageFormat(url),
- url: twitterMediaSrc
- });
-
- DevMode ? console.log(twitterImageFile) : "";
- StartDownload(twitterImageFile);
+
+ fileNameObj["username"] = data.link_url != undefined ? Utility.SplitURL(data.link_url, 3) : Utility.SplitURL(data.tab_url, 3);
+ fileNameObj["tweetId"] = tweetId;
+ fileNameObj["use_prefix"] = data.use_prefix;
+ fileNameObj["photo_count"] = data.link_url != undefined ? `img${Utility.SplitURL(data.link_url, 7)}` : "img1";
+ filename = Twitter.BuildFileName(twitterConfig, fileNameObj) + Twitter.ImageFormatType(data.info_url);
+
+ if (generalSettings["global_use_autorename_folder"].value == true && twitterConfig["twitter_save_image_to_folder_based_on_username"].value == true){
+ filename = `AutoRename/Twitter/${fileNameObj.username}/${filename}`
+ } else if (generalSettings["global_use_autorename_folder"].value == true && twitterConfig["twitter_save_image_to_folder_based_on_username"].value == false){
+ filename = `AutoRename/Twitter/${filename}`
+ }
+
+ let twitterFileProp = [];
+ twitterFileProp.push({
+ filename: filename,
+ url: Twitter.MediaSrc(data.info_url),
+ website: "Twitter",
+
+ });
+
+ data.download_queue == false ? DownloadManager.StartDownload(twitterFileProp) : DownloadManager.AddDownloadQueue(twitterFileProp);
+
+ })
}
\ No newline at end of file
diff --git a/lib/FileSaver/FileSaver.js b/lib/FileSaver/FileSaver.js
new file mode 100644
index 0000000..54fc090
--- /dev/null
+++ b/lib/FileSaver/FileSaver.js
@@ -0,0 +1,188 @@
+(function (global, factory) {
+ if (typeof define === "function" && define.amd) {
+ define([], factory);
+ } else if (typeof exports !== "undefined") {
+ factory();
+ } else {
+ var mod = {
+ exports: {}
+ };
+ factory();
+ global.FileSaver = mod.exports;
+ }
+})(this, function () {
+ "use strict";
+
+ /*
+ * FileSaver.js
+ * A saveAs() FileSaver implementation.
+ *
+ * By Eli Grey, http://eligrey.com
+ *
+ * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
+ * source : http://purl.eligrey.com/github/FileSaver.js
+ */
+ // The one and only way of getting global scope in all environments
+ // https://stackoverflow.com/q/3277182/1008999
+ 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;
+
+ function bom(blob, opts) {
+ if (typeof opts === 'undefined') opts = {
+ autoBom: false
+ };else if (typeof opts !== 'object') {
+ console.warn('Deprecated: 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 (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, opts) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.responseType = 'blob';
+
+ xhr.onload = function () {
+ saveAs(xhr.response, name, opts);
+ };
+
+ xhr.onerror = function () {
+ console.error('could not download file');
+ };
+
+ xhr.send();
+ }
+
+ function corsEnabled(url) {
+ var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker
+
+ xhr.open('HEAD', url, false);
+
+ try {
+ xhr.send();
+ } catch (e) {}
+
+ return xhr.status >= 200 && xhr.status <= 299;
+ } // `a.click()` doesn'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);
+ }
+ } // Detect WebView inside a native macOS app by ruling out all browsers
+ // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
+ // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
+
+
+ var isMacOSWebView = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent);
+ 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) unless this is a macOS WebView
+ : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) {
+ var URL = _global.URL || _global.webkitURL;
+ var a = document.createElement('a');
+ name = name || blob.name || 'download';
+ a.download = name;
+ a.rel = 'noopener'; // tabnabbing
+ // TODO: detect chrome extensions & packaged apps
+ // a.target = '_blank'
+
+ if (typeof blob === 'string') {
+ // Support regular links
+ a.href = blob;
+
+ if (a.origin !== location.origin) {
+ corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
+ } else {
+ click(a);
+ }
+ } else {
+ // Support blobs
+ a.href = URL.createObjectURL(blob);
+ setTimeout(function () {
+ URL.revokeObjectURL(a.href);
+ }, 4E4); // 40s
+
+ setTimeout(function () {
+ click(a);
+ }, 0);
+ }
+ } // Use msSaveOrOpenBlob as a second approach
+ : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
+ name = name || blob.name || 'download';
+
+ if (typeof blob === 'string') {
+ if (corsEnabled(blob)) {
+ download(blob, name, opts);
+ } else {
+ var a = document.createElement('a');
+ a.href = blob;
+ a.target = '_blank';
+ setTimeout(function () {
+ click(a);
+ });
+ }
+ } else {
+ navigator.msSaveOrOpenBlob(bom(blob, opts), name);
+ }
+ } // Fallback to using FileReader and a popup
+ : function saveAs(blob, name, opts, popup) {
+ // Open a popup immediately do go around popup blocker
+ // Mostly only available on user interaction and the fileReader is async so...
+ popup = popup || open('', '_blank');
+
+ if (popup) {
+ popup.document.title = popup.document.body.innerText = 'downloading...';
+ }
+
+ 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;
+
+ var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
+
+ if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') {
+ // Safari doesn't allow downloading of blob URLs
+ var reader = new FileReader();
+
+ reader.onloadend = function () {
+ var url = reader.result;
+ url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
+ if (popup) popup.location.href = url;else location = url;
+ popup = null; // reverse-tabnabbing #460
+ };
+
+ reader.readAsDataURL(blob);
+ } else {
+ var URL = _global.URL || _global.webkitURL;
+ var url = URL.createObjectURL(blob);
+ if (popup) popup.location = url;else location.href = url;
+ popup = null; // reverse-tabnabbing #460
+
+ setTimeout(function () {
+ URL.revokeObjectURL(url);
+ }, 4E4); // 40s
+ }
+ });
+ _global.saveAs = saveAs.saveAs = saveAs;
+
+ if (typeof module !== 'undefined') {
+ module.exports = saveAs;
+ }
+});
diff --git a/lib/JSZip/jszip-utils.js b/lib/JSZip/jszip-utils.js
new file mode 100644
index 0000000..e98159b
--- /dev/null
+++ b/lib/JSZip/jszip-utils.js
@@ -0,0 +1,160 @@
+/*@preserve
+
+JSZipUtils - A collection of cross-browser utilities to go along with JSZip.
+
+
+(c) 2014-2019 Stuart Knightley, David Duponchel
+Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip-utils/master/LICENSE.markdown.
+
+*/
+!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.JSZipUtils=e():"undefined"!=typeof global?global.JSZipUtils=e():"undefined"!=typeof self&&(self.JSZipUtils=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
+
+(c) 2009-2016 Stuart Knightley
+Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
+
+JSZip uses the library pako released under the MIT license :
+https://github.com/nodeca/pako/blob/main/LICENSE
+*/
+
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.JSZip = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = remainingBytes > 1 ? (((chr2 & 15) << 2) | (chr3 >> 6)) : 64;
+ enc4 = remainingBytes > 2 ? (chr3 & 63) : 64;
+
+ output.push(_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4));
+
+ }
+
+ return output.join("");
+};
+
+// public method for decoding
+exports.decode = function(input) {
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0, resultIndex = 0;
+
+ var dataUrlPrefix = "data:";
+
+ if (input.substr(0, dataUrlPrefix.length) === dataUrlPrefix) {
+ // This is a common error: people give a data url
+ // (data:image/png;base64,iVBOR...) with a {base64: true} and
+ // wonders why things don't work.
+ // We can detect that the string input looks like a data url but we
+ // *can't* be sure it is one: removing everything up to the comma would
+ // be too dangerous.
+ throw new Error("Invalid base64 input, it looks like a data url.");
+ }
+
+ input = input.replace(/[^A-Za-z0-9+/=]/g, "");
+
+ var totalLength = input.length * 3 / 4;
+ if(input.charAt(input.length - 1) === _keyStr.charAt(64)) {
+ totalLength--;
+ }
+ if(input.charAt(input.length - 2) === _keyStr.charAt(64)) {
+ totalLength--;
+ }
+ if (totalLength % 1 !== 0) {
+ // totalLength is not an integer, the length does not match a valid
+ // base64 content. That can happen if:
+ // - the input is not a base64 content
+ // - the input is *almost* a base64 content, with a extra chars at the
+ // beginning or at the end
+ // - the input uses a base64 variant (base64url for example)
+ throw new Error("Invalid base64 input, bad content length.");
+ }
+ var output;
+ if (support.uint8array) {
+ output = new Uint8Array(totalLength|0);
+ } else {
+ output = new Array(totalLength|0);
+ }
+
+ while (i < input.length) {
+
+ enc1 = _keyStr.indexOf(input.charAt(i++));
+ enc2 = _keyStr.indexOf(input.charAt(i++));
+ enc3 = _keyStr.indexOf(input.charAt(i++));
+ enc4 = _keyStr.indexOf(input.charAt(i++));
+
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+
+ output[resultIndex++] = chr1;
+
+ if (enc3 !== 64) {
+ output[resultIndex++] = chr2;
+ }
+ if (enc4 !== 64) {
+ output[resultIndex++] = chr3;
+ }
+
+ }
+
+ return output;
+};
+
+},{"./support":30,"./utils":32}],2:[function(require,module,exports){
+"use strict";
+
+var external = require("./external");
+var DataWorker = require("./stream/DataWorker");
+var Crc32Probe = require("./stream/Crc32Probe");
+var DataLengthProbe = require("./stream/DataLengthProbe");
+
+/**
+ * Represent a compressed object, with everything needed to decompress it.
+ * @constructor
+ * @param {number} compressedSize the size of the data compressed.
+ * @param {number} uncompressedSize the size of the data after decompression.
+ * @param {number} crc32 the crc32 of the decompressed file.
+ * @param {object} compression the type of compression, see lib/compressions.js.
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data the compressed data.
+ */
+function CompressedObject(compressedSize, uncompressedSize, crc32, compression, data) {
+ this.compressedSize = compressedSize;
+ this.uncompressedSize = uncompressedSize;
+ this.crc32 = crc32;
+ this.compression = compression;
+ this.compressedContent = data;
+}
+
+CompressedObject.prototype = {
+ /**
+ * Create a worker to get the uncompressed content.
+ * @return {GenericWorker} the worker.
+ */
+ getContentWorker: function () {
+ var worker = new DataWorker(external.Promise.resolve(this.compressedContent))
+ .pipe(this.compression.uncompressWorker())
+ .pipe(new DataLengthProbe("data_length"));
+
+ var that = this;
+ worker.on("end", function () {
+ if (this.streamInfo["data_length"] !== that.uncompressedSize) {
+ throw new Error("Bug : uncompressed data size mismatch");
+ }
+ });
+ return worker;
+ },
+ /**
+ * Create a worker to get the compressed content.
+ * @return {GenericWorker} the worker.
+ */
+ getCompressedWorker: function () {
+ return new DataWorker(external.Promise.resolve(this.compressedContent))
+ .withStreamInfo("compressedSize", this.compressedSize)
+ .withStreamInfo("uncompressedSize", this.uncompressedSize)
+ .withStreamInfo("crc32", this.crc32)
+ .withStreamInfo("compression", this.compression)
+ ;
+ }
+};
+
+/**
+ * Chain the given worker with other workers to compress the content with the
+ * given compression.
+ * @param {GenericWorker} uncompressedWorker the worker to pipe.
+ * @param {Object} compression the compression object.
+ * @param {Object} compressionOptions the options to use when compressing.
+ * @return {GenericWorker} the new worker compressing the content.
+ */
+CompressedObject.createWorkerFrom = function (uncompressedWorker, compression, compressionOptions) {
+ return uncompressedWorker
+ .pipe(new Crc32Probe())
+ .pipe(new DataLengthProbe("uncompressedSize"))
+ .pipe(compression.compressWorker(compressionOptions))
+ .pipe(new DataLengthProbe("compressedSize"))
+ .withStreamInfo("compression", compression);
+};
+
+module.exports = CompressedObject;
+
+},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(require,module,exports){
+"use strict";
+
+var GenericWorker = require("./stream/GenericWorker");
+
+exports.STORE = {
+ magic: "\x00\x00",
+ compressWorker : function () {
+ return new GenericWorker("STORE compression");
+ },
+ uncompressWorker : function () {
+ return new GenericWorker("STORE decompression");
+ }
+};
+exports.DEFLATE = require("./flate");
+
+},{"./flate":7,"./stream/GenericWorker":28}],4:[function(require,module,exports){
+"use strict";
+
+var utils = require("./utils");
+
+/**
+ * The following functions come from pako, from pako/lib/zlib/crc32.js
+ * released under the MIT license, see pako https://github.com/nodeca/pako/
+ */
+
+// Use ordinary array, since untyped makes no boost here
+function makeTable() {
+ var c, table = [];
+
+ for(var n =0; n < 256; n++){
+ c = n;
+ for(var k =0; k < 8; k++){
+ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+ }
+ table[n] = c;
+ }
+
+ return table;
+}
+
+// Create table on load. Just 255 signed longs. Not a problem.
+var crcTable = makeTable();
+
+
+function crc32(crc, buf, len, pos) {
+ var t = crcTable, end = pos + len;
+
+ crc = crc ^ (-1);
+
+ for (var i = pos; i < end; i++ ) {
+ crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+ }
+
+ return (crc ^ (-1)); // >>> 0;
+}
+
+// That's all for the pako functions.
+
+/**
+ * Compute the crc32 of a string.
+ * This is almost the same as the function crc32, but for strings. Using the
+ * same function for the two use cases leads to horrible performances.
+ * @param {Number} crc the starting value of the crc.
+ * @param {String} str the string to use.
+ * @param {Number} len the length of the string.
+ * @param {Number} pos the starting position for the crc32 computation.
+ * @return {Number} the computed crc32.
+ */
+function crc32str(crc, str, len, pos) {
+ var t = crcTable, end = pos + len;
+
+ crc = crc ^ (-1);
+
+ for (var i = pos; i < end; i++ ) {
+ crc = (crc >>> 8) ^ t[(crc ^ str.charCodeAt(i)) & 0xFF];
+ }
+
+ return (crc ^ (-1)); // >>> 0;
+}
+
+module.exports = function crc32wrapper(input, crc) {
+ if (typeof input === "undefined" || !input.length) {
+ return 0;
+ }
+
+ var isArray = utils.getTypeOf(input) !== "string";
+
+ if(isArray) {
+ return crc32(crc|0, input, input.length, 0);
+ } else {
+ return crc32str(crc|0, input, input.length, 0);
+ }
+};
+
+},{"./utils":32}],5:[function(require,module,exports){
+"use strict";
+exports.base64 = false;
+exports.binary = false;
+exports.dir = false;
+exports.createFolders = true;
+exports.date = null;
+exports.compression = null;
+exports.compressionOptions = null;
+exports.comment = null;
+exports.unixPermissions = null;
+exports.dosPermissions = null;
+
+},{}],6:[function(require,module,exports){
+"use strict";
+
+// load the global object first:
+// - it should be better integrated in the system (unhandledRejection in node)
+// - the environment may have a custom Promise implementation (see zone.js)
+var ES6Promise = null;
+if (typeof Promise !== "undefined") {
+ ES6Promise = Promise;
+} else {
+ ES6Promise = require("lie");
+}
+
+/**
+ * Let the user use/change some implementations.
+ */
+module.exports = {
+ Promise: ES6Promise
+};
+
+},{"lie":37}],7:[function(require,module,exports){
+"use strict";
+var USE_TYPEDARRAY = (typeof Uint8Array !== "undefined") && (typeof Uint16Array !== "undefined") && (typeof Uint32Array !== "undefined");
+
+var pako = require("pako");
+var utils = require("./utils");
+var GenericWorker = require("./stream/GenericWorker");
+
+var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array";
+
+exports.magic = "\x08\x00";
+
+/**
+ * Create a worker that uses pako to inflate/deflate.
+ * @constructor
+ * @param {String} action the name of the pako function to call : either "Deflate" or "Inflate".
+ * @param {Object} options the options to use when (de)compressing.
+ */
+function FlateWorker(action, options) {
+ GenericWorker.call(this, "FlateWorker/" + action);
+
+ this._pako = null;
+ this._pakoAction = action;
+ this._pakoOptions = options;
+ // the `meta` object from the last chunk received
+ // this allow this worker to pass around metadata
+ this.meta = {};
+}
+
+utils.inherits(FlateWorker, GenericWorker);
+
+/**
+ * @see GenericWorker.processChunk
+ */
+FlateWorker.prototype.processChunk = function (chunk) {
+ this.meta = chunk.meta;
+ if (this._pako === null) {
+ this._createPako();
+ }
+ this._pako.push(utils.transformTo(ARRAY_TYPE, chunk.data), false);
+};
+
+/**
+ * @see GenericWorker.flush
+ */
+FlateWorker.prototype.flush = function () {
+ GenericWorker.prototype.flush.call(this);
+ if (this._pako === null) {
+ this._createPako();
+ }
+ this._pako.push([], true);
+};
+/**
+ * @see GenericWorker.cleanUp
+ */
+FlateWorker.prototype.cleanUp = function () {
+ GenericWorker.prototype.cleanUp.call(this);
+ this._pako = null;
+};
+
+/**
+ * Create the _pako object.
+ * TODO: lazy-loading this object isn't the best solution but it's the
+ * quickest. The best solution is to lazy-load the worker list. See also the
+ * issue #446.
+ */
+FlateWorker.prototype._createPako = function () {
+ this._pako = new pako[this._pakoAction]({
+ raw: true,
+ level: this._pakoOptions.level || -1 // default compression
+ });
+ var self = this;
+ this._pako.onData = function(data) {
+ self.push({
+ data : data,
+ meta : self.meta
+ });
+ };
+};
+
+exports.compressWorker = function (compressionOptions) {
+ return new FlateWorker("Deflate", compressionOptions);
+};
+exports.uncompressWorker = function () {
+ return new FlateWorker("Inflate", {});
+};
+
+},{"./stream/GenericWorker":28,"./utils":32,"pako":38}],8:[function(require,module,exports){
+"use strict";
+
+var utils = require("../utils");
+var GenericWorker = require("../stream/GenericWorker");
+var utf8 = require("../utf8");
+var crc32 = require("../crc32");
+var signature = require("../signature");
+
+/**
+ * Transform an integer into a string in hexadecimal.
+ * @private
+ * @param {number} dec the number to convert.
+ * @param {number} bytes the number of bytes to generate.
+ * @returns {string} the result.
+ */
+var decToHex = function(dec, bytes) {
+ var hex = "", i;
+ for (i = 0; i < bytes; i++) {
+ hex += String.fromCharCode(dec & 0xff);
+ dec = dec >>> 8;
+ }
+ return hex;
+};
+
+/**
+ * Generate the UNIX part of the external file attributes.
+ * @param {Object} unixPermissions the unix permissions or null.
+ * @param {Boolean} isDir true if the entry is a directory, false otherwise.
+ * @return {Number} a 32 bit integer.
+ *
+ * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute :
+ *
+ * TTTTsstrwxrwxrwx0000000000ADVSHR
+ * ^^^^____________________________ file type, see zipinfo.c (UNX_*)
+ * ^^^_________________________ setuid, setgid, sticky
+ * ^^^^^^^^^________________ permissions
+ * ^^^^^^^^^^______ not used ?
+ * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only
+ */
+var generateUnixExternalFileAttr = function (unixPermissions, isDir) {
+
+ var result = unixPermissions;
+ if (!unixPermissions) {
+ // I can't use octal values in strict mode, hence the hexa.
+ // 040775 => 0x41fd
+ // 0100664 => 0x81b4
+ result = isDir ? 0x41fd : 0x81b4;
+ }
+ return (result & 0xFFFF) << 16;
+};
+
+/**
+ * Generate the DOS part of the external file attributes.
+ * @param {Object} dosPermissions the dos permissions or null.
+ * @param {Boolean} isDir true if the entry is a directory, false otherwise.
+ * @return {Number} a 32 bit integer.
+ *
+ * Bit 0 Read-Only
+ * Bit 1 Hidden
+ * Bit 2 System
+ * Bit 3 Volume Label
+ * Bit 4 Directory
+ * Bit 5 Archive
+ */
+var generateDosExternalFileAttr = function (dosPermissions) {
+ // the dir flag is already set for compatibility
+ return (dosPermissions || 0) & 0x3F;
+};
+
+/**
+ * Generate the various parts used in the construction of the final zip file.
+ * @param {Object} streamInfo the hash with information about the compressed file.
+ * @param {Boolean} streamedContent is the content streamed ?
+ * @param {Boolean} streamingEnded is the stream finished ?
+ * @param {number} offset the current offset from the start of the zip file.
+ * @param {String} platform let's pretend we are this platform (change platform dependents fields)
+ * @param {Function} encodeFileName the function to encode the file name / comment.
+ * @return {Object} the zip parts.
+ */
+var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform, encodeFileName) {
+ var file = streamInfo["file"],
+ compression = streamInfo["compression"],
+ useCustomEncoding = encodeFileName !== utf8.utf8encode,
+ encodedFileName = utils.transformTo("string", encodeFileName(file.name)),
+ utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),
+ comment = file.comment,
+ encodedComment = utils.transformTo("string", encodeFileName(comment)),
+ utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),
+ useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,
+ useUTF8ForComment = utfEncodedComment.length !== comment.length,
+ dosTime,
+ dosDate,
+ extraFields = "",
+ unicodePathExtraField = "",
+ unicodeCommentExtraField = "",
+ dir = file.dir,
+ date = file.date;
+
+
+ var dataInfo = {
+ crc32 : 0,
+ compressedSize : 0,
+ uncompressedSize : 0
+ };
+
+ // if the content is streamed, the sizes/crc32 are only available AFTER
+ // the end of the stream.
+ if (!streamedContent || streamingEnded) {
+ dataInfo.crc32 = streamInfo["crc32"];
+ dataInfo.compressedSize = streamInfo["compressedSize"];
+ dataInfo.uncompressedSize = streamInfo["uncompressedSize"];
+ }
+
+ var bitflag = 0;
+ if (streamedContent) {
+ // Bit 3: the sizes/crc32 are set to zero in the local header.
+ // The correct values are put in the data descriptor immediately
+ // following the compressed data.
+ bitflag |= 0x0008;
+ }
+ if (!useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment)) {
+ // Bit 11: Language encoding flag (EFS).
+ bitflag |= 0x0800;
+ }
+
+
+ var extFileAttr = 0;
+ var versionMadeBy = 0;
+ if (dir) {
+ // dos or unix, we set the dos dir flag
+ extFileAttr |= 0x00010;
+ }
+ if(platform === "UNIX") {
+ versionMadeBy = 0x031E; // UNIX, version 3.0
+ extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);
+ } else { // DOS or other, fallback to DOS
+ versionMadeBy = 0x0014; // DOS, version 2.0
+ extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir);
+ }
+
+ // date
+ // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
+ // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
+ // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
+
+ dosTime = date.getUTCHours();
+ dosTime = dosTime << 6;
+ dosTime = dosTime | date.getUTCMinutes();
+ dosTime = dosTime << 5;
+ dosTime = dosTime | date.getUTCSeconds() / 2;
+
+ dosDate = date.getUTCFullYear() - 1980;
+ dosDate = dosDate << 4;
+ dosDate = dosDate | (date.getUTCMonth() + 1);
+ dosDate = dosDate << 5;
+ dosDate = dosDate | date.getUTCDate();
+
+ if (useUTF8ForFileName) {
+ // set the unicode path extra field. unzip needs at least one extra
+ // field to correctly handle unicode path, so using the path is as good
+ // as any other information. This could improve the situation with
+ // other archive managers too.
+ // This field is usually used without the utf8 flag, with a non
+ // unicode path in the header (winrar, winzip). This helps (a bit)
+ // with the messy Windows' default compressed folders feature but
+ // breaks on p7zip which doesn't seek the unicode path extra field.
+ // So for now, UTF-8 everywhere !
+ unicodePathExtraField =
+ // Version
+ decToHex(1, 1) +
+ // NameCRC32
+ decToHex(crc32(encodedFileName), 4) +
+ // UnicodeName
+ utfEncodedFileName;
+
+ extraFields +=
+ // Info-ZIP Unicode Path Extra Field
+ "\x75\x70" +
+ // size
+ decToHex(unicodePathExtraField.length, 2) +
+ // content
+ unicodePathExtraField;
+ }
+
+ if(useUTF8ForComment) {
+
+ unicodeCommentExtraField =
+ // Version
+ decToHex(1, 1) +
+ // CommentCRC32
+ decToHex(crc32(encodedComment), 4) +
+ // UnicodeName
+ utfEncodedComment;
+
+ extraFields +=
+ // Info-ZIP Unicode Path Extra Field
+ "\x75\x63" +
+ // size
+ decToHex(unicodeCommentExtraField.length, 2) +
+ // content
+ unicodeCommentExtraField;
+ }
+
+ var header = "";
+
+ // version needed to extract
+ header += "\x0A\x00";
+ // general purpose bit flag
+ header += decToHex(bitflag, 2);
+ // compression method
+ header += compression.magic;
+ // last mod file time
+ header += decToHex(dosTime, 2);
+ // last mod file date
+ header += decToHex(dosDate, 2);
+ // crc-32
+ header += decToHex(dataInfo.crc32, 4);
+ // compressed size
+ header += decToHex(dataInfo.compressedSize, 4);
+ // uncompressed size
+ header += decToHex(dataInfo.uncompressedSize, 4);
+ // file name length
+ header += decToHex(encodedFileName.length, 2);
+ // extra field length
+ header += decToHex(extraFields.length, 2);
+
+
+ var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields;
+
+ var dirRecord = signature.CENTRAL_FILE_HEADER +
+ // version made by (00: DOS)
+ decToHex(versionMadeBy, 2) +
+ // file header (common to file and central directory)
+ header +
+ // file comment length
+ decToHex(encodedComment.length, 2) +
+ // disk number start
+ "\x00\x00" +
+ // internal file attributes TODO
+ "\x00\x00" +
+ // external file attributes
+ decToHex(extFileAttr, 4) +
+ // relative offset of local header
+ decToHex(offset, 4) +
+ // file name
+ encodedFileName +
+ // extra field
+ extraFields +
+ // file comment
+ encodedComment;
+
+ return {
+ fileRecord: fileRecord,
+ dirRecord: dirRecord
+ };
+};
+
+/**
+ * Generate the EOCD record.
+ * @param {Number} entriesCount the number of entries in the zip file.
+ * @param {Number} centralDirLength the length (in bytes) of the central dir.
+ * @param {Number} localDirLength the length (in bytes) of the local dir.
+ * @param {String} comment the zip file comment as a binary string.
+ * @param {Function} encodeFileName the function to encode the comment.
+ * @return {String} the EOCD record.
+ */
+var generateCentralDirectoryEnd = function (entriesCount, centralDirLength, localDirLength, comment, encodeFileName) {
+ var dirEnd = "";
+ var encodedComment = utils.transformTo("string", encodeFileName(comment));
+
+ // end of central dir signature
+ dirEnd = signature.CENTRAL_DIRECTORY_END +
+ // number of this disk
+ "\x00\x00" +
+ // number of the disk with the start of the central directory
+ "\x00\x00" +
+ // total number of entries in the central directory on this disk
+ decToHex(entriesCount, 2) +
+ // total number of entries in the central directory
+ decToHex(entriesCount, 2) +
+ // size of the central directory 4 bytes
+ decToHex(centralDirLength, 4) +
+ // offset of start of central directory with respect to the starting disk number
+ decToHex(localDirLength, 4) +
+ // .ZIP file comment length
+ decToHex(encodedComment.length, 2) +
+ // .ZIP file comment
+ encodedComment;
+
+ return dirEnd;
+};
+
+/**
+ * Generate data descriptors for a file entry.
+ * @param {Object} streamInfo the hash generated by a worker, containing information
+ * on the file entry.
+ * @return {String} the data descriptors.
+ */
+var generateDataDescriptors = function (streamInfo) {
+ var descriptor = "";
+ descriptor = signature.DATA_DESCRIPTOR +
+ // crc-32 4 bytes
+ decToHex(streamInfo["crc32"], 4) +
+ // compressed size 4 bytes
+ decToHex(streamInfo["compressedSize"], 4) +
+ // uncompressed size 4 bytes
+ decToHex(streamInfo["uncompressedSize"], 4);
+
+ return descriptor;
+};
+
+
+/**
+ * A worker to concatenate other workers to create a zip file.
+ * @param {Boolean} streamFiles `true` to stream the content of the files,
+ * `false` to accumulate it.
+ * @param {String} comment the comment to use.
+ * @param {String} platform the platform to use, "UNIX" or "DOS".
+ * @param {Function} encodeFileName the function to encode file names and comments.
+ */
+function ZipFileWorker(streamFiles, comment, platform, encodeFileName) {
+ GenericWorker.call(this, "ZipFileWorker");
+ // The number of bytes written so far. This doesn't count accumulated chunks.
+ this.bytesWritten = 0;
+ // The comment of the zip file
+ this.zipComment = comment;
+ // The platform "generating" the zip file.
+ this.zipPlatform = platform;
+ // the function to encode file names and comments.
+ this.encodeFileName = encodeFileName;
+ // Should we stream the content of the files ?
+ this.streamFiles = streamFiles;
+ // If `streamFiles` is false, we will need to accumulate the content of the
+ // files to calculate sizes / crc32 (and write them *before* the content).
+ // This boolean indicates if we are accumulating chunks (it will change a lot
+ // during the lifetime of this worker).
+ this.accumulate = false;
+ // The buffer receiving chunks when accumulating content.
+ this.contentBuffer = [];
+ // The list of generated directory records.
+ this.dirRecords = [];
+ // The offset (in bytes) from the beginning of the zip file for the current source.
+ this.currentSourceOffset = 0;
+ // The total number of entries in this zip file.
+ this.entriesCount = 0;
+ // the name of the file currently being added, null when handling the end of the zip file.
+ // Used for the emitted metadata.
+ this.currentFile = null;
+
+
+
+ this._sources = [];
+}
+utils.inherits(ZipFileWorker, GenericWorker);
+
+/**
+ * @see GenericWorker.push
+ */
+ZipFileWorker.prototype.push = function (chunk) {
+
+ var currentFilePercent = chunk.meta.percent || 0;
+ var entriesCount = this.entriesCount;
+ var remainingFiles = this._sources.length;
+
+ if(this.accumulate) {
+ this.contentBuffer.push(chunk);
+ } else {
+ this.bytesWritten += chunk.data.length;
+
+ GenericWorker.prototype.push.call(this, {
+ data : chunk.data,
+ meta : {
+ currentFile : this.currentFile,
+ percent : entriesCount ? (currentFilePercent + 100 * (entriesCount - remainingFiles - 1)) / entriesCount : 100
+ }
+ });
+ }
+};
+
+/**
+ * The worker started a new source (an other worker).
+ * @param {Object} streamInfo the streamInfo object from the new source.
+ */
+ZipFileWorker.prototype.openedSource = function (streamInfo) {
+ this.currentSourceOffset = this.bytesWritten;
+ this.currentFile = streamInfo["file"].name;
+
+ var streamedContent = this.streamFiles && !streamInfo["file"].dir;
+
+ // don't stream folders (because they don't have any content)
+ if(streamedContent) {
+ var record = generateZipParts(streamInfo, streamedContent, false, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);
+ this.push({
+ data : record.fileRecord,
+ meta : {percent:0}
+ });
+ } else {
+ // we need to wait for the whole file before pushing anything
+ this.accumulate = true;
+ }
+};
+
+/**
+ * The worker finished a source (an other worker).
+ * @param {Object} streamInfo the streamInfo object from the finished source.
+ */
+ZipFileWorker.prototype.closedSource = function (streamInfo) {
+ this.accumulate = false;
+ var streamedContent = this.streamFiles && !streamInfo["file"].dir;
+ var record = generateZipParts(streamInfo, streamedContent, true, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);
+
+ this.dirRecords.push(record.dirRecord);
+ if(streamedContent) {
+ // after the streamed file, we put data descriptors
+ this.push({
+ data : generateDataDescriptors(streamInfo),
+ meta : {percent:100}
+ });
+ } else {
+ // the content wasn't streamed, we need to push everything now
+ // first the file record, then the content
+ this.push({
+ data : record.fileRecord,
+ meta : {percent:0}
+ });
+ while(this.contentBuffer.length) {
+ this.push(this.contentBuffer.shift());
+ }
+ }
+ this.currentFile = null;
+};
+
+/**
+ * @see GenericWorker.flush
+ */
+ZipFileWorker.prototype.flush = function () {
+
+ var localDirLength = this.bytesWritten;
+ for(var i = 0; i < this.dirRecords.length; i++) {
+ this.push({
+ data : this.dirRecords[i],
+ meta : {percent:100}
+ });
+ }
+ var centralDirLength = this.bytesWritten - localDirLength;
+
+ var dirEnd = generateCentralDirectoryEnd(this.dirRecords.length, centralDirLength, localDirLength, this.zipComment, this.encodeFileName);
+
+ this.push({
+ data : dirEnd,
+ meta : {percent:100}
+ });
+};
+
+/**
+ * Prepare the next source to be read.
+ */
+ZipFileWorker.prototype.prepareNextSource = function () {
+ this.previous = this._sources.shift();
+ this.openedSource(this.previous.streamInfo);
+ if (this.isPaused) {
+ this.previous.pause();
+ } else {
+ this.previous.resume();
+ }
+};
+
+/**
+ * @see GenericWorker.registerPrevious
+ */
+ZipFileWorker.prototype.registerPrevious = function (previous) {
+ this._sources.push(previous);
+ var self = this;
+
+ previous.on("data", function (chunk) {
+ self.processChunk(chunk);
+ });
+ previous.on("end", function () {
+ self.closedSource(self.previous.streamInfo);
+ if(self._sources.length) {
+ self.prepareNextSource();
+ } else {
+ self.end();
+ }
+ });
+ previous.on("error", function (e) {
+ self.error(e);
+ });
+ return this;
+};
+
+/**
+ * @see GenericWorker.resume
+ */
+ZipFileWorker.prototype.resume = function () {
+ if(!GenericWorker.prototype.resume.call(this)) {
+ return false;
+ }
+
+ if (!this.previous && this._sources.length) {
+ this.prepareNextSource();
+ return true;
+ }
+ if (!this.previous && !this._sources.length && !this.generatedError) {
+ this.end();
+ return true;
+ }
+};
+
+/**
+ * @see GenericWorker.error
+ */
+ZipFileWorker.prototype.error = function (e) {
+ var sources = this._sources;
+ if(!GenericWorker.prototype.error.call(this, e)) {
+ return false;
+ }
+ for(var i = 0; i < sources.length; i++) {
+ try {
+ sources[i].error(e);
+ } catch(e) {
+ // the `error` exploded, nothing to do
+ }
+ }
+ return true;
+};
+
+/**
+ * @see GenericWorker.lock
+ */
+ZipFileWorker.prototype.lock = function () {
+ GenericWorker.prototype.lock.call(this);
+ var sources = this._sources;
+ for(var i = 0; i < sources.length; i++) {
+ sources[i].lock();
+ }
+};
+
+module.exports = ZipFileWorker;
+
+},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(require,module,exports){
+"use strict";
+
+var compressions = require("../compressions");
+var ZipFileWorker = require("./ZipFileWorker");
+
+/**
+ * Find the compression to use.
+ * @param {String} fileCompression the compression defined at the file level, if any.
+ * @param {String} zipCompression the compression defined at the load() level.
+ * @return {Object} the compression object to use.
+ */
+var getCompression = function (fileCompression, zipCompression) {
+
+ var compressionName = fileCompression || zipCompression;
+ var compression = compressions[compressionName];
+ if (!compression) {
+ throw new Error(compressionName + " is not a valid compression method !");
+ }
+ return compression;
+};
+
+/**
+ * Create a worker to generate a zip file.
+ * @param {JSZip} zip the JSZip instance at the right root level.
+ * @param {Object} options to generate the zip file.
+ * @param {String} comment the comment to use.
+ */
+exports.generateWorker = function (zip, options, comment) {
+
+ var zipFileWorker = new ZipFileWorker(options.streamFiles, comment, options.platform, options.encodeFileName);
+ var entriesCount = 0;
+ try {
+
+ zip.forEach(function (relativePath, file) {
+ entriesCount++;
+ var compression = getCompression(file.options.compression, options.compression);
+ var compressionOptions = file.options.compressionOptions || options.compressionOptions || {};
+ var dir = file.dir, date = file.date;
+
+ file._compressWorker(compression, compressionOptions)
+ .withStreamInfo("file", {
+ name : relativePath,
+ dir : dir,
+ date : date,
+ comment : file.comment || "",
+ unixPermissions : file.unixPermissions,
+ dosPermissions : file.dosPermissions
+ })
+ .pipe(zipFileWorker);
+ });
+ zipFileWorker.entriesCount = entriesCount;
+ } catch (e) {
+ zipFileWorker.error(e);
+ }
+
+ return zipFileWorker;
+};
+
+},{"../compressions":3,"./ZipFileWorker":8}],10:[function(require,module,exports){
+"use strict";
+
+/**
+ * Representation a of zip file in js
+ * @constructor
+ */
+function JSZip() {
+ // if this constructor is used without `new`, it adds `new` before itself:
+ if(!(this instanceof JSZip)) {
+ return new JSZip();
+ }
+
+ if(arguments.length) {
+ throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
+ }
+
+ // object containing the files :
+ // {
+ // "folder/" : {...},
+ // "folder/data.txt" : {...}
+ // }
+ // NOTE: we use a null prototype because we do not
+ // want filenames like "toString" coming from a zip file
+ // to overwrite methods and attributes in a normal Object.
+ this.files = Object.create(null);
+
+ this.comment = null;
+
+ // Where we are in the hierarchy
+ this.root = "";
+ this.clone = function() {
+ var newObj = new JSZip();
+ for (var i in this) {
+ if (typeof this[i] !== "function") {
+ newObj[i] = this[i];
+ }
+ }
+ return newObj;
+ };
+}
+JSZip.prototype = require("./object");
+JSZip.prototype.loadAsync = require("./load");
+JSZip.support = require("./support");
+JSZip.defaults = require("./defaults");
+
+// TODO find a better way to handle this version,
+// a require('package.json').version doesn't work with webpack, see #327
+JSZip.version = "3.10.1";
+
+JSZip.loadAsync = function (content, options) {
+ return new JSZip().loadAsync(content, options);
+};
+
+JSZip.external = require("./external");
+module.exports = JSZip;
+
+},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(require,module,exports){
+"use strict";
+var utils = require("./utils");
+var external = require("./external");
+var utf8 = require("./utf8");
+var ZipEntries = require("./zipEntries");
+var Crc32Probe = require("./stream/Crc32Probe");
+var nodejsUtils = require("./nodejsUtils");
+
+/**
+ * Check the CRC32 of an entry.
+ * @param {ZipEntry} zipEntry the zip entry to check.
+ * @return {Promise} the result.
+ */
+function checkEntryCRC32(zipEntry) {
+ return new external.Promise(function (resolve, reject) {
+ var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe());
+ worker.on("error", function (e) {
+ reject(e);
+ })
+ .on("end", function () {
+ if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) {
+ reject(new Error("Corrupted zip : CRC32 mismatch"));
+ } else {
+ resolve();
+ }
+ })
+ .resume();
+ });
+}
+
+module.exports = function (data, options) {
+ var zip = this;
+ options = utils.extend(options || {}, {
+ base64: false,
+ checkCRC32: false,
+ optimizedBinaryString: false,
+ createFolders: false,
+ decodeFileName: utf8.utf8decode
+ });
+
+ if (nodejsUtils.isNode && nodejsUtils.isStream(data)) {
+ return external.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file."));
+ }
+
+ return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64)
+ .then(function (data) {
+ var zipEntries = new ZipEntries(options);
+ zipEntries.load(data);
+ return zipEntries;
+ }).then(function checkCRC32(zipEntries) {
+ var promises = [external.Promise.resolve(zipEntries)];
+ var files = zipEntries.files;
+ if (options.checkCRC32) {
+ for (var i = 0; i < files.length; i++) {
+ promises.push(checkEntryCRC32(files[i]));
+ }
+ }
+ return external.Promise.all(promises);
+ }).then(function addFiles(results) {
+ var zipEntries = results.shift();
+ var files = zipEntries.files;
+ for (var i = 0; i < files.length; i++) {
+ var input = files[i];
+
+ var unsafeName = input.fileNameStr;
+ var safeName = utils.resolve(input.fileNameStr);
+
+ zip.file(safeName, input.decompressed, {
+ binary: true,
+ optimizedBinaryString: true,
+ date: input.date,
+ dir: input.dir,
+ comment: input.fileCommentStr.length ? input.fileCommentStr : null,
+ unixPermissions: input.unixPermissions,
+ dosPermissions: input.dosPermissions,
+ createFolders: options.createFolders
+ });
+ if (!input.dir) {
+ zip.file(safeName).unsafeOriginalName = unsafeName;
+ }
+ }
+ if (zipEntries.zipComment.length) {
+ zip.comment = zipEntries.zipComment;
+ }
+
+ return zip;
+ });
+};
+
+},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(require,module,exports){
+"use strict";
+
+var utils = require("../utils");
+var GenericWorker = require("../stream/GenericWorker");
+
+/**
+ * A worker that use a nodejs stream as source.
+ * @constructor
+ * @param {String} filename the name of the file entry for this stream.
+ * @param {Readable} stream the nodejs stream.
+ */
+function NodejsStreamInputAdapter(filename, stream) {
+ GenericWorker.call(this, "Nodejs stream input adapter for " + filename);
+ this._upstreamEnded = false;
+ this._bindStream(stream);
+}
+
+utils.inherits(NodejsStreamInputAdapter, GenericWorker);
+
+/**
+ * Prepare the stream and bind the callbacks on it.
+ * Do this ASAP on node 0.10 ! A lazy binding doesn't always work.
+ * @param {Stream} stream the nodejs stream to use.
+ */
+NodejsStreamInputAdapter.prototype._bindStream = function (stream) {
+ var self = this;
+ this._stream = stream;
+ stream.pause();
+ stream
+ .on("data", function (chunk) {
+ self.push({
+ data: chunk,
+ meta : {
+ percent : 0
+ }
+ });
+ })
+ .on("error", function (e) {
+ if(self.isPaused) {
+ this.generatedError = e;
+ } else {
+ self.error(e);
+ }
+ })
+ .on("end", function () {
+ if(self.isPaused) {
+ self._upstreamEnded = true;
+ } else {
+ self.end();
+ }
+ });
+};
+NodejsStreamInputAdapter.prototype.pause = function () {
+ if(!GenericWorker.prototype.pause.call(this)) {
+ return false;
+ }
+ this._stream.pause();
+ return true;
+};
+NodejsStreamInputAdapter.prototype.resume = function () {
+ if(!GenericWorker.prototype.resume.call(this)) {
+ return false;
+ }
+
+ if(this._upstreamEnded) {
+ this.end();
+ } else {
+ this._stream.resume();
+ }
+
+ return true;
+};
+
+module.exports = NodejsStreamInputAdapter;
+
+},{"../stream/GenericWorker":28,"../utils":32}],13:[function(require,module,exports){
+"use strict";
+
+var Readable = require("readable-stream").Readable;
+
+var utils = require("../utils");
+utils.inherits(NodejsStreamOutputAdapter, Readable);
+
+/**
+* A nodejs stream using a worker as source.
+* @see the SourceWrapper in http://nodejs.org/api/stream.html
+* @constructor
+* @param {StreamHelper} helper the helper wrapping the worker
+* @param {Object} options the nodejs stream options
+* @param {Function} updateCb the update callback.
+*/
+function NodejsStreamOutputAdapter(helper, options, updateCb) {
+ Readable.call(this, options);
+ this._helper = helper;
+
+ var self = this;
+ helper.on("data", function (data, meta) {
+ if (!self.push(data)) {
+ self._helper.pause();
+ }
+ if(updateCb) {
+ updateCb(meta);
+ }
+ })
+ .on("error", function(e) {
+ self.emit("error", e);
+ })
+ .on("end", function () {
+ self.push(null);
+ });
+}
+
+
+NodejsStreamOutputAdapter.prototype._read = function() {
+ this._helper.resume();
+};
+
+module.exports = NodejsStreamOutputAdapter;
+
+},{"../utils":32,"readable-stream":16}],14:[function(require,module,exports){
+"use strict";
+
+module.exports = {
+ /**
+ * True if this is running in Nodejs, will be undefined in a browser.
+ * In a browser, browserify won't include this file and the whole module
+ * will be resolved an empty object.
+ */
+ isNode : typeof Buffer !== "undefined",
+ /**
+ * Create a new nodejs Buffer from an existing content.
+ * @param {Object} data the data to pass to the constructor.
+ * @param {String} encoding the encoding to use.
+ * @return {Buffer} a new Buffer.
+ */
+ newBufferFrom: function(data, encoding) {
+ if (Buffer.from && Buffer.from !== Uint8Array.from) {
+ return Buffer.from(data, encoding);
+ } else {
+ if (typeof data === "number") {
+ // Safeguard for old Node.js versions. On newer versions,
+ // Buffer.from(number) / Buffer(number, encoding) already throw.
+ throw new Error("The \"data\" argument must not be a number");
+ }
+ return new Buffer(data, encoding);
+ }
+ },
+ /**
+ * Create a new nodejs Buffer with the specified size.
+ * @param {Integer} size the size of the buffer.
+ * @return {Buffer} a new Buffer.
+ */
+ allocBuffer: function (size) {
+ if (Buffer.alloc) {
+ return Buffer.alloc(size);
+ } else {
+ var buf = new Buffer(size);
+ buf.fill(0);
+ return buf;
+ }
+ },
+ /**
+ * Find out if an object is a Buffer.
+ * @param {Object} b the object to test.
+ * @return {Boolean} true if the object is a Buffer, false otherwise.
+ */
+ isBuffer : function(b){
+ return Buffer.isBuffer(b);
+ },
+
+ isStream : function (obj) {
+ return obj &&
+ typeof obj.on === "function" &&
+ typeof obj.pause === "function" &&
+ typeof obj.resume === "function";
+ }
+};
+
+},{}],15:[function(require,module,exports){
+"use strict";
+var utf8 = require("./utf8");
+var utils = require("./utils");
+var GenericWorker = require("./stream/GenericWorker");
+var StreamHelper = require("./stream/StreamHelper");
+var defaults = require("./defaults");
+var CompressedObject = require("./compressedObject");
+var ZipObject = require("./zipObject");
+var generate = require("./generate");
+var nodejsUtils = require("./nodejsUtils");
+var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter");
+
+
+/**
+ * Add a file in the current folder.
+ * @private
+ * @param {string} name the name of the file
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
+ * @param {Object} originalOptions the options of the file
+ * @return {Object} the new file.
+ */
+var fileAdd = function(name, data, originalOptions) {
+ // be sure sub folders exist
+ var dataType = utils.getTypeOf(data),
+ parent;
+
+
+ /*
+ * Correct options.
+ */
+
+ var o = utils.extend(originalOptions || {}, defaults);
+ o.date = o.date || new Date();
+ if (o.compression !== null) {
+ o.compression = o.compression.toUpperCase();
+ }
+
+ if (typeof o.unixPermissions === "string") {
+ o.unixPermissions = parseInt(o.unixPermissions, 8);
+ }
+
+ // UNX_IFDIR 0040000 see zipinfo.c
+ if (o.unixPermissions && (o.unixPermissions & 0x4000)) {
+ o.dir = true;
+ }
+ // Bit 4 Directory
+ if (o.dosPermissions && (o.dosPermissions & 0x0010)) {
+ o.dir = true;
+ }
+
+ if (o.dir) {
+ name = forceTrailingSlash(name);
+ }
+ if (o.createFolders && (parent = parentFolder(name))) {
+ folderAdd.call(this, parent, true);
+ }
+
+ var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false;
+ if (!originalOptions || typeof originalOptions.binary === "undefined") {
+ o.binary = !isUnicodeString;
+ }
+
+
+ var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0;
+
+ if (isCompressedEmpty || o.dir || !data || data.length === 0) {
+ o.base64 = false;
+ o.binary = true;
+ data = "";
+ o.compression = "STORE";
+ dataType = "string";
+ }
+
+ /*
+ * Convert content to fit.
+ */
+
+ var zipObjectContent = null;
+ if (data instanceof CompressedObject || data instanceof GenericWorker) {
+ zipObjectContent = data;
+ } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) {
+ zipObjectContent = new NodejsStreamInputAdapter(name, data);
+ } else {
+ zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64);
+ }
+
+ var object = new ZipObject(name, zipObjectContent, o);
+ this.files[name] = object;
+ /*
+ TODO: we can't throw an exception because we have async promises
+ (we can have a promise of a Date() for example) but returning a
+ promise is useless because file(name, data) returns the JSZip
+ object for chaining. Should we break that to allow the user
+ to catch the error ?
+
+ return external.Promise.resolve(zipObjectContent)
+ .then(function () {
+ return object;
+ });
+ */
+};
+
+/**
+ * Find the parent folder of the path.
+ * @private
+ * @param {string} path the path to use
+ * @return {string} the parent folder, or ""
+ */
+var parentFolder = function (path) {
+ if (path.slice(-1) === "/") {
+ path = path.substring(0, path.length - 1);
+ }
+ var lastSlash = path.lastIndexOf("/");
+ return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
+};
+
+/**
+ * Returns the path with a slash at the end.
+ * @private
+ * @param {String} path the path to check.
+ * @return {String} the path with a trailing slash.
+ */
+var forceTrailingSlash = function(path) {
+ // Check the name ends with a /
+ if (path.slice(-1) !== "/") {
+ path += "/"; // IE doesn't like substr(-1)
+ }
+ return path;
+};
+
+/**
+ * Add a (sub) folder in the current folder.
+ * @private
+ * @param {string} name the folder's name
+ * @param {boolean=} [createFolders] If true, automatically create sub
+ * folders. Defaults to false.
+ * @return {Object} the new folder.
+ */
+var folderAdd = function(name, createFolders) {
+ createFolders = (typeof createFolders !== "undefined") ? createFolders : defaults.createFolders;
+
+ name = forceTrailingSlash(name);
+
+ // Does this folder already exist?
+ if (!this.files[name]) {
+ fileAdd.call(this, name, null, {
+ dir: true,
+ createFolders: createFolders
+ });
+ }
+ return this.files[name];
+};
+
+/**
+* Cross-window, cross-Node-context regular expression detection
+* @param {Object} object Anything
+* @return {Boolean} true if the object is a regular expression,
+* false otherwise
+*/
+function isRegExp(object) {
+ return Object.prototype.toString.call(object) === "[object RegExp]";
+}
+
+// return the actual prototype of JSZip
+var out = {
+ /**
+ * @see loadAsync
+ */
+ load: function() {
+ throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
+ },
+
+
+ /**
+ * Call a callback function for each entry at this folder level.
+ * @param {Function} cb the callback function:
+ * function (relativePath, file) {...}
+ * It takes 2 arguments : the relative path and the file.
+ */
+ forEach: function(cb) {
+ var filename, relativePath, file;
+ // ignore warning about unwanted properties because this.files is a null prototype object
+ /* eslint-disable-next-line guard-for-in */
+ for (filename in this.files) {
+ file = this.files[filename];
+ relativePath = filename.slice(this.root.length, filename.length);
+ if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root
+ cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn...
+ }
+ }
+ },
+
+ /**
+ * Filter nested files/folders with the specified function.
+ * @param {Function} search the predicate to use :
+ * function (relativePath, file) {...}
+ * It takes 2 arguments : the relative path and the file.
+ * @return {Array} An array of matching elements.
+ */
+ filter: function(search) {
+ var result = [];
+ this.forEach(function (relativePath, entry) {
+ if (search(relativePath, entry)) { // the file matches the function
+ result.push(entry);
+ }
+
+ });
+ return result;
+ },
+
+ /**
+ * Add a file to the zip file, or search a file.
+ * @param {string|RegExp} name The name of the file to add (if data is defined),
+ * the name of the file to find (if no data) or a regex to match files.
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
+ * @param {Object} o File options
+ * @return {JSZip|Object|Array} this JSZip object (when adding a file),
+ * a file (when searching by string) or an array of files (when searching by regex).
+ */
+ file: function(name, data, o) {
+ if (arguments.length === 1) {
+ if (isRegExp(name)) {
+ var regexp = name;
+ return this.filter(function(relativePath, file) {
+ return !file.dir && regexp.test(relativePath);
+ });
+ }
+ else { // text
+ var obj = this.files[this.root + name];
+ if (obj && !obj.dir) {
+ return obj;
+ } else {
+ return null;
+ }
+ }
+ }
+ else { // more than one argument : we have data !
+ name = this.root + name;
+ fileAdd.call(this, name, data, o);
+ }
+ return this;
+ },
+
+ /**
+ * Add a directory to the zip file, or search.
+ * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
+ * @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
+ */
+ folder: function(arg) {
+ if (!arg) {
+ return this;
+ }
+
+ if (isRegExp(arg)) {
+ return this.filter(function(relativePath, file) {
+ return file.dir && arg.test(relativePath);
+ });
+ }
+
+ // else, name is a new folder
+ var name = this.root + arg;
+ var newFolder = folderAdd.call(this, name);
+
+ // Allow chaining by returning a new object with this folder as the root
+ var ret = this.clone();
+ ret.root = newFolder.name;
+ return ret;
+ },
+
+ /**
+ * Delete a file, or a directory and all sub-files, from the zip
+ * @param {string} name the name of the file to delete
+ * @return {JSZip} this JSZip object
+ */
+ remove: function(name) {
+ name = this.root + name;
+ var file = this.files[name];
+ if (!file) {
+ // Look for any folders
+ if (name.slice(-1) !== "/") {
+ name += "/";
+ }
+ file = this.files[name];
+ }
+
+ if (file && !file.dir) {
+ // file
+ delete this.files[name];
+ } else {
+ // maybe a folder, delete recursively
+ var kids = this.filter(function(relativePath, file) {
+ return file.name.slice(0, name.length) === name;
+ });
+ for (var i = 0; i < kids.length; i++) {
+ delete this.files[kids[i].name];
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * @deprecated This method has been removed in JSZip 3.0, please check the upgrade guide.
+ */
+ generate: function() {
+ throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
+ },
+
+ /**
+ * Generate the complete zip file as an internal stream.
+ * @param {Object} options the options to generate the zip file :
+ * - compression, "STORE" by default.
+ * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
+ * @return {StreamHelper} the streamed zip file.
+ */
+ generateInternalStream: function(options) {
+ var worker, opts = {};
+ try {
+ opts = utils.extend(options || {}, {
+ streamFiles: false,
+ compression: "STORE",
+ compressionOptions : null,
+ type: "",
+ platform: "DOS",
+ comment: null,
+ mimeType: "application/zip",
+ encodeFileName: utf8.utf8encode
+ });
+
+ opts.type = opts.type.toLowerCase();
+ opts.compression = opts.compression.toUpperCase();
+
+ // "binarystring" is preferred but the internals use "string".
+ if(opts.type === "binarystring") {
+ opts.type = "string";
+ }
+
+ if (!opts.type) {
+ throw new Error("No output type specified.");
+ }
+
+ utils.checkSupport(opts.type);
+
+ // accept nodejs `process.platform`
+ if(
+ opts.platform === "darwin" ||
+ opts.platform === "freebsd" ||
+ opts.platform === "linux" ||
+ opts.platform === "sunos"
+ ) {
+ opts.platform = "UNIX";
+ }
+ if (opts.platform === "win32") {
+ opts.platform = "DOS";
+ }
+
+ var comment = opts.comment || this.comment || "";
+ worker = generate.generateWorker(this, opts, comment);
+ } catch (e) {
+ worker = new GenericWorker("error");
+ worker.error(e);
+ }
+ return new StreamHelper(worker, opts.type || "string", opts.mimeType);
+ },
+ /**
+ * Generate the complete zip file asynchronously.
+ * @see generateInternalStream
+ */
+ generateAsync: function(options, onUpdate) {
+ return this.generateInternalStream(options).accumulate(onUpdate);
+ },
+ /**
+ * Generate the complete zip file asynchronously.
+ * @see generateInternalStream
+ */
+ generateNodeStream: function(options, onUpdate) {
+ options = options || {};
+ if (!options.type) {
+ options.type = "nodebuffer";
+ }
+ return this.generateInternalStream(options).toNodejsStream(onUpdate);
+ }
+};
+module.exports = out;
+
+},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(require,module,exports){
+"use strict";
+/*
+ * This file is used by module bundlers (browserify/webpack/etc) when
+ * including a stream implementation. We use "readable-stream" to get a
+ * consistent behavior between nodejs versions but bundlers often have a shim
+ * for "stream". Using this shim greatly improve the compatibility and greatly
+ * reduce the final size of the bundle (only one stream implementation, not
+ * two).
+ */
+module.exports = require("stream");
+
+},{"stream":undefined}],17:[function(require,module,exports){
+"use strict";
+var DataReader = require("./DataReader");
+var utils = require("../utils");
+
+function ArrayReader(data) {
+ DataReader.call(this, data);
+ for(var i = 0; i < this.data.length; i++) {
+ data[i] = data[i] & 0xFF;
+ }
+}
+utils.inherits(ArrayReader, DataReader);
+/**
+ * @see DataReader.byteAt
+ */
+ArrayReader.prototype.byteAt = function(i) {
+ return this.data[this.zero + i];
+};
+/**
+ * @see DataReader.lastIndexOfSignature
+ */
+ArrayReader.prototype.lastIndexOfSignature = function(sig) {
+ var sig0 = sig.charCodeAt(0),
+ sig1 = sig.charCodeAt(1),
+ sig2 = sig.charCodeAt(2),
+ sig3 = sig.charCodeAt(3);
+ for (var i = this.length - 4; i >= 0; --i) {
+ if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) {
+ return i - this.zero;
+ }
+ }
+
+ return -1;
+};
+/**
+ * @see DataReader.readAndCheckSignature
+ */
+ArrayReader.prototype.readAndCheckSignature = function (sig) {
+ var sig0 = sig.charCodeAt(0),
+ sig1 = sig.charCodeAt(1),
+ sig2 = sig.charCodeAt(2),
+ sig3 = sig.charCodeAt(3),
+ data = this.readData(4);
+ return sig0 === data[0] && sig1 === data[1] && sig2 === data[2] && sig3 === data[3];
+};
+/**
+ * @see DataReader.readData
+ */
+ArrayReader.prototype.readData = function(size) {
+ this.checkOffset(size);
+ if(size === 0) {
+ return [];
+ }
+ var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
+ this.index += size;
+ return result;
+};
+module.exports = ArrayReader;
+
+},{"../utils":32,"./DataReader":18}],18:[function(require,module,exports){
+"use strict";
+var utils = require("../utils");
+
+function DataReader(data) {
+ this.data = data; // type : see implementation
+ this.length = data.length;
+ this.index = 0;
+ this.zero = 0;
+}
+DataReader.prototype = {
+ /**
+ * Check that the offset will not go too far.
+ * @param {string} offset the additional offset to check.
+ * @throws {Error} an Error if the offset is out of bounds.
+ */
+ checkOffset: function(offset) {
+ this.checkIndex(this.index + offset);
+ },
+ /**
+ * Check that the specified index will not be too far.
+ * @param {string} newIndex the index to check.
+ * @throws {Error} an Error if the index is out of bounds.
+ */
+ checkIndex: function(newIndex) {
+ if (this.length < this.zero + newIndex || newIndex < 0) {
+ throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?");
+ }
+ },
+ /**
+ * Change the index.
+ * @param {number} newIndex The new index.
+ * @throws {Error} if the new index is out of the data.
+ */
+ setIndex: function(newIndex) {
+ this.checkIndex(newIndex);
+ this.index = newIndex;
+ },
+ /**
+ * Skip the next n bytes.
+ * @param {number} n the number of bytes to skip.
+ * @throws {Error} if the new index is out of the data.
+ */
+ skip: function(n) {
+ this.setIndex(this.index + n);
+ },
+ /**
+ * Get the byte at the specified index.
+ * @param {number} i the index to use.
+ * @return {number} a byte.
+ */
+ byteAt: function() {
+ // see implementations
+ },
+ /**
+ * Get the next number with a given byte size.
+ * @param {number} size the number of bytes to read.
+ * @return {number} the corresponding number.
+ */
+ readInt: function(size) {
+ var result = 0,
+ i;
+ this.checkOffset(size);
+ for (i = this.index + size - 1; i >= this.index; i--) {
+ result = (result << 8) + this.byteAt(i);
+ }
+ this.index += size;
+ return result;
+ },
+ /**
+ * Get the next string with a given byte size.
+ * @param {number} size the number of bytes to read.
+ * @return {string} the corresponding string.
+ */
+ readString: function(size) {
+ return utils.transformTo("string", this.readData(size));
+ },
+ /**
+ * Get raw data without conversion, bytes.
+ * @param {number} size the number of bytes to read.
+ * @return {Object} the raw data, implementation specific.
+ */
+ readData: function() {
+ // see implementations
+ },
+ /**
+ * Find the last occurrence of a zip signature (4 bytes).
+ * @param {string} sig the signature to find.
+ * @return {number} the index of the last occurrence, -1 if not found.
+ */
+ lastIndexOfSignature: function() {
+ // see implementations
+ },
+ /**
+ * Read the signature (4 bytes) at the current position and compare it with sig.
+ * @param {string} sig the expected signature
+ * @return {boolean} true if the signature matches, false otherwise.
+ */
+ readAndCheckSignature: function() {
+ // see implementations
+ },
+ /**
+ * Get the next date.
+ * @return {Date} the date.
+ */
+ readDate: function() {
+ var dostime = this.readInt(4);
+ return new Date(Date.UTC(
+ ((dostime >> 25) & 0x7f) + 1980, // year
+ ((dostime >> 21) & 0x0f) - 1, // month
+ (dostime >> 16) & 0x1f, // day
+ (dostime >> 11) & 0x1f, // hour
+ (dostime >> 5) & 0x3f, // minute
+ (dostime & 0x1f) << 1)); // second
+ }
+};
+module.exports = DataReader;
+
+},{"../utils":32}],19:[function(require,module,exports){
+"use strict";
+var Uint8ArrayReader = require("./Uint8ArrayReader");
+var utils = require("../utils");
+
+function NodeBufferReader(data) {
+ Uint8ArrayReader.call(this, data);
+}
+utils.inherits(NodeBufferReader, Uint8ArrayReader);
+
+/**
+ * @see DataReader.readData
+ */
+NodeBufferReader.prototype.readData = function(size) {
+ this.checkOffset(size);
+ var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
+ this.index += size;
+ return result;
+};
+module.exports = NodeBufferReader;
+
+},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(require,module,exports){
+"use strict";
+var DataReader = require("./DataReader");
+var utils = require("../utils");
+
+function StringReader(data) {
+ DataReader.call(this, data);
+}
+utils.inherits(StringReader, DataReader);
+/**
+ * @see DataReader.byteAt
+ */
+StringReader.prototype.byteAt = function(i) {
+ return this.data.charCodeAt(this.zero + i);
+};
+/**
+ * @see DataReader.lastIndexOfSignature
+ */
+StringReader.prototype.lastIndexOfSignature = function(sig) {
+ return this.data.lastIndexOf(sig) - this.zero;
+};
+/**
+ * @see DataReader.readAndCheckSignature
+ */
+StringReader.prototype.readAndCheckSignature = function (sig) {
+ var data = this.readData(4);
+ return sig === data;
+};
+/**
+ * @see DataReader.readData
+ */
+StringReader.prototype.readData = function(size) {
+ this.checkOffset(size);
+ // this will work because the constructor applied the "& 0xff" mask.
+ var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
+ this.index += size;
+ return result;
+};
+module.exports = StringReader;
+
+},{"../utils":32,"./DataReader":18}],21:[function(require,module,exports){
+"use strict";
+var ArrayReader = require("./ArrayReader");
+var utils = require("../utils");
+
+function Uint8ArrayReader(data) {
+ ArrayReader.call(this, data);
+}
+utils.inherits(Uint8ArrayReader, ArrayReader);
+/**
+ * @see DataReader.readData
+ */
+Uint8ArrayReader.prototype.readData = function(size) {
+ this.checkOffset(size);
+ if(size === 0) {
+ // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of [].
+ return new Uint8Array(0);
+ }
+ var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size);
+ this.index += size;
+ return result;
+};
+module.exports = Uint8ArrayReader;
+
+},{"../utils":32,"./ArrayReader":17}],22:[function(require,module,exports){
+"use strict";
+
+var utils = require("../utils");
+var support = require("../support");
+var ArrayReader = require("./ArrayReader");
+var StringReader = require("./StringReader");
+var NodeBufferReader = require("./NodeBufferReader");
+var Uint8ArrayReader = require("./Uint8ArrayReader");
+
+/**
+ * Create a reader adapted to the data.
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to read.
+ * @return {DataReader} the data reader.
+ */
+module.exports = function (data) {
+ var type = utils.getTypeOf(data);
+ utils.checkSupport(type);
+ if (type === "string" && !support.uint8array) {
+ return new StringReader(data);
+ }
+ if (type === "nodebuffer") {
+ return new NodeBufferReader(data);
+ }
+ if (support.uint8array) {
+ return new Uint8ArrayReader(utils.transformTo("uint8array", data));
+ }
+ return new ArrayReader(utils.transformTo("array", data));
+};
+
+},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(require,module,exports){
+"use strict";
+exports.LOCAL_FILE_HEADER = "PK\x03\x04";
+exports.CENTRAL_FILE_HEADER = "PK\x01\x02";
+exports.CENTRAL_DIRECTORY_END = "PK\x05\x06";
+exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07";
+exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06";
+exports.DATA_DESCRIPTOR = "PK\x07\x08";
+
+},{}],24:[function(require,module,exports){
+"use strict";
+
+var GenericWorker = require("./GenericWorker");
+var utils = require("../utils");
+
+/**
+ * A worker which convert chunks to a specified type.
+ * @constructor
+ * @param {String} destType the destination type.
+ */
+function ConvertWorker(destType) {
+ GenericWorker.call(this, "ConvertWorker to " + destType);
+ this.destType = destType;
+}
+utils.inherits(ConvertWorker, GenericWorker);
+
+/**
+ * @see GenericWorker.processChunk
+ */
+ConvertWorker.prototype.processChunk = function (chunk) {
+ this.push({
+ data : utils.transformTo(this.destType, chunk.data),
+ meta : chunk.meta
+ });
+};
+module.exports = ConvertWorker;
+
+},{"../utils":32,"./GenericWorker":28}],25:[function(require,module,exports){
+"use strict";
+
+var GenericWorker = require("./GenericWorker");
+var crc32 = require("../crc32");
+var utils = require("../utils");
+
+/**
+ * A worker which calculate the crc32 of the data flowing through.
+ * @constructor
+ */
+function Crc32Probe() {
+ GenericWorker.call(this, "Crc32Probe");
+ this.withStreamInfo("crc32", 0);
+}
+utils.inherits(Crc32Probe, GenericWorker);
+
+/**
+ * @see GenericWorker.processChunk
+ */
+Crc32Probe.prototype.processChunk = function (chunk) {
+ this.streamInfo.crc32 = crc32(chunk.data, this.streamInfo.crc32 || 0);
+ this.push(chunk);
+};
+module.exports = Crc32Probe;
+
+},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(require,module,exports){
+"use strict";
+
+var utils = require("../utils");
+var GenericWorker = require("./GenericWorker");
+
+/**
+ * A worker which calculate the total length of the data flowing through.
+ * @constructor
+ * @param {String} propName the name used to expose the length
+ */
+function DataLengthProbe(propName) {
+ GenericWorker.call(this, "DataLengthProbe for " + propName);
+ this.propName = propName;
+ this.withStreamInfo(propName, 0);
+}
+utils.inherits(DataLengthProbe, GenericWorker);
+
+/**
+ * @see GenericWorker.processChunk
+ */
+DataLengthProbe.prototype.processChunk = function (chunk) {
+ if(chunk) {
+ var length = this.streamInfo[this.propName] || 0;
+ this.streamInfo[this.propName] = length + chunk.data.length;
+ }
+ GenericWorker.prototype.processChunk.call(this, chunk);
+};
+module.exports = DataLengthProbe;
+
+
+},{"../utils":32,"./GenericWorker":28}],27:[function(require,module,exports){
+"use strict";
+
+var utils = require("../utils");
+var GenericWorker = require("./GenericWorker");
+
+// the size of the generated chunks
+// TODO expose this as a public variable
+var DEFAULT_BLOCK_SIZE = 16 * 1024;
+
+/**
+ * A worker that reads a content and emits chunks.
+ * @constructor
+ * @param {Promise} dataP the promise of the data to split
+ */
+function DataWorker(dataP) {
+ GenericWorker.call(this, "DataWorker");
+ var self = this;
+ this.dataIsReady = false;
+ this.index = 0;
+ this.max = 0;
+ this.data = null;
+ this.type = "";
+
+ this._tickScheduled = false;
+
+ dataP.then(function (data) {
+ self.dataIsReady = true;
+ self.data = data;
+ self.max = data && data.length || 0;
+ self.type = utils.getTypeOf(data);
+ if(!self.isPaused) {
+ self._tickAndRepeat();
+ }
+ }, function (e) {
+ self.error(e);
+ });
+}
+
+utils.inherits(DataWorker, GenericWorker);
+
+/**
+ * @see GenericWorker.cleanUp
+ */
+DataWorker.prototype.cleanUp = function () {
+ GenericWorker.prototype.cleanUp.call(this);
+ this.data = null;
+};
+
+/**
+ * @see GenericWorker.resume
+ */
+DataWorker.prototype.resume = function () {
+ if(!GenericWorker.prototype.resume.call(this)) {
+ return false;
+ }
+
+ if (!this._tickScheduled && this.dataIsReady) {
+ this._tickScheduled = true;
+ utils.delay(this._tickAndRepeat, [], this);
+ }
+ return true;
+};
+
+/**
+ * Trigger a tick a schedule an other call to this function.
+ */
+DataWorker.prototype._tickAndRepeat = function() {
+ this._tickScheduled = false;
+ if(this.isPaused || this.isFinished) {
+ return;
+ }
+ this._tick();
+ if(!this.isFinished) {
+ utils.delay(this._tickAndRepeat, [], this);
+ this._tickScheduled = true;
+ }
+};
+
+/**
+ * Read and push a chunk.
+ */
+DataWorker.prototype._tick = function() {
+
+ if(this.isPaused || this.isFinished) {
+ return false;
+ }
+
+ var size = DEFAULT_BLOCK_SIZE;
+ var data = null, nextIndex = Math.min(this.max, this.index + size);
+ if (this.index >= this.max) {
+ // EOF
+ return this.end();
+ } else {
+ switch(this.type) {
+ case "string":
+ data = this.data.substring(this.index, nextIndex);
+ break;
+ case "uint8array":
+ data = this.data.subarray(this.index, nextIndex);
+ break;
+ case "array":
+ case "nodebuffer":
+ data = this.data.slice(this.index, nextIndex);
+ break;
+ }
+ this.index = nextIndex;
+ return this.push({
+ data : data,
+ meta : {
+ percent : this.max ? this.index / this.max * 100 : 0
+ }
+ });
+ }
+};
+
+module.exports = DataWorker;
+
+},{"../utils":32,"./GenericWorker":28}],28:[function(require,module,exports){
+"use strict";
+
+/**
+ * A worker that does nothing but passing chunks to the next one. This is like
+ * a nodejs stream but with some differences. On the good side :
+ * - it works on IE 6-9 without any issue / polyfill
+ * - it weights less than the full dependencies bundled with browserify
+ * - it forwards errors (no need to declare an error handler EVERYWHERE)
+ *
+ * A chunk is an object with 2 attributes : `meta` and `data`. The former is an
+ * object containing anything (`percent` for example), see each worker for more
+ * details. The latter is the real data (String, Uint8Array, etc).
+ *
+ * @constructor
+ * @param {String} name the name of the stream (mainly used for debugging purposes)
+ */
+function GenericWorker(name) {
+ // the name of the worker
+ this.name = name || "default";
+ // an object containing metadata about the workers chain
+ this.streamInfo = {};
+ // an error which happened when the worker was paused
+ this.generatedError = null;
+ // an object containing metadata to be merged by this worker into the general metadata
+ this.extraStreamInfo = {};
+ // true if the stream is paused (and should not do anything), false otherwise
+ this.isPaused = true;
+ // true if the stream is finished (and should not do anything), false otherwise
+ this.isFinished = false;
+ // true if the stream is locked to prevent further structure updates (pipe), false otherwise
+ this.isLocked = false;
+ // the event listeners
+ this._listeners = {
+ "data":[],
+ "end":[],
+ "error":[]
+ };
+ // the previous worker, if any
+ this.previous = null;
+}
+
+GenericWorker.prototype = {
+ /**
+ * Push a chunk to the next workers.
+ * @param {Object} chunk the chunk to push
+ */
+ push : function (chunk) {
+ this.emit("data", chunk);
+ },
+ /**
+ * End the stream.
+ * @return {Boolean} true if this call ended the worker, false otherwise.
+ */
+ end : function () {
+ if (this.isFinished) {
+ return false;
+ }
+
+ this.flush();
+ try {
+ this.emit("end");
+ this.cleanUp();
+ this.isFinished = true;
+ } catch (e) {
+ this.emit("error", e);
+ }
+ return true;
+ },
+ /**
+ * End the stream with an error.
+ * @param {Error} e the error which caused the premature end.
+ * @return {Boolean} true if this call ended the worker with an error, false otherwise.
+ */
+ error : function (e) {
+ if (this.isFinished) {
+ return false;
+ }
+
+ if(this.isPaused) {
+ this.generatedError = e;
+ } else {
+ this.isFinished = true;
+
+ this.emit("error", e);
+
+ // in the workers chain exploded in the middle of the chain,
+ // the error event will go downward but we also need to notify
+ // workers upward that there has been an error.
+ if(this.previous) {
+ this.previous.error(e);
+ }
+
+ this.cleanUp();
+ }
+ return true;
+ },
+ /**
+ * Add a callback on an event.
+ * @param {String} name the name of the event (data, end, error)
+ * @param {Function} listener the function to call when the event is triggered
+ * @return {GenericWorker} the current object for chainability
+ */
+ on : function (name, listener) {
+ this._listeners[name].push(listener);
+ return this;
+ },
+ /**
+ * Clean any references when a worker is ending.
+ */
+ cleanUp : function () {
+ this.streamInfo = this.generatedError = this.extraStreamInfo = null;
+ this._listeners = [];
+ },
+ /**
+ * Trigger an event. This will call registered callback with the provided arg.
+ * @param {String} name the name of the event (data, end, error)
+ * @param {Object} arg the argument to call the callback with.
+ */
+ emit : function (name, arg) {
+ if (this._listeners[name]) {
+ for(var i = 0; i < this._listeners[name].length; i++) {
+ this._listeners[name][i].call(this, arg);
+ }
+ }
+ },
+ /**
+ * Chain a worker with an other.
+ * @param {Worker} next the worker receiving events from the current one.
+ * @return {worker} the next worker for chainability
+ */
+ pipe : function (next) {
+ return next.registerPrevious(this);
+ },
+ /**
+ * Same as `pipe` in the other direction.
+ * Using an API with `pipe(next)` is very easy.
+ * Implementing the API with the point of view of the next one registering
+ * a source is easier, see the ZipFileWorker.
+ * @param {Worker} previous the previous worker, sending events to this one
+ * @return {Worker} the current worker for chainability
+ */
+ registerPrevious : function (previous) {
+ if (this.isLocked) {
+ throw new Error("The stream '" + this + "' has already been used.");
+ }
+
+ // sharing the streamInfo...
+ this.streamInfo = previous.streamInfo;
+ // ... and adding our own bits
+ this.mergeStreamInfo();
+ this.previous = previous;
+ var self = this;
+ previous.on("data", function (chunk) {
+ self.processChunk(chunk);
+ });
+ previous.on("end", function () {
+ self.end();
+ });
+ previous.on("error", function (e) {
+ self.error(e);
+ });
+ return this;
+ },
+ /**
+ * Pause the stream so it doesn't send events anymore.
+ * @return {Boolean} true if this call paused the worker, false otherwise.
+ */
+ pause : function () {
+ if(this.isPaused || this.isFinished) {
+ return false;
+ }
+ this.isPaused = true;
+
+ if(this.previous) {
+ this.previous.pause();
+ }
+ return true;
+ },
+ /**
+ * Resume a paused stream.
+ * @return {Boolean} true if this call resumed the worker, false otherwise.
+ */
+ resume : function () {
+ if(!this.isPaused || this.isFinished) {
+ return false;
+ }
+ this.isPaused = false;
+
+ // if true, the worker tried to resume but failed
+ var withError = false;
+ if(this.generatedError) {
+ this.error(this.generatedError);
+ withError = true;
+ }
+ if(this.previous) {
+ this.previous.resume();
+ }
+
+ return !withError;
+ },
+ /**
+ * Flush any remaining bytes as the stream is ending.
+ */
+ flush : function () {},
+ /**
+ * Process a chunk. This is usually the method overridden.
+ * @param {Object} chunk the chunk to process.
+ */
+ processChunk : function(chunk) {
+ this.push(chunk);
+ },
+ /**
+ * Add a key/value to be added in the workers chain streamInfo once activated.
+ * @param {String} key the key to use
+ * @param {Object} value the associated value
+ * @return {Worker} the current worker for chainability
+ */
+ withStreamInfo : function (key, value) {
+ this.extraStreamInfo[key] = value;
+ this.mergeStreamInfo();
+ return this;
+ },
+ /**
+ * Merge this worker's streamInfo into the chain's streamInfo.
+ */
+ mergeStreamInfo : function () {
+ for(var key in this.extraStreamInfo) {
+ if (!Object.prototype.hasOwnProperty.call(this.extraStreamInfo, key)) {
+ continue;
+ }
+ this.streamInfo[key] = this.extraStreamInfo[key];
+ }
+ },
+
+ /**
+ * Lock the stream to prevent further updates on the workers chain.
+ * After calling this method, all calls to pipe will fail.
+ */
+ lock: function () {
+ if (this.isLocked) {
+ throw new Error("The stream '" + this + "' has already been used.");
+ }
+ this.isLocked = true;
+ if (this.previous) {
+ this.previous.lock();
+ }
+ },
+
+ /**
+ *
+ * Pretty print the workers chain.
+ */
+ toString : function () {
+ var me = "Worker " + this.name;
+ if (this.previous) {
+ return this.previous + " -> " + me;
+ } else {
+ return me;
+ }
+ }
+};
+
+module.exports = GenericWorker;
+
+},{}],29:[function(require,module,exports){
+"use strict";
+
+var utils = require("../utils");
+var ConvertWorker = require("./ConvertWorker");
+var GenericWorker = require("./GenericWorker");
+var base64 = require("../base64");
+var support = require("../support");
+var external = require("../external");
+
+var NodejsStreamOutputAdapter = null;
+if (support.nodestream) {
+ try {
+ NodejsStreamOutputAdapter = require("../nodejs/NodejsStreamOutputAdapter");
+ } catch(e) {
+ // ignore
+ }
+}
+
+/**
+ * Apply the final transformation of the data. If the user wants a Blob for
+ * example, it's easier to work with an U8intArray and finally do the
+ * ArrayBuffer/Blob conversion.
+ * @param {String} type the name of the final type
+ * @param {String|Uint8Array|Buffer} content the content to transform
+ * @param {String} mimeType the mime type of the content, if applicable.
+ * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format.
+ */
+function transformZipOutput(type, content, mimeType) {
+ switch(type) {
+ case "blob" :
+ return utils.newBlob(utils.transformTo("arraybuffer", content), mimeType);
+ case "base64" :
+ return base64.encode(content);
+ default :
+ return utils.transformTo(type, content);
+ }
+}
+
+/**
+ * Concatenate an array of data of the given type.
+ * @param {String} type the type of the data in the given array.
+ * @param {Array} dataArray the array containing the data chunks to concatenate
+ * @return {String|Uint8Array|Buffer} the concatenated data
+ * @throws Error if the asked type is unsupported
+ */
+function concat (type, dataArray) {
+ var i, index = 0, res = null, totalLength = 0;
+ for(i = 0; i < dataArray.length; i++) {
+ totalLength += dataArray[i].length;
+ }
+ switch(type) {
+ case "string":
+ return dataArray.join("");
+ case "array":
+ return Array.prototype.concat.apply([], dataArray);
+ case "uint8array":
+ res = new Uint8Array(totalLength);
+ for(i = 0; i < dataArray.length; i++) {
+ res.set(dataArray[i], index);
+ index += dataArray[i].length;
+ }
+ return res;
+ case "nodebuffer":
+ return Buffer.concat(dataArray);
+ default:
+ throw new Error("concat : unsupported type '" + type + "'");
+ }
+}
+
+/**
+ * Listen a StreamHelper, accumulate its content and concatenate it into a
+ * complete block.
+ * @param {StreamHelper} helper the helper to use.
+ * @param {Function} updateCallback a callback called on each update. Called
+ * with one arg :
+ * - the metadata linked to the update received.
+ * @return Promise the promise for the accumulation.
+ */
+function accumulate(helper, updateCallback) {
+ return new external.Promise(function (resolve, reject){
+ var dataArray = [];
+ var chunkType = helper._internalType,
+ resultType = helper._outputType,
+ mimeType = helper._mimeType;
+ helper
+ .on("data", function (data, meta) {
+ dataArray.push(data);
+ if(updateCallback) {
+ updateCallback(meta);
+ }
+ })
+ .on("error", function(err) {
+ dataArray = [];
+ reject(err);
+ })
+ .on("end", function (){
+ try {
+ var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType);
+ resolve(result);
+ } catch (e) {
+ reject(e);
+ }
+ dataArray = [];
+ })
+ .resume();
+ });
+}
+
+/**
+ * An helper to easily use workers outside of JSZip.
+ * @constructor
+ * @param {Worker} worker the worker to wrap
+ * @param {String} outputType the type of data expected by the use
+ * @param {String} mimeType the mime type of the content, if applicable.
+ */
+function StreamHelper(worker, outputType, mimeType) {
+ var internalType = outputType;
+ switch(outputType) {
+ case "blob":
+ case "arraybuffer":
+ internalType = "uint8array";
+ break;
+ case "base64":
+ internalType = "string";
+ break;
+ }
+
+ try {
+ // the type used internally
+ this._internalType = internalType;
+ // the type used to output results
+ this._outputType = outputType;
+ // the mime type
+ this._mimeType = mimeType;
+ utils.checkSupport(internalType);
+ this._worker = worker.pipe(new ConvertWorker(internalType));
+ // the last workers can be rewired without issues but we need to
+ // prevent any updates on previous workers.
+ worker.lock();
+ } catch(e) {
+ this._worker = new GenericWorker("error");
+ this._worker.error(e);
+ }
+}
+
+StreamHelper.prototype = {
+ /**
+ * Listen a StreamHelper, accumulate its content and concatenate it into a
+ * complete block.
+ * @param {Function} updateCb the update callback.
+ * @return Promise the promise for the accumulation.
+ */
+ accumulate : function (updateCb) {
+ return accumulate(this, updateCb);
+ },
+ /**
+ * Add a listener on an event triggered on a stream.
+ * @param {String} evt the name of the event
+ * @param {Function} fn the listener
+ * @return {StreamHelper} the current helper.
+ */
+ on : function (evt, fn) {
+ var self = this;
+
+ if(evt === "data") {
+ this._worker.on(evt, function (chunk) {
+ fn.call(self, chunk.data, chunk.meta);
+ });
+ } else {
+ this._worker.on(evt, function () {
+ utils.delay(fn, arguments, self);
+ });
+ }
+ return this;
+ },
+ /**
+ * Resume the flow of chunks.
+ * @return {StreamHelper} the current helper.
+ */
+ resume : function () {
+ utils.delay(this._worker.resume, [], this._worker);
+ return this;
+ },
+ /**
+ * Pause the flow of chunks.
+ * @return {StreamHelper} the current helper.
+ */
+ pause : function () {
+ this._worker.pause();
+ return this;
+ },
+ /**
+ * Return a nodejs stream for this helper.
+ * @param {Function} updateCb the update callback.
+ * @return {NodejsStreamOutputAdapter} the nodejs stream.
+ */
+ toNodejsStream : function (updateCb) {
+ utils.checkSupport("nodestream");
+ if (this._outputType !== "nodebuffer") {
+ // an object stream containing blob/arraybuffer/uint8array/string
+ // is strange and I don't know if it would be useful.
+ // I you find this comment and have a good usecase, please open a
+ // bug report !
+ throw new Error(this._outputType + " is not supported by this method");
+ }
+
+ return new NodejsStreamOutputAdapter(this, {
+ objectMode : this._outputType !== "nodebuffer"
+ }, updateCb);
+ }
+};
+
+
+module.exports = StreamHelper;
+
+},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(require,module,exports){
+"use strict";
+
+exports.base64 = true;
+exports.array = true;
+exports.string = true;
+exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
+exports.nodebuffer = typeof Buffer !== "undefined";
+// contains true if JSZip can read/generate Uint8Array, false otherwise.
+exports.uint8array = typeof Uint8Array !== "undefined";
+
+if (typeof ArrayBuffer === "undefined") {
+ exports.blob = false;
+}
+else {
+ var buffer = new ArrayBuffer(0);
+ try {
+ exports.blob = new Blob([buffer], {
+ type: "application/zip"
+ }).size === 0;
+ }
+ catch (e) {
+ try {
+ var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder;
+ var builder = new Builder();
+ builder.append(buffer);
+ exports.blob = builder.getBlob("application/zip").size === 0;
+ }
+ catch (e) {
+ exports.blob = false;
+ }
+ }
+}
+
+try {
+ exports.nodestream = !!require("readable-stream").Readable;
+} catch(e) {
+ exports.nodestream = false;
+}
+
+},{"readable-stream":16}],31:[function(require,module,exports){
+"use strict";
+
+var utils = require("./utils");
+var support = require("./support");
+var nodejsUtils = require("./nodejsUtils");
+var GenericWorker = require("./stream/GenericWorker");
+
+/**
+ * The following functions come from pako, from pako/lib/utils/strings
+ * released under the MIT license, see pako https://github.com/nodeca/pako/
+ */
+
+// Table with utf8 lengths (calculated by first byte of sequence)
+// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
+// because max possible codepoint is 0x10ffff
+var _utf8len = new Array(256);
+for (var i=0; i<256; i++) {
+ _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1);
+}
+_utf8len[254]=_utf8len[254]=1; // Invalid sequence start
+
+// convert string to array (typed, when possible)
+var string2buf = function (str) {
+ var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
+
+ // count binary size
+ for (m_pos = 0; m_pos < str_len; m_pos++) {
+ c = str.charCodeAt(m_pos);
+ if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) {
+ c2 = str.charCodeAt(m_pos+1);
+ if ((c2 & 0xfc00) === 0xdc00) {
+ c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+ m_pos++;
+ }
+ }
+ buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
+ }
+
+ // allocate buffer
+ if (support.uint8array) {
+ buf = new Uint8Array(buf_len);
+ } else {
+ buf = new Array(buf_len);
+ }
+
+ // convert
+ for (i=0, m_pos = 0; i < buf_len; m_pos++) {
+ c = str.charCodeAt(m_pos);
+ if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) {
+ c2 = str.charCodeAt(m_pos+1);
+ if ((c2 & 0xfc00) === 0xdc00) {
+ c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+ m_pos++;
+ }
+ }
+ if (c < 0x80) {
+ /* one byte */
+ buf[i++] = c;
+ } else if (c < 0x800) {
+ /* two bytes */
+ buf[i++] = 0xC0 | (c >>> 6);
+ buf[i++] = 0x80 | (c & 0x3f);
+ } else if (c < 0x10000) {
+ /* three bytes */
+ buf[i++] = 0xE0 | (c >>> 12);
+ buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+ buf[i++] = 0x80 | (c & 0x3f);
+ } else {
+ /* four bytes */
+ buf[i++] = 0xf0 | (c >>> 18);
+ buf[i++] = 0x80 | (c >>> 12 & 0x3f);
+ buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+ buf[i++] = 0x80 | (c & 0x3f);
+ }
+ }
+
+ return buf;
+};
+
+// Calculate max possible position in utf8 buffer,
+// that will not break sequence. If that's not possible
+// - (very small limits) return max size as is.
+//
+// buf[] - utf8 bytes array
+// max - length limit (mandatory);
+var utf8border = function(buf, max) {
+ var pos;
+
+ max = max || buf.length;
+ if (max > buf.length) { max = buf.length; }
+
+ // go back from last position, until start of sequence found
+ pos = max-1;
+ while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }
+
+ // Fuckup - very small and broken sequence,
+ // return max, because we should return something anyway.
+ if (pos < 0) { return max; }
+
+ // If we came to start of buffer - that means vuffer is too small,
+ // return max too.
+ if (pos === 0) { return max; }
+
+ return (pos + _utf8len[buf[pos]] > max) ? pos : max;
+};
+
+// convert array to string
+var buf2string = function (buf) {
+ var i, out, c, c_len;
+ var len = buf.length;
+
+ // Reserve max possible length (2 words per char)
+ // NB: by unknown reasons, Array is significantly faster for
+ // String.fromCharCode.apply than Uint16Array.
+ var utf16buf = new Array(len*2);
+
+ for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; }
+
+ // apply mask on first byte
+ c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
+ // join the rest
+ while (c_len > 1 && i < len) {
+ c = (c << 6) | (buf[i++] & 0x3f);
+ c_len--;
+ }
+
+ // terminated by end of string?
+ if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }
+
+ if (c < 0x10000) {
+ utf16buf[out++] = c;
+ } else {
+ c -= 0x10000;
+ utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
+ utf16buf[out++] = 0xdc00 | (c & 0x3ff);
+ }
+ }
+
+ // shrinkBuf(utf16buf, out)
+ if (utf16buf.length !== out) {
+ if(utf16buf.subarray) {
+ utf16buf = utf16buf.subarray(0, out);
+ } else {
+ utf16buf.length = out;
+ }
+ }
+
+ // return String.fromCharCode.apply(null, utf16buf);
+ return utils.applyFromCharCode(utf16buf);
+};
+
+
+// That's all for the pako functions.
+
+
+/**
+ * Transform a javascript string into an array (typed if possible) of bytes,
+ * UTF-8 encoded.
+ * @param {String} str the string to encode
+ * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string.
+ */
+exports.utf8encode = function utf8encode(str) {
+ if (support.nodebuffer) {
+ return nodejsUtils.newBufferFrom(str, "utf-8");
+ }
+
+ return string2buf(str);
+};
+
+
+/**
+ * Transform a bytes array (or a representation) representing an UTF-8 encoded
+ * string into a javascript string.
+ * @param {Array|Uint8Array|Buffer} buf the data de decode
+ * @return {String} the decoded string.
+ */
+exports.utf8decode = function utf8decode(buf) {
+ if (support.nodebuffer) {
+ return utils.transformTo("nodebuffer", buf).toString("utf-8");
+ }
+
+ buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf);
+
+ return buf2string(buf);
+};
+
+/**
+ * A worker to decode utf8 encoded binary chunks into string chunks.
+ * @constructor
+ */
+function Utf8DecodeWorker() {
+ GenericWorker.call(this, "utf-8 decode");
+ // the last bytes if a chunk didn't end with a complete codepoint.
+ this.leftOver = null;
+}
+utils.inherits(Utf8DecodeWorker, GenericWorker);
+
+/**
+ * @see GenericWorker.processChunk
+ */
+Utf8DecodeWorker.prototype.processChunk = function (chunk) {
+
+ var data = utils.transformTo(support.uint8array ? "uint8array" : "array", chunk.data);
+
+ // 1st step, re-use what's left of the previous chunk
+ if (this.leftOver && this.leftOver.length) {
+ if(support.uint8array) {
+ var previousData = data;
+ data = new Uint8Array(previousData.length + this.leftOver.length);
+ data.set(this.leftOver, 0);
+ data.set(previousData, this.leftOver.length);
+ } else {
+ data = this.leftOver.concat(data);
+ }
+ this.leftOver = null;
+ }
+
+ var nextBoundary = utf8border(data);
+ var usableData = data;
+ if (nextBoundary !== data.length) {
+ if (support.uint8array) {
+ usableData = data.subarray(0, nextBoundary);
+ this.leftOver = data.subarray(nextBoundary, data.length);
+ } else {
+ usableData = data.slice(0, nextBoundary);
+ this.leftOver = data.slice(nextBoundary, data.length);
+ }
+ }
+
+ this.push({
+ data : exports.utf8decode(usableData),
+ meta : chunk.meta
+ });
+};
+
+/**
+ * @see GenericWorker.flush
+ */
+Utf8DecodeWorker.prototype.flush = function () {
+ if(this.leftOver && this.leftOver.length) {
+ this.push({
+ data : exports.utf8decode(this.leftOver),
+ meta : {}
+ });
+ this.leftOver = null;
+ }
+};
+exports.Utf8DecodeWorker = Utf8DecodeWorker;
+
+/**
+ * A worker to endcode string chunks into utf8 encoded binary chunks.
+ * @constructor
+ */
+function Utf8EncodeWorker() {
+ GenericWorker.call(this, "utf-8 encode");
+}
+utils.inherits(Utf8EncodeWorker, GenericWorker);
+
+/**
+ * @see GenericWorker.processChunk
+ */
+Utf8EncodeWorker.prototype.processChunk = function (chunk) {
+ this.push({
+ data : exports.utf8encode(chunk.data),
+ meta : chunk.meta
+ });
+};
+exports.Utf8EncodeWorker = Utf8EncodeWorker;
+
+},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(require,module,exports){
+"use strict";
+
+var support = require("./support");
+var base64 = require("./base64");
+var nodejsUtils = require("./nodejsUtils");
+var external = require("./external");
+require("setimmediate");
+
+
+/**
+ * Convert a string that pass as a "binary string": it should represent a byte
+ * array but may have > 255 char codes. Be sure to take only the first byte
+ * and returns the byte array.
+ * @param {String} str the string to transform.
+ * @return {Array|Uint8Array} the string in a binary format.
+ */
+function string2binary(str) {
+ var result = null;
+ if (support.uint8array) {
+ result = new Uint8Array(str.length);
+ } else {
+ result = new Array(str.length);
+ }
+ return stringToArrayLike(str, result);
+}
+
+/**
+ * Create a new blob with the given content and the given type.
+ * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use
+ * an Uint8Array because the stock browser of android 4 won't accept it (it
+ * will be silently converted to a string, "[object Uint8Array]").
+ *
+ * Use only ONE part to build the blob to avoid a memory leak in IE11 / Edge:
+ * when a large amount of Array is used to create the Blob, the amount of
+ * memory consumed is nearly 100 times the original data amount.
+ *
+ * @param {String} type the mime type of the blob.
+ * @return {Blob} the created blob.
+ */
+exports.newBlob = function(part, type) {
+ exports.checkSupport("blob");
+
+ try {
+ // Blob constructor
+ return new Blob([part], {
+ type: type
+ });
+ }
+ catch (e) {
+
+ try {
+ // deprecated, browser only, old way
+ var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder;
+ var builder = new Builder();
+ builder.append(part);
+ return builder.getBlob(type);
+ }
+ catch (e) {
+
+ // well, fuck ?!
+ throw new Error("Bug : can't construct the Blob.");
+ }
+ }
+
+
+};
+/**
+ * The identity function.
+ * @param {Object} input the input.
+ * @return {Object} the same input.
+ */
+function identity(input) {
+ return input;
+}
+
+/**
+ * Fill in an array with a string.
+ * @param {String} str the string to use.
+ * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
+ * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
+ */
+function stringToArrayLike(str, array) {
+ for (var i = 0; i < str.length; ++i) {
+ array[i] = str.charCodeAt(i) & 0xFF;
+ }
+ return array;
+}
+
+/**
+ * An helper for the function arrayLikeToString.
+ * This contains static information and functions that
+ * can be optimized by the browser JIT compiler.
+ */
+var arrayToStringHelper = {
+ /**
+ * Transform an array of int into a string, chunk by chunk.
+ * See the performances notes on arrayLikeToString.
+ * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
+ * @param {String} type the type of the array.
+ * @param {Integer} chunk the chunk size.
+ * @return {String} the resulting string.
+ * @throws Error if the chunk is too big for the stack.
+ */
+ stringifyByChunk: function(array, type, chunk) {
+ var result = [], k = 0, len = array.length;
+ // shortcut
+ if (len <= chunk) {
+ return String.fromCharCode.apply(null, array);
+ }
+ while (k < len) {
+ if (type === "array" || type === "nodebuffer") {
+ result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
+ }
+ else {
+ result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
+ }
+ k += chunk;
+ }
+ return result.join("");
+ },
+ /**
+ * Call String.fromCharCode on every item in the array.
+ * This is the naive implementation, which generate A LOT of intermediate string.
+ * This should be used when everything else fail.
+ * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
+ * @return {String} the result.
+ */
+ stringifyByChar: function(array){
+ var resultStr = "";
+ for(var i = 0; i < array.length; i++) {
+ resultStr += String.fromCharCode(array[i]);
+ }
+ return resultStr;
+ },
+ applyCanBeUsed : {
+ /**
+ * true if the browser accepts to use String.fromCharCode on Uint8Array
+ */
+ uint8array : (function () {
+ try {
+ return support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1;
+ } catch (e) {
+ return false;
+ }
+ })(),
+ /**
+ * true if the browser accepts to use String.fromCharCode on nodejs Buffer.
+ */
+ nodebuffer : (function () {
+ try {
+ return support.nodebuffer && String.fromCharCode.apply(null, nodejsUtils.allocBuffer(1)).length === 1;
+ } catch (e) {
+ return false;
+ }
+ })()
+ }
+};
+
+/**
+ * Transform an array-like object to a string.
+ * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
+ * @return {String} the result.
+ */
+function arrayLikeToString(array) {
+ // Performances notes :
+ // --------------------
+ // String.fromCharCode.apply(null, array) is the fastest, see
+ // see http://jsperf.com/converting-a-uint8array-to-a-string/2
+ // but the stack is limited (and we can get huge arrays !).
+ //
+ // result += String.fromCharCode(array[i]); generate too many strings !
+ //
+ // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
+ // TODO : we now have workers that split the work. Do we still need that ?
+ var chunk = 65536,
+ type = exports.getTypeOf(array),
+ canUseApply = true;
+ if (type === "uint8array") {
+ canUseApply = arrayToStringHelper.applyCanBeUsed.uint8array;
+ } else if (type === "nodebuffer") {
+ canUseApply = arrayToStringHelper.applyCanBeUsed.nodebuffer;
+ }
+
+ if (canUseApply) {
+ while (chunk > 1) {
+ try {
+ return arrayToStringHelper.stringifyByChunk(array, type, chunk);
+ } catch (e) {
+ chunk = Math.floor(chunk / 2);
+ }
+ }
+ }
+
+ // no apply or chunk error : slow and painful algorithm
+ // default browser on android 4.*
+ return arrayToStringHelper.stringifyByChar(array);
+}
+
+exports.applyFromCharCode = arrayLikeToString;
+
+
+/**
+ * Copy the data from an array-like to an other array-like.
+ * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
+ * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
+ * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
+ */
+function arrayLikeToArrayLike(arrayFrom, arrayTo) {
+ for (var i = 0; i < arrayFrom.length; i++) {
+ arrayTo[i] = arrayFrom[i];
+ }
+ return arrayTo;
+}
+
+// a matrix containing functions to transform everything into everything.
+var transform = {};
+
+// string to ?
+transform["string"] = {
+ "string": identity,
+ "array": function(input) {
+ return stringToArrayLike(input, new Array(input.length));
+ },
+ "arraybuffer": function(input) {
+ return transform["string"]["uint8array"](input).buffer;
+ },
+ "uint8array": function(input) {
+ return stringToArrayLike(input, new Uint8Array(input.length));
+ },
+ "nodebuffer": function(input) {
+ return stringToArrayLike(input, nodejsUtils.allocBuffer(input.length));
+ }
+};
+
+// array to ?
+transform["array"] = {
+ "string": arrayLikeToString,
+ "array": identity,
+ "arraybuffer": function(input) {
+ return (new Uint8Array(input)).buffer;
+ },
+ "uint8array": function(input) {
+ return new Uint8Array(input);
+ },
+ "nodebuffer": function(input) {
+ return nodejsUtils.newBufferFrom(input);
+ }
+};
+
+// arraybuffer to ?
+transform["arraybuffer"] = {
+ "string": function(input) {
+ return arrayLikeToString(new Uint8Array(input));
+ },
+ "array": function(input) {
+ return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
+ },
+ "arraybuffer": identity,
+ "uint8array": function(input) {
+ return new Uint8Array(input);
+ },
+ "nodebuffer": function(input) {
+ return nodejsUtils.newBufferFrom(new Uint8Array(input));
+ }
+};
+
+// uint8array to ?
+transform["uint8array"] = {
+ "string": arrayLikeToString,
+ "array": function(input) {
+ return arrayLikeToArrayLike(input, new Array(input.length));
+ },
+ "arraybuffer": function(input) {
+ return input.buffer;
+ },
+ "uint8array": identity,
+ "nodebuffer": function(input) {
+ return nodejsUtils.newBufferFrom(input);
+ }
+};
+
+// nodebuffer to ?
+transform["nodebuffer"] = {
+ "string": arrayLikeToString,
+ "array": function(input) {
+ return arrayLikeToArrayLike(input, new Array(input.length));
+ },
+ "arraybuffer": function(input) {
+ return transform["nodebuffer"]["uint8array"](input).buffer;
+ },
+ "uint8array": function(input) {
+ return arrayLikeToArrayLike(input, new Uint8Array(input.length));
+ },
+ "nodebuffer": identity
+};
+
+/**
+ * Transform an input into any type.
+ * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
+ * If no output type is specified, the unmodified input will be returned.
+ * @param {String} outputType the output type.
+ * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
+ * @throws {Error} an Error if the browser doesn't support the requested output type.
+ */
+exports.transformTo = function(outputType, input) {
+ if (!input) {
+ // undefined, null, etc
+ // an empty string won't harm.
+ input = "";
+ }
+ if (!outputType) {
+ return input;
+ }
+ exports.checkSupport(outputType);
+ var inputType = exports.getTypeOf(input);
+ var result = transform[inputType][outputType](input);
+ return result;
+};
+
+/**
+ * Resolve all relative path components, "." and "..", in a path. If these relative components
+ * traverse above the root then the resulting path will only contain the final path component.
+ *
+ * All empty components, e.g. "//", are removed.
+ * @param {string} path A path with / or \ separators
+ * @returns {string} The path with all relative path components resolved.
+ */
+exports.resolve = function(path) {
+ var parts = path.split("/");
+ var result = [];
+ for (var index = 0; index < parts.length; index++) {
+ var part = parts[index];
+ // Allow the first and last component to be empty for trailing slashes.
+ if (part === "." || (part === "" && index !== 0 && index !== parts.length - 1)) {
+ continue;
+ } else if (part === "..") {
+ result.pop();
+ } else {
+ result.push(part);
+ }
+ }
+ return result.join("/");
+};
+
+/**
+ * Return the type of the input.
+ * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
+ * @param {Object} input the input to identify.
+ * @return {String} the (lowercase) type of the input.
+ */
+exports.getTypeOf = function(input) {
+ if (typeof input === "string") {
+ return "string";
+ }
+ if (Object.prototype.toString.call(input) === "[object Array]") {
+ return "array";
+ }
+ if (support.nodebuffer && nodejsUtils.isBuffer(input)) {
+ return "nodebuffer";
+ }
+ if (support.uint8array && input instanceof Uint8Array) {
+ return "uint8array";
+ }
+ if (support.arraybuffer && input instanceof ArrayBuffer) {
+ return "arraybuffer";
+ }
+};
+
+/**
+ * Throw an exception if the type is not supported.
+ * @param {String} type the type to check.
+ * @throws {Error} an Error if the browser doesn't support the requested type.
+ */
+exports.checkSupport = function(type) {
+ var supported = support[type.toLowerCase()];
+ if (!supported) {
+ throw new Error(type + " is not supported by this platform");
+ }
+};
+
+exports.MAX_VALUE_16BITS = 65535;
+exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1
+
+/**
+ * Prettify a string read as binary.
+ * @param {string} str the string to prettify.
+ * @return {string} a pretty string.
+ */
+exports.pretty = function(str) {
+ var res = "",
+ code, i;
+ for (i = 0; i < (str || "").length; i++) {
+ code = str.charCodeAt(i);
+ res += "\\x" + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
+ }
+ return res;
+};
+
+/**
+ * Defer the call of a function.
+ * @param {Function} callback the function to call asynchronously.
+ * @param {Array} args the arguments to give to the callback.
+ */
+exports.delay = function(callback, args, self) {
+ setImmediate(function () {
+ callback.apply(self || null, args || []);
+ });
+};
+
+/**
+ * Extends a prototype with an other, without calling a constructor with
+ * side effects. Inspired by nodejs' `utils.inherits`
+ * @param {Function} ctor the constructor to augment
+ * @param {Function} superCtor the parent constructor to use
+ */
+exports.inherits = function (ctor, superCtor) {
+ var Obj = function() {};
+ Obj.prototype = superCtor.prototype;
+ ctor.prototype = new Obj();
+};
+
+/**
+ * Merge the objects passed as parameters into a new one.
+ * @private
+ * @param {...Object} var_args All objects to merge.
+ * @return {Object} a new object with the data of the others.
+ */
+exports.extend = function() {
+ var result = {}, i, attr;
+ for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
+ for (attr in arguments[i]) {
+ if (Object.prototype.hasOwnProperty.call(arguments[i], attr) && typeof result[attr] === "undefined") {
+ result[attr] = arguments[i][attr];
+ }
+ }
+ }
+ return result;
+};
+
+/**
+ * Transform arbitrary content into a Promise.
+ * @param {String} name a name for the content being processed.
+ * @param {Object} inputData the content to process.
+ * @param {Boolean} isBinary true if the content is not an unicode string
+ * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character.
+ * @param {Boolean} isBase64 true if the string content is encoded with base64.
+ * @return {Promise} a promise in a format usable by JSZip.
+ */
+exports.prepareContent = function(name, inputData, isBinary, isOptimizedBinaryString, isBase64) {
+
+ // if inputData is already a promise, this flatten it.
+ var promise = external.Promise.resolve(inputData).then(function(data) {
+
+
+ var isBlob = support.blob && (data instanceof Blob || ["[object File]", "[object Blob]"].indexOf(Object.prototype.toString.call(data)) !== -1);
+
+ if (isBlob && typeof FileReader !== "undefined") {
+ return new external.Promise(function (resolve, reject) {
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ resolve(e.target.result);
+ };
+ reader.onerror = function(e) {
+ reject(e.target.error);
+ };
+ reader.readAsArrayBuffer(data);
+ });
+ } else {
+ return data;
+ }
+ });
+
+ return promise.then(function(data) {
+ var dataType = exports.getTypeOf(data);
+
+ if (!dataType) {
+ return external.Promise.reject(
+ new Error("Can't read the data of '" + name + "'. Is it " +
+ "in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?")
+ );
+ }
+ // special case : it's way easier to work with Uint8Array than with ArrayBuffer
+ if (dataType === "arraybuffer") {
+ data = exports.transformTo("uint8array", data);
+ } else if (dataType === "string") {
+ if (isBase64) {
+ data = base64.decode(data);
+ }
+ else if (isBinary) {
+ // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask
+ if (isOptimizedBinaryString !== true) {
+ // this is a string, not in a base64 format.
+ // Be sure that this is a correct "binary string"
+ data = string2binary(data);
+ }
+ }
+ }
+ return data;
+ });
+};
+
+},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"setimmediate":54}],33:[function(require,module,exports){
+"use strict";
+var readerFor = require("./reader/readerFor");
+var utils = require("./utils");
+var sig = require("./signature");
+var ZipEntry = require("./zipEntry");
+var support = require("./support");
+// class ZipEntries {{{
+/**
+ * All the entries in the zip file.
+ * @constructor
+ * @param {Object} loadOptions Options for loading the stream.
+ */
+function ZipEntries(loadOptions) {
+ this.files = [];
+ this.loadOptions = loadOptions;
+}
+ZipEntries.prototype = {
+ /**
+ * Check that the reader is on the specified signature.
+ * @param {string} expectedSignature the expected signature.
+ * @throws {Error} if it is an other signature.
+ */
+ checkSignature: function(expectedSignature) {
+ if (!this.reader.readAndCheckSignature(expectedSignature)) {
+ this.reader.index -= 4;
+ var signature = this.reader.readString(4);
+ throw new Error("Corrupted zip or bug: unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")");
+ }
+ },
+ /**
+ * Check if the given signature is at the given index.
+ * @param {number} askedIndex the index to check.
+ * @param {string} expectedSignature the signature to expect.
+ * @return {boolean} true if the signature is here, false otherwise.
+ */
+ isSignature: function(askedIndex, expectedSignature) {
+ var currentIndex = this.reader.index;
+ this.reader.setIndex(askedIndex);
+ var signature = this.reader.readString(4);
+ var result = signature === expectedSignature;
+ this.reader.setIndex(currentIndex);
+ return result;
+ },
+ /**
+ * Read the end of the central directory.
+ */
+ readBlockEndOfCentral: function() {
+ this.diskNumber = this.reader.readInt(2);
+ this.diskWithCentralDirStart = this.reader.readInt(2);
+ this.centralDirRecordsOnThisDisk = this.reader.readInt(2);
+ this.centralDirRecords = this.reader.readInt(2);
+ this.centralDirSize = this.reader.readInt(4);
+ this.centralDirOffset = this.reader.readInt(4);
+
+ this.zipCommentLength = this.reader.readInt(2);
+ // warning : the encoding depends of the system locale
+ // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded.
+ // On a windows machine, this field is encoded with the localized windows code page.
+ var zipComment = this.reader.readData(this.zipCommentLength);
+ var decodeParamType = support.uint8array ? "uint8array" : "array";
+ // To get consistent behavior with the generation part, we will assume that
+ // this is utf8 encoded unless specified otherwise.
+ var decodeContent = utils.transformTo(decodeParamType, zipComment);
+ this.zipComment = this.loadOptions.decodeFileName(decodeContent);
+ },
+ /**
+ * Read the end of the Zip 64 central directory.
+ * Not merged with the method readEndOfCentral :
+ * The end of central can coexist with its Zip64 brother,
+ * I don't want to read the wrong number of bytes !
+ */
+ readBlockZip64EndOfCentral: function() {
+ this.zip64EndOfCentralSize = this.reader.readInt(8);
+ this.reader.skip(4);
+ // this.versionMadeBy = this.reader.readString(2);
+ // this.versionNeeded = this.reader.readInt(2);
+ this.diskNumber = this.reader.readInt(4);
+ this.diskWithCentralDirStart = this.reader.readInt(4);
+ this.centralDirRecordsOnThisDisk = this.reader.readInt(8);
+ this.centralDirRecords = this.reader.readInt(8);
+ this.centralDirSize = this.reader.readInt(8);
+ this.centralDirOffset = this.reader.readInt(8);
+
+ this.zip64ExtensibleData = {};
+ var extraDataSize = this.zip64EndOfCentralSize - 44,
+ index = 0,
+ extraFieldId,
+ extraFieldLength,
+ extraFieldValue;
+ while (index < extraDataSize) {
+ extraFieldId = this.reader.readInt(2);
+ extraFieldLength = this.reader.readInt(4);
+ extraFieldValue = this.reader.readData(extraFieldLength);
+ this.zip64ExtensibleData[extraFieldId] = {
+ id: extraFieldId,
+ length: extraFieldLength,
+ value: extraFieldValue
+ };
+ }
+ },
+ /**
+ * Read the end of the Zip 64 central directory locator.
+ */
+ readBlockZip64EndOfCentralLocator: function() {
+ this.diskWithZip64CentralDirStart = this.reader.readInt(4);
+ this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8);
+ this.disksCount = this.reader.readInt(4);
+ if (this.disksCount > 1) {
+ throw new Error("Multi-volumes zip are not supported");
+ }
+ },
+ /**
+ * Read the local files, based on the offset read in the central part.
+ */
+ readLocalFiles: function() {
+ var i, file;
+ for (i = 0; i < this.files.length; i++) {
+ file = this.files[i];
+ this.reader.setIndex(file.localHeaderOffset);
+ this.checkSignature(sig.LOCAL_FILE_HEADER);
+ file.readLocalPart(this.reader);
+ file.handleUTF8();
+ file.processAttributes();
+ }
+ },
+ /**
+ * Read the central directory.
+ */
+ readCentralDir: function() {
+ var file;
+
+ this.reader.setIndex(this.centralDirOffset);
+ while (this.reader.readAndCheckSignature(sig.CENTRAL_FILE_HEADER)) {
+ file = new ZipEntry({
+ zip64: this.zip64
+ }, this.loadOptions);
+ file.readCentralPart(this.reader);
+ this.files.push(file);
+ }
+
+ if (this.centralDirRecords !== this.files.length) {
+ if (this.centralDirRecords !== 0 && this.files.length === 0) {
+ // We expected some records but couldn't find ANY.
+ // This is really suspicious, as if something went wrong.
+ throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length);
+ } else {
+ // We found some records but not all.
+ // Something is wrong but we got something for the user: no error here.
+ // console.warn("expected", this.centralDirRecords, "records in central dir, got", this.files.length);
+ }
+ }
+ },
+ /**
+ * Read the end of central directory.
+ */
+ readEndOfCentral: function() {
+ var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);
+ if (offset < 0) {
+ // Check if the content is a truncated zip or complete garbage.
+ // A "LOCAL_FILE_HEADER" is not required at the beginning (auto
+ // extractible zip for example) but it can give a good hint.
+ // If an ajax request was used without responseType, we will also
+ // get unreadable data.
+ var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER);
+
+ if (isGarbage) {
+ throw new Error("Can't find end of central directory : is this a zip file ? " +
+ "If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html");
+ } else {
+ throw new Error("Corrupted zip: can't find end of central directory");
+ }
+
+ }
+ this.reader.setIndex(offset);
+ var endOfCentralDirOffset = offset;
+ this.checkSignature(sig.CENTRAL_DIRECTORY_END);
+ this.readBlockEndOfCentral();
+
+
+ /* extract from the zip spec :
+ 4) If one of the fields in the end of central directory
+ record is too small to hold required data, the field
+ should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
+ ZIP64 format record should be created.
+ 5) The end of central directory record and the
+ Zip64 end of central directory locator record must
+ reside on the same disk when splitting or spanning
+ an archive.
+ */
+ if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) {
+ this.zip64 = true;
+
+ /*
+ Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from
+ the zip file can fit into a 32bits integer. This cannot be solved : JavaScript represents
+ all numbers as 64-bit double precision IEEE 754 floating point numbers.
+ So, we have 53bits for integers and bitwise operations treat everything as 32bits.
+ see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
+ and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5
+ */
+
+ // should look for a zip64 EOCD locator
+ offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
+ if (offset < 0) {
+ throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");
+ }
+ this.reader.setIndex(offset);
+ this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
+ this.readBlockZip64EndOfCentralLocator();
+
+ // now the zip64 EOCD record
+ if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) {
+ // console.warn("ZIP64 end of central directory not where expected.");
+ this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
+ if (this.relativeOffsetEndOfZip64CentralDir < 0) {
+ throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");
+ }
+ }
+ this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
+ this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
+ this.readBlockZip64EndOfCentral();
+ }
+
+ var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize;
+ if (this.zip64) {
+ expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator
+ expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize;
+ }
+
+ var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset;
+
+ if (extraBytes > 0) {
+ // console.warn(extraBytes, "extra bytes at beginning or within zipfile");
+ if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) {
+ // The offsets seem wrong, but we have something at the specified offset.
+ // So… we keep it.
+ } else {
+ // the offset is wrong, update the "zero" of the reader
+ // this happens if data has been prepended (crx files for example)
+ this.reader.zero = extraBytes;
+ }
+ } else if (extraBytes < 0) {
+ throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes.");
+ }
+ },
+ prepareReader: function(data) {
+ this.reader = readerFor(data);
+ },
+ /**
+ * Read a zip file and create ZipEntries.
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file.
+ */
+ load: function(data) {
+ this.prepareReader(data);
+ this.readEndOfCentral();
+ this.readCentralDir();
+ this.readLocalFiles();
+ }
+};
+// }}} end of ZipEntries
+module.exports = ZipEntries;
+
+},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utils":32,"./zipEntry":34}],34:[function(require,module,exports){
+"use strict";
+var readerFor = require("./reader/readerFor");
+var utils = require("./utils");
+var CompressedObject = require("./compressedObject");
+var crc32fn = require("./crc32");
+var utf8 = require("./utf8");
+var compressions = require("./compressions");
+var support = require("./support");
+
+var MADE_BY_DOS = 0x00;
+var MADE_BY_UNIX = 0x03;
+
+/**
+ * Find a compression registered in JSZip.
+ * @param {string} compressionMethod the method magic to find.
+ * @return {Object|null} the JSZip compression object, null if none found.
+ */
+var findCompression = function(compressionMethod) {
+ for (var method in compressions) {
+ if (!Object.prototype.hasOwnProperty.call(compressions, method)) {
+ continue;
+ }
+ if (compressions[method].magic === compressionMethod) {
+ return compressions[method];
+ }
+ }
+ return null;
+};
+
+// class ZipEntry {{{
+/**
+ * An entry in the zip file.
+ * @constructor
+ * @param {Object} options Options of the current file.
+ * @param {Object} loadOptions Options for loading the stream.
+ */
+function ZipEntry(options, loadOptions) {
+ this.options = options;
+ this.loadOptions = loadOptions;
+}
+ZipEntry.prototype = {
+ /**
+ * say if the file is encrypted.
+ * @return {boolean} true if the file is encrypted, false otherwise.
+ */
+ isEncrypted: function() {
+ // bit 1 is set
+ return (this.bitFlag & 0x0001) === 0x0001;
+ },
+ /**
+ * say if the file has utf-8 filename/comment.
+ * @return {boolean} true if the filename/comment is in utf-8, false otherwise.
+ */
+ useUTF8: function() {
+ // bit 11 is set
+ return (this.bitFlag & 0x0800) === 0x0800;
+ },
+ /**
+ * Read the local part of a zip file and add the info in this object.
+ * @param {DataReader} reader the reader to use.
+ */
+ readLocalPart: function(reader) {
+ var compression, localExtraFieldsLength;
+
+ // we already know everything from the central dir !
+ // If the central dir data are false, we are doomed.
+ // On the bright side, the local part is scary : zip64, data descriptors, both, etc.
+ // The less data we get here, the more reliable this should be.
+ // Let's skip the whole header and dash to the data !
+ reader.skip(22);
+ // in some zip created on windows, the filename stored in the central dir contains \ instead of /.
+ // Strangely, the filename here is OK.
+ // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
+ // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
+ // Search "unzip mismatching "local" filename continuing with "central" filename version" on
+ // the internet.
+ //
+ // I think I see the logic here : the central directory is used to display
+ // content and the local directory is used to extract the files. Mixing / and \
+ // may be used to display \ to windows users and use / when extracting the files.
+ // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394
+ this.fileNameLength = reader.readInt(2);
+ localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
+ // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding.
+ this.fileName = reader.readData(this.fileNameLength);
+ reader.skip(localExtraFieldsLength);
+
+ if (this.compressedSize === -1 || this.uncompressedSize === -1) {
+ throw new Error("Bug or corrupted zip : didn't get enough information from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)");
+ }
+
+ compression = findCompression(this.compressionMethod);
+ if (compression === null) { // no compression found
+ throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")");
+ }
+ this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize));
+ },
+
+ /**
+ * Read the central part of a zip file and add the info in this object.
+ * @param {DataReader} reader the reader to use.
+ */
+ readCentralPart: function(reader) {
+ this.versionMadeBy = reader.readInt(2);
+ reader.skip(2);
+ // this.versionNeeded = reader.readInt(2);
+ this.bitFlag = reader.readInt(2);
+ this.compressionMethod = reader.readString(2);
+ this.date = reader.readDate();
+ this.crc32 = reader.readInt(4);
+ this.compressedSize = reader.readInt(4);
+ this.uncompressedSize = reader.readInt(4);
+ var fileNameLength = reader.readInt(2);
+ this.extraFieldsLength = reader.readInt(2);
+ this.fileCommentLength = reader.readInt(2);
+ this.diskNumberStart = reader.readInt(2);
+ this.internalFileAttributes = reader.readInt(2);
+ this.externalFileAttributes = reader.readInt(4);
+ this.localHeaderOffset = reader.readInt(4);
+
+ if (this.isEncrypted()) {
+ throw new Error("Encrypted zip are not supported");
+ }
+
+ // will be read in the local part, see the comments there
+ reader.skip(fileNameLength);
+ this.readExtraFields(reader);
+ this.parseZIP64ExtraField(reader);
+ this.fileComment = reader.readData(this.fileCommentLength);
+ },
+
+ /**
+ * Parse the external file attributes and get the unix/dos permissions.
+ */
+ processAttributes: function () {
+ this.unixPermissions = null;
+ this.dosPermissions = null;
+ var madeBy = this.versionMadeBy >> 8;
+
+ // Check if we have the DOS directory flag set.
+ // We look for it in the DOS and UNIX permissions
+ // but some unknown platform could set it as a compatibility flag.
+ this.dir = this.externalFileAttributes & 0x0010 ? true : false;
+
+ if(madeBy === MADE_BY_DOS) {
+ // first 6 bits (0 to 5)
+ this.dosPermissions = this.externalFileAttributes & 0x3F;
+ }
+
+ if(madeBy === MADE_BY_UNIX) {
+ this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF;
+ // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8);
+ }
+
+ // fail safe : if the name ends with a / it probably means a folder
+ if (!this.dir && this.fileNameStr.slice(-1) === "/") {
+ this.dir = true;
+ }
+ },
+
+ /**
+ * Parse the ZIP64 extra field and merge the info in the current ZipEntry.
+ * @param {DataReader} reader the reader to use.
+ */
+ parseZIP64ExtraField: function() {
+ if (!this.extraFields[0x0001]) {
+ return;
+ }
+
+ // should be something, preparing the extra reader
+ var extraReader = readerFor(this.extraFields[0x0001].value);
+
+ // I really hope that these 64bits integer can fit in 32 bits integer, because js
+ // won't let us have more.
+ if (this.uncompressedSize === utils.MAX_VALUE_32BITS) {
+ this.uncompressedSize = extraReader.readInt(8);
+ }
+ if (this.compressedSize === utils.MAX_VALUE_32BITS) {
+ this.compressedSize = extraReader.readInt(8);
+ }
+ if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) {
+ this.localHeaderOffset = extraReader.readInt(8);
+ }
+ if (this.diskNumberStart === utils.MAX_VALUE_32BITS) {
+ this.diskNumberStart = extraReader.readInt(4);
+ }
+ },
+ /**
+ * Read the central part of a zip file and add the info in this object.
+ * @param {DataReader} reader the reader to use.
+ */
+ readExtraFields: function(reader) {
+ var end = reader.index + this.extraFieldsLength,
+ extraFieldId,
+ extraFieldLength,
+ extraFieldValue;
+
+ if (!this.extraFields) {
+ this.extraFields = {};
+ }
+
+ while (reader.index + 4 < end) {
+ extraFieldId = reader.readInt(2);
+ extraFieldLength = reader.readInt(2);
+ extraFieldValue = reader.readData(extraFieldLength);
+
+ this.extraFields[extraFieldId] = {
+ id: extraFieldId,
+ length: extraFieldLength,
+ value: extraFieldValue
+ };
+ }
+
+ reader.setIndex(end);
+ },
+ /**
+ * Apply an UTF8 transformation if needed.
+ */
+ handleUTF8: function() {
+ var decodeParamType = support.uint8array ? "uint8array" : "array";
+ if (this.useUTF8()) {
+ this.fileNameStr = utf8.utf8decode(this.fileName);
+ this.fileCommentStr = utf8.utf8decode(this.fileComment);
+ } else {
+ var upath = this.findExtraFieldUnicodePath();
+ if (upath !== null) {
+ this.fileNameStr = upath;
+ } else {
+ // ASCII text or unsupported code page
+ var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName);
+ this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray);
+ }
+
+ var ucomment = this.findExtraFieldUnicodeComment();
+ if (ucomment !== null) {
+ this.fileCommentStr = ucomment;
+ } else {
+ // ASCII text or unsupported code page
+ var commentByteArray = utils.transformTo(decodeParamType, this.fileComment);
+ this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray);
+ }
+ }
+ },
+
+ /**
+ * Find the unicode path declared in the extra field, if any.
+ * @return {String} the unicode path, null otherwise.
+ */
+ findExtraFieldUnicodePath: function() {
+ var upathField = this.extraFields[0x7075];
+ if (upathField) {
+ var extraReader = readerFor(upathField.value);
+
+ // wrong version
+ if (extraReader.readInt(1) !== 1) {
+ return null;
+ }
+
+ // the crc of the filename changed, this field is out of date.
+ if (crc32fn(this.fileName) !== extraReader.readInt(4)) {
+ return null;
+ }
+
+ return utf8.utf8decode(extraReader.readData(upathField.length - 5));
+ }
+ return null;
+ },
+
+ /**
+ * Find the unicode comment declared in the extra field, if any.
+ * @return {String} the unicode comment, null otherwise.
+ */
+ findExtraFieldUnicodeComment: function() {
+ var ucommentField = this.extraFields[0x6375];
+ if (ucommentField) {
+ var extraReader = readerFor(ucommentField.value);
+
+ // wrong version
+ if (extraReader.readInt(1) !== 1) {
+ return null;
+ }
+
+ // the crc of the comment changed, this field is out of date.
+ if (crc32fn(this.fileComment) !== extraReader.readInt(4)) {
+ return null;
+ }
+
+ return utf8.utf8decode(extraReader.readData(ucommentField.length - 5));
+ }
+ return null;
+ }
+};
+module.exports = ZipEntry;
+
+},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(require,module,exports){
+"use strict";
+
+var StreamHelper = require("./stream/StreamHelper");
+var DataWorker = require("./stream/DataWorker");
+var utf8 = require("./utf8");
+var CompressedObject = require("./compressedObject");
+var GenericWorker = require("./stream/GenericWorker");
+
+/**
+ * A simple object representing a file in the zip file.
+ * @constructor
+ * @param {string} name the name of the file
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
+ * @param {Object} options the options of the file
+ */
+var ZipObject = function(name, data, options) {
+ this.name = name;
+ this.dir = options.dir;
+ this.date = options.date;
+ this.comment = options.comment;
+ this.unixPermissions = options.unixPermissions;
+ this.dosPermissions = options.dosPermissions;
+
+ this._data = data;
+ this._dataBinary = options.binary;
+ // keep only the compression
+ this.options = {
+ compression : options.compression,
+ compressionOptions : options.compressionOptions
+ };
+};
+
+ZipObject.prototype = {
+ /**
+ * Create an internal stream for the content of this object.
+ * @param {String} type the type of each chunk.
+ * @return StreamHelper the stream.
+ */
+ internalStream: function (type) {
+ var result = null, outputType = "string";
+ try {
+ if (!type) {
+ throw new Error("No output type specified.");
+ }
+ outputType = type.toLowerCase();
+ var askUnicodeString = outputType === "string" || outputType === "text";
+ if (outputType === "binarystring" || outputType === "text") {
+ outputType = "string";
+ }
+ result = this._decompressWorker();
+
+ var isUnicodeString = !this._dataBinary;
+
+ if (isUnicodeString && !askUnicodeString) {
+ result = result.pipe(new utf8.Utf8EncodeWorker());
+ }
+ if (!isUnicodeString && askUnicodeString) {
+ result = result.pipe(new utf8.Utf8DecodeWorker());
+ }
+ } catch (e) {
+ result = new GenericWorker("error");
+ result.error(e);
+ }
+
+ return new StreamHelper(result, outputType, "");
+ },
+
+ /**
+ * Prepare the content in the asked type.
+ * @param {String} type the type of the result.
+ * @param {Function} onUpdate a function to call on each internal update.
+ * @return Promise the promise of the result.
+ */
+ async: function (type, onUpdate) {
+ return this.internalStream(type).accumulate(onUpdate);
+ },
+
+ /**
+ * Prepare the content as a nodejs stream.
+ * @param {String} type the type of each chunk.
+ * @param {Function} onUpdate a function to call on each internal update.
+ * @return Stream the stream.
+ */
+ nodeStream: function (type, onUpdate) {
+ return this.internalStream(type || "nodebuffer").toNodejsStream(onUpdate);
+ },
+
+ /**
+ * Return a worker for the compressed content.
+ * @private
+ * @param {Object} compression the compression object to use.
+ * @param {Object} compressionOptions the options to use when compressing.
+ * @return Worker the worker.
+ */
+ _compressWorker: function (compression, compressionOptions) {
+ if (
+ this._data instanceof CompressedObject &&
+ this._data.compression.magic === compression.magic
+ ) {
+ return this._data.getCompressedWorker();
+ } else {
+ var result = this._decompressWorker();
+ if(!this._dataBinary) {
+ result = result.pipe(new utf8.Utf8EncodeWorker());
+ }
+ return CompressedObject.createWorkerFrom(result, compression, compressionOptions);
+ }
+ },
+ /**
+ * Return a worker for the decompressed content.
+ * @private
+ * @return Worker the worker.
+ */
+ _decompressWorker : function () {
+ if (this._data instanceof CompressedObject) {
+ return this._data.getContentWorker();
+ } else if (this._data instanceof GenericWorker) {
+ return this._data;
+ } else {
+ return new DataWorker(this._data);
+ }
+ }
+};
+
+var removedMethods = ["asText", "asBinary", "asNodeBuffer", "asUint8Array", "asArrayBuffer"];
+var removedFn = function () {
+ throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
+};
+
+for(var i = 0; i < removedMethods.length; i++) {
+ ZipObject.prototype[removedMethods[i]] = removedFn;
+}
+module.exports = ZipObject;
+
+},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(require,module,exports){
+(function (global){
+'use strict';
+var Mutation = global.MutationObserver || global.WebKitMutationObserver;
+
+var scheduleDrain;
+
+{
+ if (Mutation) {
+ var called = 0;
+ var observer = new Mutation(nextTick);
+ var element = global.document.createTextNode('');
+ observer.observe(element, {
+ characterData: true
+ });
+ scheduleDrain = function () {
+ element.data = (called = ++called % 2);
+ };
+ } else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
+ var channel = new global.MessageChannel();
+ channel.port1.onmessage = nextTick;
+ scheduleDrain = function () {
+ channel.port2.postMessage(0);
+ };
+ } else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
+ scheduleDrain = function () {
+
+ // Create a
+
+
@@ -30,6 +31,15 @@
+
+
+
@@ -43,11 +53,6 @@
- -
-
-
-
@@ -250,6 +259,10 @@
+
+ |
+ |
+
@@ -677,7 +690,7 @@
AutoRename |
- Copyright © 2022 Dasutein |
+ Copyright © 2023 Dasutein |
MIT License |
@@ -746,9 +759,17 @@
https://momentjs.com |
- SweetAlert (version 2.1.4) |
+ SweetAlert (version 2.1.3) |
https://github.com/t4t5/sweetalert |
+
+ JSZip (version 3.10.1) |
+ https://github.com/Stuk/jszi |
+
+
+ FileSaver (version 2.0.4) |
+ https://github.com/eligrey/FileSaver.js/ |
+
@@ -784,6 +805,54 @@
+
+
+
+
+
+
+ |
+ |
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -803,11 +872,16 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+