├── src ├── angular │ ├── prefix.js │ ├── premodule.js │ ├── .jshintrc │ ├── suffix.js │ └── module.js ├── vanilla │ ├── prefix.js │ └── suffix.js └── core │ ├── api │ ├── cache │ │ ├── stubcache.js │ │ ├── indexeddb.js │ │ └── cache.js │ └── models │ │ ├── contact.js │ │ ├── tag.js │ │ ├── file.js │ │ ├── thread.js │ │ ├── message.js │ │ └── message_draft.js │ └── utilities │ ├── class.js │ ├── types.js │ ├── string.js │ ├── json.js │ ├── properties.js │ ├── domevents.js │ ├── datetime.js │ ├── data.js │ ├── url.js │ └── xmlhttprequest.js ├── .gitignore ├── test ├── angular │ ├── .jshintrc │ └── api.spec.js ├── helpers │ ├── lib │ │ ├── data.js │ │ └── mock-promises-0.0.2.js │ └── matchers │ │ ├── matchers.js │ │ └── jasmine-sinon.js ├── .jscsrc ├── .jshintrc ├── utilities │ ├── string.spec.js │ ├── url.spec.js │ ├── xmlhttprequest.spec.js │ └── filters.spec.js └── api │ ├── file.spec.js │ ├── draft.spec.js │ ├── inbox.spec.js │ ├── message.spec.js │ └── tag.spec.js ├── scripts └── travis │ └── test.sh ├── .travis.yml ├── karma.conf.js ├── examples ├── list-namespaces │ ├── app.js │ └── index.html ├── minievents.js ├── index.html ├── list-threads │ ├── index.html │ └── app.js ├── reply-to-threads │ ├── index.html │ └── app.js ├── draft-messages │ ├── index.html │ └── app.js └── summernote.css ├── .jscsrc ├── package.json ├── LICENSE.md ├── .jshintrc ├── CONTRIBUTING.md ├── gulpfile.js └── README.md /src/angular/prefix.js: -------------------------------------------------------------------------------- 1 | ;(function(window) { 'use strict'; 2 | -------------------------------------------------------------------------------- /src/angular/premodule.js: -------------------------------------------------------------------------------- 1 | function setup(window, angular) { 2 | var module = -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /bower_components 3 | /build 4 | .DS_Store 5 | *~ 6 | *.bak 7 | *.swp 8 | -------------------------------------------------------------------------------- /src/angular/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.jshintrc", 3 | "globals": { 4 | "angular": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/angular/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "globals": { 4 | "inject": false, 5 | "module": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/vanilla/prefix.js: -------------------------------------------------------------------------------- 1 | ;(function(window) { 'use strict'; 2 | if (typeof global === 'object' && global.window) { 3 | window = global.window; 4 | } 5 | -------------------------------------------------------------------------------- /scripts/travis/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | gulp lint 6 | gulp build 7 | 8 | karma start karma.conf.js --single-run --browsers Firefox 9 | 10 | -------------------------------------------------------------------------------- /test/helpers/lib/data.js: -------------------------------------------------------------------------------- 1 | function __extend(obj, newContent) { 2 | if (obj && typeof obj === 'object') { 3 | if (isArray(obj)) { 4 | return merge(merge([], obj), newContent); 5 | } else { 6 | return merge(merge({}, obj), newContent); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.11 4 | 5 | addons: 6 | firefox: "28.0" 7 | 8 | before_script: 9 | - export DISPLAY=:99.0 10 | - sh -e /etc/init.d/xvfb start 11 | - npm install karma-firefox-launcher 12 | - npm install -g gulp karma-cli 13 | 14 | script: 15 | - ./scripts/travis/test.sh 16 | 17 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | browsers: ['Chrome'], 4 | frameworks: ['jasmine'], 5 | 6 | files: [ 7 | 'src/core/utilities/*.js', 8 | 'src/core/**/*.js', 9 | 'test/helpers/**/*.js', 10 | 'src/angular/module.js', 11 | 'test/**/*.js' 12 | ], 13 | 14 | reporters: ['dots'] 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/vanilla/suffix.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { 2 | // RequireJS 3 | define(function() { 4 | return InboxAPI; 5 | }); 6 | } else if (typeof module === 'object' && typeof require === 'function') { 7 | // CommonJS/Browserify 8 | module.exports = InboxAPI; 9 | } else { 10 | window.InboxAPI = InboxAPI; 11 | } 12 | 13 | })(this); 14 | -------------------------------------------------------------------------------- /test/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": { 4 | "value": 200, 5 | "allowComments": true, 6 | "allowRegex": true 7 | }, 8 | "disallowSpacesInsideObjectBrackets": null, 9 | "disallowSpacesInsideArrayBrackets": null, 10 | "disallowMultipleLineBreaks": null, 11 | "requireCurlyBraces": [ 12 | "else", 13 | "for", 14 | "while", 15 | "do", 16 | "try", 17 | "catch" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "loopfunc": true, 4 | "globals": { 5 | "jasmine": false, 6 | "it": false, 7 | "iit": false, 8 | "describe": false, 9 | "ddescribe": false, 10 | "beforeEach": false, 11 | "afterEach": false, 12 | "expect": false, 13 | "mockPromises": false, 14 | "sinon": false, 15 | "spyOn": false, 16 | 17 | "FILTER_NAMES_OPTS": false, 18 | "__extend": false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/core/api/cache/stubcache.js: -------------------------------------------------------------------------------- 1 | function INStubCache(inbox) { 2 | INCache.call(this, inbox); 3 | } 4 | 5 | INCache.register('stub', INStubCache); 6 | 7 | INStubCache.prototype.get = function(id, callback) { 8 | callback(null, null); 9 | }; 10 | 11 | INStubCache.prototype.getByType = function(type, callback) { 12 | callback(null, null); 13 | }; 14 | 15 | INStubCache.prototype.persist = function(id, object, callback) { 16 | callback(null, null); 17 | }; 18 | 19 | INStubCache.prototype.remove = function(id, callback) { 20 | callback(null, null); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/list-namespaces/app.js: -------------------------------------------------------------------------------- 1 | angular.module('inbox_list_namespaces', ['inbox']). 2 | config(['$inboxProvider', function($inboxProvider) { 3 | $inboxProvider. 4 | baseUrl('http://localhost:5555'). 5 | appId('test'); 6 | }]). 7 | controller('inboxNamespacesCtrl', ['$scope', '$inbox', function(scope, $inbox) { 8 | var self = this; 9 | function updateList() { 10 | $inbox.namespaces().then(function(namespaces) { 11 | self.namespaces = namespaces; 12 | }, function(error) { 13 | self.namespaces = null; 14 | }); 15 | } 16 | updateList(); 17 | var updateId = setInterval(updateList, 600000); 18 | }]); 19 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": { 4 | "value": 100, 5 | "allowComments": true, 6 | "allowRegex": true 7 | }, 8 | "disallowSpacesInsideObjectBrackets": null, 9 | "disallowSpacesInsideArrayBrackets": null, 10 | "disallowMultipleLineBreaks": null, 11 | "requireCurlyBraces": [ 12 | "else", 13 | "for", 14 | "while", 15 | "do", 16 | "try", 17 | "catch" 18 | ], 19 | "requireSpaceAfterKeywords": [ 20 | "if", 21 | "else", 22 | "for", 23 | "while", 24 | "do", 25 | "switch", 26 | "return", 27 | "try" 28 | ], 29 | "validateJSDoc": null 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inboxjs", 3 | "version": "0.0.0", 4 | "description": "Client support SDK for http://inboxapp.co/", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "author": "", 10 | "license": "TBD", 11 | "dependencies": { 12 | "gulp": "^3.7.0", 13 | "gulp-concat": "^2.2.0", 14 | "gulp-jscs": "^1.1.0", 15 | "gulp-jshint": "^1.6.2", 16 | "gulp-connect": "^2.0.5", 17 | "yargs": "^1.2.6", 18 | "merge-stream": "^0.1.5" 19 | }, 20 | "devDependencies": { 21 | "karma": "^0.12.16", 22 | "karma-jasmine": "^0.1.5", 23 | "jshint-stylish": "^0.2.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/utilities/string.spec.js: -------------------------------------------------------------------------------- 1 | describe('stringFormat', function() { 2 | it('should replace `%@` with Object.toString()-ified parameter', function() { 3 | expect(formatString('%@/%@/%@', 'foo', 123, null)).toBe('foo/123/null'); 4 | }); 5 | 6 | 7 | it('should replace `%@` markers with the empty string when no parameters remain', function() { 8 | expect(formatString('%@.%@jpeg', 'test')).toBe('test.jpeg'); 9 | }); 10 | }); 11 | 12 | 13 | describe('capitalizeString', function() { 14 | it('should capitalize each word in the string separated by whitespace', function() { 15 | expect(capitalizeString('foo bar baz')).toBe('Foo Bar Baz'); 16 | expect(capitalizeString('foo\tbar\tbaz')).toBe('Foo\tBar\tBaz'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/angular/suffix.js: -------------------------------------------------------------------------------- 1 | return module; 2 | } 3 | 4 | if (typeof angular === 'object' && angular && typeof angular.module === 'function') { 5 | // AngularJS already loaded, register Inbox modules 6 | setup(window, angular); 7 | } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { 8 | // RequireJS 9 | // TODO: don't assume AngularJS module is named 'angular' 10 | define(['angular'], function(angular) { 11 | return setup(window, angular); 12 | }); 13 | } else if (typeof module === 'object' && typeof require === 'function') { 14 | // CommonJS/Browserify 15 | // TODO: don't assume AngularJS module is named 'angular' 16 | var commonjsAngular = require('angular'); 17 | module.exports = setup(window, commonjsAngular); 18 | } 19 | 20 | })(this); 21 | -------------------------------------------------------------------------------- /test/utilities/url.spec.js: -------------------------------------------------------------------------------- 1 | describe('formatUrl', function() { 2 | it('should replace `%@` with Object.toString()-ified parameter', function() { 3 | expect(formatUrl('%@/%@/%@', 'foo', 123, null)).toBe('foo/123/null'); 4 | }); 5 | 6 | 7 | it('should replace `%@` markers with the empty string when no parameters remain', function() { 8 | expect(formatUrl('%@.%@jpeg', 'test')).toBe('test.jpeg'); 9 | }); 10 | 11 | 12 | it('should remove leading forward slashes from parameters', function() { 13 | expect(formatUrl('%@/%@', 'http://api.inboxapp.co/', '//fakeNamespaceId')). 14 | toBe('http://api.inboxapp.co/fakeNamespaceId'); 15 | }); 16 | 17 | 18 | it('should remove trailing forward slashes from parameters', function() { 19 | expect(formatUrl('%@/%@/%@', 'http://api.inboxapp.co/', 'fakeNamespaceId/', 'foo//')). 20 | toBe('http://api.inboxapp.co/fakeNamespaceId/foo'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/minievents.js: -------------------------------------------------------------------------------- 1 | function Events(target){ 2 | var events = {}; 3 | target = target || this 4 | /** 5 | * On: listen to events 6 | */ 7 | target.on = function(type, func, ctx){ 8 | events[type] || (events[type] = []) 9 | events[type].push({f:func, c:ctx}) 10 | } 11 | /** 12 | * Off: stop listening to event / specific callback 13 | */ 14 | target.off = function(type, func){ 15 | var list = events[type] || [], 16 | i = list.length = func ? list.length : 0 17 | while(i-->0) func == list[i].f && list.splice(i,1) 18 | } 19 | /** 20 | * Emit: send event, callbacks will be triggered 21 | */ 22 | target.emit = function(){ 23 | var args = Array.apply([], arguments), 24 | list = events[args.shift()] || [], 25 | i = list.length, j 26 | for(j=0;j 2 | 3 | 4 | Inbox SDK Examples 5 | 6 | 9 | 10 | 11 | 14 |
15 |
16 |
17 |
18 |

Inbox.js Examples

19 |
20 |
21 | 27 |
28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/list-namespaces/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inbox SDK Examples --- List Namespaces 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Email AddressProvider
{{ns.emailAddress}}{{ns.provider}}
35 |
36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /test/helpers/matchers/matchers.js: -------------------------------------------------------------------------------- 1 | ;(function (window) { 2 | 'use strict'; 3 | 4 | var toContainObject = { 5 | toContainObject: function(expected) { 6 | var mismatchKeys = [], 7 | mismatchValues = [], 8 | not = this.isNot, 9 | actual = this.actual, 10 | objectContaining = jasmine.objectContaining(expected); 11 | 12 | function notMatch (mismatchKeys, mismatchValues) { 13 | var env = jasmine.getEnv(); 14 | 15 | var hasKey = function(obj, keyName) { 16 | return obj != null && obj[keyName] !== jasmine.undefined; 17 | }; 18 | 19 | for (var property in expected) { 20 | var keysMatch = hasKey(actual, property) && hasKey(expected, property), 21 | propertiesMatch = env.equals_(expected[property], actual[property], mismatchKeys, mismatchValues); 22 | 23 | if (keysMatch && propertiesMatch) { 24 | var message = 'expected: ' + jasmine.pp(expected) + ' and actual: ' + jasmine.pp(actual) + ' have matching \'' + property + '\' value.'; 25 | 26 | mismatchValues.push(message); 27 | } 28 | } 29 | } 30 | 31 | this.message = function () { 32 | if (not) 33 | notMatch(mismatchKeys, mismatchValues); 34 | 35 | return mismatchKeys.concat(mismatchValues).join('\n'); 36 | }; 37 | 38 | return objectContaining 39 | .jasmineMatches(this.actual, mismatchKeys, mismatchValues); 40 | } 41 | }; 42 | 43 | beforeEach(function() { 44 | this.addMatchers(toContainObject); 45 | }); 46 | }(this)); 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 2, 3 | "node": true, 4 | "undef": true, 5 | "expr": true, 6 | "browser": true, 7 | "eqnull": true, 8 | "strict": false, 9 | "sub": true, 10 | "globals": { 11 | "InboxAPI": false, 12 | "INCache": false, 13 | "INDraft": false, 14 | "INFile": false, 15 | "INIDBCache": false, 16 | "INNamespace": false, 17 | "INMessage": false, 18 | "INModelObject": false, 19 | "INStubCache": false, 20 | "INTag": false, 21 | "INThread": false, 22 | "INContact": false, 23 | 24 | 25 | "addListener": true, 26 | "addListeners": false, 27 | "apiRequest": false, 28 | "apiRequestPromise": false, 29 | "applyFilters": false, 30 | "capitalizeString": false, 31 | "convertFromRaw": false, 32 | "defineProperty": false, 33 | "defineResourceMapping": false, 34 | "deleteModel": false, 35 | "endsWith": false, 36 | "formatString": false, 37 | "formatUrl": false, 38 | "forEach": false, 39 | "fromArray": false, 40 | "getCacheByName": false, 41 | "getNamespace": false, 42 | "getCacheType": false, 43 | "hasProperty": false, 44 | "inherits": false, 45 | "isArray": true, 46 | "isBlob": false, 47 | "isFile": false, 48 | "map": false, 49 | "mappingForProperty": false, 50 | "merge": false, 51 | "mergeModelArray": false, 52 | "noop": false, 53 | "parseJSON": true, 54 | "persistModel": false, 55 | "reloadModel": false, 56 | "toJSON": true, 57 | "toUnixTimestamp": false, 58 | "uploadFile": false, 59 | "valueFn": false, 60 | 61 | 62 | "CONFIGURABLE": true, 63 | "HEADER_REGEXP": true, 64 | "INVISIBLE": true, 65 | "WRITABLE": true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/angular/api.spec.js: -------------------------------------------------------------------------------- 1 | describe('angular', function() { 2 | beforeEach(module('inbox')); 3 | describe('$inbox', function() { 4 | it('should use baseUrl/appId from $inboxProvider', function() { 5 | module(function($inboxProvider) { 6 | $inboxProvider. 7 | baseUrl('foo.com/bar'). 8 | appId('testApp'); 9 | }); 10 | inject(function($inbox) { 11 | expect($inbox._.baseUrl).toBe('foo.com/bar'); 12 | expect($inbox._.appId).toBe('testApp'); 13 | }); 14 | }); 15 | 16 | 17 | it('should use default baseUrl if baseUrl is not defined', function() { 18 | module(function($inboxProvider) { 19 | $inboxProvider.appId('testApp'); 20 | }); 21 | inject(function($inbox) { 22 | expect(typeof $inbox._.baseUrl).toBe('string'); 23 | }); 24 | }); 25 | 26 | 27 | it('should throw if appId is not defined', function() { 28 | expect(function() { 29 | inject(function($inbox) {}); 30 | }).toThrow(); 31 | }); 32 | 33 | 34 | describe('', function() { 35 | // Misc. verifications 36 | beforeEach(module(function($inboxProvider) { 37 | $inboxProvider.appId('testApp'); 38 | })); 39 | 40 | 41 | it('should be instance of InboxAPI', inject(function($inbox) { 42 | expect($inbox instanceof InboxAPI).toBe(true); 43 | })); 44 | 45 | 46 | it('should use $q promises', inject(function($inbox, $q) { 47 | var qPromise = $q.defer().promise; 48 | var inboxPromise = $inbox._.promise(function() {}); 49 | expect(typeof inboxPromise.constructor).toBe('function'); 50 | expect(inboxPromise.constructor).toBe(qPromise.constructor); 51 | })); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/core/utilities/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function 3 | * @name isArray 4 | * @private 5 | * 6 | * @description 7 | * Test if a given value is an Array. If Array.isArray is supported, it's possible to test if 8 | * a value from a different sandbox is an array, but this should not be depended on. The native 9 | * implementation is primarily deferred to for performance reasons, and nothing else. 10 | * 11 | * @param {*} arr value to test if it is an array. 12 | * 13 | * @returns {boolean} true if the value is an array, otherwise false. 14 | */ 15 | var isArray = (function() { 16 | if (typeof Array.isArray === 'function') { 17 | return Array.isArray; 18 | } 19 | return function(arr) { 20 | return Object.prototype.toString.call(arr) === '[object Array]'; 21 | }; 22 | })(); 23 | 24 | 25 | /** 26 | * @function 27 | * @name isFile 28 | * @private 29 | * 30 | * @description 31 | * Returns true if value.toString() results in [object File]. It is possible to get false positives 32 | * this way, but unlikely. 33 | * 34 | * @param {object} obj value to test if it is a File object. 35 | * 36 | * @returns {boolean} true if the value is determined to be a File, otherwise false. 37 | */ 38 | function isFile(obj) { 39 | return obj && Object.prototype.toString.call(obj) === '[object File]'; 40 | } 41 | 42 | var BLOB_REGEXP = /^\[object (Blob|File)\]$/; 43 | 44 | 45 | /** 46 | * @function 47 | * @name isBlob 48 | * @private 49 | * 50 | * @description 51 | * Returns true if value.toString() results in [object File] or [object Blob]. It is possible to 52 | * get false positives this way, but unlikely. 53 | * 54 | * Since this would also detect File objects, one should be careful when using this. 55 | * 56 | * @param {object} obj value to test if it is a File object. 57 | * 58 | * @returns {boolean} true if the value is determined to be a File or Blob, otherwise false. 59 | */ 60 | function isBlob(obj) { 61 | return obj && BLOB_REGEXP.test(Object.prototype.toString.call(obj)); 62 | } 63 | -------------------------------------------------------------------------------- /examples/list-threads/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inbox SDK Examples --- List Namespaces 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 |
22 |
23 |
24 |
25 |
Threads
26 | 27 | 28 | 29 | 30 | 31 | 32 |
{{thread.subject | shorten}}
33 |
34 |
35 |
36 |
37 |
38 |

{{msg.subject}}

39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /src/angular/module.js: -------------------------------------------------------------------------------- 1 | angular.module('inbox', []). 2 | provider('$inbox', function() { 3 | var config = { 4 | baseUrl: null, 5 | http: { 6 | headers: {}, 7 | withCredentials: false 8 | } 9 | }; 10 | 11 | this.baseUrl = function(value) { 12 | if (arguments.length >= 1) { 13 | config.baseUrl = typeof value === 'string' ? value : null; 14 | return this; 15 | } 16 | return config.baseUrl; 17 | }; 18 | 19 | this.appId = function(value) { 20 | if (arguments.length >= 1) { 21 | config.appId = '' + value; 22 | return this; 23 | } 24 | return config.appId; 25 | }; 26 | 27 | this.withCredentials = function(value) { 28 | if (!arguments.length) { 29 | return config.http.withCredentials; 30 | } 31 | config.http.withCredentials = !!value; 32 | return this; 33 | }; 34 | 35 | this.setRequestHeader = function(header, value) { 36 | if (arguments.length > 1) { 37 | header = ('' + header).toLowerCase(); 38 | if (HEADER_REGEXP.test(header)) { 39 | config.http.headers[header] = value; 40 | } 41 | } 42 | return this; 43 | }; 44 | 45 | this.InboxAPI = InboxAPI; 46 | InboxAPI.INContact = INContact; 47 | InboxAPI.INDraft = INDraft; 48 | InboxAPI.INFile = INFile; 49 | InboxAPI.INMessage = INMessage; 50 | InboxAPI.INNamespace = INNamespace; 51 | InboxAPI.INTag = INTag; 52 | InboxAPI.INThread = INThread; 53 | 54 | this.$get = ['$q', function($q) { 55 | var tempConfig; 56 | var Promise = function(resolver) { 57 | if (typeof resolver !== 'function') { 58 | throw new TypeError('resolver must be a function'); 59 | } 60 | var deferred = $q.defer(); 61 | resolver(function(value) { 62 | deferred.resolve(value); 63 | }, function(reason) { 64 | deferred.reject(reason); 65 | }); 66 | return deferred.promise; 67 | }; 68 | tempConfig = angular.extend({promise: Promise}, config); 69 | return new InboxAPI(tempConfig); 70 | }]; 71 | }); 72 | -------------------------------------------------------------------------------- /src/core/utilities/string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function 3 | * @name formatString 4 | * @private 5 | * 6 | * @description 7 | * Given a template string, replace each `%@` in the string with a stringified value (arguments 8 | * following the template). 9 | * 10 | * E.G, formatString('%@, %@!', 'Hello', 'World') -> 'Hello, World!' 11 | * 12 | * @param {string} template the string template to process. 13 | * @param {...*} args values to replace the instances of `%@` in the template string. 14 | * 15 | * @returns {string} the processed string. 16 | */ 17 | function formatString(template, args) { 18 | var i = 0, ii; 19 | args = Array.prototype.slice.call(arguments, 1); 20 | ii = args.length; 21 | return template.replace(/\%\@/g, function() { 22 | if (i < ii) { 23 | return '' + args[i++]; 24 | } 25 | return ''; 26 | }); 27 | } 28 | 29 | 30 | /** 31 | * @function 32 | * @name endsWith 33 | * @private 34 | * 35 | * @description 36 | * Returns true if a string ends with a given search, otherwise false. 37 | * 38 | * @param {string} str target string which is tested to see if it ends with the search string 39 | * @param {string} search string to sesarch for at the end of the target string. 40 | * 41 | * @returns {boolean} true if the target string ends with the search string, otherwise false 42 | */ 43 | function endsWith(str, search) { 44 | if (typeof str === 'undefined') str = ''; 45 | str = '' + str; 46 | var position = str.length; 47 | position -= search.length; 48 | var lastIndex = str.indexOf(search, position); 49 | return lastIndex !== -1 && lastIndex === position; 50 | } 51 | 52 | var CAPITALIZE_STRING_REGEXP = /(^.)|(\s.)/g; 53 | function capitalizeStringReplacer(c) { 54 | return c.toUpperCase(); 55 | } 56 | 57 | 58 | /** 59 | * @function 60 | * @name capitalizeString 61 | * @private 62 | * 63 | * @description 64 | * Capitalize each word in a string, similar to [NSString capitalizedString] in Foundation. 65 | * 66 | * @param {string} str the string to capitalize 67 | * 68 | * @returns {string} the string, with the first letter of each word capitalized. 69 | */ 70 | function capitalizeString(str) { 71 | // Based on NSString#capitalizeString() 72 | return ('' + str).replace(CAPITALIZE_STRING_REGEXP, capitalizeStringReplacer); 73 | } 74 | -------------------------------------------------------------------------------- /src/core/utilities/json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function 3 | * @name toJSON 4 | * @private 5 | * 6 | * @description 7 | * Stringify an object. Technically, it's possible to stringify strings, but this use case is not 8 | * supported here. 9 | * 10 | * This requires the JSON object to be available. If it's not available, consider using a 11 | * polyfill such as http://bestiejs.github.io/json3/ 12 | * 13 | * @param {object} maybeJSON value to stringify as JSON. 14 | * @param {function=} replacer optional callback for replacing values. 15 | * @param {number=} indent optional indent value, for printing JSON in a pretty fashion. Unused 16 | * by the framework, but may be used to assist in debugging in the future. 17 | * 18 | * @returns {string} the stringified JSON, or unstringified string or function if maybeJSON is 19 | * not an acceptable type. 20 | */ 21 | var toJSON = (function() { 22 | if (window.JSON && typeof window.JSON.stringify === 'function') { 23 | return function(maybeJSON, replacer, indent) { 24 | if (typeof maybeJSON !== 'string' && typeof maybeJSON !== 'function') { 25 | return JSON.stringify(maybeJSON, replacer, indent); 26 | } else { 27 | return maybeJSON; 28 | } 29 | }; 30 | } else { 31 | return function(maybeJSON) { 32 | throw new TypeError('Cannot perform `toJSON` on ' + maybeJSON + ': JSON.stringify not ' + 33 | 'available.'); 34 | }; 35 | } 36 | })(); 37 | 38 | 39 | /** 40 | * @function 41 | * @name parseJSON 42 | * @private 43 | * 44 | * @description 45 | * Parse a JSON string to a value. 46 | * 47 | * This requires the JSON object to be available. If it's not available, consider using a 48 | * polyfill such as http://bestiejs.github.io/json3/ 49 | * 50 | * @param {string} json the JSON string to parse 51 | * @param {function=} reviver optional reviver, unused by the framework. 52 | * 53 | * @returns {object|string|number} the parsed JSON value. 54 | */ 55 | var parseJSON = (function() { 56 | if (window.JSON && typeof window.JSON.parse === 'function') { 57 | return function(json, reviver) { 58 | if (typeof json === 'string') { 59 | if (typeof reviver !== 'function') reviver = null; 60 | return JSON.parse(json, reviver); 61 | } 62 | return json; 63 | }; 64 | } else { 65 | return function(json) { 66 | throw new TypeError('Cannot perform `parseJSON` on ' + json + ': JSON.parse not ' + 67 | 'available.'); 68 | }; 69 | } 70 | })(); 71 | -------------------------------------------------------------------------------- /src/core/utilities/properties.js: -------------------------------------------------------------------------------- 1 | var INVISIBLE = 1; 2 | var CONFIGURABLE = 2; 3 | var WRITABLE = 4; 4 | 5 | 6 | /** 7 | * @function 8 | * @name hasProperty 9 | * @private 10 | * 11 | * @description 12 | * Helper for invoking Object#hasOwnProperty. Older versions of IE have issues calling this on DOM 13 | * objects, and it's also possible to invoke hasOwnProperty on functions using this. 14 | * 15 | * @param {object|function} obj the object to test for the presence of propertyName 16 | * @param {string} propertyName the property name to test for. 17 | * 18 | * @returns {boolean} true if the target object has own property `propertyName`, otherwise false. 19 | */ 20 | function hasProperty(obj, propertyName) { 21 | if (obj === null || obj === undefined) { 22 | return false; 23 | } 24 | return (obj.hasOwnProperty && obj.hasOwnProperty(propertyName)) || 25 | Object.prototype.hasOwnProperty.call(obj, propertyName); 26 | } 27 | 28 | 29 | /** 30 | * @function 31 | * @name defineProperty 32 | * @private 33 | * 34 | * @description 35 | * Helper for defining properties. Typically used for defining data properties, but also enables 36 | * defining getters/setters, though these are not currently used by the framework. 37 | * 38 | * Flags: 39 | * 1. INVISIBLE --- If specified, the property is non-enumerable 40 | * 2. CONFIGURABLE --- If specified, the property is configurable 41 | * 3. WRITABLE --- If specified, the property is writable 42 | * 43 | * @param {object|function} object the target object on which to define properties. 44 | * @param {string} name the property name to define. 45 | * @param {numnber} flags flags --- any bitwise combination of INVISIBLE (1), CONFIGURABLE (2), or 46 | * WRITABLE (4), or 0 for none of the above. 47 | * @param {function} get a function to invoke for getting a property. Not supported in old browsers. 48 | * @param {function} set a function to invoke for setting a property value. Not supported in old 49 | * browsers. 50 | * @param {*} value If specified, it is a data value for the property. 51 | */ 52 | function defineProperty(object, name, flags, get, set, value) { 53 | if (Object.defineProperty) { 54 | var defn = { 55 | enumerable: !(flags & INVISIBLE), 56 | configurable: !!(flags & CONFIGURABLE), 57 | writable: !!(flags & WRITABLE) 58 | }; 59 | if (typeof get === 'function') { 60 | defn.get = get; 61 | if (typeof set === 'function') { 62 | defn.set = set; 63 | } 64 | } else if (arguments.length > 5) { 65 | defn.value = value; 66 | } 67 | Object.defineProperty(object, name, defn); 68 | } else { 69 | if (typeof get === 'function') { 70 | object[name] = get(); 71 | } else if (arguments.length > 5) { 72 | object[name] = value; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/core/utilities/domevents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @function 3 | * @name addListener 4 | * @private 5 | * 6 | * @description 7 | * Adds an event listener to a DOM object. 8 | * 9 | * @param {object} object target of the event (DOM object) 10 | * @param {string} event name of the event, such as 'load'. 11 | * @param {function} handler callback to be invoked in response to the event. 12 | */ 13 | var addListener = (function() { 14 | if (typeof window.addEventListener === 'function') { 15 | return function addEventListener(object, event, handler) { 16 | return object.addEventListener(event, handler); 17 | }; 18 | } else if (typeof window.attachEvent === 'function') { 19 | return function attachEventListener(object, event, handler) { 20 | return object.attachEvent('on' + event, handler); 21 | }; 22 | } else { 23 | return function addListenerUnavailable(object, event) { 24 | throw new TypeError('Unable to add event listener "' + event + '" to object ' + object + 25 | ': addEventListener and attachEvent are unavailable'); 26 | }; 27 | } 28 | })(); 29 | 30 | 31 | /** 32 | * @function 33 | * @name removeListener 34 | * @private 35 | * 36 | * @description 37 | * Removes an event listener to a DOM object. 38 | * 39 | * @param {object} object target of the event (DOM object) 40 | * @param {string} event name of the event, such as 'load'. 41 | * @param {function} handler callback to remove from the target's handlers for the event. 42 | */ 43 | var removeListener = (function() { 44 | if (typeof window.addEventListener === 'function') { 45 | return function removeEventListener(object, event, handler) { 46 | return object.removeEventListener(event, handler); 47 | }; 48 | } else if (typeof window.attachEvent === 'function') { 49 | return function detachEvent(object, event, handler) { 50 | return object.detachEvent('on' + event, handler); 51 | }; 52 | } else { 53 | return function removeListenerUnavailable(object, event) { 54 | throw new TypeError('Unable to add event listener "' + event + '" to object ' + object + 55 | ': removeEventListener and detachEvent are unavailable'); 56 | }; 57 | } 58 | })(); 59 | 60 | 61 | /** 62 | * @function 63 | * @name addListeners 64 | * @private 65 | * 66 | * @description 67 | * For each key/value in object 'listeners', add an event listener for 'key', whose value is the 68 | * handler. 69 | * 70 | * @param {object} object target of the event (DOM object) 71 | * @param {object} listeners object whose keys are event names, and whose values are handlers 72 | * for the respective event name. 73 | */ 74 | function addListeners(object, listeners) { 75 | var key; 76 | for (key in listeners) { 77 | if (listeners.hasOwnProperty(key) && typeof listeners[key] === 'function') { 78 | addListener(object, key, listeners[key]); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/utilities/xmlhttprequest.spec.js: -------------------------------------------------------------------------------- 1 | describe('XMLHttpRequest', function() { 2 | var haveOwnPromise = window.hasOwnProperty('Promise'); 3 | var inbox; 4 | var server; 5 | var XHR = apiRequestPromise; 6 | function valueFn(value) { return value; } 7 | 8 | beforeEach(function() { 9 | window.Promise = mockPromises.getMockPromise(window.Promise); 10 | server = sinon.fakeServer.create(); 11 | inbox = new InboxAPI({ 12 | appId: '', 13 | baseUrl: 'http://api.inboxapp.co/' 14 | }); 15 | }); 16 | 17 | 18 | afterEach(function() { 19 | server.restore(); 20 | if (haveOwnPromise) { 21 | window.Promise = mockPromises.getOriginalPromise(); 22 | } else { 23 | delete window.Promise; 24 | } 25 | }); 26 | 27 | it('should call xhr.setRequestHeader with configured headers matching pattern', function() { 28 | var fulfilled = jasmine.createSpy('load'); 29 | inbox.setRequestHeader('test-header', 'test-value'); 30 | spyOn(sinon.FakeXMLHttpRequest.prototype, 'setRequestHeader').andCallThrough(); 31 | var promise = XHR(inbox, 'get', 'http://api.inboxapp.co/', valueFn).then(fulfilled); 32 | expect(sinon.FakeXMLHttpRequest.prototype.setRequestHeader). 33 | toHaveBeenCalledWith('test-header', 'test-value'); 34 | server.respond([200, { 'Content-Type': 'application/json' }, '{}']); 35 | mockPromises.executeForPromise(promise); 36 | expect(fulfilled).toHaveBeenCalled(); 37 | }); 38 | 39 | 40 | it('should not call xhr.setRequestHeader with badly named headers', function() { 41 | var fulfilled = jasmine.createSpy('load'); 42 | var headers = inbox.http().headers = {}; 43 | headers['[test header]'] = 'test-value'; 44 | spyOn(sinon.FakeXMLHttpRequest.prototype, 'setRequestHeader').andCallThrough(); 45 | var promise = XHR(inbox, 'get', 'http://api.inboxapp.co/', valueFn).then(fulfilled); 46 | server.respond([200, { 'Content-Type': 'application/json' }, '{}']); 47 | mockPromises.executeForPromise(promise); 48 | expect(fulfilled).toHaveBeenCalled(); 49 | expect(sinon.FakeXMLHttpRequest.prototype.setRequestHeader).not.toHaveBeenCalled(); 50 | }); 51 | 52 | 53 | it('should set withCredentials with boolean value of httpConfig.withCredentials', function() { 54 | var fulfilled = jasmine.createSpy('load'); 55 | var send = sinon.FakeXMLHttpRequest.prototype.send; 56 | spyOn(sinon.FakeXMLHttpRequest.prototype, 'send').andCallFake(function() { 57 | expect(this.withCredentials).toBe(true); 58 | send.apply(this, Array.prototype.slice.call(arguments, 0)); 59 | }); 60 | inbox.http('withCredentials', {}); 61 | var promise = XHR(inbox, 'get', 'http://api.inboxapp.co/', valueFn).then(fulfilled); 62 | server.respond([200, { 'Content-Type': 'application/json' }, '{}']); 63 | mockPromises.executeForPromise(promise); 64 | expect(fulfilled).toHaveBeenCalled(); 65 | expect(sinon.FakeXMLHttpRequest.prototype.send).toHaveBeenCalled(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/api/file.spec.js: -------------------------------------------------------------------------------- 1 | describe('INFile', function() { 2 | var haveOwnPromise = window.hasOwnProperty('Promise'); 3 | var mockFile1; 4 | var inbox; 5 | var server; 6 | 7 | beforeEach(function() { 8 | window.Promise = mockPromises.getMockPromise(window.Promise); 9 | server = sinon.fakeServer.create(); 10 | inbox = new InboxAPI({ 11 | appId: '', 12 | baseUrl: 'http://api.inboxapp.co/' 13 | }); 14 | 15 | mockFile1 = { 16 | "id": '84umizq7c4jtrew491brpa6iu', 17 | "filename": "image.jpg", 18 | "namespace": 'fake_namespace_id' 19 | }; 20 | inbox = new InboxAPI({ 21 | appId: '', 22 | baseUrl: 'http://api.inboxapp.co/' 23 | }); 24 | }); 25 | 26 | afterEach(function() { 27 | server.restore(); 28 | if (haveOwnPromise) { 29 | window.Promise = mockPromises.getOriginalPromise(); 30 | } else { 31 | delete window.Promise; 32 | } 33 | }); 34 | 35 | describe('resourceUrl()', function() { 36 | it ('should be null if the model is unsynced', function() { 37 | expect ((new INFile(inbox, null, 'fake_namespace_id')).resourceUrl()).toBe(null); 38 | }); 39 | 40 | it('should be /n//files/', function() { 41 | expect ((new INFile(namespace, mockFile1)).resourceUrl()).toBe('http://api.inboxapp.co/n/fake_namespace_id/files/84umizq7c4jtrew491brpa6iu'); 42 | }); 43 | }); 44 | 45 | 46 | describe('downloadUrl()', function() { 47 | it ('should be null if the model is unsynced', function() { 48 | expect ((new INFile(inbox, null, 'fake_namespace_id')).downloadUrl()).toBe(null); 49 | }); 50 | 51 | it('should be /n//files//download', function() { 52 | expect ((new INFile(namespace, mockFile1)).downloadUrl()).toBe('http://api.inboxapp.co/n/fake_namespace_id/files/84umizq7c4jtrew491brpa6iu/download'); 53 | }); 54 | }); 55 | 56 | 57 | describe('download()', function() { 58 | it ('should return a promise that yields a blob', function () { 59 | var file = new INFile(namespace, mockFile1); 60 | var fulfilled = jasmine.createSpy('load').andCallFake(function(response) { 61 | expect(response instanceof Blob).toBe(true); 62 | }); 63 | var promise = file.download().then(fulfilled); 64 | server.respond([200, { 'Content-Type': 'application/json' }, 'garbage data']); 65 | mockPromises.executeForPromise(promise); 66 | expect(fulfilled).toHaveBeenCalled(); 67 | }); 68 | 69 | it ('should yield a blob with the correct filename', function () { 70 | var file = new INFile(namespace, mockFile1); 71 | var fulfilled = jasmine.createSpy('load').andCallFake(function(blob) { 72 | expect(blob.fileName).toBe(file.filename); 73 | }); 74 | var promise = file.download().then(fulfilled); 75 | server.respond([200, { 'Content-Type': 'application/json' }, 'garbage data']); 76 | mockPromises.executeForPromise(promise); 77 | expect(fulfilled).toHaveBeenCalled(); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /examples/list-threads/app.js: -------------------------------------------------------------------------------- 1 | angular.module('inbox_list_namespaces', ['inbox', 'ngSanitize']). 2 | config(['$inboxProvider', function($inboxProvider) { 3 | $inboxProvider. 4 | baseUrl('http://localhost:5555'). 5 | appId('test'); 6 | }]). 7 | service('$namespaces', ['$inbox', function($inbox) { 8 | var updateId = null, updateRate = null; 9 | var self = this; 10 | self.namespaces = null; 11 | Events(self); 12 | function setNamespaces(value) { 13 | self.namespaces = value; 14 | self.emit('update', value); 15 | } 16 | 17 | function updateList() { 18 | $inbox.namespaces().then(function(namespaces) { 19 | setNamespaces(namespaces); 20 | }, function(error) { 21 | setNamespaces(null); 22 | }); 23 | } 24 | 25 | function clearScheduledUpdate() { 26 | if (updateId !== null) { 27 | clearInterval(updateId); 28 | updateId = null; 29 | } 30 | } 31 | 32 | function updateRate(ms) { 33 | clearScheduledUpdate(); 34 | if (arguments.length > 0) { 35 | updateRate = ms; 36 | } 37 | updateId = setInterval(updateList, updateRate); 38 | } 39 | 40 | self.scheduleUpdate = updateRate; 41 | updateList(); 42 | }]). 43 | filter('shorten', function() { 44 | return function(input) { 45 | if (typeof input === 'string' && input.length > 64) { 46 | return input.substring(0, 60) + ' ...'; 47 | } 48 | return input; 49 | } 50 | }). 51 | controller('threadsCtrl', ['$scope', '$namespaces', function(scope, $namespaces) { 52 | var threads = scope.threads = {}; 53 | var filters = scope.filters = {}; 54 | var self = this; 55 | function loadThreads(namespace, idx) { 56 | var _2WeeksAgo = ((new Date().getTime() - 1209600000) / 1000) >>> 0; 57 | if (!threads[namespace.id]) threads[namespace.id] = []; 58 | namespace.threads(threads[namespace.id], { 59 | lastMessageAfter: _2WeeksAgo, 60 | limit: 1000 61 | }).then(function(threads) { 62 | threads.sort(function(a, b) { 63 | a = a.lastMessageDate.getTime(); 64 | b = b.lastMessageDate.getTime(); 65 | return b - a; 66 | }); 67 | return threads; 68 | }, function(error) { 69 | console.log(error); 70 | }); 71 | } 72 | function update(namespaces) { 73 | if (namespaces) { 74 | var seen = {}; 75 | var key; 76 | for (var i=0; i 2 | 3 | 4 | 5 | Inbox SDK Examples --- Reply to Threads 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 |
36 |
37 |
38 |
39 |
Threads
40 | 41 | 42 | 43 | 44 | 45 | 46 |
{{thread.subject | shorten}}
47 |
48 |
49 |
50 |

Current thread: {{Threads.selectedThreadID}}

51 |
52 |
53 |

{{msg.subject}}

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | #Contributing to Inbox.js 2 | 3 | - [Get the code](#get-the-code) 4 | - [Install dependencies](#install-dependencies) 5 | - [Working with the code](#working-with-the-code) 6 | - [Commit Message Guidelines](#commit-message-guidelines) 7 | 8 | ## Get the code 9 | 10 | ```bash 11 | git clone https://github.com/inboxapp/inbox.js.git 12 | 13 | cd inbox.js 14 | ``` 15 | 16 | ## Install dependencies 17 | 18 | ```bash 19 | # Install devDependencies from NPM 20 | npm install 21 | 22 | # For testing, install an appropriate [karma](http://karma-runner.github.io/) 23 | # browser launcher 24 | npm install karma-chrome-launcher 25 | ``` 26 | 27 | ## Working with the code 28 | 29 | ```bash 30 | # Perform style checks to ensure that the code meets style guidelines 31 | gulp lint 32 | 33 | # Build the frameworks 34 | gulp build 35 | ``` 36 | 37 | ## Running unit tests 38 | 39 | ```bash 40 | # Run unit tests 41 | gulp test 42 | 43 | # Run unit tests with custom browsers 44 | gulp test --browsers 45 | ``` 46 | 47 | ##Commit message guidelines 48 | 49 | Inbox.js is using a commit message scheme based on that used by [angular.js](https://github.com/angular/angular.js) and [conventional-changelog](https://www.npmjs.org/package/conventional-changelog), in order to simplify generation of meaningful changelogs, simplify bisecting to find and fix regressions, and to clarify what a given change has done. 50 | 51 | All contributions should fit this format, and all contributions should be squashed as appropriate. 52 | 53 | ### Commit Message Format 54 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 55 | format that includes a **type**, a **scope** and a **subject**: 56 | 57 | ``` 58 | (): 59 | 60 | 61 | 62 |