diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..51d130c --- /dev/null +++ b/.jshintrc @@ -0,0 +1,45 @@ +{ + "strict": true, + "globalstrict": true, + "jquery": true, + "node": true, + "browser": true, + "camelcase": true, + "nonew": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "newcap": true, + "regexp": true, + "evil": true, + "eqnull": true, + "expr": true, + "trailing": true, + "undef": true, + "unused": true, + + "predef": [ + "self", + "console", + "importScripts", + "cryptoLib", + "Backbone", + "forge", + "uuid", + "Lawnchair", + "_", + "process", + "describe", + "it", + "chai", + "ok", + "equal", + "deepEqual", + "start", + "chrome" + ], + + "globals": { + "app": true + } +} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 79930e6..3803ba3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -42,15 +42,19 @@ module.exports = function(grunt) { }, jshint: { - all: ['Gruntfile.js', 'src/js/**/*.js'] + all: ['Gruntfile.js', 'src/js/**/*.js'], + options: { + jshintrc: '.jshintrc' + } }, qunit: { all: { options: { urls: [ - 'http://localhost:<%= connect.test.options.port %>/test/unit/index.html', - 'http://localhost:<%= connect.test.options.port %>/test/integration/index.html'] + 'http://localhost:<%= connect.test.options.port %>/test/unit/index.html', + 'http://localhost:<%= connect.test.options.port %>/test/integration/index.html' + ] } } } @@ -63,7 +67,7 @@ module.exports = function(grunt) { // Default task(s). grunt.registerTask('dev', ['connect:dev']); - grunt.registerTask('test', ['jshint', 'connect:test', 'qunit']); + grunt.registerTask('test', ['connect:test', 'qunit']); grunt.registerTask('prod', ['connect:prod']); }; \ No newline at end of file diff --git a/src/background.js b/src/background.js new file mode 100644 index 0000000..15286bd --- /dev/null +++ b/src/background.js @@ -0,0 +1,10 @@ +'use strict'; + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('index.html', { + 'bounds': { + 'width': 805, + 'height': 620 + } + }); +}); \ No newline at end of file diff --git a/src/css/styles.css b/src/css/styles.css index 2edfe63..4977e38 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -41,4 +41,14 @@ textarea.ui-input-text { max-width: 400px; margin-left: auto; margin-right: auto; +} + +div#sandboxDiv iframe { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + height: 100%; + width: 100%; } \ No newline at end of file diff --git a/src/index.html b/src/index.html index 5bd74e9..900d615 100755 --- a/src/index.html +++ b/src/index.html @@ -5,7 +5,6 @@ Mail - @@ -13,9 +12,7 @@ - - - + @@ -41,18 +38,14 @@ - - - - - - - - - - + + - + +
+ +
+ \ No newline at end of file diff --git a/src/js/app-router.js b/src/js/app-router.js index 59f0b68..483aa91 100644 --- a/src/js/app-router.js +++ b/src/js/app-router.js @@ -16,13 +16,7 @@ login: function() { // init email dao and dependencies - var util = new cryptoLib.Util(window, uuid); - var crypto = new app.crypto.Crypto(window, util); - var cloudstorage = new app.dao.CloudStorage(window, $); - var jsonDao = new app.dao.LawnchairDAO(Lawnchair); - var devicestorage = new app.dao.DeviceStorage(util, crypto, jsonDao, null); - var keychain = new app.dao.KeychainDAO(jsonDao, cloudstorage); - this.emailDao = new app.dao.EmailDAO(jsonDao, crypto, devicestorage, cloudstorage, util, keychain); + this.emailDao = null; var loginView = new app.view.LoginView({ dao: this.emailDao diff --git a/src/js/sandbox-loader.js b/src/js/sandbox-loader.js new file mode 100644 index 0000000..6450b15 --- /dev/null +++ b/src/js/sandbox-loader.js @@ -0,0 +1,37 @@ +(function() { + 'use strict'; + + /** + * The Template Loader. Used to asynchronously load templates located in separate .html files + */ + app.util.tpl = { + + // Hash of preloaded templates for the app + templates: {}, + + // Get template by name from hash of preloaded templates + get: function(name) { + return this.templates[name]; + } + + }; + + /** + * Load templates and start the application + */ + $(document).ready(function() { + console.log('sandbox loaded'); + + window.onmessage = function(e) { + app.util.tpl.templates = e.data; + + // remember references to main window + window.mainWindow = e.source; + window.mainWindowOrigin = e.origin; + + var router = new app.Router(); + Backbone.history.start(); + }; + }); + +}()); \ No newline at end of file diff --git a/src/js/view/login-view.js b/src/js/view/login-view.js index 3a1837e..cc74686 100644 --- a/src/js/view/login-view.js +++ b/src/js/view/login-view.js @@ -40,15 +40,21 @@ textVisible: true, theme: 'c' }); - this.dao.init(account, password, function(err) { - $.mobile.loading('hide'); - if (err) { - window.alert(err.errMsg); - return; - } - window.location = '#accounts/' + account.get('emailAddress') + '/folders'; - }); + window.location = '#accounts/test@example.com/folders'; + window.mainWindow.postMessage({ + cmd: 'hello back from sandbox' + }, window.mainWindowOrigin); + + // this.dao.init(account, password, function(err) { + // $.mobile.loading('hide'); + // if (err) { + // window.alert(err.errMsg); + // return; + // } + + // window.location = '#accounts/' + account.get('emailAddress') + '/folders'; + // }); } }); diff --git a/src/js/window-loader.js b/src/js/window-loader.js new file mode 100644 index 0000000..b5122ab --- /dev/null +++ b/src/js/window-loader.js @@ -0,0 +1,77 @@ +(function() { + 'use strict'; + + /** + * The Template Loader. Used to asynchronously load templates located in separate .html files + */ + app.util.tpl = { + + // Hash of preloaded templates for the app + templates: {}, + + // Recursively pre-load all the templates for the app. + loadTemplates: function(names, callback) { + var that = this; + + var loadTemplate = function(index) { + var name = names[index]; + console.log('Loading template: ' + name); + $.get('tpl/' + name + '.html', function(data) { + that.templates[name] = data; + index++; + if (index < names.length) { + loadTemplate(index); + } else { + callback(); + } + }); + }; + + loadTemplate(0); + }, + + // Get template by name from hash of preloaded templates + get: function(name) { + return this.templates[name]; + } + + }; + + /** + * Load templates and start the application + */ + $(document).ready(function() { + // are we running in native app or in browser? + var isBrowser = false; + if (document.URL.indexOf("http") === 0 || document.URL.indexOf("chrome") === 0) { + isBrowser = true; + } + + if (!isBrowser) { + document.addEventListener("deviceready", onDeviceReady, false); + } else { + onDeviceReady(); + } + + function onDeviceReady() { + console.log('Starting in Browser: ' + isBrowser); + app.util.tpl.loadTemplates([ + 'login', + 'compose', + 'folderlist', + 'messagelist', + 'messagelistitem', + 'read' + ], function() { + // set listener for events from sandbox + window.onmessage = function(e) { + console.log(e.data); + }; + // init sandbox ui + var sandbox = document.getElementById('sandboxFrame').contentWindow; + sandbox.postMessage(app.util.tpl.templates, '*'); + }); + } + }); + +}()); \ No newline at end of file diff --git a/src/lib/lawnchair/lawnchair-git.js b/src/lib/lawnchair/lawnchair-git.js new file mode 100644 index 0000000..6a7c630 --- /dev/null +++ b/src/lib/lawnchair/lawnchair-git.js @@ -0,0 +1,293 @@ +/** + * Lawnchair! + * --- + * clientside json store + * + */ +var Lawnchair = function (options, callback) { + // ensure Lawnchair was called as a constructor + if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); + + // lawnchair requires json + if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.' + // options are optional; callback is not + if (arguments.length <= 2) { + callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; + options = (typeof arguments[0] === 'function') ? {} : arguments[0] || {}; + } else { + throw 'Incorrect # of ctor args!' + } + + // default configuration + this.record = options.record || 'record' // default for records + this.name = options.name || 'records' // default name for underlying store + + // mixin first valid adapter + var adapter + // if the adapter is passed in we try to load that only + if (options.adapter) { + + // the argument passed should be an array of prefered adapters + // if it is not, we convert it + if(typeof(options.adapter) === 'string'){ + options.adapter = [options.adapter]; + } + + // iterates over the array of passed adapters + for(var j = 0, k = options.adapter.length; j < k; j++){ + + // itirates over the array of available adapters + for (var i = Lawnchair.adapters.length-1; i >= 0; i--) { + if (Lawnchair.adapters[i].adapter === options.adapter[j]) { + adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; + if (adapter) break + } + } + if (adapter) break + } + + // otherwise find the first valid adapter for this env + } + else { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined + if (adapter) break + } + } + + // we have failed + if (!adapter) throw 'No valid adapter.' + + // yay! mixin the adapter + for (var j in adapter) + this[j] = adapter[j] + + // call init for each mixed in plugin + for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) + Lawnchair.plugins[i].call(this) + + // init the adapter + this.init(options, callback) +} + +Lawnchair.adapters = [] + +/** + * queues an adapter for mixin + * === + * - ensures an adapter conforms to a specific interface + * + */ +Lawnchair.adapter = function (id, obj) { + // add the adapter id to the adapter obj + // ugly here for a cleaner dsl for implementing adapters + obj['adapter'] = id + // methods required to implement a lawnchair adapter + var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ') + , indexOf = this.prototype.indexOf + // mix in the adapter + for (var i in obj) { + if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i + } + // if we made it this far the adapter interface is valid + // insert the new adapter as the preferred adapter + Lawnchair.adapters.splice(0,0,obj) +} + +Lawnchair.plugins = [] + +/** + * generic shallow extension for plugins + * === + * - if an init method is found it registers it to be called when the lawnchair is inited + * - yes we could use hasOwnProp but nobody here is an asshole + */ +Lawnchair.plugin = function (obj) { + for (var i in obj) + i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] +} + +/** + * helpers + * + */ +Lawnchair.prototype = { + + isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' }, + + /** + * this code exists for ie8... for more background see: + * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream + */ + indexOf: function(ary, item, i, l) { + if (ary.indexOf) return ary.indexOf(item) + for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i + return -1 + }, + + // awesome shorthand callbacks as strings. this is shameless theft from dojo. + lambda: function (callback) { + return this.fn(this.record, callback) + }, + + // first stab at named parameters for terse callbacks; dojo: first != best // ;D + fn: function (name, callback) { + return typeof callback == 'string' ? new Function(name, callback) : callback + }, + + // returns a unique identifier (by way of Backbone.localStorage.js) + // TODO investigate smaller UUIDs to cut on storage cost + uuid: function () { + var S4 = function () { + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); + } + return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); + }, + + // a classic iterator + each: function (callback) { + var cb = this.lambda(callback) + // iterate from chain + if (this.__results) { + for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i) + } + // otherwise iterate the entire collection + else { + this.all(function(r) { + for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) + }) + } + return this + } +// -- +}; +// window.name code courtesy Remy Sharp: http://24ways.org/2009/breaking-out-the-edges-of-the-browser +Lawnchair.adapter('window-name', (function() { + if (typeof window==='undefined') { + window = { top: { } }; // node/optimizer compatibility + } + + // edited from the original here by elsigh + // Some sites store JSON data in window.top.name, but some folks (twitter on iPad) + // put simple strings in there - we should make sure not to cause a SyntaxError. + var data = {} + try { + data = JSON.parse(window.top.name) + } catch (e) {} + + + return { + + valid: function () { + return typeof window.top.name != 'undefined' + }, + + init: function (options, callback) { + data[this.name] = data[this.name] || {index:[],store:{}} + this.index = data[this.name].index + this.store = data[this.name].store + this.fn(this.name, callback).call(this, this) + return this + }, + + keys: function (callback) { + this.fn('keys', callback).call(this, this.index) + return this + }, + + save: function (obj, cb) { + // data[key] = value + ''; // force to string + // window.top.name = JSON.stringify(data); + var key = obj.key || this.uuid() + this.exists(key, function(exists) { + if (!exists) { + if (obj.key) delete obj.key + this.index.push(key) + } + this.store[key] = obj + + try { + window.top.name = JSON.stringify(data) // TODO wow, this is the only diff from the memory adapter + } catch(e) { + // restore index/store to previous value before JSON exception + if (!exists) { + this.index.pop(); + delete this.store[key]; + } + throw e; + } + + if (cb) { + obj.key = key + this.lambda(cb).call(this, obj) + } + }) + return this + }, + + batch: function (objs, cb) { + var r = [] + for (var i = 0, l = objs.length; i < l; i++) { + this.save(objs[i], function(record) { + r.push(record) + }) + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + get: function (keyOrArray, cb) { + var r; + if (this.isArray(keyOrArray)) { + r = [] + for (var i = 0, l = keyOrArray.length; i < l; i++) { + r.push(this.store[keyOrArray[i]]) + } + } else { + r = this.store[keyOrArray] + if (r) r.key = keyOrArray + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + exists: function (key, cb) { + this.lambda(cb).call(this, !!(this.store[key])) + return this + }, + + all: function (cb) { + var r = [] + for (var i = 0, l = this.index.length; i < l; i++) { + var obj = this.store[this.index[i]] + obj.key = this.index[i] + r.push(obj) + } + this.fn(this.name, cb).call(this, r) + return this + }, + + remove: function (keyOrArray, cb) { + var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] + for (var i = 0, l = del.length; i < l; i++) { + var key = del[i].key ? del[i].key : del[i] + var where = this.indexOf(this.index, key) + if (where < 0) continue /* key not present */ + delete this.store[key] + this.index.splice(where, 1) + } + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this + }, + + nuke: function (cb) { + this.store = data[this.name].store = {} + this.index = data[this.name].index = [] + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this + } + } +///// +})()) diff --git a/src/lib/lawnchair/lawnchair-git.min.js b/src/lib/lawnchair/lawnchair-git.min.js deleted file mode 100644 index ee47926..0000000 --- a/src/lib/lawnchair/lawnchair-git.min.js +++ /dev/null @@ -1,14 +0,0 @@ -var Lawnchair=function(e,h){if(!(this instanceof Lawnchair))return new Lawnchair(e,h);if(!JSON)throw"JSON unavailable! Include http://www.json.org/json2.js to fix.";if(arguments.length<=2){h=typeof arguments[0]==="function"?arguments[0]:arguments[1];e=typeof arguments[0]==="function"?{}:arguments[0]||{}}else throw"Incorrect # of ctor args!";this.record=e.record||"record";this.name=e.name||"records";var a;if(e.adapter){if(typeof e.adapter==="string")e.adapter=[e.adapter];for(var b=0,c=e.adapter.length;b< -c;b++){for(var d=Lawnchair.adapters.length-1;d>=0;d--)if(Lawnchair.adapters[d].adapter===e.adapter[b])if(a=Lawnchair.adapters[d].valid()?Lawnchair.adapters[d]:undefined)break;if(a)break}}else{d=0;for(c=Lawnchair.adapters.length;d0||b&&c.lambda(b).call(c)})};for(d=0;d + + + + Mail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/integration/index.html b/test/integration/index.html index b9b7b0a..9793584 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -20,7 +20,7 @@ - + diff --git a/test/unit/index.html b/test/unit/index.html index fe09cf5..1067282 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -20,7 +20,7 @@ - +