diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c346b13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bower_components/ +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..251516b --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2015, Clay Reimann + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/assets/GitHub-Mark-120px-plus.png b/assets/GitHub-Mark-120px-plus.png new file mode 100644 index 0000000..ea6ff54 Binary files /dev/null and b/assets/GitHub-Mark-120px-plus.png differ diff --git a/assets/GitHub-Mark-32px.png b/assets/GitHub-Mark-32px.png new file mode 100644 index 0000000..8b25551 Binary files /dev/null and b/assets/GitHub-Mark-32px.png differ diff --git a/assets/GitHub-Mark-64px.png b/assets/GitHub-Mark-64px.png new file mode 100644 index 0000000..182a1a3 Binary files /dev/null and b/assets/GitHub-Mark-64px.png differ diff --git a/chrome.js b/chrome.js new file mode 100644 index 0000000..581246c --- /dev/null +++ b/chrome.js @@ -0,0 +1,72 @@ +// Clay Reimann, 2015 +// See repository LICENSE for details + +console.log('Initializing extension'); +function initalizeSuggestionService() { + chrome.storage.sync.get(['githubs'], + function(settings) { + console.log(settings); + var githubsDefs = settings.githubs; + if (githubsDefs.length < 1) { + return; + } + var suggester = new SuggestionService(githubsDefs); + + chrome.omnibox.onInputStarted.addListener( + function() { + console.log('inputStarted'); + chrome.omnibox.setDefaultSuggestion({ + description: 'Type to search for a repository' + }); + } + ); + + chrome.omnibox.onInputChanged.addListener( + function(text, suggest) { + if (text.length < 1) { return; } + var suggestions = suggester.getRepositoriesMatching(text) + .map(function(repo) { + var matches = repo.url.match(new RegExp(text, "i")); + return { + content: repo.name, + description: 'Open ' + repo.url.slice(0, matches.index) + + '' + matches[0] + '' + + repo.url.slice(matches.index + matches[0].length) + '' + }; + }); + console.log('returning ' + suggestions.length + ' suggestions'); + console.log(suggestions); + + chrome.omnibox.setDefaultSuggestion({ + description: suggestions.length + ' results for "%s"' + (suggestions.length > 5 ? '. Enter a more specific query.' : '') + }); + suggest(suggestions); + } + ); + + chrome.omnibox.onInputEntered.addListener( + function(repoName, disposition) { + console.log(repoName + ' ' + disposition); + var url = suggester.getUrlForRepo(repoName) + if (disposition === 'currentTab') { + chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { + chrome.tabs.update(tabs[0].id, {url: url}); + }); + } else if (disposition === 'newForegroundTab') { + chrome.tabs.create({url: url, active: true}); + } else if (disposition === 'newBackgroundTab') { + chrome.tabs.create({url: url}); + } + + } + ); + } + ); +} + +initalizeSuggestionService(); +chrome.runtime.onMessage.addListener(function(message) { + if (message === 'settings-updated') { + initalizeSuggestionService(); + } +}) diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..c81dbe5 --- /dev/null +++ b/manifest.json @@ -0,0 +1,29 @@ +{ + "manifest_version": 2, + "name": "Github Repo Search", + "version": "1.0.0", + "omnibox": { "keyword" : "gh" }, + "icons": { + "16": "assets/GitHub-Mark-32px.png", + "32": "assets/GitHub-Mark-32px.png", + "64": "assets/GitHub-Mark-64px.png", + "128": "assets/Github-Mark-120px-plus.png" + + }, + "options_ui": { + "page": "options/options.html", + "chrome_style": true + }, + "permissions": [ + "tabs", + "storage" + ], + "background": { + "persistent": true, + "scripts": [ + "node_modules/github-api/github.js", + "search.js", + "chrome.js" + ] + } +} diff --git a/options/options.html b/options/options.html new file mode 100644 index 0000000..8748ef3 --- /dev/null +++ b/options/options.html @@ -0,0 +1,36 @@ + + + + + + + + Omnibox Github Search + + + + + + +
+
+
+ +
+
+ +
+
+
+
+
+ + + + + + diff --git a/options/options.js b/options/options.js new file mode 100644 index 0000000..3eda590 --- /dev/null +++ b/options/options.js @@ -0,0 +1,72 @@ +var FORM = document.getElementById('settings-form'); +FORM.addEventListener("submit", processForm); +var SAVE_BUTTON = document.getElementById('save-button') + +function processForm(e) { + e.preventDefault(); + var token = document.getElementById('oauth-token-1').value; + var url = document.getElementById('github-url-1').value; + chrome.storage.sync.set({ + githubs: [ + { + url: url, + token: token + } + ] + }); + chrome.runtime.sendMessage('settings-updated'); +} + +var FORM_GROUP_CLASS_NAME = 'form-group'; +var LABEL_CLASS_NAME = 'col-sm-2 control-label'; +var INPUT_DIV_CLASS_NAME = 'col-sm-6' +var INPUT_CLASS_NAME = 'form-control' +function createInputGroup(id, title, placeholder, value) { + var inputLabel = document.createElement('label'); + inputLabel.innerText = title; + inputLabel.setAttribute('for', id) + inputLabel.className = LABEL_CLASS_NAME; + var input = document.createElement('input'); + input.className = INPUT_CLASS_NAME; + input.setAttribute('id', id); + input.setAttribute('placeholder', placeholder); + input.value = value; + + var inputDiv = document.createElement('div'); + inputDiv.className = INPUT_DIV_CLASS_NAME; + inputDiv.appendChild(input); + + var inputGroup = document.createElement('div') + inputGroup.className = FORM_GROUP_CLASS_NAME; + inputGroup.appendChild(inputLabel); + inputGroup.appendChild(inputDiv); + + return inputGroup +} + +function addInstance(token, url) { + var num = FORM.children.length; + var fieldset = document.createElement('fieldset'); + var legend = document.createElement('legend'); + legend.innerText = 'Instance ' + num; + + var tokenGroup = createInputGroup('oauth-token-' + num, 'OAuth Token', 'token', token); + var urlGroup = createInputGroup('github-url-' + num, 'Github URL', 'https://api.github.com', url); + + fieldset.appendChild(legend); + fieldset.appendChild(tokenGroup); + fieldset.appendChild(urlGroup); + FORM.insertBefore(fieldset, SAVE_BUTTON); +} + +chrome.storage.sync.get(['githubs'], function(settings) { + var githubs = settings.githubs; + + if (!githubs) { + addInstance('', ''); + } else { + githubs.forEach(function(def) { + addInstance(def.token, def.url); + }); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..e863d20 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "github-search", + "version": "0.1.0", + "description": "Search github repositiories from the chrome omnibox", + "main": "search.js", + "scripts": { + "test": "gulp test" + }, + "keywords": [ + "github", + "search", + "developer", + "devtools", + "productivity" + ], + "author": "Clay Reimann", + "license": "ISC" +} diff --git a/search.js b/search.js new file mode 100644 index 0000000..d5108b9 --- /dev/null +++ b/search.js @@ -0,0 +1,99 @@ +/** + * A service that suggests repositories based on user input + * @param {Array[Object]} ghInstances - an array of objects with `url` and `token` properties + */ +var SuggestionService = function(ghInstances) { + this.GITHUBS = ghInstances.map(function(instance) { + return new Github({ + apiUrl: instance.url, + token: instance.token, + auth: 'oauth' + }); + }) + this.REPOS = []; + this.OUTSTANDING_API_REQUESTS = 0; + this.getRepos(); +} + +SuggestionService.prototype.makeRequest = function() { + this.OUTSTANDING_API_REQUESTS += 1; +}; +SuggestionService.prototype.finishRequest = function() { + this.OUTSTANDING_API_REQUESTS -= 1; +}; +SuggestionService.prototype.isReady = function() { + return this.OUTSTANDING_API_REQUESTS === 0; +}; + +SuggestionService.prototype.addRepository = function(github, repo) { + var contains = false; + this.REPOS.forEach(function(r) { + if (r.github === github && r.name === repo.full_name) { + contains = true; + } + }); + if (!contains) { + this.REPOS.push({ + github: github, + name: repo.full_name, + url: repo.html_url + }); + } +}; + +SuggestionService.prototype.getRepos = function() { + var _this = this; + this.REPOS = []; + this.GITHUBS.forEach(function(github) { + var user = github.getUser(); + + _this.makeRequest(); + user.repos(function(err, repos) { + console.log(repos.map(function(repo) { + return repo.full_name; + })); + _this.finishRequest(); + + if (!repos) { return; } + repos.forEach(function(repo) { + repo.user = user; + _this.addRepository(github, repo); + }); + }); + + _this.makeRequest(); + user.orgs(function(err, orgs) { + _this.finishRequest(); + + if (!orgs) { return; } + orgs.forEach(function(org) { + + _this.makeRequest(); + user.orgRepos(org.login, function(err, repos) { + _this.finishRequest(); + + console.log(repos.map(function(repo) { + return repo.full_name; + })); + if (!repos) { return; } + repos.forEach(function(repo) { + _this.addRepository(github, repo); + }); + }); + }); + }); + }); +}; + +SuggestionService.prototype.getRepositoriesMatching = function(text) { + return this.REPOS.filter(function(repo) { + return repo.name.search(new RegExp(text, 'i')) !== -1; + }); +} + +SuggestionService.prototype.getUrlForRepo = function(fullName) { + var repo = this.REPOS.filter(function(r) { + return r.name === fullName; + })[0]; + return repo ? repo.url : undefined; +};