diff --git a/package.json b/package.json index c4a289b..facbe67 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "csslint": "^0.10.0", "escodegen": "^1.4.1", "esprima": "^1.2.2", + "github-api": "^0.7.0", "lodash": "^2.4.1", "moment": "^2.8.3", "normalize.css": "^3.0.2", diff --git a/src/html/token.html b/src/html/token.html index 45a725a..9296b96 100644 --- a/src/html/token.html +++ b/src/html/token.html @@ -2,7 +2,7 @@ diff --git a/src/js/components/Editor.jsx b/src/js/components/Editor.jsx index 093f121..567307b 100644 --- a/src/js/components/Editor.jsx +++ b/src/js/components/Editor.jsx @@ -32,7 +32,7 @@ var Editor = React.createClass({ statics: { topics: function() { return { - ContentChange: 'ContentChange' + ContentChange: 'Editor_ContentChange' }; } }, diff --git a/src/js/components/Livecoding.jsx b/src/js/components/Livecoding.jsx index 0972f6c..c98e922 100644 --- a/src/js/components/Livecoding.jsx +++ b/src/js/components/Livecoding.jsx @@ -6,7 +6,10 @@ var React = require('react'); // Include libraries. -var PubSub = require('pubsub-js'); +var PubSub = require('pubsub-js'); +var Authentication = require('../util/Authentication.js'); +var _ = require('lodash'); +var util = require('../util/util.js'); // Include all top-level components. var MenuBar = require('./MenuBar.jsx'); @@ -18,6 +21,9 @@ var updateData = require('../../../.tmp/updates.json'); // Create the React component. var Livecoding = React.createClass({ + // TODO: get/set from local storage + _token: null, + // Set the initial state. As the application grows, so // will the number of state properties. getInitialState: function() { @@ -69,6 +75,7 @@ var Livecoding = React.createClass({ PubSub.subscribe(Editor.topics().ContentChange, self.handleContentChange); PubSub.subscribe(MenuBar.topics().ModeChange, self.handleModeChange); PubSub.subscribe(MenuBar.topics().ItemClick, self.handleMenuItemClick); + PubSub.subscribe(Authentication.topics().Token, self.handleAuthenticationToken); }, // Every time **Editor**'s content changes it hands **Livecoding** @@ -112,10 +119,70 @@ var Livecoding = React.createClass({ case 'file:save': - console.log('TODO'); + // Is user logged in? If so, save. + if (this._token) { + + // Create payload. + var data = _.pick(this.state, [ + 'html', + 'javascript', + 'css', + 'mode' + ]); + + // Save to gist. + Authentication.save(this._token, data) + .then(function(gist) { + + // Do nothing for now. + util.log(gist); + }); + } else { + + // There's no way to tell GitHub "after you give me a token, I want to save/delete/etc" + // So we'll store the desired function call in a stack, + this.afterAuthentication.push('save'); + // and then we'll make the login call. + Authentication.login(); + } + break; } - } + }, + + handleAuthenticationToken: function(topic, response) { + + // Save the token. + this._token = response.token; + + // Get next step. + var next = this.afterAuthentication.pop(); + + switch(next) { + case 'save': + + // Create payload. + var data = _.pick(this.state, [ + 'html', + 'javascript', + 'css', + 'mode' + ]); + + // Save to gist. + Authentication.save(this._token, data) + .then(function(gist) { + + // Do nothing for now. + util.log(gist); + }); + break; + } + + }, + + // Store the desired function call after authentication. + afterAuthentication: [] }); diff --git a/src/js/components/MenuBar.jsx b/src/js/components/MenuBar.jsx index a500680..405f708 100644 --- a/src/js/components/MenuBar.jsx +++ b/src/js/components/MenuBar.jsx @@ -13,8 +13,8 @@ var MenuBar = React.createClass({ statics: { topics: function() { return { - ItemClick: 'ItemClick', - ModeChange: 'ModeChange' + ItemClick: 'MenuBar_ItemClick', + ModeChange: 'MenuBar_ModeChange' }; } }, diff --git a/src/js/util/Authentication.js b/src/js/util/Authentication.js new file mode 100644 index 0000000..679653b --- /dev/null +++ b/src/js/util/Authentication.js @@ -0,0 +1,69 @@ +var util = require('./util.js'); +var PubSub = require('pubsub-js'); +var GitHub = require('github-api'); + +var Authentication = { + + topics: function() { + return { + Token: 'Authentication_Token' + }; + }, + + _CLIENT_ID: '7f06406d4740f8839007', + + login: function() { + open('https://github.com/login/oauth/authorize?client_id=' + this._CLIENT_ID + '&scope=gist', 'popup', 'width=1015,height=500'); + }, + + save: function(token, data) { + + return new Promise(function(resolve, reject) { + + var github = new GitHub({ + token: token, + auth: 'oauth' + }); + + var options = { + mode: data.mode + }; + + // Use `water` terminology to support old + // livecoding.io gists. + var files = { + files: { + 'water.html': { content: data.html }, + 'water.js': { content: data.javascript }, + 'water.css': { content: data.css }, + 'options.json': { + content: JSON.stringify(options, null, 4) + } + } + + }; + + github.getGist().create(files, function(error, gist) { + if (error) { + reject(error); + } else { + resolve(gist); + } + }); + + }); + } + +}; + +module.exports = Authentication; + +window.handleToken = function(code) { + + var url = 'http://powerful-chamber-3695.herokuapp.com/authenticate/' + code; + + util.getJSON(url) + .then(function(response) { + PubSub.publish(Authentication.topics().Token, response); + }); +}; \ No newline at end of file diff --git a/src/js/util/util.js b/src/js/util/util.js index 81078f3..fff9873 100644 --- a/src/js/util/util.js +++ b/src/js/util/util.js @@ -2,6 +2,24 @@ module.exports = { log: function(value) { console.log(JSON.stringify(value, null, 4)); + }, + + // from https://mathiasbynens.be/notes/xhr-responsetype-json + getJSON: function(url) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('get', url, true); + xhr.responseType = 'json'; + xhr.onload = function() { + var status = xhr.status; + if (status === 200) { + resolve(xhr.response); + } else { + reject(status); + } + }; + xhr.send(); + }); } }; \ No newline at end of file