diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..2f05f06
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["es2015"],
+ "plugins": ["transform-strict-mode"]
+}
diff --git a/.gitignore b/.gitignore
index c346b13..5b79226 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
-bower_components/
+build/
node_modules/
+bower_components/
diff --git a/LICENSE b/LICENSE.txt
similarity index 100%
rename from LICENSE
rename to LICENSE.txt
diff --git a/assets/GitHub-Mark-120px-plus.png b/assets/assets/GitHub-Mark-120px-plus.png
similarity index 100%
rename from assets/GitHub-Mark-120px-plus.png
rename to assets/assets/GitHub-Mark-120px-plus.png
diff --git a/assets/GitHub-Mark-32px.png b/assets/assets/GitHub-Mark-32px.png
similarity index 100%
rename from assets/GitHub-Mark-32px.png
rename to assets/assets/GitHub-Mark-32px.png
diff --git a/assets/GitHub-Mark-64px.png b/assets/assets/GitHub-Mark-64px.png
similarity index 100%
rename from assets/GitHub-Mark-64px.png
rename to assets/assets/GitHub-Mark-64px.png
diff --git a/chrome.js b/chrome.js
deleted file mode 100644
index 581246c..0000000
--- a/chrome.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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/gulpfile.babel.js b/gulpfile.babel.js
new file mode 100644
index 0000000..e1c8c91
--- /dev/null
+++ b/gulpfile.babel.js
@@ -0,0 +1,105 @@
+/**
+ * Clay Reimann, 2015
+ * See LICENSE.txt for details
+ */
+'use strict';
+
+import gulp from 'gulp';
+import webpack from 'webpack-stream';
+
+import del from 'del';
+import sequence from 'run-sequence';
+
+const VENDOR_ASSETS = [
+ 'bower_components/bootstrap/dist/css/bootstrap.min.css',
+ 'bower_components/jquery/dist/jquery.min.js',
+ 'bower_components/bootstrap/dist/js/bootstrap.min.js'
+]
+
+const PLATFORMS = [
+ 'chrome'
+];
+
+gulp.task('build', PLATFORMS.map((p) => `build:${p}`));
+
+PLATFORMS.forEach((platform) => {
+ gulp.task(`build:${platform}`, function(done) {
+ sequence(
+ `${platform}:clean`,
+ `${platform}:move-assets`,
+ `${platform}:move-vendor`,
+ `${platform}:compile`,
+ `${platform}:zip-extension`,
+ done);
+ });
+});
+
+////////////////////////////
+// Build Chrome extension //
+////////////////////////////
+gulp.task('chrome:clean', function(done) {
+ del('build/chrome/**').then(() => {
+ done()
+ });
+});
+
+gulp.task('chrome:move-assets', function() {
+ return gulp.src([
+ 'platform/chrome/manifest.json',
+ 'platform/chrome/options/options.html',
+ 'assets/**'
+ ])
+ .pipe(gulp.dest('build/chrome'))
+ ;
+});
+
+gulp.task('chrome:move-vendor', function() {
+ return gulp.src(VENDOR_ASSETS)
+ .pipe(gulp.dest('build/chrome/vendor'))
+})
+
+var WEBPACK_CONFIG = {
+ entry: {
+ chrome: './platform/chrome/chrome.js',
+ options: './platform/chrome/options/options.js'
+ },
+ output: {
+ filename: '[name].min.js'
+ },
+ resolve: {
+ extensions: ['', '.js']
+ },
+ externals: {
+ chrome: 'chrome'
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.js$/,
+ exclude: /(node_modules|bower_components)/,
+ loader: 'babel',
+ query: {
+ presets: ['es2015']
+ }
+ }
+ ]
+ },
+ plugins: [
+ // new webpack.webpack.optimize.UglifyJsPlugin()
+ ],
+ devtool: 'source-map',
+ stats: {
+ colors: 'true'
+ }
+};
+gulp.task('chrome:compile', function(done) {
+ return gulp.src(['platform/chrome/chrome.js', 'platform/chrome/options/options.html'])
+ .pipe(webpack(WEBPACK_CONFIG))
+ .pipe(gulp.dest('build/chrome'))
+ ;
+});
+
+gulp.task('chrome:zip-extension', function(done) {
+ // do stuff
+ done();
+})
diff --git a/lib/search-service.js b/lib/search-service.js
new file mode 100644
index 0000000..5a7266b
--- /dev/null
+++ b/lib/search-service.js
@@ -0,0 +1,110 @@
+/**
+ * Clay Reimann, 2015
+ * See LICENSE.txt for details
+ */
+
+import Github from 'github-api';
+
+class SuggestionService {
+ /**
+ * Constructs a new SuggestionService
+ *
+ * @param {array} githubDescriptors - an array of objects that describe the github instances to be searched
+ * the objects should be of the format
+ * {
+ * url: the github url
+ * auth: {oauth, basic} to describe the service type
+ * token: required when auth == oauth
+ * username: required when auth == basic
+ * password: required when auth == basic
+ * }
+ */
+ constructor(githubDescriptors) {
+ this.__githubs = {};
+ githubDescriptors.forEach((descriptor) => {
+ let opts = {
+ apiUrl: descriptor.url,
+ auth: descriptor.auth
+ };
+ if (descriptor.auth === 'oauth') {
+ opts.token = descriptor.token;
+ } else {
+ opts.username = descriptor.username;
+ opts.password = descriptor.password;
+ }
+ this.__githubs[descriptor.url] = new Github(opts);
+ });
+
+ this.__repos = [];
+ }
+
+ isLoaded() {
+ return this.__repos.length > 0;
+ }
+
+ addRepository(githubUrl, repository) {
+ let isAdded = this.__repos.reduce((found, r) => {
+ return found || (r.githubUrl === githubUrl && r.name === repository.full_name);
+ }, false);
+
+ if (!isAdded) {
+ this.__repos.push({
+ githubUrl,
+ name: repository.full_name,
+ url: repository.html_url
+ });
+ }
+ }
+
+ addRepositories(githubUrl, list) {
+ if (!list) {
+ return;
+ }
+
+ list.forEach((repository) => {
+ this.addRepository(githubUrl, repository);
+ });
+ }
+
+ queryGithubsForRepositories() {
+ this.__repos = [];
+ Object.keys(this.__githubs).forEach((githubUrl) => {
+ let github = this.__githubs[githubUrl];
+ let user = github.getUser();
+
+ user.repos((err, repoList) => {
+ this.addRepositories(githubUrl, repoList);
+ })
+
+ user.orgs((err, orgs) => {
+ if (!orgs) {
+ return;
+ }
+
+ orgs.forEach((org) => {
+ user.orgRepos(org.login, (err, repoList) => {
+ console.log(`got repos for ${org.login}`);
+ this.addRepositories(githubUrl, repoList);
+ })
+ })
+ })
+ })
+ }
+
+ getRepositoriesMatching(text) {
+ return this.__repos.filter(function(repo) {
+ return repo.name.search(new RegExp(text, 'i')) !== -1;
+ }).sort(function(a, b) {
+ return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
+ });
+ }
+
+ getUrlForRepo(repoFullName) {
+ return this.__repos.reduce(function(found, repo) {
+ return found || (repo.name === repoFullName ? repo.url : found);
+ }, undefined);
+ }
+
+}
+
+module.exports = SuggestionService;
diff --git a/package.json b/package.json
index e863d20..f325bed 100644
--- a/package.json
+++ b/package.json
@@ -14,5 +14,18 @@
"productivity"
],
"author": "Clay Reimann",
- "license": "ISC"
+ "license": "ISC",
+ "devDependencies": {
+ "babel-core": "^6.3.15",
+ "babel-loader": "^6.2.0",
+ "babel-plugin-transform-strict-mode": "^6.3.13",
+ "babel-preset-es2015": "^6.3.13",
+ "gulp": "^3.9.0",
+ "run-sequence": "^1.1.5",
+ "webpack-stream": "^2.3.0"
+ },
+ "dependencies": {
+ "del": "^2.2.0",
+ "github-api": "github:clayreimann/github#add-webpack"
+ }
}
diff --git a/platform/chrome/chrome.js b/platform/chrome/chrome.js
new file mode 100644
index 0000000..0fee64a
--- /dev/null
+++ b/platform/chrome/chrome.js
@@ -0,0 +1,117 @@
+/**
+ * Clay Reimann, 2015
+ * See LICENSE.txt for details
+ */
+import SuggestionService from '../../lib/search-service';
+
+const MORE_RESULTS = ' Append ">" to see the next page.'
+let CACHE = {};
+
+function initalizeSuggestionService() {
+ chrome.storage.sync.get(['githubs'], bindCallbacks);
+}
+
+function parseOptions(text) {
+ let page = 0;
+ let isMeta = false;
+ let command = '';
+ let chars = text.split('');
+
+ while (chars[chars.length - 1] === '>') {
+ page++;
+ chars.pop()
+ }
+
+ text = chars.join('');
+
+
+ return {
+ isMeta,
+ page,
+ text: chars.join('')
+ }
+}
+
+function getSuggestFunction(suggester) {
+ return function(text, suggest) {
+ let page;
+ if (text.length < 1) { return; }
+ ({page, text} = parseOptions(text));
+
+ let suggestions;
+ if (CACHE.hasOwnProperty(text)) {
+ suggestions = CACHE[text]
+ } else {
+ suggestions = suggester.getRepositoriesMatching(text)
+ .map(function(repo) {
+ let 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) + ''
+ };
+ });
+ CACHE[text] = suggestions;
+ }
+ console.log('returning ' + suggestions.length + ' suggestions');
+ console.log(suggestions.map((s) => s.content.length));
+ console.log(suggestions.map((s) => s.content));
+
+
+ let description = `${suggestions.length} results for query %s.`
+ if (suggestions.length > 5) {
+ description += ` Page ${page + 1} of ${Math.ceil(suggestions.length / 5)}. ${MORE_RESULTS}`
+ }
+
+ for (let i = 0; i < page && suggestions.length > 5; i++) {
+ suggestions = suggestions.slice(5);
+ }
+ chrome.omnibox.setDefaultSuggestion({description});
+ suggest(suggestions);
+ };
+}
+
+function getCompletionFunction(suggester) {
+ return function(repoName, disposition) {
+ console.log(repoName + ' ' + disposition);
+ let 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});
+ }
+
+ }
+}
+
+function bindCallbacks(settings) {
+ let githubInstances = settings.githubs || [];
+ if (githubInstances.length < 1) {
+ return;
+ }
+ let suggester = new SuggestionService(githubInstances);
+ CACHE = {};
+ suggester.queryGithubsForRepositories();
+
+ chrome.omnibox.onInputChanged.addListener(getSuggestFunction(suggester));
+ chrome.omnibox.onInputEntered.addListener(getCompletionFunction(suggester));
+}
+
+initalizeSuggestionService();
+chrome.omnibox.onInputStarted.addListener(function() {
+ console.log('inputStarted');
+ chrome.omnibox.setDefaultSuggestion({
+ description: 'Type to search for a repository'
+ });
+});
+
+chrome.runtime.onMessage.addListener(function(message) {
+ if (message === 'settings-updated') {
+ initalizeSuggestionService();
+ }
+});
diff --git a/manifest.json b/platform/chrome/manifest.json
similarity index 76%
rename from manifest.json
rename to platform/chrome/manifest.json
index c81dbe5..af8f785 100644
--- a/manifest.json
+++ b/platform/chrome/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Github Repo Search",
- "version": "1.0.0",
+ "version": "0.9.0",
"omnibox": { "keyword" : "gh" },
"icons": {
"16": "assets/GitHub-Mark-32px.png",
@@ -11,7 +11,7 @@
},
"options_ui": {
- "page": "options/options.html",
+ "page": "options.html",
"chrome_style": true
},
"permissions": [
@@ -21,9 +21,7 @@
"background": {
"persistent": true,
"scripts": [
- "node_modules/github-api/github.js",
- "search.js",
- "chrome.js"
+ "chrome.min.js"
]
}
}
diff --git a/options/options.html b/platform/chrome/options/options.html
similarity index 51%
rename from options/options.html
rename to platform/chrome/options/options.html
index 8748ef3..3012b19 100644
--- a/options/options.html
+++ b/platform/chrome/options/options.html
@@ -8,7 +8,7 @@
Omnibox Github Search
-
+