Skip to content

Commit 321f036

Browse files
author
onlylemi
committed
chrome extension
1 parent bbba055 commit 321f036

File tree

13 files changed

+464
-1
lines changed

13 files changed

+464
-1
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2016 Jianbin Qi
3+
Copyright (c) 2016 onlylemi
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Download file/floder For GitHub. Chrome Extension
2+
3+
![](https://raw.githubusercontent.com/onlylemi/res/master/download-any-for-github-icon.png)
4+
5+
It's a chrome extension. When we look up some resources in `GitHub`, and usually we need to look them in local. At this time we must use `git clone`, `Download ZIP` or `Open in Desktop`. But soemtimes we don't need to download the whole repository and only to look some floders or some files. So this chrome extension can help you achieve your demand.
6+
7+
It can download any files or floders from repository in github.
8+
9+
## Preview
10+
11+
Let we look named [butterknife](https://github.com/JakeWharton/butterknife) repository from [JakeWharton](https://github.com/JakeWharton).
12+
13+
![](https://raw.githubusercontent.com/onlylemi/res/master/download-any-for-github-preview.png)
14+
15+
## Thanks
16+
17+
* [gitzip](https://github.com/KinoLien/gitzip)
18+
19+
## About
20+
21+
Welcome to pull requests.
22+
23+
If you have any new idea about this project, feel free to [contact me](mailto:onlylemi.com@gmial.com). :smiley:

css/main.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
table.files td.download {
2+
width: 16px;
3+
text-align: right;
4+
padding-right: 6px;
5+
color: #6DBE51;
6+
}
7+
8+
.display-none{
9+
display: none;
10+
}
11+
12+
.loading{
13+
position: relative;
14+
top: 3px;
15+
display: inline-block;
16+
margin-top: -3px;
17+
margin-left: -2px;
18+
}

icon/icon-128.png

4.33 KB
Loading

icon/icon-16.png

500 Bytes
Loading

icon/icon-32.png

965 Bytes
Loading

js/API.js

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
(function(scope){
2+
function fn(){};
3+
4+
var repoExp = new RegExp("^https://github.com/([^/]+)/([^/]+)(/(tree|blob)/([^/]+)(/(.*))?)?");
5+
var githubProvidedUrl = new RegExp("^https://api.github.com/.*");
6+
var isBusy = false;
7+
8+
var statusHandle = function(status){
9+
if(status == 'error' || status == 'done') isBusy = false;
10+
else isBusy = true;
11+
};
12+
13+
/**
14+
* @typedef ResolvedURL
15+
* @type Object
16+
* @property {string} author - The project owner
17+
* @property {string} project - The project name
18+
* @property {string} branch - The default branch or other branches
19+
* @property {string} type - The type of url link, values: tree, blob, link?
20+
* @property {string} path - The path of target file/dir based on root repo url.
21+
* @property {string} inputUrl - The input url
22+
* @property {string} rootUrl - The root dir url
23+
*/
24+
25+
/**
26+
* This callback would call by each progress changes.
27+
* @callback progressCallback
28+
* @param {string} status - indicates the status description like 'error', 'prepare', 'processing', 'done'
29+
* @param {string} message - the messages of the above status.
30+
* @param {number} percent - from 0 to 100, indicates the progress percentage.
31+
*/
32+
var progressCallback = function(status, message, percent){};
33+
34+
/**
35+
* Resolve the github repo url for recognize author, project name, branch name, and so on.
36+
* @private
37+
* @param {string} repoUrl - The github repo url.
38+
* @param {ResolvedURL}
39+
*/
40+
function resolveUrl(repoUrl){
41+
if(typeof repoUrl != 'string') return;
42+
var matches = repoUrl.match(repoExp);
43+
if(matches && matches.length > 0){
44+
var root = (matches[5])?
45+
"https://github.com/" + matches[1] + "/" + matches[2] + "/tree/" + matches[5] :
46+
repoUrl;
47+
return {
48+
author: matches[1],
49+
project: matches[2],
50+
branch: matches[5],
51+
type: matches[4],
52+
path: matches[7] || '',
53+
inputUrl: repoUrl,
54+
rootUrl: root
55+
};
56+
}
57+
}
58+
59+
/**
60+
* Force to trigger download dialog for any mine-type files using Native A Element.
61+
* @param {string} url - The URL.
62+
* @param {object|undefined} callbackScope - The scope of the progressCallback function.
63+
*/
64+
function downloadZipUseElement(url, callbackScope){
65+
var down = document.createElement('a');
66+
down.setAttribute('download', true);
67+
down.href = url;
68+
down.addEventListener('click', function(e){
69+
progressCallback.call(callbackScope, 'done', 'Saving File.');
70+
});
71+
setTimeout(function(){
72+
// link has to be in the page DOM for it to work with Firefox
73+
document.body.appendChild(down);
74+
down.click();
75+
down.parentNode.removeChild(down);
76+
},100);
77+
}
78+
79+
/**
80+
* Force to trigger download dialog for any mine-type files.
81+
* @param {string} url - The URL.
82+
* @param {object|undefined} callbackScope - The scope of the progressCallback function.
83+
*/
84+
function downloadZip(url, callbackScope){
85+
callbackScope = callbackScope || scope;
86+
if(url){
87+
progressCallback.call(callbackScope, 'processing', 'Fetching target url: ' + url);
88+
89+
$.get(url)
90+
.fail(function(jqXHR, textStatus, errorThrown){
91+
console.error('downloadZip > $.get fail:', textStatus);
92+
if (errorThrown) throw errorThrown;
93+
})
94+
95+
.done(function(data, textStatus, jqXHR){
96+
var blob = new Blob([data], {
97+
type: jqXHR.getResponseHeader('Content-Type') ||
98+
'application/octet-stream'
99+
});
100+
101+
var down = document.createElement('a');
102+
down.download = url.substring(url.lastIndexOf('/') + 1);
103+
down.href = URL.createObjectURL(blob);
104+
105+
down.addEventListener('click', function(e){
106+
progressCallback.call(callbackScope, 'done', 'Saving File.');
107+
});
108+
109+
setTimeout(function(){
110+
// link has to be in the page DOM for it to work with Firefox
111+
document.body.appendChild(down);
112+
down.click();
113+
down.parentNode.removeChild(down);
114+
}, 100);
115+
});
116+
}
117+
}
118+
119+
/**
120+
* Download zip file from github api url.
121+
* @param {string} zipName - The zip file name.
122+
* @param {string} url - The github api url.
123+
* @param {object|undefined} callbackScope - The scope of the progressCallback function.
124+
*/
125+
function zipIt(zipName, url, callbackScope){
126+
callbackScope = callbackScope || scope;
127+
if(url && githubProvidedUrl.test(url)){
128+
progressCallback.call(callbackScope, 'prepare', 'Fetching list of Dir contains files.');
129+
$.ajax({
130+
url: url + "?recursive=1",
131+
success: function(results){
132+
var promises = [];
133+
var fileContents = [];
134+
if(results.truncated){
135+
progressCallback.call(callbackScope, 'error', 'The tree travels is over than API limitation (500 files)');
136+
throw ("The tree travels is over than API limitation (500 files)");
137+
};
138+
progressCallback._idx = 0;
139+
progressCallback._len = 0;
140+
results.tree.forEach(function(item){
141+
if(item.type == "blob") progressCallback._len++;
142+
});
143+
results.tree.forEach(function(item){
144+
if(item.type == "blob"){
145+
promises.push(Promise.resolve(
146+
$.ajax({
147+
url: item.url,
148+
success: (function(path){
149+
return function(results){
150+
fileContents.push({path:path,content:results.content});
151+
progressCallback.call(callbackScope, 'processing', 'Fetched ' + path,
152+
++progressCallback._idx / (progressCallback._len * 2) * 100);
153+
};
154+
})(item.path)
155+
})
156+
));
157+
}
158+
});
159+
160+
Promise.all(promises).then(function() {
161+
var zip = new JSZip();
162+
fileContents.forEach(function(item){
163+
progressCallback.call(callbackScope, 'processing', 'Compressing ' + item.path,
164+
++progressCallback._idx / (progressCallback._len * 2) * 100);
165+
zip.file(item.path, item.content, {createFolders:true,base64:true});
166+
});
167+
saveAs(zip.generate({type:"blob"}), zipName + ".zip");
168+
progressCallback.call(callbackScope, 'done', 'Saving ' + zipName + '.zip');
169+
},function(item){
170+
if(item){
171+
progressCallback.call(callbackScope, 'error', 'Error: ' + JSON.stringify(item));
172+
throw (JSON.stringify(item) + " ERROR");
173+
}
174+
});
175+
},
176+
error:function(e){
177+
progressCallback.call(callbackScope, 'error', 'Error: ' + e);
178+
throw (e);
179+
}
180+
});
181+
}
182+
}
183+
184+
/**
185+
* Download zip for single file from input repo URL.
186+
* @param {string} pathToFolder - The URL of the Github repository.
187+
* @param {object|undefined} callbackScope - The scope of the progressCallback function.
188+
*/
189+
function createURL(pathToFolder, callbackScope){
190+
if(isBusy) throw "GitZip is busy...";
191+
callbackScope = callbackScope || scope;
192+
progressCallback.call(callbackScope, 'prepare', 'Resolving URL');
193+
var resolved = resolveUrl(pathToFolder);
194+
if(!resolved){
195+
progressCallback.call(callbackScope, 'error', 'Invalid URL: value is [' + pathToFolder.toString() + ']');
196+
throw "INVALID URL";
197+
}
198+
if(!resolved.path){
199+
// root
200+
var durl = [
201+
"https://github.com", resolved.author, resolved.project,
202+
"archive", (resolved.branch || 'master')
203+
].join('/');
204+
var gitURL = durl + ".zip";
205+
// downloadZip(gitURL, callbackScope);
206+
downloadZipUseElement(gitURL, callbackScope);
207+
} else{
208+
// get up level url
209+
var originInput = resolved.inputUrl;
210+
if(resolved.type == "tree"){
211+
var news = originInput.split('/');
212+
news.pop();
213+
resolved = resolveUrl(news.join('/'));
214+
}
215+
progressCallback.call(callbackScope, 'prepare', 'Finding file/dir content path from resolved URL');
216+
$.ajax({
217+
url: "https://api.github.com/repos/"+ resolved.author +
218+
"/" + resolved.project + "/contents/" + resolved.path +
219+
(resolved.branch? ("?ref=" + resolved.branch) : ""),
220+
success: function(results) {
221+
var templateText = '';
222+
if(!Array.isArray(results)){
223+
if(results.message){
224+
progressCallback.call(callbackScope, 'error', 'Github said: '+results.message);
225+
throw ("Error: " + results.message);
226+
}else downloadZip(results.download_url, callbackScope);
227+
return;
228+
}
229+
for(var i = 0, len = results.length; i < len; i++){
230+
var item = results[i];
231+
// target has found
232+
if(item.type == "dir" && item.html_url == originInput){
233+
var valueText = item.path;
234+
var pathText = valueText.split('/').pop();
235+
var urlText = item.git_url;
236+
zipIt(pathText, urlText, callbackScope);
237+
break;
238+
}
239+
if(i + 1 == len){
240+
progressCallback.call(callbackScope, 'error', 'File/Dir content not found.');
241+
}
242+
}
243+
},
244+
error: function(results){
245+
progressCallback.call(callbackScope, 'error', 'Github said: ' + JSON.stringify(results));
246+
throw (JSON.stringify(results));
247+
}
248+
});
249+
}
250+
}
251+
252+
/**
253+
* Register the progress callback for handleing the progress is changing.
254+
* @param {progressCallback} inputFn - The progress callback.
255+
*/
256+
function registerCallback(inputFn){
257+
if(typeof inputFn == 'function'){
258+
// progressCallback = callback;
259+
progressCallback = function(){
260+
inputFn.apply(this, arguments);
261+
statusHandle.apply(this, arguments);
262+
};
263+
}
264+
}
265+
266+
fn.zipRepo = createURL;
267+
fn.zipFromApiUrl = zipIt;
268+
fn.downloadFile = downloadZip;
269+
fn.registerCallback = registerCallback;
270+
271+
scope.GitZip = fn;
272+
273+
})(window);

js/FileSaver.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/background.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
chrome.runtime.onInstalled.addListener(function (details) {
4+
console.log('previousVersion', details.previousVersion);
5+
});
6+
7+
chrome.tabs.onUpdated.addListener(function (tabId) {
8+
chrome.pageAction.show(tabId);
9+
});

0 commit comments

Comments
 (0)