diff --git a/css/main.css b/css/main.css index d10a5a4..ad56ac2 100644 --- a/css/main.css +++ b/css/main.css @@ -10,8 +10,8 @@ } *[class*="gitzip-"]{ - transition:all 0.5s ease; - -webkit-transition:all 0.5s ease; /* Safari and Chrome */ + transition:all 0.3s ease; + -webkit-transition:all 0.3s ease; /* Safari and Chrome */ } .gitzip-show{ @@ -34,15 +34,43 @@ border-bottom: 1px solid #222; } +.gitzip-close{ + float: right; + font-weight: 800; + cursor: pointer; + visibility: hidden; +} + +.gitzip-fail .gitzip-close{ + visibility: visible; +} + .gitzip-body{ - margin: 15px; - padding: 0px; + margin: 10px 0; height: 246px; overflow: auto; } -.gitzip-dark .gitzip-body{ - color: #b3b3b3; +.gitzip-body p{ + margin: 0; + padding: 0 15px; + color: #333; +} + +.gitzip-dark .gitzip-body p{ + color: #f0f6fc; +} + +.gitzip-body p.warn{ + background-color: #fffbe7; + color: #624115; + border: 1px solid #fff4c7; +} + +.gitzip-body p.error{ + background-color: #fff0f0; + color: #ff081e; + border: 1px solid #ffd6d6; } .gitzip-dark .gitzip-body p.warn, .gitzip-dark .gitzip-body p.error{ @@ -63,30 +91,60 @@ .gitzip-check-mark{ position: absolute; - left: -10px; + left: -10px; top: 0px; font-size: 18px; margin:0 !important; visibility: hidden; opacity: 0; + color: #333; +} + +.gitzip-dark.gitzip-check-mark{ + color: #f0f6fc; +} + +.gitzip-check-wrap{ + position: absolute; + left: -5px; + top: 0px; + height: 100%; + display: none; + align-items: center; } .gitzip-collect-wrap{ position: fixed; right: 28px; bottom: 28px; + z-index: 1000; +} + +.gitzip-collect-arrow{ + position: absolute; + right: 0px; + bottom: 0px; visibility: hidden; opacity: 0; - z-index: 1000; width: 56px; height: 56px; + color: #333; } -.gitzip-collect-wrap:hover{ +.gitzip-dark .gitzip-collect-arrow{ + color: #f0f6fc; +} + +.gitzip-collect-arrow:hover{ width: 270px; height: 65px; border-radius: 8px; - background-color: black; + color: #f0f6fc; + background-color: #333; +} + +.gitzip-dark .gitzip-collect-arrow:hover{ + background-color: transparent; } .gitzip-collect-down{ @@ -101,18 +159,18 @@ bottom: 4px; } -.gitzip-collect-wrap:hover .gitzip-collect-down{ - color: #fff; -} - .gitzip-collect-dash{ box-shadow: 1px 1px 10px 0px #d0d0d0; background-color: #fff; border-radius: 4px; border: 1px solid #ddd; - display: none; visibility: hidden; opacity: 0; + position: absolute; + right: 0; + bottom: 0; + width: 0px; + height: 0px; } .gitzip-dark .gitzip-collect-dash { @@ -122,6 +180,7 @@ } .gitzip-downloading .gitzip-collect-dash{ + width: 100%; height: 100%; display: block; visibility: visible; @@ -142,9 +201,8 @@ padding-left:10px; } -.gitzip-collect-wrap:hover .gitzip-collect-tip{ +.gitzip-collect-arrow:hover .gitzip-collect-tip{ visibility: visible; opacity: 1; - color: #fff; left: 0px; } diff --git a/css/popup.css b/css/popup.css index 4445f63..964121b 100644 --- a/css/popup.css +++ b/css/popup.css @@ -18,13 +18,14 @@ body{ font-size: 14px; line-height: 1.42857143; color: #333; + margin: 5px; + background-color: #f5f5f5; } .panel{ width: 400px; height: 130px; background-color: #fff; - border: 1px solid transparent; border-radius: 4px; } @@ -70,3 +71,31 @@ body{ padding: 4px 5px 0px; display: none; } + +body.dark-theme { + color: #f0f6fc; + background-color: #1e2126; +} + +.dark-theme .panel-heading { + color: #f0f6fc; + background-color: #1e2126; + border-color: #222; +} + +.dark-theme .panel{ + background-color: #000; +} + +.dark-theme .form-control{ + outline: none !important; + color: #aaa; + background-color: #000; + border: 1px solid #222; + -webkit-box-shadow: inset 0 1px 1px rgba(255,255,255,.075); + box-shadow: inset 0 1px 1px rgba(255,255,255,.075); +} + +.dark-theme .form-control:focus{ + border: 1px solid #555; +} diff --git a/images/icon-128px.png b/images/icon-128px.png index 6954ef1..5292028 100644 Binary files a/images/icon-128px.png and b/images/icon-128px.png differ diff --git a/images/icon-empty.png b/images/icon-empty.png deleted file mode 100644 index 663ddbb..0000000 Binary files a/images/icon-empty.png and /dev/null differ diff --git a/js/background.js b/js/background.js index da002ce..e8f0f98 100644 --- a/js/background.js +++ b/js/background.js @@ -1,108 +1,92 @@ -// For ga -// (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ -// (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), -// m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -// })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - -// ga('create', 'UA-9111169-4', 'auto'); - -// // Removes failing protocol check. @see: http://stackoverflow.com/a/22152353/1958200 -// ga('set', 'checkProtocolTask', function(){}); - -// ga('send', 'pageview', '/background.html'); - -const colorNames = ['AliceBlue','AntiqueWhite','Aqua','Aquamarine','Azure','Beige','Bisque','Black','BlanchedAlmond','Blue','BlueViolet','Brown','BurlyWood','CadetBlue','Chartreuse','Chocolate','Coral','CornflowerBlue','Cornsilk','Crimson','Cyan','DarkBlue','DarkCyan','DarkGoldenRod','DarkGray','DarkGrey','DarkGreen','DarkKhaki','DarkMagenta','DarkOliveGreen','DarkOrange','DarkOrchid','DarkRed','DarkSalmon','DarkSeaGreen','DarkSlateBlue','DarkSlateGray','DarkSlateGrey','DarkTurquoise','DarkViolet','DeepPink','DeepSkyBlue','DimGray','DimGrey','DodgerBlue','FireBrick','FloralWhite','ForestGreen','Fuchsia','Gainsboro','GhostWhite','Gold','GoldenRod','Gray','Grey','Green','GreenYellow','HoneyDew','HotPink','IndianRed','Indigo','Ivory','Khaki','Lavender','LavenderBlush','LawnGreen','LemonChiffon','LightBlue','LightCoral','LightCyan','LightGoldenRodYellow','LightGray','LightGrey','LightGreen','LightPink','LightSalmon','LightSeaGreen','LightSkyBlue','LightSlateGray','LightSlateGrey','LightSteelBlue','LightYellow','Lime','LimeGreen','Linen','Magenta','Maroon','MediumAquaMarine','MediumBlue','MediumOrchid','MediumPurple','MediumSeaGreen','MediumSlateBlue','MediumSpringGreen','MediumTurquoise','MediumVioletRed','MidnightBlue','MintCream','MistyRose','Moccasin','NavajoWhite','Navy','OldLace','Olive','OliveDrab','Orange','OrangeRed','Orchid','PaleGoldenRod','PaleGreen','PaleTurquoise','PaleVioletRed','PapayaWhip','PeachPuff','Peru','Pink','Plum','PowderBlue','Purple','RebeccaPurple','Red','RosyBrown','RoyalBlue','SaddleBrown','Salmon','SandyBrown','SeaGreen','SeaShell','Sienna','Silver','SkyBlue','SlateBlue','SlateGray','SlateGrey','Snow','SpringGreen','SteelBlue','Tan','Teal','Thistle','Tomato','Turquoise','Violet','Wheat','White','WhiteSmoke','Yellow','YellowGreen']; -const colorValues = ['f0f8ff','faebd7','00ffff','7fffd4','f0ffff','f5f5dc','ffe4c4','000000','ffebcd','0000ff','8a2be2','a52a2a','deb887','5f9ea0','7fff00','d2691e','ff7f50','6495ed','fff8dc','dc143c','00ffff','00008b','008b8b','b8860b','a9a9a9','a9a9a9','006400','bdb76b','8b008b','556b2f','ff8c00','9932cc','8b0000','e9967a','8fbc8f','483d8b','2f4f4f','2f4f4f','00ced1','9400d3','ff1493','00bfff','696969','696969','1e90ff','b22222','fffaf0','228b22','ff00ff','dcdcdc','f8f8ff','ffd700','daa520','808080','808080','008000','adff2f','f0fff0','ff69b4','cd5c5c','4b0082','fffff0','f0e68c','e6e6fa','fff0f5','7cfc00','fffacd','add8e6','f08080','e0ffff','fafad2','d3d3d3','d3d3d3','90ee90','ffb6c1','ffa07a','20b2aa','87cefa','778899','778899','b0c4de','ffffe0','00ff00','32cd32','faf0e6','ff00ff','800000','66cdaa','0000cd','ba55d3','9370db','3cb371','7b68ee','00fa9a','48d1cc','c71585','191970','f5fffa','ffe4e1','ffe4b5','ffdead','000080','fdf5e6','808000','6b8e23','ffa500','ff4500','da70d6','eee8aa','98fb98','afeeee','db7093','ffefd5','ffdab9','cd853f','ffc0cb','dda0dd','b0e0e6','800080','663399','ff0000','bc8f8f','4169e1','8b4513','fa8072','f4a460','2e8b57','fff5ee','a0522d','c0c0c0','87ceeb','6a5acd','708090','708090','fffafa','00ff7f','4682b4','d2b48c','008080','d8bfd8','ff6347','40e0d0','ee82ee','f5deb3','ffffff','f5f5f5','ffff00','9acd32']; -const colorValRe = /^(#)?([0-9a-fA-F]+)$/; - -function convertHexToRgba(hex){ - let sixDigits = "a0a0a0"; - if ( hex.length == 6 ) { - sixDigits = hex; - } else if ( hex.length == 3 ) { - sixDigits = ""; // reset - for(let i=0; i < 3; i++) { - sixDigits += hex[i] + hex[i]; - } - } - return [ - parseInt(sixDigits.slice(0, 2), 16), - parseInt(sixDigits.slice(2, 4), 16), - parseInt(sixDigits.slice(4, 6), 16), - 1 - ]; -} - -function getValidThemeColorSet(theme){ - if ( theme.colors && theme.colors.icons ) { - var matches, nameIdx = -1; - var colorLowerNames = colorNames.map(n => n.toLowerCase()); - if ( matches = theme.colors.icons.match(/rgb(a)?\((.*)\)/) ) { - var colorSets = matches.pop().split(",").map(function(str){ return str.trim(); }); - return colorSets.slice(0, 3).map(function(str){ return parseInt(str); }).concat([parseFloat(colorSets[3]) || 1]); - } else if ( (nameIdx = colorLowerNames.indexOf(theme.colors.icons.toLowerCase())) != -1 ) { - return convertHexToRgba(colorValues[nameIdx]); - } else if ( matches = theme.colors.icons.match(colorValRe) ) { - return convertHexToRgba(matches.pop()); - } - } - // default use #bababa - return convertHexToRgba("bababa"); -} - -function dynamicChangeColor(tabId) { - var canvas = document.createElement("canvas"); - var ctx = canvas.getContext("2d"); - - var poolimg = new Image(); - - poolimg.onload = function () { - //draw background image - ctx.drawImage(poolimg, 0, 0); - - var imageData = ctx.getImageData(0, 0, 128, 128); - - var outputData = ctx.createImageData(128, 128); - - browser.theme.getCurrent() - .then(function(theme){ - var colorSets = getValidThemeColorSet(theme); - for (var i = 0; i < imageData.data.length; i += 4) { - outputData.data[i + 0] = colorSets[0]; - outputData.data[i + 1] = colorSets[1]; - outputData.data[i + 2] = colorSets[2]; - outputData.data[i + 3] = Math.floor(imageData.data[i + 3] * colorSets[3]); - } - browser.pageAction.setIcon({ - tabId: tabId, - imageData: outputData - }); - browser.pageAction.show(tabId); - }); - }; - - poolimg.src = browser.runtime.getURL("images/icon-128px.png"); -} // Received a message from content script -browser.runtime.onMessage.addListener(function(request, sender, sendResponse) { +browser.runtime.onMessage.addListener(function(request, sender) { switch (request.action){ case "showIcon": - dynamicChangeColor(sender.tab.id); - return true; + return browser.pageAction.show(sender.tab.id); case "getKey": return browser.storage.sync.get("gitzip-github-token") .then(function(res){ return res["gitzip-github-token"] || ""; }); case "setKey": return browser.storage.sync.set( {"gitzip-github-token": request.value} ); - // case "gaTrack": - // var eventGaObj = { - // hitType: 'event', - // eventCategory: request.baseRepo, // /author/repo/ - // eventAction: request.userAction, - // eventLabel: request.githubUrl - // }; - // ga('send', eventGaObj); - // return; + case "createContextNested": + var main = browser.contextMenus.create({ + id: "gitzip-nested", + title: "GitZip Download", + contexts: ["all"] + }); + browser.contextMenus.create({ + id: "gitzip-nested-items", + parentId: main, + title: "Checked Items", + contexts: ["all"], + enabled: false + }); + browser.contextMenus.create({ + id: "gitzip-nested-seperator", + parentId: main, + title: "seperator", + type: "separator", + contexts: ["all"] + }); + browser.contextMenus.create({ + id: "gitzip-nested-selected", + parentId: main, + title: "(selected)", + contexts: ["all"], + enabled: false + }); + browser.contextMenus.create({ + id: "gitzip-nested-current", + parentId: main, + title: "(current)", + contexts: ["all"], + enabled: false + }); + return false; + case "updateContextNested": + var id = request.target; + var updateObj = { enabled: request.enabled !== false }; + switch (id) { + case "selected": + updateObj.title = updateObj.enabled ? ("Selected " + (request.urlType == "blob" ? "File - " : "Folder - ") + request.urlName) : "(selected)"; + break; + case "current": + if (request.root === true) + updateObj.title = "Whole Repository"; + else + updateObj.title = updateObj.enabled ? ("Current " + (request.urlType == "blob" ? "File - " : "Folder - ") + request.urlName) : "(current)"; + break; + } + return browser.contextMenus.update("gitzip-nested-" + id, updateObj); + case "removeContext": + return browser.contextMenus.removeAll(); + } +}); + +browser.contextMenus.onClicked.addListener(function(info, tab){ + if ( info.menuItemId.toString().indexOf("gitzip-") != -1 ) { + browser.tabs.sendMessage(tab.id, {action: info.menuItemId + "-clicked"}); } }); + +browser.tabs.onActivated.addListener(function(activeInfo) { + // handle other tabs active + browser.contextMenus.removeAll(); + + // change back to current tab + browser.tabs.sendMessage(activeInfo.tabId, {action: "github-tab-active", from: "onActivated" }); +}); + +browser.tabs.onUpdated.addListener(function(tabId, changeInfo){ + if ( changeInfo.status == "loading" ) { + browser.contextMenus.removeAll(); + } else if ( changeInfo.status == "complete" ) { + // coding like this because it would cause error during current page loading and then shift another tab quickly. + browser.tabs.query({currentWindow: true, active: true}, function(tabs){ + var tab = tabs[0]; + if(tab) browser.tabs.sendMessage(tab.id, {action: "github-tab-active", from: "onUpdated" }); + }); + } +}); + diff --git a/js/content/main.js b/js/content/main.js old mode 100644 new mode 100755 index a95791c..1234725 --- a/js/content/main.js +++ b/js/content/main.js @@ -1,11 +1,22 @@ // It would work on github.com - var repoExp = new RegExp("^https://github.com/([^/]+)/([^/]+)(/(tree|blob)/([^/]+)(/(.*))?)?"); +// For the request callback end point detection +var repoCommitExp = new RegExp("^https://github.com/([^/]+)/([^/]+)/commit/.*"); -function showGitzipIcon(){ - // just show the icon - browser.runtime.sendMessage({action: "showIcon"}); -} +var isStorageCallback = false; + +var isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + +// default +// var selectBehaviour = "both"; +var isOnlyDoubleClick = false; +var isOnlySingleCheck = false; +var isBoth = true; + +const defaultOptions = { + selectBehaviour: 'both', + theme: 'default' +}; /** * Resolve the github repo url for recognize author, project name, branch name, and so on. @@ -14,12 +25,18 @@ function showGitzipIcon(){ * @param {ResolvedURL} */ function resolveUrl(repoUrl){ - if(typeof repoUrl != 'string') return; + if(typeof repoUrl != 'string') return false; var matches = repoUrl.match(repoExp); if(matches && matches.length > 0){ - var root = (matches[5])? + var rootUrl = (matches[5])? "https://github.com/" + matches[1] + "/" + matches[2] + "/tree/" + matches[5] : - repoUrl; + "https://github.com/" + matches[1] + "/" + matches[2]; + + var strType = matches[4]; + if ( !strType && (repoUrl.length - rootUrl.length > 1) ) { // means no type and url different with root + return false; + } + return { author: matches[1], project: matches[2], @@ -27,9 +44,10 @@ function resolveUrl(repoUrl){ type: matches[4], path: matches[7] || '', inputUrl: repoUrl, - rootUrl: root + rootUrl: rootUrl }; } + return false; } // https://api.github.com/repos/peers/peerjs/git/trees/bfd406219ffd35f4ad870638f2180b27b4e9c374 @@ -46,16 +64,36 @@ function getInfoUrl(author, project, path, branch) { + path + (branch ? ("?ref=" + branch) : ""); } -var zipContents = function(filename, contents){ - var currDate = new Date(); +function base64toBlob(base64Data, contentType) { + contentType = contentType || ''; + var sliceSize = 1024; + var byteCharacters = atob(base64Data); + var bytesLength = byteCharacters.length; + var slicesCount = Math.ceil(bytesLength / sliceSize); + var byteArrays = new Array(slicesCount); + + for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { + var begin = sliceIndex * sliceSize; + var end = Math.min(begin + sliceSize, bytesLength); + + var bytes = new Array(end - begin); + for (var offset = begin, i = 0; offset < end; ++i, ++offset) { + bytes[i] = byteCharacters[offset].charCodeAt(0); + } + byteArrays[sliceIndex] = new Uint8Array(bytes); + } + return new Blob(byteArrays, { type: contentType }); +} + +function zipContents(filename, contents){ + var currDate = new Date(); var dateWithOffset = new Date(currDate.getTime() - currDate.getTimezoneOffset() * 60000); // replace the default date with dateWithOffset JSZip.defaults.date = dateWithOffset; var zip = new JSZip(); - contents.forEach(function(item){ - zip.file(item.path, item.content, { createFolders:true, base64:true }); + zip.file(item.path, item.content, {createFolders:true,base64:true}); }); return new Promise(function(res, rej){ zip.generateAsync({type:"blob"}) @@ -77,12 +115,12 @@ function callAjax(url, token){ xmlhttp.onreadystatechange = function(){ if (xmlhttp.readyState == 4){ if(xmlhttp.status == 200){ - resolve(xmlhttp.response); + resolve(xmlhttp); }else if(xmlhttp.status >= 400){ - reject(xmlhttp.response); + reject(xmlhttp); } } - }; + } xmlhttp.responseType = "json"; xmlhttp.open("GET", url, true); if ( token ) xmlhttp.setRequestHeader("Authorization", "token " + token); @@ -90,26 +128,40 @@ function callAjax(url, token){ }); } -var itemCollectSelector = ".repository-content .js-navigation-item"; +function hasRepoContainer(list) { + if ( list && list.length ) { + for (var i = 0, len = list.length; i < len; i++) { + var item = list[i]; + if (item.querySelector && item.querySelector(".repository-content")) { + return true; + } + } + } + return false; +} + +var itemCollectSelector = ".repository-content div.js-navigation-item"; var Pool = { _locked: false, _el: null, _dashBody: null, + _arrow: null, init: function(){ // create dom // Make the dom on right bottom var self = this; - var isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if(!self._el){ var wrap = document.createElement('div'), + arrow = document.createElement('div'), dash = document.createElement('div'), down = document.createElement('p'), tip = document.createElement('p'); wrap.className = "gitzip-collect-wrap" + (isDark ? " gitzip-dark" : ""); dash.className = "gitzip-collect-dash"; + arrow.className = "gitzip-collect-arrow"; down.className = "gitzip-collect-down"; tip.className = "gitzip-collect-tip"; @@ -122,6 +174,13 @@ var Pool = { var c = document.createElement("div"); c.className = "gitzip-header"; c.appendChild(document.createTextNode("Progress Dashboard")); + + var close = document.createElement("span"); + close.className = "gitzip-close"; + close.appendChild(document.createTextNode("\u2715")); + close.addEventListener('click', function(){ self.reset(); }); + + c.appendChild(close); return c; })() ); @@ -134,13 +193,19 @@ var Pool = { })() ); + // arrow + arrow.appendChild(down); + arrow.appendChild(tip); + + // wrap + wrap.appendChild(arrow); wrap.appendChild(dash); - wrap.appendChild(down); - wrap.appendChild(tip); + document.body.appendChild(wrap); self._el = wrap; self._dashBody = dash.querySelector(".gitzip-body"); + self._arrow = arrow; // hook events down.addEventListener('click', function(){ self.download(); }); @@ -149,245 +214,622 @@ var Pool = { self.reset(); }, - show: function(){ this._el && this._el.classList.add("gitzip-show"); }, - hide: function(){ this._el && this._el.classList.remove("gitzip-show"); }, + show: function(){ this._arrow && this._arrow.classList.add("gitzip-show"); }, + hide: function(){ this._arrow && this._arrow.classList.remove("gitzip-show"); }, reset: function(){ var self = this; - !!checkHaveAnyCheck()? self.show() : self.hide(); + checkHaveAnyCheck()? self.show() : self.hide(); self._el.classList.remove("gitzip-downloading"); + self._el.classList.remove("gitzip-fail"); while (self._dashBody.firstChild) { self._dashBody.removeChild(self._dashBody.firstChild); } self._locked = false; }, - download: function(){ + checkTokenAndScope: function(){ var self = this; - if(self._locked) return; - - self._locked = true; + var checkUrl = "https://api.github.com/rate_limit"; + var isPrivate = !!document.querySelector(".flex-auto .octicon-lock"); - self._el.classList.add("gitzip-downloading"); + return browser.runtime.sendMessage({action: "getKey"}) + .then(function(key){ + + if ( !key ) { + if ( isPrivate ) return Promise.reject("You should have token with `repo` scope."); + else { + self.log("Running in the rate limit.", "warn"); + return key; + } + } - var checkedItems = document.querySelectorAll(itemCollectSelector + " p.gitzip-show"); + self.log("Check token and scopes..."); + + return callAjax(checkUrl, key) + .then(function(xmlResponse){ + // return status 200 means token is valid + if ( isPrivate ) { + var strScopes = xmlResponse.getResponseHeader("X-OAuth-Scopes"); + var scopes = strScopes ? strScopes.split(",").map(function(str){ return str.trim(); }) : null; + if ( !scopes || scopes.indexOf("repo") == -1 ) { + return Promise.reject("Your token cannot access private repo."); + } + } + return key; + }); + }).catch(function(err){ + if ( typeof err == "string" ) { + self.log(err, "error"); + self.log("Please click GitZip extension icon to get private token.", "warn"); + return Promise.reject(); + } else return Promise.reject(err); + }); + }, + handleApiError: function(xmlResponse){ + var self = this; + if ( xmlResponse ) { + var status = xmlResponse.status; + var response = xmlResponse.response; + var message = (response && response.message) ? response.message : xmlResponse.statusText; + self.log(message, "error"); + if (message.indexOf("rate limit exceeded") >= 0){ + self.log("Please click GitZip extension icon to get token or input your token.", "warn"); + } + if ( status == 401 ) { + self.log("Your token is invalid, please re-login github and get token again.", "warn"); + } + } + }, + downloadPromiseProcess: function(resolvedUrl, infoAjaxItems){ + var self = this, + fileContents = [], + currentKey = ""; - self.log("Collect checked items..."); var treeAjaxItems = []; var blobAjaxCollection = []; - var fileContents = []; + + // start progress + self.checkTokenAndScope().then(function(key){ + currentKey = key || ""; + var infoUrl = getInfoUrl(resolvedUrl.author, resolvedUrl.project, resolvedUrl.path, resolvedUrl.branch); + return callAjax(infoUrl, currentKey).then(function(xmlResponse){ + var listRes = xmlResponse.response; + listRes + .filter(function(item){ + return infoAjaxItems.some(function(info){ + return (info.title === item.name || info.alias === item.name) && ( + (info.type == 'tree' && item.type == 'dir') || + (info.type == 'blob' && item.type == 'file') + ); + }); + }) + .forEach(function(item){ + if(item.type == "dir"){ + treeAjaxItems.push({ title: item.name, url: item.git_url }); + }else{ + blobAjaxCollection.push({ path: item.name, blobUrl: item.git_url }); + self.log(item.name + " url fetched.") + } + }); + }); + }).then(function(){ + var promises = treeAjaxItems.map(function(item){ + var fetchedUrl = item.url + "?recursive=1"; + return callAjax(fetchedUrl, currentKey).then(function(xmlResponse){ + var treeRes = xmlResponse.response; + treeRes.tree.forEach(function(blobItem){ + if(blobItem.type == "blob"){ + var path = item.title + "/" + blobItem.path; + blobAjaxCollection.push({ path: path, blobUrl: blobItem.url }); + self.log(path + " url fetched."); + } + }); + }); + }); + return Promise.all(promises); + }).then(function(){ + self.log("Collect blob contents..."); + var promises = blobAjaxCollection.map(function(item){ + var fetchedUrl = item.blobUrl; + return callAjax(fetchedUrl, currentKey).then(function(xmlResponse){ + var blobRes = xmlResponse.response; + fileContents.push({ path: item.path, content: blobRes.content }); + self.log(item.path + " content has collected."); + }); + }); + return Promise.all(promises); + }).then(function(){ + if ( treeAjaxItems.length == 0 && blobAjaxCollection.length == 1) { + self.log("Trigger download..."); + // to save as file + var singleItem = fileContents[0]; + return saveAs(base64toBlob(singleItem.content), singleItem.path); + } else { + self.log("Zip contents and trigger download..."); + return zipContents([resolvedUrl.project].concat(resolvedUrl.path.split('/')).join('-'), fileContents); + } + }).then(function(){ + self.reset(); + }).catch(function(err){ + self.handleApiError(err); + }); + }, + downloadItems: function(items){ + var self = this; + if(self._locked || !items.length) return; + + self._locked = true; + + self._el.classList.add("gitzip-downloading"); + var infoAjaxItems = []; var resolvedUrl = resolveUrl(window.location.href); - var currentKey = ""; - + self.log("Collect blob urls..."); - for(var idx = 0, len = checkedItems.length; idx < len; idx++){ - var item = checkedItems[idx], - href = item.getAttribute('gitzip-href'), + for(var idx = 0, len = items.length; idx < len; idx++){ + var item = isOnlyDoubleClick ? items[idx] : items[idx].closest("div.gitzip-check-wrap"), type = item.getAttribute('gitzip-type'), - title = item.getAttribute('gitzip-title'); + title = item.getAttribute('gitzip-title'), + href = item.getAttribute('gitzip-href'); - infoAjaxItems.push({ + var itemInfo = { type: type, title: title, href: href - }); + }, titleSplits; - // ga - // var looklink = item.closest("tr").querySelector("td.content a"); - // if(looklink){ - // var baseRepo = [resolvedUrl.author, resolvedUrl.project].join("/"); - // var githubUrl = looklink.getAttribute("href").substring(1); // ignore slash "/" from begin - // browser.runtime.sendMessage({ - // action: "gaTrack", - // baseRepo: baseRepo, - // githubUrl: githubUrl, - // userAction: "collected" - // }); - // } + if ( (titleSplits = title.split("/")).length > 1 ) itemInfo.alias = titleSplits[0]; + infoAjaxItems.push(itemInfo); } + + self.downloadPromiseProcess(resolvedUrl, infoAjaxItems); + }, + downloadSingle: function(selectedEl){ + this.downloadItems( selectedEl.querySelectorAll(isOnlyDoubleClick ? "p.gitzip-check-mark" : "div.gitzip-check-wrap") ); + }, + downloadAll: function(){ + this.downloadItems(document.querySelectorAll(itemCollectSelector + (isOnlyDoubleClick ? " p.gitzip-check-mark" : " div.gitzip-check-wrap") )); + }, + download: function(){ + this.downloadItems(document.querySelectorAll(itemCollectSelector + (isOnlyDoubleClick ? " p.gitzip-show" : " div.gitzip-check-wrap input:checked") )); + }, + downloadFile: function(resolvedUrl){ + var self = this; + if(self._locked) return; - // start progress - browser.runtime - .sendMessage({action: "getKey"}) - .then(function(key){ - currentKey = key || ""; - var infoUrl = getInfoUrl(resolvedUrl.author, resolvedUrl.project, resolvedUrl.path, resolvedUrl.branch); - return callAjax(infoUrl, currentKey).then(function(listRes){ - listRes - .filter(function(item){ - return infoAjaxItems.some(function(info){ - return info.title == item.name && ( - (info.type == 'tree' && item.type == 'dir') || - (info.type == 'blob' && item.type == 'file') - ); - }); - }) - .forEach(function(item){ - if(item.type == "dir"){ - treeAjaxItems.push({ title: item.name, url: item.git_url }); - }else{ - blobAjaxCollection.push({ path: item.name, blobUrl: item.git_url }); - self.log(item.name + " url fetched.") - } - }); - }); - }) - .then(function(){ - var promises = treeAjaxItems.map(function(item){ - var fetchedUrl = item.url + "?recursive=1"; - return callAjax(fetchedUrl, currentKey).then(function(treeRes){ - treeRes.tree.forEach(function(blobItem){ - if(blobItem.type == "blob"){ - var path = item.title + "/" + blobItem.path; - blobAjaxCollection.push({ path: path, blobUrl: blobItem.url }); - self.log(path + " url fetched."); - } - }); - }); - }); - return Promise.all(promises); - }) - .then(function(){ - self.log("Collect blob contents..."); - var promises = blobAjaxCollection.map(function(item){ - var fetchedUrl = item.blobUrl; - return callAjax(fetchedUrl, currentKey).then(function(blobRes){ - fileContents.push({ path: item.path, content: blobRes.content }); - self.log(item.path + " content has collected."); - }); - }); - return Promise.all(promises); - }) - .then(function(){ - self.log("Zip contents and trigger download..."); - return zipContents([resolvedUrl.project].concat(resolvedUrl.path.split('/')).join('-'), fileContents); - }) - .then(function(){ self.reset(); }) - .catch(function(err){ - console.log(err); - var message = err.message? err.message : err; - self.log(message); - if (message.indexOf("rate limit exceeded") >= 0){ - self.log("Please press GitZip extension icon to get token or input your token."); - } - if (message.indexOf("Bad credentials") >= 0){ - self.log("Your token has expired, please get token again."); - } - }); + self._locked = true; + + self._el.classList.add("gitzip-downloading"); + + var breadcrumb = document.querySelector(".repository-content .file-navigation .js-path-segment"), + rootAnchor = breadcrumb ? breadcrumb.querySelector("a") : null; + if ( rootAnchor && rootAnchor.href ) { + // for the cases like this: https://github.com/Microsoft/CNTK/blob/aayushg/autoencoder/Tools/build-and-test + // to find the branch in the case of branch has slash charactor. + var hrefSplits = rootAnchor.href.split("/tree/"); + if ( hrefSplits.length > 1 && resolvedUrl.branch != hrefSplits[1] ) { + var newBranch = hrefSplits[1]; + var inputSplits = resolvedUrl.inputUrl.split(newBranch); + var newPath = inputSplits[1].slice(1); + var newRoot = "https://github.com/" + resolvedUrl.author + "/" + resolvedUrl.project + "/tree/" + newBranch; + + resolvedUrl.branch = newBranch; + resolvedUrl.path = newPath; + resolvedUrl.rootUrl = newRoot; + } + } + + self.checkTokenAndScope().then(function(key){ + self.log("Collect blob content..."); + + currentKey = key || ""; + var params = []; + var fetchedUrl = "https://api.github.com/repos/" + resolvedUrl.author + "/" + resolvedUrl.project + "/contents/" + resolvedUrl.path; + + if ( resolvedUrl.branch ) params.push("ref=" + resolvedUrl.branch); + if ( params.length ) fetchedUrl += "?" + params.join('&'); + + return callAjax(fetchedUrl, currentKey); + }).then(function(xmlResponse){ + var treeRes = xmlResponse.response; + self.log(treeRes.name + " content has collected."); + self.log("Trigger download..."); + return saveAs(base64toBlob(treeRes.content), treeRes.name); + }).then(function(){ + self.reset(); + }).catch(function(err){ + self.handleApiError(err); + }); }, - log: function(message){ - this._dashBody.appendChild(document.createTextNode(message)); - this._dashBody.appendChild(document.createElement("br")); - this._dashBody.scrollTop = this._dashBody.scrollHeight - this._dashBody.clientHeight; + log: function(message, type){ + var self = this, + pNode = document.createElement("p"), + textNode = document.createTextNode(message); + + type && pNode.classList.add(type); + if (type == "error") self._el.classList.add("gitzip-fail"); + + pNode.appendChild(textNode); + + self._dashBody.appendChild(pNode); + self._dashBody.scrollTop = self._dashBody.scrollHeight - self._dashBody.clientHeight; } }; +function applyTheme() { + if (Pool._el) { + isDark && Pool._el.classList.add("gitzip-dark"); + !isDark && Pool._el.classList.remove("gitzip-dark"); + } + var markers = document.querySelectorAll("p.gitzip-check-mark"); + var markersLength = markers.length; + for(var i = 0; i < markersLength; i++){ + isDark && markers[i].classList.add("gitzip-dark"); + !isDark && markers[i].classList.remove("gitzip-dark"); + } +} + +browser.storage.local.get(defaultOptions, function(items){ + if (items) { + if (items.theme == "default") isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + else isDark = items.theme == "dark"; + applyTheme(); + + isOnlyDoubleClick = items.selectBehaviour == "double-click"; + isOnlySingleCheck = items.selectBehaviour == "single-check"; + isBoth = items.selectBehaviour == "both"; + + isStorageCallback = true; + var callbackEvent = new CustomEvent("storagecallback", {}); + window.dispatchEvent(callbackEvent); + } +}); + +browser.storage.onChanged.addListener(function(changes, area){ + if (area == "local") { + if (changes.theme) { + var newValue = changes.theme.newValue; + if (newValue == "default") isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + else isDark = newValue == "dark"; + applyTheme(); + } + + if (changes.selectBehaviour) { + var newValue = changes.selectBehaviour.newValue; + + isOnlyDoubleClick = newValue == "double-click"; + isOnlySingleCheck = newValue == "single-check"; + isBoth = newValue == "both"; + + // rebind + var items = document.querySelectorAll(itemCollectSelector); + var itemLen = items.length; + if(itemLen){ + for(var i = 0; i < itemLen; i++){ + var item = items[i]; + // reset + item._hasBind = false; + + // remove events + item.removeEventListener("dblclick", onItemDblClick); + item.removeEventListener("mouseenter", onItemEnter); + item.removeEventListener("mouseleave", onItemLeave); + + // remove custom markers + var toRemoveMark = item.querySelector("p.gitzip-check-mark"); + var toRemoveWrap = item.querySelector("div.gitzip-check-wrap"); + toRemoveMark && toRemoveMark.remove(); + toRemoveWrap && toRemoveWrap.remove(); + } + } + appendToIcons(true); + } + } +}); + function createMark(parent, height, title, type, href){ - if(parent && !parent.querySelector("p.gitzip-check-mark")){ - var checkp = document.createElement('p'); - - checkp.setAttribute("gitzip-title", title); - checkp.setAttribute("gitzip-type", type); - checkp.setAttribute("gitzip-href", href); - checkp.className = "gitzip-check-mark"; - checkp.appendChild(document.createTextNode("\u2713")); - checkp.style.cssText = "line-height:" + height + "px;"; - - parent.appendChild(checkp); + var target = parent.querySelector(isOnlyDoubleClick ? "p.gitzip-check-mark" : "div.gitzip-check-wrap"); + if (parent && !target) { + if (isOnlyDoubleClick) { + var checkp = document.createElement('p'); + + checkp.setAttribute("gitzip-title", title); + checkp.setAttribute("gitzip-type", type); + checkp.setAttribute("gitzip-href", href); + checkp.className = (isDark ? "gitzip-dark " : "") + "gitzip-check-mark"; + checkp.appendChild(document.createTextNode("\u2713")); + checkp.style.cssText = "line-height:" + height + "px;"; + + parent.appendChild(checkp); + + target = checkp; + } else { + var checkw = document.createElement('div'); + var cb = document.createElement("input"); + + cb.type = "checkbox"; + + checkw.setAttribute("gitzip-title", title); + checkw.setAttribute("gitzip-type", type); + checkw.setAttribute("gitzip-href", href); + + checkw.className = "gitzip-check-wrap"; + + checkw.appendChild(cb); + parent.appendChild(checkw); + + target = checkw; + } } + return target; } function checkHaveAnyCheck(){ - var checkItems = document.querySelectorAll(itemCollectSelector + " p.gitzip-show"); - return checkItems.length? checkItems : false; + var checkItems = document.querySelectorAll(itemCollectSelector + (isOnlyDoubleClick ? " p.gitzip-show" : " div.gitzip-check-wrap input:checked") ); + return checkItems.length > 0; } function onItemDblClick(e){ - var markTarget = e.target.closest(".js-navigation-item").querySelector('p.gitzip-check-mark'); - if(markTarget) markTarget.classList.toggle("gitzip-show"); - !!checkHaveAnyCheck()? Pool.show() : Pool.hide(); + if (isBoth) { + var markTarget = e.target.closest(".js-navigation-item").querySelector('div.gitzip-check-wrap'); + if(markTarget) { + var cb = markTarget.querySelector('input'); + cb.click(); + } + } else if (isOnlyDoubleClick) { + var markTarget = e.target.closest(".js-navigation-item").querySelector('p.gitzip-check-mark'); + if(markTarget) markTarget.classList.toggle("gitzip-show"); + checkHaveAnyCheck()? Pool.show() : Pool.hide(); + applyItemsContext(); + } } -function hookItemEvents(){ - - // show icon in every github page. - showGitzipIcon(); - - function appendToIcons(){ - var items = document.querySelectorAll(itemCollectSelector); - var itemLen = items.length; - if(itemLen){ - for(var i = 0; i < itemLen; i++){ - var item = items[i], - link = item.querySelector("a[href]"), - blob = item.querySelector(".octicon-file-text, .octicon-file"), - tree = item.querySelector(".octicon-file-directory"); +function onItemEnter(e) { + var markTarget = e.target.closest(".js-navigation-item").querySelector('div.gitzip-check-wrap'); + if (markTarget && !markTarget.style.display) { + markTarget.style.display = "flex"; + } +} + +function onItemLeave(e) { + var markTarget = e.target.closest(".js-navigation-item").querySelector('div.gitzip-check-wrap'); + if (markTarget && !markTarget.querySelector('input:checked')) { + markTarget.style.display = ""; + } +} + +var cacheSelectEl = null; +var currentSelectEl = null; +function generateEnterItemHandler(title, type){ + return function(){ + applySelectedContext(title, type); + cacheSelectEl = this; + } +} + +function leaveItemHandler() { + applySelectedContext(); + cacheSelectEl = null; +} + +function applyItemsContext() { + browser.runtime.sendMessage({ + action: "updateContextNested", + target: "items", + enabled: checkHaveAnyCheck() + }); +} + +function applySelectedContext(name, type) { + var enabled = !!name && !!type; + browser.runtime.sendMessage({ + action: "updateContextNested", + urlName: name, + urlType: type, + enabled: enabled, + target: "selected" + }); +} + +function applyCurrentContext(name, type) { + browser.runtime.sendMessage({ + action: "updateContextNested", + urlName: name, + urlType: type, + root: !name && !type, + target: "current" + }); +} + +function restoreContextStatus(){ + var resolvedUrl = resolveUrl(window.location.href); + var baseRepo = [resolvedUrl.author, resolvedUrl.project].join("/"); + var fileNavigation = document.querySelector(".repository-content .file-navigation"); + var singleFileNavigation = document.querySelector(".repository-content .breadcrumb .js-repo-root"); + var downloadBtn = fileNavigation ? fileNavigation.querySelector("div[data-target='get-repo.modal'] a[href^='/" + baseRepo + "/']") : null; + var pathText = resolvedUrl.path.split('/').pop(); + var urlType = resolvedUrl.type; + var breadcrumb; + + if ( fileNavigation && (breadcrumb = fileNavigation.querySelector(".js-repo-root")) ) { + // in tree view + applyCurrentContext(pathText, urlType); + } else if ( singleFileNavigation ) { + // in file view + applyCurrentContext(pathText, urlType); + applySelectedContext(); + } else if (downloadBtn) { + // in root + applyCurrentContext(); + } + + // the checked items + applyItemsContext(); +} + +// Check is in available view +function isAvailableView(){ + return !!document.querySelector("head meta[value=repo_source]") && resolveUrl(window.location.href) !== false; +} + +function isItemsBind() { + var items = document.querySelectorAll(itemCollectSelector); + var itemLen = items.length; + return itemLen ? !!items[itemLen-1]._hasBind : false; +} + +function appendToIcons(isRebind){ + var items = document.querySelectorAll(itemCollectSelector); + var itemLen = items.length; + if(itemLen){ + for(var i = 0; i < itemLen; i++){ + var item = items[i], + link = item.querySelector("a[href]"), + blob = item.querySelector(".octicon-file-text, .octicon-file"), + tree = item.querySelector(".octicon-file-directory, .octicon-file-directory-fill"); + + if(!item._hasBind && link && (tree || blob)){ + var title = link.textContent, + type = tree? "tree" : "blob"; + + if (isBoth || isOnlySingleCheck) { + // reset status if not checked + onItemLeave({ target: item }); + } + + var markTarget = createMark(item, item.offsetHeight, title, type, link.href); + if (isBoth || isOnlySingleCheck) { + markTarget.querySelector("input").addEventListener('change', function(){ + checkHaveAnyCheck()? Pool.show() : Pool.hide(); + applyItemsContext(); + }); + } + + if (isBoth || isOnlyDoubleClick) { + item.addEventListener("dblclick", onItemDblClick); + } - if(!item._hasBind && link && (tree || blob)){ - createMark( - item, - item.offsetHeight, - link.textContent, - tree? "tree" : "blob", - link.href - ); - item.addEventListener("dblclick", onItemDblClick); - item._hasBind = true; + if (isRebind !== true) { + item.addEventListener("mouseenter", generateEnterItemHandler(title, type, link.href) ); + item.addEventListener("mouseleave", leaveItemHandler); } + + if (isBoth || isOnlySingleCheck) { + item.addEventListener("mouseenter", onItemEnter); + item.addEventListener("mouseleave", onItemLeave); + } + + item._hasBind = true; } } } +} - var lazyCaseObserver = null; - var repoContent = document.querySelector(".repository-content"); - var lazyElement = repoContent ? repoContent.querySelector(".js-navigation-container") : null; - - if(lazyElement){ - // lazy case - lazyCaseObserver = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - var addNodes = mutation.addedNodes; - addNodes && addNodes.length && addNodes.forEach(function(el){ - if(el.classList && (el.classList.contains("js-details-container") || el.classList.contains("js-navigation-container"))){ - appendToIcons(); - lazyCaseObserver.disconnect(); - lazyCaseObserver = null; - } - }); - }); - }); - lazyCaseObserver.observe(repoContent, { childList: true, subtree: true } ); - } +function isAnyItemExist() { + return !!document.querySelector(itemCollectSelector); +} + +function hookItemEvents(){ + + var theInterval = null; + + function waitStorageHandler() { + appendToIcons(); + Pool._el && Pool.reset(); + currentSelectEl = cacheSelectEl = null; + + if (!theInterval) { + theInterval = setInterval(function(){ + if (!isAnyItemExist() || isItemsBind()) { + clearInterval(theInterval); + theInterval = null; + } else { + appendToIcons(); + } + }, 100); + } + } - if (document.querySelector(itemCollectSelector)) { - appendToIcons(); + if (isAnyItemExist()) { + if (isStorageCallback) waitStorageHandler(); + else window.addEventListener("storagecallback", waitStorageHandler); } + window.addEventListener('popstate', (ev) => { + if (isAnyItemExist()) { + waitStorageHandler(); + } + }); + + window.addEventListener('contextmenu', (ev) => { + currentSelectEl = cacheSelectEl; + }) + + function onRequestsObserved( batch ) { + var entries = batch.getEntries(); + if (entries.some(resource => repoCommitExp.test(resource.name)) && isAnyItemExist()) { + if (isStorageCallback) waitStorageHandler(); + else window.addEventListener("storagecallback", waitStorageHandler); + } + } + + var requestObserver = new PerformanceObserver( onRequestsObserved ); + requestObserver.observe({ type: 'resource' }); + Pool.init(); } -// pjax detection -function hookMutationObserver(){ - // select the target node - var target = document.querySelector("*[data-pjax-container]"); +function hookChromeEvents(){ - // create an observer instance - var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - var addNodes = mutation.addedNodes; - if(addNodes && addNodes.length) hookItemEvents(); - }); + browser.runtime.onMessage.addListener(function(request, sender, sendResponse) { + switch (request.action){ + case "getCurrentPath": + sendResponse(window.location.href); + case "github-tab-active": + // from the background event + // means tab active changed. + // if it is in github.com now + browser.runtime.sendMessage({action: "showIcon"}); + if ( isAvailableView() ) { + browser.runtime.sendMessage({action: "createContextNested"}); + restoreContextStatus(); + } else { + browser.runtime.sendMessage({action: "removeContext"}); + } + return true; + case "gitzip-nested-items-clicked": + Pool.download(); + return true; + case "gitzip-nested-selected-clicked": + Pool.downloadSingle(currentSelectEl); + return true; + case "gitzip-nested-current-clicked": + var resolvedUrl = resolveUrl(window.location.href); + var baseRepo = [resolvedUrl.author, resolvedUrl.project].join("/"); + + var fileNavigation = document.querySelector(".repository-content .file-navigation"); + var singleFileNavigation = document.querySelector(".repository-content .breadcrumb .js-repo-root"); + + var breadcrumb, + downloadBtn = fileNavigation ? fileNavigation.querySelector("div[data-target='get-repo.modal'] a[href^='/" + baseRepo + "/']") : null; + + if ( fileNavigation && (breadcrumb = fileNavigation.querySelector(".js-repo-root")) ) { + // in tree view + Pool.downloadAll(); + } else if ( singleFileNavigation ) { + // in file view + Pool.downloadFile(resolvedUrl); + } else if ( downloadBtn ) { + // in root + downloadBtn.click(); + } else { + alert("Unknown Operation"); + } + return true; + } }); - - // pass in the target node, as well as the observer options - target && observer.observe(target, { childList: true } ); - - // later, you can stop observing - // observer.disconnect(); } -// Property run_at is "document_end" as default in Content Script -// refers: https://developer.chrome.com/extensions/content_scripts -hookMutationObserver(); hookItemEvents(); +hookChromeEvents(); diff --git a/js/popup.js b/js/popup.js index 38ccf3c..6d39ea8 100644 --- a/js/popup.js +++ b/js/popup.js @@ -1,3 +1,33 @@ + +let isDark = true; + +const defaultOptions = { + selectBehaviour: 'both', + theme: 'default' +}; + +function applyTheme() { + isDark && document.body.classList.add("dark-theme"); + !isDark && document.body.classList.remove("dark-theme"); +} + +browser.storage.local.get(defaultOptions, function(items){ + if (items) { + if (items.theme == "default") isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + else isDark = items.theme == "dark"; + applyTheme(); + } +}); + +browser.storage.onChanged.addListener(function(changes, area){ + if (area == "local" && changes.theme) { + var newValue = changes.theme.newValue; + if (newValue == "default") isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + else isDark = newValue == "dark"; + applyTheme(); + } +}); + // The DOMContentLoaded means the popup.html page has load. (trigger this event after click the ext icon) document.addEventListener('DOMContentLoaded', function() { // alert("has loaded"); @@ -35,4 +65,5 @@ document.addEventListener('DOMContentLoaded', function() { } }); + applyTheme(); }, false); diff --git a/manifest.json b/manifest.json old mode 100644 new mode 100755 index 8ec3b26..0edb163 --- a/manifest.json +++ b/manifest.json @@ -3,13 +3,17 @@ "name": "GitZip", "description": "It can make the sub-directories and files of github repository as zip and download it", - "version": "0.3.2", + "version": "1.0.0", "page_action": { - "default_icon": "images/icon-empty.png", + "default_icon": "images/icon-128px.png", "default_popup": "popup.html" }, + "options_ui": { + "page": "options.html" + }, + "background": { "page": "background.html" }, @@ -17,6 +21,7 @@ "permissions": [ "storage", "activeTab", + "contextMenus", "theme", "*://github.com/*", "https://api.github.com/repos/*" diff --git a/options.html b/options.html new file mode 100644 index 0000000..ee2360a --- /dev/null +++ b/options.html @@ -0,0 +1 @@ +Gitzip Options
\ No newline at end of file diff --git a/static/js/main.d164c8cf.js b/static/js/main.d164c8cf.js new file mode 100644 index 0000000..b76814c --- /dev/null +++ b/static/js/main.d164c8cf.js @@ -0,0 +1,3 @@ +/*! For license information please see main.d164c8cf.js.LICENSE.txt */ +!function(){"use strict";var e={110:function(e,t,n){var r=n(441),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},a={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function u(e){return r.isMemo(e)?i:l[e.$$typeof]||o}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var s=Object.defineProperty,c=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!==typeof n){if(h){var o=p(n);o&&o!==h&&e(t,o,r)}var i=c(n);f&&(i=i.concat(f(n)));for(var l=u(t),m=u(n),v=0;v