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;
+};