├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Brocfile.js ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── adapters │ └── google-drive.js ├── components │ ├── document-creator.js │ └── share-modal.js ├── lib │ ├── auth.js │ ├── change-observer.js │ ├── config.js │ ├── document-source.js │ ├── document.js │ ├── loader.js │ ├── local-cache.js │ ├── login-hint.js │ ├── picker.js │ ├── reference │ │ ├── get.js │ │ ├── map-reference.js │ │ └── null-reference.js │ ├── serializer.js │ ├── state.js │ ├── uri.js │ ├── util.js │ └── uuid.js └── mixins │ ├── application-route-mixin.js │ ├── authenticated-route-mixin.js │ └── login-controller-mixin.js ├── app ├── .gitkeep ├── adapters │ ├── application.js │ └── google-drive-permission.js ├── authenticators │ └── gdrive.js ├── components │ ├── document-creator.js │ └── share-modal.js ├── initializers │ ├── google-drive-auth.js │ ├── google-drive.js │ ├── load-config.js │ ├── require-google-libraries.js │ ├── share-modal.js │ └── store-extensions.js ├── models │ └── google-drive-permission.js └── templates │ └── components │ ├── document-creator.hbs │ └── share-modal.hbs ├── blueprints ├── .jshintrc └── ember-gdrive │ ├── files │ └── app │ │ ├── controllers │ │ ├── document.js │ │ ├── error.js │ │ ├── items.js │ │ └── login.js │ │ ├── models │ │ └── item.js │ │ ├── router.js │ │ ├── routes │ │ ├── application.js │ │ ├── document.js │ │ ├── document │ │ │ └── index.js │ │ └── items.js │ │ └── templates │ │ ├── application.hbs │ │ ├── document.hbs │ │ ├── error.hbs │ │ ├── index.hbs │ │ ├── items.hbs │ │ └── login.hbs │ └── index.js ├── bower.json ├── config └── environment.js ├── design_notes.md ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── dummy │ ├── .jshintrc │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ ├── .gitkeep │ │ │ └── app.css │ │ ├── templates │ │ │ ├── .gitkeep │ │ │ ├── application.hbs │ │ │ └── components │ │ │ │ └── .gitkeep │ │ └── views │ │ │ └── .gitkeep │ ├── config │ │ └── environment.js │ └── public │ │ ├── .gitkeep │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html ├── test-helper.js └── unit │ └── .gitkeep └── vendor ├── .gitkeep └── share-modal.css /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": "vars" 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | 5 | .bowerrc 6 | .editorconfig 7 | .ember-cli 8 | .travis.yml 9 | .npmignore 10 | **/.gitkeep 11 | bower.json 12 | Brocfile.js 13 | testem.json 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - "npm config set spin false" 14 | - "npm install -g npm@^2" 15 | 16 | install: 17 | - npm install -g bower 18 | - npm install 19 | - bower install 20 | 21 | script: 22 | - npm test 23 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* global require, module */ 3 | 4 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 5 | 6 | var app = new EmberAddon(); 7 | 8 | // Use `app.import` to add additional libraries to the generated 9 | // output files. 10 | // 11 | // If you need to use different assets in different 12 | // environments, specify an object as the first parameter. That 13 | // object's keys should be the environment name and the values 14 | // should be the asset to use in that environment. 15 | // 16 | // If the library that you are including contains AMD or ES6 17 | // modules that you would like to import into your application 18 | // please specify an object with the list of modules as keys 19 | // along with the exports of each module as its value. 20 | 21 | module.exports = app.toTree(); 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember-gdrive 2 | 3 | This README outlines the details of collaborating on this Ember addon. 4 | 5 | ## Installation 6 | 7 | * `git clone` this repository 8 | * `npm install` 9 | * `bower install` 10 | 11 | ## Running 12 | 13 | * `ember server` 14 | * Visit your app at http://localhost:4200. 15 | 16 | ## Running Tests 17 | 18 | * `ember test` 19 | * `ember test --server` 20 | 21 | ## Building 22 | 23 | * `ember build` 24 | 25 | ## Development with symbolic links on Windows 26 | 27 | In order to make development easier with the addon, on windows (and probably other operating systems in a similar fashion), we can make use of symbolic links to quickly 'deploy' ember-cli addons into a project that uses them. 28 | 29 | 1. Navigate to addon root (in our case `ember-gdrive\\`). 30 | 2. `npm link` will add the folder into the global addon library under the folder name (`ember-gdrive'`). 31 | 3. Navigate to project folder (in our case `storypad\\`). 32 | 4. `npm link ember-gdrive`. This will create a symbolic link to the `ember-gdrive` folder inside the `node-modules` folder. Any change to ember-gdrive will automatically update here. 33 | 34 | ### Notes about this 35 | 36 | * Some sort of file watch might be necessary. It looks like changes I make do not really reflect until I stop the storypad application and then start it again via `ember-server`. 37 | * Since `ember-cli` addons do not compile or build in any way and all it takes is to copy the folder's contents, this works especially well in this case. An addon that requires building, for instance the old version of `ember-gdrive` would not be as straightforward to link. 38 | * For the `npm link` command to work, the console process needs to be run with administrative privileges. 39 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/addon/.gitkeep -------------------------------------------------------------------------------- /addon/adapters/google-drive.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import DS from 'ember-data'; 3 | import uuid from 'ember-gdrive/lib/uuid'; 4 | import ChangeObserver from 'ember-gdrive/lib/change-observer'; 5 | 6 | import { modelKey } from 'ember-gdrive/lib/util'; 7 | 8 | var Adapter = DS.Adapter.extend({ 9 | defaultSerializer: '-google-drive', 10 | 11 | documentSource: null, 12 | document: Ember.computed.alias('documentSource.document'), 13 | namespace: 'v1', 14 | 15 | 16 | ref: function() { 17 | var ref = this.get('document.ref'), 18 | namespace = this.get('namespace'); 19 | 20 | return ref.get(namespace).materialize(); 21 | }.property('document.ref', 'namespace'), 22 | 23 | recordCreatedRemotely: function(store, typeKey, data) { 24 | store.push(typeKey, data); 25 | }, 26 | recordUpdatedRemotely: function(store, typeKey, data) { 27 | store.push(typeKey, data); 28 | }, 29 | recordDeletedRemotely: function(store, typeKey, id) { 30 | var deletedRecord = store.getById(typeKey, id); 31 | store.unloadRecord(deletedRecord); 32 | }, 33 | 34 | recordCreatedLocally: function(store, typeKey, data, event) { 35 | store.push(typeKey, data); 36 | }, 37 | recordUpdatedLocally: function(store, typeKey, data) { 38 | store.push(typeKey, data); 39 | }, 40 | recordDeletedLocally: function(store, typeKey, id) { 41 | var deletedRecord = store.getById(typeKey, id); 42 | if (deletedRecord && !deletedRecord.get('isDeleted')) { 43 | deletedRecord.destroyRecord(); 44 | } 45 | }, 46 | 47 | changeObserver: function() { 48 | return ChangeObserver.create({ ref: this.get('ref'), target: this }); 49 | }.property(), 50 | 51 | observeRecordData: function(store, typeKey, id) { 52 | return this.get('changeObserver').observeRecordData(store, typeKey, id); 53 | }, 54 | 55 | observeIdentityMap: function(store, typeKey) { 56 | return this.get('changeObserver').observeIdentityMap(store, typeKey); 57 | }, 58 | 59 | generateIdForRecord: function(store, inputProperties) { 60 | return uuid(); 61 | }, 62 | 63 | find: function(store, type, id) { 64 | this.observeRecordData(store, type.typeKey, id); 65 | return Ember.RSVP.resolve(this.get('ref').get(modelKey(type), id).value()); 66 | }, 67 | 68 | createRecord: function(store, type, snapshot) { 69 | var record = snapshot.record, 70 | serializedRecord = record.serialize({includeId: true}), 71 | id = record.get('id'), 72 | ref = this.get('ref'); 73 | 74 | this.beginSave('createRecord'); 75 | ref.get(modelKey(type), id).set(serializedRecord); 76 | this.endSave('createRecord'); 77 | 78 | this.observeRecordData(store, type.typeKey, id); 79 | 80 | return Ember.RSVP.resolve(this.get('ref').get(modelKey(type), id).value()); 81 | }, 82 | 83 | updateRecord: function(store, type, snapshot) { 84 | var record = snapshot.record, 85 | serializedRecord = record.serialize({includeId: true}), 86 | id = record.get('id'), 87 | ref = this.get('ref'); 88 | 89 | this.beginSave('updateRecord'); 90 | ref.get(modelKey(type), id).set(serializedRecord); 91 | this.endSave('updateRecord'); 92 | 93 | this.observeRecordData(store, type.typeKey, id); 94 | 95 | return Ember.RSVP.resolve(ref.get(modelKey(type), id).value()); 96 | }, 97 | 98 | findAll: function(store, type) { 99 | var adapter = this, 100 | ref = this.get('ref'), 101 | identityMap = ref.get(modelKey(type)).value() || {}, 102 | keys = ref.get(modelKey(type)).keys(), 103 | serializedRecords = []; 104 | 105 | for (var i = 0; i < keys.length; i++) { 106 | serializedRecords.push( identityMap[ keys[i] ] ); 107 | } 108 | 109 | this.observeIdentityMap(store, type.typeKey); 110 | 111 | serializedRecords.forEach(function(data) { 112 | adapter.observeRecordData(store, type.typeKey, data.id); 113 | }); 114 | 115 | return Ember.RSVP.resolve( serializedRecords ); 116 | }, 117 | 118 | deleteRecord: function(store, type, snapshot) { 119 | var record = snapshot.record, 120 | ref = this.get('ref'), 121 | id = record.get('id'); 122 | ref.get(modelKey(type)).delete(id); 123 | return Ember.RSVP.resolve(); 124 | }, 125 | 126 | undo: function() { 127 | this.get('document').undo(); 128 | }, 129 | 130 | redo: function() { 131 | this.get('document').redo(); 132 | }, 133 | 134 | beginSave: function(name) { 135 | this.get('document').beginSave(name); 136 | }, 137 | 138 | endSave: function(name) { 139 | this.get('document').endSave(name); 140 | } 141 | }); 142 | 143 | export default Adapter; -------------------------------------------------------------------------------- /addon/components/document-creator.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | tagName: 'document-creator', 5 | classNames: 'document-creator', 6 | documentTitle: null, 7 | isCreating: false, 8 | 9 | actions: { 10 | createDocument: function () { 11 | this.createDocument(); 12 | }, 13 | }, 14 | 15 | createDocument: function () { 16 | var title = this.get('documentTitle').trim(), 17 | component = this; 18 | 19 | this.set('isCreating', true); 20 | 21 | return this.get('documentSource').createDocument(title).then(function (doc) { 22 | component.sendAction('documentCreated', doc); 23 | component.set('isCreating', false); 24 | }, function (error) { 25 | component.set('isCreating', false); 26 | }); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /addon/components/share-modal.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | tagName: 'share-modal', 5 | classNameBindings: ['isOpen:open'], 6 | 7 | shareAddress: null, 8 | permissions: [], 9 | isLoaded: false, 10 | isError: false, 11 | isOpen: false, 12 | 13 | showError: Ember.computed.and('isLoaded', 'isError'), 14 | showData: function () { 15 | return this.get('isLoaded') && !this.get('isError'); 16 | }.property('isLoaded', 'isError'), 17 | 18 | onOpening: function () { 19 | this.loadData(); 20 | }.on('willOpen'), 21 | 22 | onClosing: function () { 23 | this.set('shareAddress', null); 24 | }.on('willClose'), 25 | 26 | closeOnClick: function (event) { 27 | if (event.target === this.get('element')) { 28 | this.close(); 29 | } 30 | }.on('click'), 31 | 32 | open: function () { 33 | this.trigger('willOpen'); 34 | this.set('isOpen', true); 35 | }, 36 | 37 | close: function () { 38 | this.trigger('willClose'); 39 | this.set('isOpen', false); 40 | }, 41 | 42 | 'open-when': false, 43 | 44 | openWhen: function () { 45 | if (!this.get('open-when')) { 46 | return; 47 | } 48 | this.open(); 49 | this.set('open-when', false); 50 | }.observes('open-when'), 51 | 52 | actions: { 53 | 54 | share: function () { 55 | this.share(); 56 | }, 57 | 58 | removePermission: function (permission) { 59 | this.removePermission(permission); 60 | }, 61 | 62 | close: function () { 63 | this.close(); 64 | } 65 | }, 66 | 67 | loadData: function () { 68 | var component = this, 69 | store = component.get('store'); 70 | 71 | component.set('isLoaded', false); 72 | component.set('isError', false); 73 | 74 | store.unloadAll('google-drive-permission'); 75 | store.find('google-drive-permission').then(function (permissions) { 76 | component.set('permissions', permissions); 77 | component.set('isLoaded', true); 78 | }, function () { 79 | component.set('isLoaded', true); 80 | component.set('isError', true); 81 | }); 82 | }, 83 | 84 | share: function () { 85 | var component = this; 86 | var permission = component.get('store').createRecord('google-drive-permission', { 87 | emailAddress: this.get('shareAddress'), 88 | type: 'user', // user, group, domain, anyone 89 | role: 'writer' // owner, reader, writer 90 | }); 91 | permission.save().then(function () { 92 | component.set('shareAddress', null); 93 | component.loadData(); 94 | }, function (error) { 95 | permission.rollback(); 96 | alert(error.message); 97 | }); 98 | }, 99 | 100 | removePermission: function (permission) { 101 | var component = this; 102 | if (confirm('Are you sure you wish to revoke this user\'s access to the current document?')) { 103 | permission.deleteRecord(); 104 | permission.save().then(null, function (error) { 105 | permission.rollback(); 106 | Ember.run.once(component, component.loadData, null); 107 | alert(error.message); 108 | }); 109 | } 110 | }, 111 | }); 112 | -------------------------------------------------------------------------------- /addon/lib/auth.js: -------------------------------------------------------------------------------- 1 | /*global gapi */ 2 | var INSTALL_SCOPE = 'https://www.googleapis.com/auth/drive.install', 3 | FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file', 4 | OPENID_SCOPE = 'openid'; 5 | 6 | import Ember from 'ember'; 7 | import loader from 'ember-gdrive/lib/loader'; 8 | import { fetchLoginHint } from 'ember-gdrive/lib/login-hint'; 9 | import config from 'ember-gdrive/lib/config'; 10 | 11 | var merge = function(a, b) { 12 | return Ember.merge(a || {}, b || {}); 13 | }; 14 | 15 | var Auth = Ember.Object.extend({ 16 | isAuthenticated: false, 17 | isUnauthenticated: Ember.computed.not('isAuthenticated'), 18 | 19 | user: null, 20 | userID: Ember.computed.alias('user.id'), 21 | permissions: [INSTALL_SCOPE, FILE_SCOPE, OPENID_SCOPE], 22 | 23 | authorize: function(options) { 24 | var finalOptions = merge({ 25 | scope: this.permissions, 26 | authuser: -1, 27 | client_id: config.get('GOOGLE_CLIENT_ID'), 28 | immediate: false 29 | }, options || {}); 30 | 31 | Ember.assert('GOOGLE_CLIENT_ID was not set', finalOptions.client_id); 32 | 33 | return new Ember.RSVP.Promise(function(resolve, reject) { 34 | loader.load().then(function () { 35 | gapi.auth.authorize(finalOptions, function (result) { 36 | if (result && !result.error) { 37 | Ember.run(null, resolve, result); 38 | } else { 39 | Ember.run(null, reject, result && result.error ? result.error : 'unauthenticated'); 40 | } 41 | }); 42 | }); 43 | }, 'ember-gdrive: Auth#authorize'); 44 | }, 45 | 46 | authorizeImmediate: function(options) { 47 | return this.authorize(merge({ 48 | login_hint: fetchLoginHint(), 49 | immediate: true 50 | }, options || {})); 51 | }, 52 | 53 | fetchCurrentUser: function() { 54 | return new Ember.RSVP.Promise(function(resolve, reject) { 55 | gapi.client.oauth2.userinfo.get().execute(function(user) { 56 | if (user.id) { 57 | Ember.run(null, resolve, user); 58 | } 59 | else { 60 | Ember.run(null, reject); 61 | } 62 | }); 63 | }, 'GoogleDriveAuth _fetchUserObject'); 64 | }, 65 | 66 | close: function(){ 67 | return new Ember.RSVP.Promise(function(resolve){ 68 | gapi.auth.signOut(); 69 | resolve(); 70 | }); 71 | } 72 | 73 | }); 74 | 75 | export default Auth; 76 | -------------------------------------------------------------------------------- /addon/lib/change-observer.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { normalizeTypeKey } from 'ember-gdrive/lib/util'; 3 | import { pluck } from 'ember-gdrive/lib/util'; 4 | 5 | function logEventToConsole(e) { 6 | console.log({ 7 | type: e.type, 8 | property: e.property, 9 | oldValue: e.oldValue, 10 | newValue: e.newValue, 11 | isLocal: e.isLocal, 12 | bubbles: e.bubbles, 13 | sessionId: e.sessionId, 14 | userId: e.userId 15 | }); 16 | } 17 | 18 | function logEventToBlackHole(e) {} 19 | 20 | var logEvent = logEventToBlackHole; 21 | 22 | export default Ember.Object.extend(Ember.ActionHandler, { 23 | ref: null, 24 | target: null, 25 | observedMap: function() { return {}; }.property(), 26 | 27 | observeRecordData: function(store, typeKey, id) { 28 | var observer = this, 29 | observedMap = this.get('observedMap'), 30 | key = [normalizeTypeKey(typeKey), id].join('/'), 31 | ref = this.get('ref'); 32 | 33 | if (observedMap[key]) { 34 | return Ember.RSVP.Promise.resolve(); 35 | } 36 | else { 37 | observedMap[key] = true; // can set this to the promise and return that every time 38 | } 39 | 40 | ref.get(normalizeTypeKey(typeKey), id).changed(function(e) { 41 | if (e.type === 'value_changed') { 42 | Ember.run(function(){ 43 | observer.recordDataChanged(store, typeKey, id, e); 44 | }); 45 | } 46 | }); 47 | }, 48 | 49 | observeIdentityMap: function(store, typeKey) { 50 | var observer = this, 51 | observedMap = this.get('observedMap'), 52 | key = [normalizeTypeKey(typeKey)].join('/'), 53 | ref = this.get('ref'); 54 | 55 | if (observedMap[key]) { 56 | return Ember.RSVP.Promise.resolve(); 57 | } 58 | else { 59 | observedMap[key] = true; 60 | } 61 | 62 | ref.get(normalizeTypeKey(typeKey)).materialize().changed(function(e) { 63 | if (e.type === 'value_changed') { 64 | Ember.run.once(observer, 'identityMapChanged', store, typeKey, e); 65 | } 66 | }); 67 | }, 68 | 69 | recordDataChanged: function(store, typeKey, id, e) { 70 | logEvent(e); 71 | 72 | var ref = this.get('ref'); 73 | var data = ref.get(normalizeTypeKey(typeKey), id).value(); 74 | 75 | // if a record is getting deleted its attributes will all get set to null 76 | // shouldn't be raising update events after a record gets deleted 77 | if (!data) { 78 | return; 79 | } 80 | 81 | if (e.isLocal) { 82 | this.get('target').recordUpdatedLocally(store, typeKey, data); 83 | } 84 | else { 85 | this.get('target').recordUpdatedRemotely(store, typeKey, data); 86 | } 87 | }, 88 | 89 | identityMapChanged: function(store, typeKey, e) { 90 | logEvent(e); 91 | 92 | var ref = this.get('ref'); 93 | var data, newRecordId; 94 | 95 | 96 | if (e.isLocal && e.oldValue === null && e.newValue) { 97 | newRecordId = e.newValue.get('id'); 98 | data = ref.get(normalizeTypeKey(typeKey), newRecordId).value(); 99 | this.get('target').recordCreatedLocally(store, typeKey, data); 100 | } 101 | 102 | else if (e.isLocal && e.oldValue && e.newValue === null) { 103 | this.get('target').recordDeletedLocally(store, typeKey, e.oldValue.get('id')); 104 | } 105 | 106 | else if (!e.isLocal && e.oldValue === null && e.newValue) { 107 | newRecordId = e.newValue.get('id'); 108 | data = ref.get(normalizeTypeKey(typeKey), newRecordId).value(); 109 | 110 | this.get('target').recordCreatedRemotely(store, typeKey, data); 111 | } 112 | 113 | else if (!e.isLocal && e.oldValue && e.newValue === null) { 114 | var deletedRecordId = e.oldValue.get('id'); 115 | this.get('target').recordDeletedRemotely(store, typeKey, deletedRecordId); 116 | } 117 | } 118 | 119 | }); 120 | -------------------------------------------------------------------------------- /addon/lib/config.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var Config = Ember.Object.extend({ 4 | GOOGLE_API_KEY: null, 5 | GOOGLE_MIME_TYPE: null, 6 | GOOGLE_CLIENT_ID: null, 7 | 8 | load: function (configuration) { 9 | if (configuration) { 10 | if (configuration.GOOGLE_API_KEY && configuration.GOOGLE_API_KEY !== '') { 11 | this.set('GOOGLE_API_KEY', configuration.GOOGLE_API_KEY); 12 | } else { 13 | throw new Error('The GOOGLE_API_KEY configuration property has not been set.'); 14 | } 15 | 16 | if (configuration.GOOGLE_MIME_TYPE && configuration.GOOGLE_MIME_TYPE !== 'application/') { 17 | this.set('GOOGLE_MIME_TYPE', configuration.GOOGLE_MIME_TYPE); 18 | } else { 19 | throw new Error('The GOOGLE_MIME_TYPE configuration property has not been set.'); 20 | } 21 | 22 | if (configuration.GOOGLE_CLIENT_ID && configuration.GOOGLE_CLIENT_ID !== '') { 23 | this.set('GOOGLE_CLIENT_ID', configuration.GOOGLE_CLIENT_ID); 24 | } else { 25 | throw new Error('The GOOGLE_CLIENT_ID configuration property has not been set.'); 26 | } 27 | } else { 28 | throw new Error('The \'ember-gdrive\' configuration group is not present. Please add a configuration group containing the Google API information to your application configuration\'s \'ENV.APP\' object.'); 29 | } 30 | } 31 | }); 32 | 33 | export default Config.create(); -------------------------------------------------------------------------------- /addon/lib/document-source.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Document from 'ember-gdrive/lib/document'; 3 | import State from 'ember-gdrive/lib/state'; 4 | 5 | var DocumentSource = Ember.Object.extend({ 6 | document: null, 7 | id: Ember.computed.alias('document.id'), 8 | isLoaded: Ember.computed.bool('id'), 9 | 10 | state: function() { 11 | return State.create(); 12 | }.property(), 13 | 14 | openFromState: function() { 15 | var state = this.get('state'); 16 | if (state.get('isOpen')) { 17 | return this.load(state.get('fileID')); 18 | } 19 | else { 20 | return Ember.RSVP.Promise.reject('failed to open'); 21 | } 22 | }, 23 | 24 | createFromState: function() { 25 | var state = this.get('state'); 26 | var documentSource = this; 27 | 28 | if (state.get('isCreate')) { 29 | var title = window.prompt('Enter a document name') || 'Untitled document'; 30 | 31 | return Document.create({title: title}).then(function(doc) { 32 | documentSource.set('document', doc); 33 | return doc; 34 | }); 35 | } else { 36 | return Ember.RSVP.Promise.reject('failed to create'); 37 | } 38 | }, 39 | 40 | createDocument: function (title) { 41 | var documentSource = this; 42 | if (!Ember.isEmpty(title)) { 43 | return Document.create({title: title}).then(function(doc) { 44 | documentSource.set('document', doc); 45 | return doc; 46 | }); 47 | } 48 | }, 49 | 50 | load: function(documentId) { 51 | Ember.assert('Document with id ' + this.get('id') + ' was already loaded', !this.get('isLoaded')); 52 | 53 | var documentSource = this; 54 | return Document.find( documentId ).then(function(doc) { 55 | documentSource.set('document', doc); 56 | return doc; 57 | }); 58 | } 59 | 60 | }); 61 | 62 | export default DocumentSource; -------------------------------------------------------------------------------- /addon/lib/document.js: -------------------------------------------------------------------------------- 1 | /*global gapi*/ 2 | import Ember from 'ember'; 3 | import MapReference from 'ember-gdrive/lib/reference/map-reference'; 4 | import config from 'ember-gdrive/lib/config'; 5 | 6 | var Document = Ember.Object.extend(Ember.Evented, { 7 | id: null, 8 | content: null, 9 | title: Ember.computed.alias('meta.title'), 10 | 11 | hasUnsavedChanges: false, 12 | isSaving: false, 13 | 14 | canUndo: false, 15 | canRedo: false, 16 | 17 | collaborators: function() { return []; }.property(), 18 | 19 | isSaved: function() { 20 | return !this.get('hasUnsavedChanges') && !this.get('isSaving'); 21 | }.property('hasUnsavedChanges', 'isSaving'), 22 | 23 | init: function(googleDocument, documentId) { 24 | Ember.assert('You must pass in a valid google document.', !!googleDocument); 25 | 26 | this.set('content', googleDocument); 27 | this.set('id', documentId); 28 | 29 | this._observeSaveState(); 30 | this._observeUndoRedoState(); 31 | this._observeCollaborators(); 32 | 33 | this._refreshCollaborators(); 34 | 35 | this._loadMeta(); 36 | }, 37 | 38 | ref: function() { 39 | var googleDocument = this.get('content'); 40 | var model = googleDocument.getModel(); 41 | var root = model.getRoot(); 42 | 43 | return new MapReference(model, null, null, root); 44 | }.property('content').readOnly(), 45 | 46 | meta: {}, 47 | 48 | /* undo/redo */ 49 | 50 | beginSave: function(name) { 51 | this.get('content').getModel().beginCompoundOperation(); 52 | }, 53 | 54 | endSave: function(name) { 55 | this.get('content').getModel().endCompoundOperation(); 56 | }, 57 | 58 | undo: function() { 59 | if (this.get('canUndo')) { 60 | this.get('content').getModel().undo(); 61 | } 62 | }, 63 | 64 | redo: function() { 65 | if (this.get('canRedo')) { 66 | this.get('content').getModel().redo(); 67 | } 68 | }, 69 | 70 | _observeSaveState: function() { 71 | var document = this; 72 | var googleDocument = this.get('content'); 73 | googleDocument.addEventListener(gapi.drive.realtime.EventType.DOCUMENT_SAVE_STATE_CHANGED, function(e) { 74 | document.set('hasUnsavedChanges', e.isPending); 75 | document.set('isSaving', e.isSaving); 76 | if (document.get('isSaved')) { 77 | document.trigger('saved'); 78 | } 79 | }); 80 | }, 81 | 82 | _observeUndoRedoState: function() { 83 | var document = this; 84 | var googleDocument = this.get('content'); 85 | googleDocument.getModel().addEventListener(gapi.drive.realtime.EventType.UNDO_REDO_STATE_CHANGED, function(e) { 86 | document.set('canUndo', e.canUndo); 87 | document.set('canRedo', e.canRedo); 88 | }); 89 | }, 90 | 91 | _observeCollaborators: function() { 92 | var document = this; 93 | var googleDocument = this.get('content'); 94 | 95 | googleDocument.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_JOINED, function(e) { 96 | document._refreshCollaborators(); 97 | }); 98 | 99 | googleDocument.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_LEFT, function(e) { 100 | document._refreshCollaborators(); 101 | }); 102 | }, 103 | 104 | _refreshCollaborators: function() { 105 | var collaborators = this.get('collaborators'); 106 | var googleDocument = this.get('content'); 107 | 108 | collaborators.clear(); 109 | collaborators.pushObjects( googleDocument.getCollaborators() ); 110 | }, 111 | 112 | _loadMeta: function() { 113 | var document = this; 114 | this._fetchMeta(this.get('id')).then(function(meta) { 115 | document.set('meta', meta); 116 | }); 117 | }, 118 | 119 | _fetchMeta: function(documentId) { 120 | return new Ember.RSVP.Promise(function(resolve, reject) { 121 | gapi.client.drive.files.get({fileId: documentId}).execute(function(googleFileMeta) { 122 | if (googleFileMeta.error) { 123 | reject(googleFileMeta); 124 | } 125 | else { 126 | resolve(googleFileMeta); 127 | } 128 | }); 129 | }); 130 | } 131 | 132 | }); 133 | 134 | var loadPromises = {}; 135 | 136 | Document.reopenClass({ 137 | find: function(documentId) { 138 | if (loadPromises[documentId]) { 139 | return loadPromises[documentId]; 140 | } 141 | 142 | loadPromises[documentId] = new Ember.RSVP.Promise(function(resolve, reject){ 143 | gapi.drive.realtime.load(documentId, 144 | function(d) { Ember.run(null, resolve, d); }, 145 | Ember.K, 146 | function(e) { Ember.run(null, reject, e); } 147 | ); 148 | }).then(function(googleDocument) { 149 | return new Document(googleDocument, documentId); 150 | }, function(e) { 151 | delete loadPromises[documentId]; // don't store error promises so they can be retried 152 | console.log('oh my, gonna make an error'); 153 | 154 | if(e.type === gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED) { 155 | throw new Error('Token refresh required'); 156 | } else if(e.type === gapi.drive.realtime.ErrorType.CLIENT_ERROR) { 157 | throw new Error('An Error happened: ' + e.message); 158 | } else if(e.type === gapi.drive.realtime.ErrorType.NOT_FOUND || e.type === gapi.drive.realtime.ErrorType.FORBIDDEN) { 159 | throw new Error('The file was not found. It does not exist or you do not have read access to the file.'); 160 | } else { 161 | throw new Error('Unknown error occured.'); 162 | } 163 | }); 164 | 165 | return loadPromises[documentId]; 166 | }, 167 | create: function(params) { 168 | return this._sendCreateRequest(params).then(function(googleFile) { 169 | if (googleFile.error) { 170 | return Ember.RSVP.reject(new Error(googleFile.error.message)); 171 | } 172 | else { 173 | return Document.find(googleFile.id); 174 | } 175 | }); 176 | }, 177 | _sendCreateRequest: function(params) { 178 | return new Ember.RSVP.Promise(function(resolve, reject) { 179 | gapi.client.drive.files.insert({ 180 | 'resource': { 181 | mimeType: config.get('GOOGLE_MIME_TYPE'), 182 | title: Ember.get(params, 'title') 183 | } 184 | }).execute(function(d){ Ember.run(null, resolve, d); }); 185 | }); 186 | } 187 | }); 188 | 189 | export default Document; -------------------------------------------------------------------------------- /addon/lib/loader.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var loader = {}; 4 | 5 | loader.promise = null; 6 | loader.loaded = 0; 7 | loader.libraryCount = 4; 8 | 9 | loader.load = function () { 10 | if (loader.promise) { 11 | return loader.promise; 12 | } 13 | 14 | loader.promise = new Ember.RSVP.Promise(function (resolve, reject) { 15 | gapi.load('auth:client,drive-realtime,drive-share', function () { 16 | 17 | gapi.client.load('oauth2', 'v2', function () { 18 | loader.loaded++; 19 | if (loader.loaded >= loader.libraryCount) { 20 | resolve(); 21 | } 22 | }); 23 | 24 | gapi.client.load('drive', 'v2', function () { 25 | loader.loaded++; 26 | if (loader.loaded >= loader.libraryCount) { 27 | resolve(); 28 | } 29 | }); 30 | 31 | gapi.load('drive-share', function () { 32 | loader.loaded++; 33 | if (loader.loaded >= loader.libraryCount) { 34 | resolve(); 35 | } 36 | }); 37 | 38 | gapi.load('picker', function () { 39 | loader.loaded++; 40 | if (loader.loaded >= loader.libraryCount) { 41 | resolve(); 42 | } 43 | }); 44 | 45 | }); 46 | }); 47 | 48 | return loader.promise; 49 | }; 50 | 51 | loader.load(); 52 | 53 | export default loader; -------------------------------------------------------------------------------- /addon/lib/local-cache.js: -------------------------------------------------------------------------------- 1 | var LocalCache = function(namespace) { 2 | this.namespace = namespace; 3 | }; 4 | LocalCache.prototype.get = function(id) { 5 | return localStorage.getItem(this.namespace + ':' + id); 6 | }; 7 | LocalCache.prototype.set = function(id, value) { 8 | localStorage.setItem(this.namespace + ':' + id, value); 9 | }; 10 | 11 | export default LocalCache; -------------------------------------------------------------------------------- /addon/lib/login-hint.js: -------------------------------------------------------------------------------- 1 | import Cache from 'ember-gdrive/lib/local-cache'; 2 | import { extractQueryParams, getDocumentIdFromLocation } from 'ember-gdrive/lib/uri'; 3 | 4 | function cacheLoginHint(documentId, userId) { 5 | var cache = new Cache('document_login_hint'); 6 | cache.set(documentId, userId); 7 | } 8 | 9 | function fetchLoginHint() { 10 | var userId = extractQueryParams().userId; 11 | if (!userId) { 12 | var cache = new Cache('document_login_hint'); 13 | userId = cache.get(getDocumentIdFromLocation()); 14 | } 15 | return userId; 16 | } 17 | 18 | 19 | export { cacheLoginHint, fetchLoginHint }; 20 | -------------------------------------------------------------------------------- /addon/lib/picker.js: -------------------------------------------------------------------------------- 1 | /*global google*/ 2 | import Ember from 'ember'; 3 | import config from 'ember-gdrive/lib/config'; 4 | 5 | export default Ember.Object.extend(Ember.Evented, { 6 | token: null, 7 | apiKey: config.get('GOOGLE_API_KEY'), 8 | mimeTypes: config.get('GOOGLE_MIME_TYPE'), 9 | 10 | show: function() { 11 | this.get('googlePicker').setVisible(true); 12 | }, 13 | 14 | googlePicker: function() { 15 | var callback = this.googlePickerCallback.bind(this), 16 | token = this.get('token'), 17 | apiKey = this.get('apiKey'), 18 | mimeTypes = this.get('mimeTypes'); 19 | 20 | var view = new google.picker.View(google.picker.ViewId.DOCS); 21 | view.setMimeTypes(mimeTypes); 22 | 23 | return new google.picker.PickerBuilder() 24 | .addView(view) 25 | .enableFeature(google.picker.Feature.NAV_HIDDEN) 26 | .setDeveloperKey(apiKey) 27 | .setSelectableMimeTypes(mimeTypes) 28 | .setOAuthToken(token) 29 | .setCallback(callback) 30 | .build(); 31 | }.property('token'), 32 | 33 | googlePickerCallback: function(result) { 34 | if (result.action === google.picker.Action.PICKED) { 35 | this.trigger('selected', result.docs[0]); 36 | } 37 | } 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /addon/lib/reference/get.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function () { 4 | if (Ember.isArray(arguments[0])) { 5 | return this.get.apply(this, arguments[0]); 6 | } 7 | 8 | var components = arguments; 9 | var cur = this; 10 | for (var i = 0; i < components.length; i++) { 11 | cur = cur._get(components[i]); 12 | } 13 | return cur; 14 | } 15 | -------------------------------------------------------------------------------- /addon/lib/reference/map-reference.js: -------------------------------------------------------------------------------- 1 | /*global gapi*/ 2 | import NullReference from 'ember-gdrive/lib/reference/null-reference'; 3 | import get from 'ember-gdrive/lib/reference/get'; 4 | 5 | var MapReference = function (model, parent, key, data) { 6 | this.model = model; 7 | this.parent = parent; 8 | this.key = key; 9 | this.data = data; 10 | }; 11 | 12 | var _isPlainObject = function (o) { 13 | // This doesn't work for basic objects such as Object.create(null) 14 | return Object(o) === o && Object.getPrototypeOf(o) === Object.prototype; 15 | }; 16 | 17 | var _serialize = function (object) { 18 | if (MapReference.isFor(object)) { 19 | return MapReference.serialize(object); 20 | } else { 21 | return object; 22 | } 23 | }; 24 | 25 | MapReference.isFor = function (data) { 26 | //return data instanceof gapi.drive.realtime.CollaborativeMap; 27 | return data && data.type === 'Map'; 28 | }; 29 | 30 | // this is used for debugging purposes to get a snapshot of the Google Drive data structure 31 | MapReference.serialize = function (object) { 32 | var serialized = {}; 33 | object.items().forEach(function (pair) { 34 | if (pair[1] instanceof Array) { 35 | // gapi returns array properties with non-writable elements, so we need to remap them as a temporary fix 36 | serialized[pair[0]] = _serialize(pair[1]).map(function (item) { 37 | return item; 38 | }); 39 | } else { 40 | serialized[pair[0]] = _serialize(pair[1]); 41 | } 42 | }); 43 | return serialized; 44 | }; 45 | 46 | MapReference.prototype.path = function (key) { 47 | if (this.parent) { 48 | return this.parent.path(this.key) + (key ? '/' + key : ''); 49 | } else { 50 | return key || ''; 51 | } 52 | }; 53 | 54 | MapReference.prototype.keys = function () { 55 | return this.data.keys(); 56 | }; 57 | 58 | MapReference.prototype.get = function (key, __components) { 59 | return get.apply(this, arguments); 60 | }; 61 | 62 | MapReference.prototype._get = function (key) { 63 | var childData = this.data.get(key); 64 | 65 | if (NullReference.isFor(childData)) { 66 | return new NullReference(this.model, this, key); 67 | } else if (MapReference.isFor(childData)) { 68 | return new MapReference(this.model, this, key, childData); 69 | } else { 70 | return childData; 71 | } 72 | }; 73 | 74 | MapReference.prototype.set = function (value) { 75 | if (arguments.length > 1) { 76 | if (arguments[1] !== undefined) { 77 | this.data.set(arguments[0], this._coerce(arguments[1])); 78 | } 79 | } else if (_isPlainObject(value)) { 80 | 81 | var keys = Object.keys(value); 82 | for (var i = 0; i < keys.length; i++) { 83 | if (value[keys[i]] !== undefined) { 84 | this.data.set(keys[i], value[keys[i]]); 85 | } 86 | } 87 | } else { 88 | this.parent.set(this.key, value); 89 | } 90 | 91 | return this.parent ? this.parent.get(this.key) : this; 92 | }; 93 | 94 | MapReference.prototype.value = function () { 95 | return _serialize(this.data); 96 | }; 97 | 98 | MapReference.prototype.delete = function (key) { 99 | this.data.delete(key); 100 | return this; 101 | }; 102 | 103 | MapReference.prototype.clear = function () { 104 | this.data.clear(); 105 | return this; 106 | }; 107 | 108 | MapReference.prototype.materialize = function () { 109 | return this; 110 | }; 111 | 112 | MapReference.prototype.changed = function (handler) { 113 | this.data.addEventListener(gapi.drive.realtime.EventType.OBJECT_CHANGED, handler); 114 | this.data.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, handler); 115 | return this; 116 | }; 117 | 118 | MapReference.prototype._coerce = function (value) { 119 | if (_isPlainObject(value)) { 120 | return this.model.createMap(value); 121 | } else { 122 | return value; 123 | } 124 | }; 125 | 126 | export default MapReference; 127 | -------------------------------------------------------------------------------- /addon/lib/reference/null-reference.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import get from 'ember-gdrive/lib/reference/get'; 3 | 4 | var NullReference = function (model, parent, key) { 5 | this.model = model; 6 | this.parent = parent; 7 | this.key = key; 8 | }; 9 | 10 | NullReference.isFor = function (data) { 11 | return data === null; 12 | }; 13 | 14 | NullReference.prototype.path = function (key) { 15 | return this.parent.path(this.key) + (key ? '/' + key : ''); 16 | }; 17 | 18 | NullReference.prototype.keys = function () { 19 | return []; 20 | }; 21 | 22 | NullReference.prototype.get = function (key, __components) { 23 | return get.apply(this, arguments); 24 | }; 25 | 26 | NullReference.prototype._get = function (key) { 27 | return new NullReference(this.model, this, key); 28 | }; 29 | 30 | NullReference.prototype.set = function (value) { 31 | var map = this.materialize(); 32 | map.set(value); 33 | return map; 34 | }; 35 | 36 | NullReference.prototype.delete = function (key) { 37 | return this; 38 | }; 39 | 40 | NullReference.prototype.value = function () { 41 | return null; 42 | }; 43 | 44 | NullReference.prototype.materialize = function () { 45 | var parent = this.parent.materialize(); 46 | parent.set( 47 | this.key, 48 | this.model.createMap() 49 | ); 50 | 51 | return parent.get(this.key); 52 | }; 53 | 54 | NullReference.prototype.changed = function () { 55 | Ember.assert('You must materialize a NullReference before adding a listener to ' + this.path(), false); 56 | }; 57 | 58 | export default NullReference; -------------------------------------------------------------------------------- /addon/lib/serializer.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import { recordKey } from 'ember-gdrive/lib/util'; 3 | 4 | function serializeId(snapshot, relationship) { 5 | if (!snapshot) { 6 | return null; 7 | } 8 | if (relationship.options.polymorphic) { 9 | return { 10 | id: snapshot.id, 11 | type: recordKey(snapshot) 12 | }; 13 | } 14 | else { 15 | return snapshot.id; 16 | } 17 | } 18 | 19 | var Serializer = DS.JSONSerializer.extend({ 20 | 21 | serializeHasMany: function(snapshot, json, relationship) { 22 | if(relationship.options.serialize === false) { 23 | return; 24 | } 25 | var key = relationship.key; 26 | var rel = snapshot.hasMany(key); 27 | 28 | if (rel){ 29 | json[key] = rel.map(function(relatedSnapshot) { 30 | return serializeId(relatedSnapshot, relationship); 31 | }); 32 | } 33 | }, 34 | 35 | serializeBelongsTo: function(snapshot, json, relationship) { 36 | if(relationship.options.serialize === false) { 37 | return; 38 | } 39 | if (relationship.options && relationship.options.async){ 40 | var key = relationship.key; 41 | var belongsTo = snapshot.belongsTo(key); 42 | json[key] = serializeId(belongsTo, relationship); 43 | } else { 44 | this._super(snapshot, json, relationship); 45 | } 46 | } 47 | }); 48 | 49 | export default Serializer; -------------------------------------------------------------------------------- /addon/lib/state.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { extractQueryParams, clearQueryString } from './uri'; 3 | 4 | export default Ember.Object.extend({ 5 | 6 | init: function() { 7 | this.set('queryParams', extractQueryParams()); 8 | clearQueryString(); 9 | }, 10 | 11 | fileID: function() { 12 | return this.get('fileIDs').objectAt(0); 13 | }.property('fileIDs'), 14 | 15 | isOpen: function() { 16 | return this.get('action') === 'open'; 17 | }.property('action'), 18 | 19 | isCreate: function() { 20 | return this.get('action') === 'create'; 21 | }.property('action'), 22 | 23 | state: function() { 24 | try { 25 | return JSON.parse(this.get('queryParams.state')); 26 | } catch (e) { 27 | return null; 28 | } 29 | }.property('queryParams'), 30 | 31 | action: Ember.computed.alias('state.action'), 32 | userID: Ember.computed.alias('state.userId'), 33 | fileIDs: Ember.computed.alias('state.ids'), 34 | folderID: Ember.computed.alias('state.folderId'), 35 | 36 | queryParams: {} 37 | 38 | }); -------------------------------------------------------------------------------- /addon/lib/uri.js: -------------------------------------------------------------------------------- 1 | function extractQueryParams() { 2 | var params = {}; 3 | location.search.substr(1).split('&').forEach(function(item) { 4 | params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]); 5 | }); 6 | return params; 7 | }; 8 | 9 | function clearQueryString() { 10 | var uri = window.location.toString(); 11 | if (uri.indexOf('?') > 0) { 12 | var cleanUri = uri.substring(0, uri.indexOf('?')); 13 | window.history.replaceState({}, document.title, cleanUri); 14 | } 15 | }; 16 | 17 | function getDocumentIdFromLocation() { 18 | return location.href.split('/d/')[1].split('/')[0]; 19 | } 20 | 21 | export { extractQueryParams, clearQueryString, getDocumentIdFromLocation }; -------------------------------------------------------------------------------- /addon/lib/util.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var normalizeTypeKey = function(typeKey) { 4 | return Ember.String.dasherize(typeKey); 5 | }; 6 | 7 | var modelKey = function(model) { 8 | return normalizeTypeKey(model.typeKey); 9 | }; 10 | 11 | var recordKey = function(snapshot) { 12 | return modelKey(snapshot.type); 13 | }; 14 | 15 | var pluck = function(values, property) { 16 | return values.map(function(o) { 17 | return o[property]; 18 | }); 19 | }; 20 | 21 | export { normalizeTypeKey, modelKey, recordKey, pluck }; 22 | -------------------------------------------------------------------------------- /addon/lib/uuid.js: -------------------------------------------------------------------------------- 1 | export default function uuid() { 2 | var n = 10; // max n is 16 3 | return new Array(n+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, n); 4 | } -------------------------------------------------------------------------------- /addon/mixins/application-route-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin'; 3 | 4 | export default Ember.Mixin.create(ApplicationRouteMixin, { 5 | beforeModel: function (transition) { 6 | 7 | this._super(transition); 8 | 9 | var route = this; 10 | var documentSource = route.get('documentSource'); 11 | var state = documentSource.get('state'); // Forces creation of the state, which clears the query params 12 | var isStatePresent = state.get('isOpen') || state.get('isCreate'); 13 | 14 | if (isStatePresent) { 15 | this.get('session').authenticate('authenticator:gdrive', { 'login_hint': state.get('userID') }).then(function () { 16 | if (state.get('isOpen')) { 17 | return documentSource.openFromState(); 18 | } else { 19 | return documentSource.createFromState(); 20 | } 21 | }).then(function (doc) { 22 | route.transitionToDocument(doc); 23 | }); 24 | } 25 | }, 26 | 27 | actions: { 28 | login: function () { 29 | this.session.authenticate('authenticator:gdrive'); 30 | }, 31 | logout: function () { 32 | var route = this, 33 | session = this.get('session'); 34 | 35 | session.invalidate().then(function () { 36 | route.transitionTo('login'); 37 | }); 38 | }, 39 | documentCreated: function (doc) { 40 | this.transitionToDocument(doc); 41 | } 42 | }, 43 | 44 | transitionToDocument: function (doc) { 45 | this.transitionTo('document', doc); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /addon/mixins/authenticated-route-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { cacheLoginHint } from 'ember-gdrive/lib/login-hint'; 3 | import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin'; 4 | 5 | export default Ember.Mixin.create(AuthenticatedRouteMixin, { 6 | model: function (params) { 7 | return this.get('documentSource').load(params.document_id); 8 | }, 9 | 10 | afterModel: function (document, transition) { 11 | var userId = this.get('session.secure.id'); 12 | var documentId = this.get('documentSource.id'); 13 | cacheLoginHint(documentId, userId); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /addon/mixins/login-controller-mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin'; 3 | 4 | export default Ember.Mixin.create(LoginControllerMixin, { 5 | authenticator: 'authenticator:gdrive' 6 | }); 7 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/app/.gitkeep -------------------------------------------------------------------------------- /app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import GoogleDriveAdapter from 'ember-gdrive/adapters/google-drive'; 2 | 3 | export default GoogleDriveAdapter; 4 | -------------------------------------------------------------------------------- /app/adapters/google-drive-permission.js: -------------------------------------------------------------------------------- 1 | /*global gapi*/ 2 | import Ember from 'ember'; 3 | import DS from 'ember-data'; 4 | 5 | var Adapter = DS.Adapter.extend({ 6 | 7 | documentSource: null, 8 | documentId: Ember.computed.alias('documentSource.document.id'), 9 | 10 | createRecord: function (store, type, snapshot) { 11 | var record = snapshot.record, 12 | options = { 13 | resource: { 14 | value: record.get('emailAddress'), 15 | type: record.get('type'), 16 | role: record.get('role') 17 | }, 18 | fileId: this.get('documentId'), 19 | sendNotificationEmails: false 20 | }; 21 | 22 | var request = gapi.client.drive.permissions.insert(options); 23 | 24 | return new Ember.RSVP.Promise(function (resolve, reject) { 25 | request.execute(function (response) { 26 | if (response.error) { 27 | Ember.run(null, reject, response.error); 28 | } else { 29 | Ember.run(null, resolve, response); 30 | } 31 | }); 32 | }); 33 | }, 34 | 35 | deleteRecord: function (store, type, snapshot) { 36 | var record = snapshot.record, 37 | request = gapi.client.drive.permissions.delete({ 38 | fileId: this.get('documentId'), 39 | permissionId: record.get('id') 40 | }); 41 | 42 | return new Ember.RSVP.Promise(function (resolve, reject) { 43 | request.execute(function (response) { 44 | if (response.error) { 45 | Ember.run(null, reject, response.error); 46 | } else { 47 | Ember.run(null, resolve, null); 48 | } 49 | }); 50 | }); 51 | }, 52 | 53 | findAll: function (store, type) { 54 | var request = gapi.client.drive.permissions.list({ 55 | fileId: this.get('documentId') 56 | }); 57 | 58 | return new Ember.RSVP.Promise(function (resolve, reject) { 59 | request.execute(function (response) { 60 | if (response.error) { 61 | Ember.run(null, reject, response.error); 62 | } else { 63 | Ember.run(null, resolve, response.items); 64 | } 65 | }); 66 | }); 67 | } 68 | }); 69 | 70 | export default Adapter; -------------------------------------------------------------------------------- /app/authenticators/gdrive.js: -------------------------------------------------------------------------------- 1 | import Base from 'simple-auth/authenticators/base'; 2 | import Auth from 'ember-gdrive/lib/auth'; 3 | 4 | var Authenticator = Base.extend({ 5 | 6 | auth: function () { 7 | return Auth.create(); 8 | }.property(), 9 | 10 | restore: function (properties) { 11 | var authenticator = this; 12 | return authenticator.get('auth').authorizeImmediate().then(function () { 13 | return authenticator.get('auth').fetchCurrentUser(); 14 | }); 15 | }, 16 | 17 | authenticate: function (options) { 18 | var authenticator = this; 19 | options = options || {}; 20 | return authenticator.get('auth').authorize(options).then(function () { 21 | return authenticator.get('auth').fetchCurrentUser(); 22 | }); 23 | }, 24 | 25 | invalidate: function () { 26 | return this.get('auth').close(); 27 | } 28 | }); 29 | 30 | export default Authenticator; 31 | -------------------------------------------------------------------------------- /app/components/document-creator.js: -------------------------------------------------------------------------------- 1 | import DocumentCreator from 'ember-gdrive/components/document-creator'; 2 | 3 | export default DocumentCreator; 4 | -------------------------------------------------------------------------------- /app/components/share-modal.js: -------------------------------------------------------------------------------- 1 | import ShareModal from 'ember-gdrive/components/share-modal'; 2 | 3 | export default ShareModal; 4 | -------------------------------------------------------------------------------- /app/initializers/google-drive-auth.js: -------------------------------------------------------------------------------- 1 | import GoogleDriveAuth from 'ember-gdrive/lib/auth'; 2 | 3 | export default { 4 | name: 'google-drive-auth', 5 | before: ['store', 'simple-auth'], 6 | initialize: function (container, application) { 7 | application.register('auth:google', GoogleDriveAuth, { 8 | instantiate: false 9 | }); 10 | 11 | application.inject('controller', 'auth', 'auth:google'); 12 | application.inject('route', 'auth', 'auth:google'); 13 | } 14 | }; -------------------------------------------------------------------------------- /app/initializers/google-drive.js: -------------------------------------------------------------------------------- 1 | import DocumentSource from 'ember-gdrive/lib/document-source'; 2 | import GoogleDriveAdapter from 'ember-gdrive/adapters/google-drive'; 3 | import GoogleDriveSerializer from 'ember-gdrive/lib/serializer'; 4 | 5 | export default { 6 | name: 'google-drive', 7 | after: 'store', 8 | initialize: function (container, application) { 9 | application.register('adapter:-google-drive', GoogleDriveAdapter); 10 | application.register('serializer:-google-drive', GoogleDriveSerializer); 11 | 12 | application.register('document-source:main', DocumentSource); 13 | 14 | application.inject('route', 'documentSource', 'document-source:main'); 15 | application.inject('controller', 'documentSource', 'document-source:main'); 16 | application.inject('adapter:application', 'documentSource', 'document-source:main'); 17 | application.inject('adapter:google-drive-permission', 'documentSource', 'document-source:main'); 18 | application.inject('component:document-creator', 'documentSource', 'document-source:main'); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /app/initializers/load-config.js: -------------------------------------------------------------------------------- 1 | import config from 'ember-gdrive/lib/config'; 2 | 3 | export default { 4 | name: 'load-config', 5 | before: 'simple-auth', 6 | 7 | initialize: function(container, app) { 8 | 9 | //get you setting off of the app instance 10 | config.load(app.get('ember-gdrive')); 11 | } 12 | }; -------------------------------------------------------------------------------- /app/initializers/require-google-libraries.js: -------------------------------------------------------------------------------- 1 | import loader from 'ember-gdrive/lib/loader'; 2 | 3 | export default { 4 | name: 'require-google-libraries', 5 | before: 'google-drive-auth', 6 | initialize: function(container, application) { 7 | application.deferReadiness(); 8 | loader.load().then(function() { 9 | application.advanceReadiness(); 10 | }); 11 | } 12 | }; -------------------------------------------------------------------------------- /app/initializers/share-modal.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'inject-store-into-share-modal', 3 | after: 'store', 4 | initialize: function(container, application) { 5 | application.inject('component:share-modal', 'store', 'store:main'); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /app/initializers/store-extensions.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import DS from 'ember-data'; 3 | 4 | export default { 5 | name: 'store-extensions', 6 | before: 'store', 7 | initialize: function () { 8 | DS.Store.reopen({ 9 | undo: function () { 10 | this._defaultAdapter().undo(); 11 | }, 12 | redo: function () { 13 | this._defaultAdapter().redo(); 14 | }, 15 | beginSave: function (name) { 16 | this._defaultAdapter().beginSave(name); 17 | }, 18 | endSave: function (name) { 19 | this._defaultAdapter().endSave(name); 20 | }, 21 | beginOperation: function (name) { 22 | this._defaultAdapter().beginSave(name); 23 | }, 24 | endOperation: function (name) { 25 | Ember.run.schedule('afterRender', this, function () { 26 | this._defaultAdapter().endSave(name); 27 | }); 28 | }, 29 | _defaultAdapter: function () { 30 | return this.container.lookup('adapter:application'); 31 | } 32 | }); 33 | } 34 | }; -------------------------------------------------------------------------------- /app/models/google-drive-permission.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var GoogleDrivePermission = DS.Model.extend({ 4 | domain: DS.attr('string'), 5 | emailAddress: DS.attr('string'), 6 | etag: DS.attr('string'), 7 | kind: DS.attr('string'), 8 | name: DS.attr('string'), 9 | photoLink: DS.attr('string'), 10 | role: DS.attr('string'), 11 | selfLink: DS.attr('string'), 12 | type: DS.attr('string') 13 | }); 14 | 15 | export default GoogleDrivePermission; -------------------------------------------------------------------------------- /app/templates/components/document-creator.hbs: -------------------------------------------------------------------------------- 1 |

Create a document

2 | {{#if isCreating}} 3 | Creating document... 4 | {{else}} 5 | {{input type="text" value=documentTitle insert-newline="createDocument" placeholder="Type document name here"}} 6 | 7 | {{/if}} 8 | -------------------------------------------------------------------------------- /app/templates/components/share-modal.hbs: -------------------------------------------------------------------------------- 1 | {{#if isOpen}} 2 | 37 | {{/if}} 38 | -------------------------------------------------------------------------------- /blueprints/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "predef": [ 4 | "console" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/controllers/document.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | 5 | isSharing: false, 6 | 7 | actions: { 8 | share: function () { 9 | this.set('isSharing', true); 10 | } 11 | 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/controllers/error.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var NOT_FOUND_REGEX = /The file was not found\. It does not exist or you do not have read access to the file\./; 4 | 5 | export default Ember.ObjectController.extend({ 6 | 7 | isAccessError: function(){ 8 | var message = this.get('message'); 9 | return (NOT_FOUND_REGEX.test(message)); 10 | }.property('message') 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/controllers/items.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.ArrayController.extend({ 4 | newItemName: '', 5 | 6 | actions: { 7 | create: function () { 8 | var item = this.store.createRecord('item', { name: this.get('newItemName') }), 9 | controller = this; 10 | item.save().then(function() { 11 | controller.set('newItemName', ''); 12 | }); 13 | }, 14 | 15 | delete: function (item) { 16 | item.destroyRecord(); 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/controllers/login.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import LoginControllerMixin from 'ember-gdrive/mixins/login-controller-mixin'; 3 | 4 | export default Ember.Controller.extend(LoginControllerMixin, {}); 5 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/models/item.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var Item = DS.Model.extend({ 4 | name: DS.attr('string', { defaultValue: 'New item' }), 5 | }); 6 | 7 | export default Item; 8 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | this.resource('document', { path: 'd/:document_id' }, function() { 10 | this.resource('items', { path: 'items' }); 11 | }); 12 | 13 | this.resource('login'); 14 | }); 15 | 16 | export default Router; 17 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/routes/application.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ApplicationRouteMixin from 'ember-gdrive/mixins/application-route-mixin'; 3 | 4 | export default Ember.Route.extend(ApplicationRouteMixin, {}); 5 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/routes/document.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import AuthenticatedRouteMixin from 'ember-gdrive/mixins/authenticated-route-mixin'; 3 | 4 | export default Ember.Route.extend(AuthenticatedRouteMixin, {}); 5 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/routes/document/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | redirect: function() { 5 | this.replaceWith('items'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/routes/items.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | title: 'Items', 5 | 6 | model: function() { 7 | return this.store.find('item'); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to ember-gdrive!

3 |
4 | {{outlet}} 5 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/templates/document.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{outlet}} 3 |
4 |
5 | 6 |
7 | {{share-modal open-when=isSharing}} -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/templates/error.hbs: -------------------------------------------------------------------------------- 1 | {{#if isAccessError}} 2 |

That document does not exist

3 | 4 |

You may be logged in as the wrong user to access this document.

5 | 6 | Sign out and login as another user 7 | {{else}} 8 |

Sorry, Something went wrong

9 | {{message}} 10 |
11 |     {{stack}}
12 |   
13 | {{/if}} 14 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if session.isAuthenticated}} 2 | Go to Google Drive 3 | {{document-creator documentCreated='documentCreated'}} 4 | {{else}} 5 | 6 | {{/if}} -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/templates/items.hbs: -------------------------------------------------------------------------------- 1 |

Items

2 | 3 | {{input placeholder="Type name and hit enter" value=newItemName action='create'}} 4 |
    5 | {{#each item in model}} 6 |
  • 7 | 8 |
  • 9 | {{/each}} 10 |
11 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/files/app/templates/login.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /blueprints/ember-gdrive/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | normalizeEntityName: function () { 5 | // this prevents an error when the entityName is 6 | // not specified (since that doesn't actually matter 7 | // to us 8 | }, 9 | 10 | afterInstall: function (options) { 11 | var addonContext = this; 12 | 13 | return addonContext.addBowerPackageToProject('ember-simple-auth', '~0.7.2') 14 | .then(function () { 15 | return addonContext.addAddonToProject('ember-cli-simple-auth', '~0.7.2'); 16 | }).then(function () { 17 | return addonContext.insertIntoFile('config/environment.js', 18 | '\n \'ember-gdrive\': {' + 19 | '\n GOOGLE_API_KEY: \'\',' + 20 | '\n GOOGLE_MIME_TYPE: \'application/\',' + 21 | '\n GOOGLE_CLIENT_ID: \'\'' + 22 | '\n },\n', { 23 | after: 'APP: {' 24 | }); 25 | }); 26 | } 27 | }; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-gdrive", 3 | "dependencies": { 4 | "ember": "1.11.1", 5 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 6 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 7 | "ember-data": "1.0.0-beta.16.1", 8 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4", 9 | "ember-qunit": "0.3.1", 10 | "ember-qunit-notifications": "0.0.7", 11 | "ember-resolver": "~0.1.15", 12 | "jquery": "^1.11.1", 13 | "loader.js": "ember-cli/loader.js#3.2.0", 14 | "qunit": "~1.17.1" 15 | } 16 | } -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /design_notes.md: -------------------------------------------------------------------------------- 1 | # create file 2 | 3 | `'?state=%7B%22folderId%22:%220AMftiYAzT3YQUk9PVA%22,%22action%22:%22create%22,%22userId%22:%22{user_id}%22%7D'` 4 | 5 | * When you login using one of the demo apps (storypad ot todoMVC, there will be a network request titled userInfo, which contains the user id) 6 | 7 | # open file 8 | `'?state=%7B%22ids%22:%5B%22{document_id_goes_here}%22%5D,%22action%22:%22open%22,%22userId%22:%22{user_id}%22%7D'` 9 | 10 | * If you don't have a specific document to open, then creating a new one will give you an id 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-gdrive', 6 | 7 | contentFor: function(type) { 8 | if (type === 'head') { 9 | return ''; 10 | } 11 | }, 12 | 13 | config: function (environment, baseConfig) { 14 | 15 | var config = {}; 16 | 17 | config.contentSecurityPolicyHeader = 'Content-Security-Policy'; 18 | config.contentSecurityPolicy = baseConfig.contentSecurityPolicy || {}; 19 | var requiredCSP = { 20 | 'default-src': 'accounts.google.com content.googleapis.com drive.google.com', 21 | 'script-src': '\'unsafe-eval\' \'unsafe-inline\' apis.google.com drive.google.com', 22 | 'connect-src': '\'unsafe-eval\' apis.google.com drive.google.com', 23 | 'img-src': 'data: ssl.gstatic.com csi.gstatic.com', 24 | 'style-src': '\'unsafe-inline\'' 25 | }; 26 | 27 | if (config.contentSecurityPolicy['default-src'] === '\'none\'') { 28 | config.contentSecurityPolicy['default-src'] = ''; 29 | } 30 | 31 | var mergeValues = function (item) { 32 | if (!config.contentSecurityPolicy[propertyName]) { 33 | config.contentSecurityPolicy[propertyName] = item; 34 | } else if (config.contentSecurityPolicy[propertyName].indexOf(item) === -1) { 35 | config.contentSecurityPolicy[propertyName] += ' ' + item; 36 | } 37 | }; 38 | 39 | for (var propertyName in requiredCSP) { 40 | requiredCSP[propertyName].split(' ').forEach(mergeValues); 41 | } 42 | 43 | return config; 44 | }, 45 | included: function (app) { 46 | app.import('vendor/share-modal.css'); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-gdrive", 3 | "version": "0.0.0", 4 | "description": "The default blueprint for ember-cli addons.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "ember server", 11 | "build": "ember build", 12 | "test": "ember try:testall" 13 | }, 14 | "repository": "", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.0.2", 22 | "ember-cli": "0.2.3", 23 | "ember-cli-app-version": "0.3.3", 24 | "ember-cli-content-security-policy": "0.4.0", 25 | "ember-cli-dependency-checker": "0.0.8", 26 | "ember-cli-htmlbars": "0.7.4", 27 | "ember-cli-ic-ajax": "0.1.1", 28 | "ember-cli-inject-live-reload": "^1.3.0", 29 | "ember-cli-qunit": "0.3.10", 30 | "ember-cli-uglify": "1.0.1", 31 | "ember-data": "1.0.0-beta.16.1", 32 | "ember-export-application-global": "^1.0.2", 33 | "ember-disable-prototype-extensions": "^1.0.0", 34 | "ember-try": "0.0.4" 35 | }, 36 | "keywords": [ 37 | "ember-addon" 38 | ], 39 | "dependencies": { 40 | "ember-cli-babel": "^5.0.0" 41 | }, 42 | "ember-addon": { 43 | "configPath": "tests/dummy/config" 44 | } 45 | } -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": false, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esnext": true 51 | } 52 | -------------------------------------------------------------------------------- /tests/dummy/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser" : true, 8 | "boss" : true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | var App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver: Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | export default Router.map(function() { 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/styles/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/templates/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/app/views/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.baseURL = '/'; 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /tests/dummy/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/dummy/public/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import Router from '../../router'; 4 | import config from '../../config/environment'; 5 | 6 | export default function startApp(attrs) { 7 | var application; 8 | 9 | var attributes = Ember.merge({}, config.APP); 10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 11 | 12 | Ember.run(function() { 13 | application = Application.create(attributes); 14 | application.setupForTesting(); 15 | application.injectTestHelpers(); 16 | }); 17 | 18 | return application; 19 | } 20 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/tests/unit/.gitkeep -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderly/ember-gdrive/107f2b80ac226a0a35e044e1cdce0bde837e58ea/vendor/.gitkeep -------------------------------------------------------------------------------- /vendor/share-modal.css: -------------------------------------------------------------------------------- 1 | share-modal * { 2 | box-sizing: border-box; 3 | } 4 | 5 | share-modal { 6 | position: fixed; 7 | display: none; 8 | align-items: center; 9 | justify-content: center; 10 | 11 | width: 100%; 12 | height: 100%; 13 | top: 0; 14 | left: 0; 15 | z-index: 999999; 16 | } 17 | 18 | share-modal.open{ 19 | display: flex; 20 | } 21 | 22 | share-modal .share-modal-body { 23 | width: 500px; 24 | overflow:hidden; 25 | background-color: #FFF; 26 | border: 1px solid #DDD; 27 | -webkit-border-radius: 5px; 28 | -moz-border-radius: 5px; 29 | -ms-border-radius: 5px; 30 | border-radius: 5px; 31 | box-shadow: 2px 2px 5px #ddd; 32 | } 33 | 34 | share-modal .share-modal-header{ 35 | padding: 10px; 36 | border-bottom: 1px solid #DDD; 37 | } 38 | 39 | share-modal .share-modal-content { 40 | padding: 10px; 41 | min-height:200px; 42 | } 43 | 44 | share-modal .share-modal-footer { 45 | padding:10px; 46 | border-top: 1px solid #DDD; 47 | background:#f4f4f4; 48 | } 49 | 50 | share-modal .share-modal-footer button { 51 | float:right; 52 | margin: 0; 53 | } 54 | 55 | share-modal .share-modal-footer:before, 56 | share-modal .share-modal-footer:after { 57 | content: ''; 58 | display: table; 59 | } 60 | share-modal .share-modal-footer:after { 61 | clear: both; 62 | } 63 | 64 | share-modal form { 65 | margin: 10px 0; 66 | } 67 | 68 | share-modal input, 69 | share-modal button { 70 | display: block; 71 | height: 35px; 72 | padding: 0 10px; 73 | margin: 10px 0; 74 | display: inline-block; 75 | 76 | border: 1px solid #e1e1e1; 77 | 78 | -webkit-border-radius: 3px; 79 | -moz-border-radius: 3px; 80 | -ms-border-radius: 3px; 81 | border-radius: 3px; 82 | 83 | font-weight: 400; 84 | font-size: 14px; 85 | color: #535353; 86 | } 87 | 88 | share-modal input[type=submit], 89 | share-modal button { 90 | cursor: pointer; 91 | } 92 | 93 | share-modal input[type=submit] { 94 | width: 100px; 95 | margin-left: 10px; 96 | } 97 | 98 | share-modal input[type=text] { 99 | width: calc(100% - 120px); 100 | margin-left: 0; 101 | } 102 | 103 | share-modal .permission-list { 104 | margin: 0; 105 | margin-left: -10px; 106 | padding: 0; 107 | } 108 | 109 | share-modal .permission-list .permission-item { 110 | color: #333; 111 | display: inline-block; 112 | margin: 5px 10px; 113 | 114 | border: 1px solid #DDD; 115 | -webkit-border-radius: 2px; 116 | -moz-border-radius: 2px; 117 | -ms-border-radius: 2px; 118 | border-radius: 2px; 119 | 120 | padding: 10px; 121 | box-sizing:border-box; 122 | text-align: center; 123 | cursor: default; 124 | } 125 | 126 | share-modal .permission-list .permission-item span { 127 | vertical-align: middle; 128 | } 129 | 130 | share-modal .permission-list .permission-item a { 131 | vertical-align: middle; 132 | font-weight: bold; 133 | cursor: pointer; 134 | color: gray; 135 | margin-left: 5px; 136 | } 137 | 138 | share-modal .permission-list .permission-item a:hover { 139 | color: black; 140 | } 141 | 142 | share-modal .permission-list .permission-item a:before { 143 | display: inline-block; 144 | content: 'x'; 145 | } 146 | --------------------------------------------------------------------------------