├── README.md ├── config.xml └── www ├── config.xml ├── css └── app.css ├── img ├── ionic.png └── sociogram.jpg ├── index.html ├── js ├── app.js ├── controllers.js ├── hmac-sha256.js └── openfb-angular.js ├── lib └── ionic │ ├── css │ ├── ionic.css │ └── ionic.min.css │ ├── fonts │ ├── ionicons.eot │ ├── ionicons.svg │ ├── ionicons.ttf │ └── ionicons.woff │ └── js │ ├── angular-ui │ ├── angular-ui-router.js │ └── angular-ui-router.min.js │ ├── angular │ ├── angular-animate.js │ ├── angular-animate.min.js │ ├── angular-resource.js │ ├── angular-resource.min.js │ ├── angular-sanitize.js │ ├── angular-sanitize.min.js │ ├── angular.js │ └── angular.min.js │ ├── ionic-angular.js │ ├── ionic-angular.min.js │ ├── ionic.bundle.js │ ├── ionic.bundle.min.js │ ├── ionic.js │ └── ionic.min.js ├── oauthcallback.html └── templates ├── all-friend-list.html ├── feed.html ├── friend-list.html ├── login.html ├── logout.html ├── menu.html ├── mutual-friend-list.html ├── person.html ├── profile.html └── share.html /README.md: -------------------------------------------------------------------------------- 1 | # Sociogram for AngularJS / Ionic # 2 | 3 | A sample application that demonstrates a lightweight approach to integrate with Facebook in your AngularJS / Ionic apps. 4 | 5 | *Change Log* 6 | 7 | 1. Added configuration for Facebook App Secret in openfb-angular.js 8 | 9 | 2. Fixed API permission issues with read_stream, publish_stream 10 | 11 | 3. Fixed Mutual Friends/Feeds/Friends GET requests as per latest Facebook API v2.5 12 | 13 | 4. Added 'Taggable Friends' option which shows all the friends from user's friend list 14 | 15 | ## Getting Started ## 16 | 17 | 1. Create an Ionic project 18 | 19 | 2. Add the inappbrowser plugin to your project 20 | 21 | cordova plugins add org.apache.cordova.inappbrowser 22 | 23 | 3. Replace the www folder of the Ionic project with the www folder in this repository 24 | 25 | 4. Create a Facebook app here: https://developers.facebook.com/apps. In the advanced settings, make sure you declare a “Valid OAuth redirect URI”. For example, if during development you access your application from http://localhost/openfb/index.html, you must declare http://localhost/openfb/oauthcallback.html as a valid redirect URI. Also add https://www.facebook.com/connect/login_success.html as a Valid OAuth redirect URI for access from Cordova. 26 | 27 | 5. Copy the Facebook App Id and paste it as the first argument of the OpenFB.init() method invocation in the run() function in app.js. 28 | 29 | 6. To run the app in the browser: Load index.html, from a location that matches the redirect URI you defined above. For example: http://localhost/openfb/index.html 30 | 31 | 7. To run the app in Cordova: Build the Ionic project and run it as a Cordova app on your device 32 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sociogram 4 | 5 | An Ionic Framework and Cordova project. 6 | 7 | 8 | Ionic Framework Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /www/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HelloCordova 4 | 5 | An Ionic Framework and Cordova project. 6 | 7 | 8 | Ionic Framework Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /www/css/app.css: -------------------------------------------------------------------------------- 1 | /* Your app's CSS, go crazy, make it your own */ 2 | 3 | .ionic-logo { 4 | display: block; 5 | margin: 15px auto; 6 | width: 96px; 7 | height: 96px; 8 | } 9 | 10 | .item-body > p { 11 | margin-top: 0; 12 | margin-bottom: 4px; 13 | } -------------------------------------------------------------------------------- /www/img/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrawroh/sociogram-angular-ionic/c1aa94439039f2ed860f2e233cb57403cb414c34/www/img/ionic.png -------------------------------------------------------------------------------- /www/img/sociogram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrawroh/sociogram-angular-ionic/c1aa94439039f2ed860f2e233cb57403cb414c34/www/img/sociogram.jpg -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sociogram 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /www/js/app.js: -------------------------------------------------------------------------------- 1 | angular.module('sociogram', ['ionic', 'openfb', 'sociogram.controllers']) 2 | 3 | .run(function ($rootScope, $state, $ionicPlatform, $window, OpenFB) { 4 | 5 | OpenFB.init('YOUR_APP_ID'); 6 | 7 | $ionicPlatform.ready(function () { 8 | if (window.StatusBar) { 9 | StatusBar.styleDefault(); 10 | } 11 | }); 12 | 13 | $rootScope.$on('$stateChangeStart', function(event, toState) { 14 | if (toState.name !== "app.login" && toState.name !== "app.logout" && !$window.sessionStorage['fbtoken']) { 15 | $state.go('app.login'); 16 | event.preventDefault(); 17 | } 18 | }); 19 | 20 | $rootScope.$on('OAuthException', function() { 21 | $state.go('app.login'); 22 | }); 23 | 24 | }) 25 | 26 | .config(function ($stateProvider, $urlRouterProvider) { 27 | $stateProvider 28 | 29 | .state('app', { 30 | url: "/app", 31 | abstract: true, 32 | templateUrl: "templates/menu.html", 33 | controller: "AppCtrl" 34 | }) 35 | 36 | .state('app.login', { 37 | url: "/login", 38 | views: { 39 | 'menuContent': { 40 | templateUrl: "templates/login.html", 41 | controller: "LoginCtrl" 42 | } 43 | } 44 | }) 45 | 46 | .state('app.logout', { 47 | url: "/logout", 48 | views: { 49 | 'menuContent': { 50 | templateUrl: "templates/logout.html", 51 | controller: "LogoutCtrl" 52 | } 53 | } 54 | }) 55 | 56 | .state('app.profile', { 57 | url: "/profile", 58 | views: { 59 | 'menuContent': { 60 | templateUrl: "templates/profile.html", 61 | controller: "ProfileCtrl" 62 | } 63 | } 64 | }) 65 | 66 | .state('app.share', { 67 | url: "/share", 68 | views: { 69 | 'menuContent': { 70 | templateUrl: "templates/share.html", 71 | controller: "ShareCtrl" 72 | } 73 | } 74 | }) 75 | 76 | .state('app.friends', { 77 | url: "/person/:personId/friends", 78 | views: { 79 | 'menuContent': { 80 | templateUrl: "templates/friend-list.html", 81 | controller: "FriendsCtrl" 82 | } 83 | } 84 | }) 85 | 86 | .state('app.allfriends', { 87 | url: "/person/:personId/allfriends", 88 | views: { 89 | 'menuContent': { 90 | templateUrl: "templates/all-friend-list.html", 91 | controller: "AllFriendsCtrl" 92 | } 93 | } 94 | }) 95 | 96 | .state('app.mutualfriends', { 97 | url: "/person/:personId/mutualfriends", 98 | views: { 99 | 'menuContent': { 100 | templateUrl: "templates/mutual-friend-list.html", 101 | controller: "MutualFriendsCtrl" 102 | } 103 | } 104 | }) 105 | .state('app.person', { 106 | url: "/person/:personId", 107 | views: { 108 | 'menuContent': { 109 | templateUrl: "templates/person.html", 110 | controller: "PersonCtrl" 111 | } 112 | } 113 | }) 114 | .state('app.feed', { 115 | url: "/person/:personId/feed", 116 | views: { 117 | 'menuContent': { 118 | templateUrl: "templates/feed.html", 119 | controller: "FeedCtrl" 120 | } 121 | } 122 | }); 123 | 124 | // fallback route 125 | $urlRouterProvider.otherwise('/app/person/me/feed'); 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /www/js/controllers.js: -------------------------------------------------------------------------------- 1 | angular.module('sociogram.controllers', []) 2 | 3 | .controller('AppCtrl', function ($scope, $state, OpenFB) { 4 | 5 | $scope.logout = function () { 6 | OpenFB.logout(); 7 | $state.go('app.login'); 8 | }; 9 | 10 | $scope.revokePermissions = function () { 11 | OpenFB.revokePermissions().then( 12 | function () { 13 | $state.go('app.login'); 14 | }, 15 | function () { 16 | alert('OpenFB : Revoke Permissions Failed!'); 17 | }); 18 | }; 19 | }) 20 | 21 | .controller('LoginCtrl', function ($scope, $location, OpenFB) { 22 | 23 | $scope.facebookLogin = function () { 24 | 25 | OpenFB.login('email,public_profile,user_friends,user_photos,user_posts,publish_actions,user_birthday').then( 26 | function () { 27 | $location.path('/app/person/me/feed'); 28 | }, 29 | function () { 30 | alert('OpenFB : Login Failed! Please Try Again...'); 31 | }); 32 | }; 33 | }) 34 | 35 | .controller('ShareCtrl', function ($scope, OpenFB) { 36 | 37 | $scope.item = {}; 38 | 39 | $scope.share = function () { 40 | OpenFB.post('/me/feed', $scope.item) 41 | .success(function () { 42 | $scope.status = "OpenFB : Item Shared Successfully!"; 43 | }) 44 | .error(function(data) { 45 | alert(data.error.message); 46 | }); 47 | }; 48 | }) 49 | 50 | .controller('ProfileCtrl', function ($scope, OpenFB) { 51 | OpenFB.get('/me?fields=id,name,email,birthday').success(function (user) { 52 | $scope.user = user; 53 | }); 54 | }) 55 | 56 | .controller('PersonCtrl', function ($scope, $stateParams, OpenFB) { 57 | OpenFB.get('/' + $stateParams.personId + '?fields=id,name,email,birthday').success(function (user) { 58 | $scope.user = user; 59 | }); 60 | }) 61 | 62 | .controller('FriendsCtrl', function ($scope, $stateParams, OpenFB) { 63 | OpenFB.get('/' + $stateParams.personId + '/friends', {limit: 50}) 64 | .success(function (result) { 65 | $scope.friends = result.data; 66 | }) 67 | .error(function(data) { 68 | alert(data.error.message); 69 | }); 70 | }) 71 | 72 | .controller('AllFriendsCtrl', function ($scope, $stateParams, OpenFB) { 73 | OpenFB.get('/' + $stateParams.personId + '/taggable_friends', {limit: 50}) 74 | .success(function (result) { 75 | $scope.friends = result.data; 76 | }) 77 | .error(function(data) { 78 | alert(data.error.message); 79 | }); 80 | }) 81 | 82 | .controller('MutualFriendsCtrl', function ($scope, $stateParams, OpenFB) { 83 | OpenFB.get('/' + $stateParams.personId + '?fields=context.fields%28mutual_friends%29', {limit: 50}) 84 | .success(function (result) { 85 | OpenFB.get('/' + result.context.id, {limit: 50}) 86 | .success(function (result) { 87 | $scope.friends = result.mutual_friends.data; 88 | }) 89 | .error(function(data) { 90 | alert(data.error.message); 91 | }); 92 | }) 93 | .error(function(data) { 94 | alert(data.error.message); 95 | }); 96 | }) 97 | 98 | .controller('FeedCtrl', function ($scope, $stateParams, OpenFB, $ionicLoading) { 99 | 100 | $scope.show = function() { 101 | $scope.loading = $ionicLoading.show({ 102 | content: 'Loading User Feed(s)...' 103 | }); 104 | }; 105 | $scope.hide = function(){ 106 | $scope.loading.hide(); 107 | }; 108 | 109 | function loadFeed() { 110 | $scope.show(); 111 | OpenFB.get('/me/feed', {limit: 30}) 112 | .success(function (result) { 113 | $scope.hide(); 114 | $scope.items = result.data; 115 | // Used with pull-to-refresh 116 | $scope.$broadcast('scroll.refreshComplete'); 117 | }) 118 | .error(function(data) { 119 | $scope.hide(); 120 | alert(data.error.message); 121 | }); 122 | } 123 | 124 | $scope.doRefresh = loadFeed; 125 | 126 | loadFeed(); 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /www/js/hmac-sha256.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 9 | 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, 10 | 2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, 11 | u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= 15 | c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; 16 | d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); 17 | (function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j 0) { 79 | oauthRedirectURL = document.location.href.substring(0, index) + 'oauthcallback.html'; 80 | } else { 81 | return alert("Can't reliably infer the OAuth redirect URI. Please specify it explicitly in openFB.init()"); 82 | } 83 | } 84 | } 85 | 86 | loginWindow = window.open(FB_LOGIN_URL + '?client_id=' + fbAppId + '&redirect_uri=' + oauthRedirectURL + 87 | '&response_type=token&display=popup&scope=' + fbScope, '_blank', 'location=no'); 88 | 89 | // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error 90 | if (runningInCordova) { 91 | loginWindow.addEventListener('loadstart', function (event) { 92 | var url = event.url; 93 | if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) { 94 | loginWindow.close(); 95 | oauthCallback(url); 96 | } 97 | }); 98 | 99 | loginWindow.addEventListener('exit', function () { 100 | // Handle the situation where the user closes the login window manually before completing the login process 101 | deferredLogin.reject({error: 'user_cancelled', error_description: 'User cancelled login process', error_reason: "user_cancelled"}); 102 | }); 103 | } 104 | // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the 105 | // oauthCallback() function. See oauthcallback.html for details. 106 | 107 | return deferredLogin.promise; 108 | 109 | } 110 | 111 | /** 112 | * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event 113 | * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container). 114 | * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the 115 | * OAuth workflow. 116 | */ 117 | function oauthCallback(url) { 118 | // Parse the OAuth data received from Facebook 119 | var queryString, 120 | obj; 121 | 122 | loginProcessed = true; 123 | if (url.indexOf("access_token=") > 0) { 124 | queryString = url.substr(url.indexOf('#') + 1); 125 | obj = parseQueryString(queryString); 126 | tokenStore['fbtoken'] = obj['access_token']; 127 | deferredLogin.resolve(); 128 | } else if (url.indexOf("error=") > 0) { 129 | queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#')); 130 | obj = parseQueryString(queryString); 131 | deferredLogin.reject(obj); 132 | } else { 133 | deferredLogin.reject(); 134 | } 135 | } 136 | 137 | /** 138 | * Application-level logout: we simply discard the token. 139 | */ 140 | function logout() { 141 | tokenStore['fbtoken'] = undefined; 142 | } 143 | 144 | /** 145 | * Helper function to de-authorize the app 146 | * @param success 147 | * @param error 148 | * @returns {*} 149 | */ 150 | function revokePermissions() { 151 | return api({method: 'DELETE', path: '/me/permissions'}) 152 | .success(function () { 153 | console.log('Permissions revoked'); 154 | }); 155 | } 156 | 157 | /** 158 | * Lets you make any Facebook Graph API request. 159 | * @param obj - Request configuration object. Can include: 160 | * method: HTTP method: GET, POST, etc. Optional - Default is 'GET' 161 | * path: path in the Facebook graph: /me, /me.friends, etc. - Required 162 | * params: queryString parameters as a map - Optional 163 | */ 164 | function api(obj) { 165 | 166 | var method = obj.method || 'GET', 167 | params = obj.params || {}; 168 | 169 | params['access_token'] = tokenStore['fbtoken']; 170 | var hash = CryptoJS.HmacSHA256(tokenStore['fbtoken'],"YOUR_APP_SECRET"); 171 | params['appsecret_proof'] = hash.toString(); 172 | 173 | return $http({method: method, url: 'https://graph.facebook.com' + obj.path, params: params}) 174 | .error(function(data, status, headers, config) { 175 | if (data.error && data.error.type === 'OAuthException') { 176 | $rootScope.$emit('OAuthException'); 177 | } 178 | }); 179 | } 180 | 181 | /** 182 | * Helper function for a POST call into the Graph API 183 | * @param path 184 | * @param params 185 | * @returns {*} 186 | */ 187 | function post(path, params) { 188 | return api({method: 'POST', path: path, params: params}); 189 | } 190 | 191 | /** 192 | * Helper function for a GET call into the Graph API 193 | * @param path 194 | * @param params 195 | * @returns {*} 196 | */ 197 | function get(path, params) { 198 | return api({method: 'GET', path: path, params: params}); 199 | } 200 | 201 | function parseQueryString(queryString) { 202 | var qs = decodeURIComponent(queryString), 203 | obj = {}, 204 | params = qs.split('&'); 205 | params.forEach(function (param) { 206 | var splitter = param.split('='); 207 | obj[splitter[0]] = splitter[1]; 208 | }); 209 | return obj; 210 | } 211 | 212 | return { 213 | init: init, 214 | login: login, 215 | logout: logout, 216 | revokePermissions: revokePermissions, 217 | api: api, 218 | post: post, 219 | get: get, 220 | oauthCallback: oauthCallback 221 | } 222 | 223 | }); 224 | 225 | // Global function called back by the OAuth login dialog 226 | function oauthCallback(url) { 227 | var injector = angular.element(document.getElementById('main')).injector(); 228 | injector.invoke(function (OpenFB) { 229 | OpenFB.oauthCallback(url); 230 | }); 231 | } 232 | -------------------------------------------------------------------------------- /www/lib/ionic/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrawroh/sociogram-angular-ionic/c1aa94439039f2ed860f2e233cb57403cb414c34/www/lib/ionic/fonts/ionicons.eot -------------------------------------------------------------------------------- /www/lib/ionic/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrawroh/sociogram-angular-ionic/c1aa94439039f2ed860f2e233cb57403cb414c34/www/lib/ionic/fonts/ionicons.ttf -------------------------------------------------------------------------------- /www/lib/ionic/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrawroh/sociogram-angular-ionic/c1aa94439039f2ed860f2e233cb57403cb414c34/www/lib/ionic/fonts/ionicons.woff -------------------------------------------------------------------------------- /www/lib/ionic/js/angular-ui/angular-ui-router.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State-based routing for AngularJS 3 | * @version v0.2.7 4 | * @link http://angular-ui.github.com/ 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | */ 7 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return E(new(E(function(){},{prototype:a})),b)}function e(a){return D(arguments,function(b){b!==a&&D(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path)if(""!==a.path[d]){if(!b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return E({},i,b)}function i(a,b){var c={};return D(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,A(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);D(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return B(a)&&a.then&&a.$$promises}if(!B(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return D(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!y(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;D(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!B(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=E({},d),s=1+m.length/3,t=!1;if(y(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(E(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return y(a.template)?this.fromString(a.template,b):y(a.templateUrl)?this.fromUrl(a.templateUrl,b):y(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return z(a)?a(b):a},this.fromUrl=function(c,d){return z(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),D(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return B(a)&&z(a.exec)&&z(a.format)&&z(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return y(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!z(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(A(a)){var b=a;a=function(){return b}}else if(!z(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=A(f);if(A(e)&&(e=a.compile(e)),!h&&!z(f)&&!C(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),E(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:A(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),E(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(A(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=A(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=u[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){v[a]||(v[a]=[]),v[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!A(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(u.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):A(b.parent)?b.parent:"";if(e&&!u[e])return m(e,b.self);for(var f in x)z(x[f])&&(b[f]=x[f](b,x.$delegates[f]));if(u[c]=b,!b[w]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){t.$current.navigable==b&&j(a,c)||t.transitionTo(b,a,{location:!1})}]),v[c])for(var g=0;g=I;d--)g=u[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=I;d").html(a).contents();return r.enter(d,c),d}},"false":{remove:function(a){a.html("")},restore:function(a,b){b.append(a)},populate:function(a,b){return b.html(a),b.contents()}}}[a.toString()]};j.append(s);var u=j.parent().inheritedData("$uiView");p.indexOf("@")<0&&(p=p+"@"+(u?u.state.name:""));var v={name:p,state:null};j.data("$uiView",v);var w=function(){if(!h){h=!0;try{m(!0)}catch(a){throw h=!1,a}h=!1}};e.$on("$stateChangeSuccess",w),e.$on("$viewContentLoading",w),m(!1)}}};return i}function t(a){var b=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/);if(!b||4!==b.length)throw new Error("Invalid state ref '"+a+"'");return{state:b[1],paramExpr:b[3]||null}}function u(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function v(a,b){return{restrict:"A",require:"?^uiSrefActive",link:function(c,d,e,f){var g=t(e.uiSref),h=null,i=u(d)||a.$current,j="FORM"===d[0].nodeName,k=j?"action":"href",l=!0,m=function(b){if(b&&(h=b),l){var c=a.href(g.state,h,{relative:i});if(!c)return l=!1,!1;d[0][k]=c,f&&f.$$setStateInfo(g.state,h)}};g.paramExpr&&(c.$watch(g.paramExpr,function(a){a!==h&&m(a)},!0),h=c.$eval(g.paramExpr)),m(),j||d.bind("click",function(d){var e=d.which||d.button;0!==e&&1!=e||d.ctrlKey||d.metaKey||d.shiftKey||(b(function(){c.$apply(function(){a.go(g.state,h,{relative:i})})}),d.preventDefault())})}}}function w(a,b,c){return{restrict:"A",controller:function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,u(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}}}function x(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(A(j))h=j;else{if(!z(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),F(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var y=b.isDefined,z=b.isFunction,A=b.isString,B=b.isObject,C=b.isArray,D=b.forEach,E=b.extend,F=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),s.$inject=["$state","$compile","$controller","$injector","$anchorScroll"],b.module("ui.router.state").directive("uiView",s),v.$inject=["$state","$timeout"],w.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",v).directive("uiSrefActive",w),x.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",x).directive("ngView",s)}(window,window.angular); 8 | -------------------------------------------------------------------------------- /www/lib/ionic/js/angular/angular-animate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.12 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(v,k,t){'use strict';k.module("ngAnimate",["ng"]).factory("$$animateReflow",["$window","$timeout",function(k,B){var d=k.requestAnimationFrame||k.webkitRequestAnimationFrame||function(d){return B(d,10,!1)},q=k.cancelAnimationFrame||k.webkitCancelAnimationFrame||function(d){return B.cancel(d)};return function(p){var k=d(p);return function(){q(k)}}}]).config(["$provide","$animateProvider",function(R,B){function d(d){for(var k=0;k=u&&a>=p&&h()}var f=b.data(n),g=d(b);if(-1!=g.className.indexOf(a)&&f){var l=f.timings,m=f.stagger,p=f.maxDuration,r=f.activeClassName,u=Math.max(l.transitionDelay, 19 | l.animationDelay)*x,w=Date.now(),v=T+" "+S,t=f.itemIndex,q="",s=[];if(0 64 | * 65 | * See {@link ngResource.$resource `$resource`} for usage. 66 | */ 67 | 68 | /** 69 | * @ngdoc object 70 | * @name ngResource.$resource 71 | * @requires $http 72 | * 73 | * @description 74 | * A factory which creates a resource object that lets you interact with 75 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 76 | * 77 | * The returned resource object has action methods which provide high-level behaviors without 78 | * the need to interact with the low level {@link ng.$http $http} service. 79 | * 80 | * Requires the {@link ngResource `ngResource`} module to be installed. 81 | * 82 | * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 83 | * `/user/:username`. If you are using a URL with a port number (e.g. 84 | * `http://example.com:8080/api`), it will be respected. 85 | * 86 | * If you are using a url with a suffix, just add the suffix, like this: 87 | * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` 88 | * or even `$resource('http://example.com/resource/:resource_id.:format')` 89 | * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 90 | * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 91 | * can escape it with `/\.`. 92 | * 93 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 94 | * `actions` methods. If any of the parameter value is a function, it will be executed every time 95 | * when a param value needs to be obtained for a request (unless the param was overridden). 96 | * 97 | * Each key value in the parameter object is first bound to url template if present and then any 98 | * excess keys are appended to the url search query after the `?`. 99 | * 100 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 101 | * URL `/path/greet?salutation=Hello`. 102 | * 103 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 104 | * the data object (useful for non-GET operations). 105 | * 106 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 107 | * default set of resource actions. The declaration should be created in the format of {@link 108 | * ng.$http#usage_parameters $http.config}: 109 | * 110 | * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 111 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 112 | * ...} 113 | * 114 | * Where: 115 | * 116 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on 117 | * your resource object. 118 | * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, 119 | * `DELETE`, and `JSONP`. 120 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of 121 | * the parameter value is a function, it will be executed every time when a param value needs to 122 | * be obtained for a request (unless the param was overridden). 123 | * - **`url`** – {string} – action specific `url` override. The url templating is supported just 124 | * like for the resource-level urls. 125 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, 126 | * see `returns` section. 127 | * - **`transformRequest`** – 128 | * `{function(data, headersGetter)|Array.}` – 129 | * transform function or an array of such functions. The transform function takes the http 130 | * request body and headers and returns its transformed (typically serialized) version. 131 | * - **`transformResponse`** – 132 | * `{function(data, headersGetter)|Array.}` – 133 | * transform function or an array of such functions. The transform function takes the http 134 | * response body and headers and returns its transformed (typically deserialized) version. 135 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the 136 | * GET request, otherwise if a cache instance built with 137 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 138 | * caching. 139 | * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that 140 | * should abort the request when resolved. 141 | * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the 142 | * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 143 | * requests with credentials} for more information. 144 | * - **`responseType`** - `{string}` - see {@link 145 | * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 146 | * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - 147 | * `response` and `responseError`. Both `response` and `responseError` interceptors get called 148 | * with `http response` object. See {@link ng.$http $http interceptors}. 149 | * 150 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 151 | * optionally extended with custom `actions`. The default set contains these actions: 152 | * 153 | * { 'get': {method:'GET'}, 154 | * 'save': {method:'POST'}, 155 | * 'query': {method:'GET', isArray:true}, 156 | * 'remove': {method:'DELETE'}, 157 | * 'delete': {method:'DELETE'} }; 158 | * 159 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 160 | * destination and parameters. When the data is returned from the server then the object is an 161 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 162 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 163 | * read, update, delete) on server-side data like this: 164 | *
165 |         var User = $resource('/user/:userId', {userId:'@id'});
166 |         var user = User.get({userId:123}, function() {
167 |           user.abc = true;
168 |           user.$save();
169 |         });
170 |      
171 | * 172 | * It is important to realize that invoking a $resource object method immediately returns an 173 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 174 | * server the existing reference is populated with the actual data. This is a useful trick since 175 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 176 | * object results in no rendering, once the data arrives from the server then the object is 177 | * populated with the data and the view automatically re-renders itself showing the new data. This 178 | * means that in most cases one never has to write a callback function for the action methods. 179 | * 180 | * The action methods on the class object or instance object can be invoked with the following 181 | * parameters: 182 | * 183 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 184 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 185 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 186 | * 187 | * Success callback is called with (value, responseHeaders) arguments. Error callback is called 188 | * with (httpResponse) argument. 189 | * 190 | * Class actions return empty instance (with additional properties below). 191 | * Instance actions return promise of the action. 192 | * 193 | * The Resource instances and collection have these additional properties: 194 | * 195 | * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this 196 | * instance or collection. 197 | * 198 | * On success, the promise is resolved with the same resource instance or collection object, 199 | * updated with data from server. This makes it easy to use in 200 | * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view 201 | * rendering until the resource(s) are loaded. 202 | * 203 | * On failure, the promise is resolved with the {@link ng.$http http response} object, without 204 | * the `resource` property. 205 | * 206 | * - `$resolved`: `true` after first server interaction is completed (either with success or 207 | * rejection), `false` before that. Knowing if the Resource has been resolved is useful in 208 | * data-binding. 209 | * 210 | * @example 211 | * 212 | * # Credit card resource 213 | * 214 | *
215 |      // Define CreditCard class
216 |      var CreditCard = $resource('/user/:userId/card/:cardId',
217 |       {userId:123, cardId:'@id'}, {
218 |        charge: {method:'POST', params:{charge:true}}
219 |       });
220 | 
221 |      // We can retrieve a collection from the server
222 |      var cards = CreditCard.query(function() {
223 |        // GET: /user/123/card
224 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
225 | 
226 |        var card = cards[0];
227 |        // each item is an instance of CreditCard
228 |        expect(card instanceof CreditCard).toEqual(true);
229 |        card.name = "J. Smith";
230 |        // non GET methods are mapped onto the instances
231 |        card.$save();
232 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
233 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
234 | 
235 |        // our custom method is mapped as well.
236 |        card.$charge({amount:9.99});
237 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
238 |      });
239 | 
240 |      // we can create an instance as well
241 |      var newCard = new CreditCard({number:'0123'});
242 |      newCard.name = "Mike Smith";
243 |      newCard.$save();
244 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
245 |      // server returns: {id:789, number:'0123', name: 'Mike Smith'};
246 |      expect(newCard.id).toEqual(789);
247 |  * 
248 | * 249 | * The object returned from this function execution is a resource "class" which has "static" method 250 | * for each action in the definition. 251 | * 252 | * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and 253 | * `headers`. 254 | * When the data is returned from the server then the object is an instance of the resource type and 255 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 256 | * operations (create, read, update, delete) on server-side data. 257 | 258 |
259 |      var User = $resource('/user/:userId', {userId:'@id'});
260 |      var user = User.get({userId:123}, function() {
261 |        user.abc = true;
262 |        user.$save();
263 |      });
264 |    
265 | * 266 | * It's worth noting that the success callback for `get`, `query` and other methods gets passed 267 | * in the response that came from the server as well as $http header getter function, so one 268 | * could rewrite the above example and get access to http headers as: 269 | * 270 |
271 |      var User = $resource('/user/:userId', {userId:'@id'});
272 |      User.get({userId:123}, function(u, getResponseHeaders){
273 |        u.abc = true;
274 |        u.$save(function(u, putResponseHeaders) {
275 |          //u => saved user object
276 |          //putResponseHeaders => $http header getter
277 |        });
278 |      });
279 |    
280 | 281 | * # Creating a custom 'PUT' request 282 | * In this example we create a custom method on our resource to make a PUT request 283 | *
284 |  *		var app = angular.module('app', ['ngResource', 'ngRoute']);
285 |  *
286 |  *		// Some APIs expect a PUT request in the format URL/object/ID
287 |  *		// Here we are creating an 'update' method 
288 |  *		app.factory('Notes', ['$resource', function($resource) {
289 |  *    return $resource('/notes/:id', null,
290 |  *        {
291 |  *            'update': { method:'PUT' }
292 |  *        });
293 |  *		}]);
294 |  *
295 |  *		// In our controller we get the ID from the URL using ngRoute and $routeParams
296 |  *		// We pass in $routeParams and our Notes factory along with $scope
297 |  *		app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
298 |                                       function($scope, $routeParams, Notes) {
299 |  *    // First get a note object from the factory
300 |  *    var note = Notes.get({ id:$routeParams.id });
301 |  *    $id = note.id;
302 |  *
303 |  *    // Now call update passing in the ID first then the object you are updating
304 |  *    Notes.update({ id:$id }, note);
305 |  *
306 |  *    // This will PUT /notes/ID with the note object in the request payload
307 |  *		}]);
308 |  * 
309 | */ 310 | angular.module('ngResource', ['ng']). 311 | factory('$resource', ['$http', '$q', function($http, $q) { 312 | 313 | var DEFAULT_ACTIONS = { 314 | 'get': {method:'GET'}, 315 | 'save': {method:'POST'}, 316 | 'query': {method:'GET', isArray:true}, 317 | 'remove': {method:'DELETE'}, 318 | 'delete': {method:'DELETE'} 319 | }; 320 | var noop = angular.noop, 321 | forEach = angular.forEach, 322 | extend = angular.extend, 323 | copy = angular.copy, 324 | isFunction = angular.isFunction; 325 | 326 | /** 327 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 328 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 329 | * segments: 330 | * segment = *pchar 331 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 332 | * pct-encoded = "%" HEXDIG HEXDIG 333 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 334 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 335 | * / "*" / "+" / "," / ";" / "=" 336 | */ 337 | function encodeUriSegment(val) { 338 | return encodeUriQuery(val, true). 339 | replace(/%26/gi, '&'). 340 | replace(/%3D/gi, '='). 341 | replace(/%2B/gi, '+'); 342 | } 343 | 344 | 345 | /** 346 | * This method is intended for encoding *key* or *value* parts of query component. We need a 347 | * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't 348 | * have to be encoded per http://tools.ietf.org/html/rfc3986: 349 | * query = *( pchar / "/" / "?" ) 350 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 351 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 352 | * pct-encoded = "%" HEXDIG HEXDIG 353 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 354 | * / "*" / "+" / "," / ";" / "=" 355 | */ 356 | function encodeUriQuery(val, pctEncodeSpaces) { 357 | return encodeURIComponent(val). 358 | replace(/%40/gi, '@'). 359 | replace(/%3A/gi, ':'). 360 | replace(/%24/g, '$'). 361 | replace(/%2C/gi, ','). 362 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 363 | } 364 | 365 | function Route(template, defaults) { 366 | this.template = template; 367 | this.defaults = defaults || {}; 368 | this.urlParams = {}; 369 | } 370 | 371 | Route.prototype = { 372 | setUrlParams: function(config, params, actionUrl) { 373 | var self = this, 374 | url = actionUrl || self.template, 375 | val, 376 | encodedVal; 377 | 378 | var urlParams = self.urlParams = {}; 379 | forEach(url.split(/\W/), function(param){ 380 | if (param === 'hasOwnProperty') { 381 | throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); 382 | } 383 | if (!(new RegExp("^\\d+$").test(param)) && param && 384 | (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 385 | urlParams[param] = true; 386 | } 387 | }); 388 | url = url.replace(/\\:/g, ':'); 389 | 390 | params = params || {}; 391 | forEach(self.urlParams, function(_, urlParam){ 392 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 393 | if (angular.isDefined(val) && val !== null) { 394 | encodedVal = encodeUriSegment(val); 395 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { 396 | return encodedVal + p1; 397 | }); 398 | } else { 399 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 400 | leadingSlashes, tail) { 401 | if (tail.charAt(0) == '/') { 402 | return tail; 403 | } else { 404 | return leadingSlashes + tail; 405 | } 406 | }); 407 | } 408 | }); 409 | 410 | // strip trailing slashes and set the url 411 | url = url.replace(/\/+$/, '') || '/'; 412 | // then replace collapse `/.` if found in the last URL path segment before the query 413 | // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` 414 | url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 415 | // replace escaped `/\.` with `/.` 416 | config.url = url.replace(/\/\\\./, '/.'); 417 | 418 | 419 | // set params - delegate param encoding to $http 420 | forEach(params, function(value, key){ 421 | if (!self.urlParams[key]) { 422 | config.params = config.params || {}; 423 | config.params[key] = value; 424 | } 425 | }); 426 | } 427 | }; 428 | 429 | 430 | function resourceFactory(url, paramDefaults, actions) { 431 | var route = new Route(url); 432 | 433 | actions = extend({}, DEFAULT_ACTIONS, actions); 434 | 435 | function extractParams(data, actionParams){ 436 | var ids = {}; 437 | actionParams = extend({}, paramDefaults, actionParams); 438 | forEach(actionParams, function(value, key){ 439 | if (isFunction(value)) { value = value(); } 440 | ids[key] = value && value.charAt && value.charAt(0) == '@' ? 441 | lookupDottedPath(data, value.substr(1)) : value; 442 | }); 443 | return ids; 444 | } 445 | 446 | function defaultResponseInterceptor(response) { 447 | return response.resource; 448 | } 449 | 450 | function Resource(value){ 451 | shallowClearAndCopy(value || {}, this); 452 | } 453 | 454 | forEach(actions, function(action, name) { 455 | var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); 456 | 457 | Resource[name] = function(a1, a2, a3, a4) { 458 | var params = {}, data, success, error; 459 | 460 | /* jshint -W086 */ /* (purposefully fall through case statements) */ 461 | switch(arguments.length) { 462 | case 4: 463 | error = a4; 464 | success = a3; 465 | //fallthrough 466 | case 3: 467 | case 2: 468 | if (isFunction(a2)) { 469 | if (isFunction(a1)) { 470 | success = a1; 471 | error = a2; 472 | break; 473 | } 474 | 475 | success = a2; 476 | error = a3; 477 | //fallthrough 478 | } else { 479 | params = a1; 480 | data = a2; 481 | success = a3; 482 | break; 483 | } 484 | case 1: 485 | if (isFunction(a1)) success = a1; 486 | else if (hasBody) data = a1; 487 | else params = a1; 488 | break; 489 | case 0: break; 490 | default: 491 | throw $resourceMinErr('badargs', 492 | "Expected up to 4 arguments [params, data, success, error], got {0} arguments", 493 | arguments.length); 494 | } 495 | /* jshint +W086 */ /* (purposefully fall through case statements) */ 496 | 497 | var isInstanceCall = this instanceof Resource; 498 | var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); 499 | var httpConfig = {}; 500 | var responseInterceptor = action.interceptor && action.interceptor.response || 501 | defaultResponseInterceptor; 502 | var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || 503 | undefined; 504 | 505 | forEach(action, function(value, key) { 506 | if (key != 'params' && key != 'isArray' && key != 'interceptor') { 507 | httpConfig[key] = copy(value); 508 | } 509 | }); 510 | 511 | if (hasBody) httpConfig.data = data; 512 | route.setUrlParams(httpConfig, 513 | extend({}, extractParams(data, action.params || {}), params), 514 | action.url); 515 | 516 | var promise = $http(httpConfig).then(function(response) { 517 | var data = response.data, 518 | promise = value.$promise; 519 | 520 | if (data) { 521 | // Need to convert action.isArray to boolean in case it is undefined 522 | // jshint -W018 523 | if (angular.isArray(data) !== (!!action.isArray)) { 524 | throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + 525 | 'response to contain an {0} but got an {1}', 526 | action.isArray?'array':'object', angular.isArray(data)?'array':'object'); 527 | } 528 | // jshint +W018 529 | if (action.isArray) { 530 | value.length = 0; 531 | forEach(data, function(item) { 532 | value.push(new Resource(item)); 533 | }); 534 | } else { 535 | shallowClearAndCopy(data, value); 536 | value.$promise = promise; 537 | } 538 | } 539 | 540 | value.$resolved = true; 541 | 542 | response.resource = value; 543 | 544 | return response; 545 | }, function(response) { 546 | value.$resolved = true; 547 | 548 | (error||noop)(response); 549 | 550 | return $q.reject(response); 551 | }); 552 | 553 | promise = promise.then( 554 | function(response) { 555 | var value = responseInterceptor(response); 556 | (success||noop)(value, response.headers); 557 | return value; 558 | }, 559 | responseErrorInterceptor); 560 | 561 | if (!isInstanceCall) { 562 | // we are creating instance / collection 563 | // - set the initial promise 564 | // - return the instance / collection 565 | value.$promise = promise; 566 | value.$resolved = false; 567 | 568 | return value; 569 | } 570 | 571 | // instance call 572 | return promise; 573 | }; 574 | 575 | 576 | Resource.prototype['$' + name] = function(params, success, error) { 577 | if (isFunction(params)) { 578 | error = success; success = params; params = {}; 579 | } 580 | var result = Resource[name].call(this, params, this, success, error); 581 | return result.$promise || result; 582 | }; 583 | }); 584 | 585 | Resource.bind = function(additionalParamDefaults){ 586 | return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 587 | }; 588 | 589 | return Resource; 590 | } 591 | 592 | return resourceFactory; 593 | }]); 594 | 595 | 596 | })(window, window.angular); 597 | -------------------------------------------------------------------------------- /www/lib/ionic/js/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.12 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(H,a,A){'use strict';function D(p,g){g=g||{};a.forEach(g,function(a,c){delete g[c]});for(var c in p)!p.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(g[c]=p[c]);return g}var v=a.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;a.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(p,g){function c(a,c){this.template=a;this.defaults=c||{};this.urlParams={}}function t(n,w,l){function r(h,d){var e={};d=x({},w,d);s(d,function(b,d){u(b)&&(b=b());var k;if(b&& 7 | b.charAt&&"@"==b.charAt(0)){k=h;var a=b.substr(1);if(null==a||""===a||"hasOwnProperty"===a||!C.test("."+a))throw v("badmember",a);for(var a=a.split("."),f=0,c=a.length;f 22 | * 23 | * See {@link ngSanitize.$sanitize `$sanitize`} for usage. 24 | */ 25 | 26 | /* 27 | * HTML Parser By Misko Hevery (misko@hevery.com) 28 | * based on: HTML Parser By John Resig (ejohn.org) 29 | * Original code by Erik Arvidsson, Mozilla Public License 30 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 31 | * 32 | * // Use like so: 33 | * htmlParser(htmlString, { 34 | * start: function(tag, attrs, unary) {}, 35 | * end: function(tag) {}, 36 | * chars: function(text) {}, 37 | * comment: function(text) {} 38 | * }); 39 | * 40 | */ 41 | 42 | 43 | /** 44 | * @ngdoc service 45 | * @name ngSanitize.$sanitize 46 | * @function 47 | * 48 | * @description 49 | * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 50 | * then serialized back to properly escaped html string. This means that no unsafe input can make 51 | * it into the returned string, however, since our parser is more strict than a typical browser 52 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 53 | * browser, won't make it through the sanitizer. 54 | * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and 55 | * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. 56 | * 57 | * @param {string} html Html input. 58 | * @returns {string} Sanitized html. 59 | * 60 | * @example 61 | 62 | 63 | 74 |
75 | Snippet: 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
DirectiveHowSourceRendered
ng-bind-htmlAutomatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value 93 |
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
 94 | </div>
95 |
ng-bindAutomatically escapes
<div ng-bind="snippet">
</div>
105 |
106 |
107 | 108 | it('should sanitize the html snippet by default', function() { 109 | expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 110 | toBe('

an html\nclick here\nsnippet

'); 111 | }); 112 | 113 | it('should inline raw snippet if bound to a trusted value', function() { 114 | expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). 115 | toBe("

an html\n" + 116 | "click here\n" + 117 | "snippet

"); 118 | }); 119 | 120 | it('should escape snippet without any filter', function() { 121 | expect(element(by.css('#bind-default div')).getInnerHtml()). 122 | toBe("<p style=\"color:blue\">an html\n" + 123 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 124 | "snippet</p>"); 125 | }); 126 | 127 | it('should update', function() { 128 | element(by.model('snippet')).clear(); 129 | element(by.model('snippet')).sendKeys('new text'); 130 | expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 131 | toBe('new text'); 132 | expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( 133 | 'new text'); 134 | expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( 135 | "new <b onclick=\"alert(1)\">text</b>"); 136 | }); 137 |
138 |
139 | */ 140 | function $SanitizeProvider() { 141 | this.$get = ['$$sanitizeUri', function($$sanitizeUri) { 142 | return function(html) { 143 | var buf = []; 144 | htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { 145 | return !/^unsafe/.test($$sanitizeUri(uri, isImage)); 146 | })); 147 | return buf.join(''); 148 | }; 149 | }]; 150 | } 151 | 152 | function sanitizeText(chars) { 153 | var buf = []; 154 | var writer = htmlSanitizeWriter(buf, angular.noop); 155 | writer.chars(chars); 156 | return buf.join(''); 157 | } 158 | 159 | 160 | // Regular Expressions for parsing tags and attributes 161 | var START_TAG_REGEXP = 162 | /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, 163 | END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, 164 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 165 | BEGIN_TAG_REGEXP = /^/g, 168 | DOCTYPE_REGEXP = /]*?)>/i, 169 | CDATA_REGEXP = //g, 170 | // Match everything outside of normal chars and " (quote character) 171 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; 172 | 173 | 174 | // Good source of info about elements and attributes 175 | // http://dev.w3.org/html5/spec/Overview.html#semantics 176 | // http://simon.html5.org/html-elements 177 | 178 | // Safe Void Elements - HTML5 179 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 180 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 181 | 182 | // Elements that you can, intentionally, leave open (and which close themselves) 183 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 184 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 185 | optionalEndTagInlineElements = makeMap("rp,rt"), 186 | optionalEndTagElements = angular.extend({}, 187 | optionalEndTagInlineElements, 188 | optionalEndTagBlockElements); 189 | 190 | // Safe Block Elements - HTML5 191 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + 192 | "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + 193 | "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 194 | 195 | // Inline Elements - HTML5 196 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + 197 | "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + 198 | "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); 199 | 200 | 201 | // Special Elements (can contain anything) 202 | var specialElements = makeMap("script,style"); 203 | 204 | var validElements = angular.extend({}, 205 | voidElements, 206 | blockElements, 207 | inlineElements, 208 | optionalEndTagElements); 209 | 210 | //Attributes that have href and hence need to be sanitized 211 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); 212 | var validAttrs = angular.extend({}, uriAttrs, makeMap( 213 | 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ 214 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 215 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 216 | 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+ 217 | 'valign,value,vspace,width')); 218 | 219 | function makeMap(str) { 220 | var obj = {}, items = str.split(','), i; 221 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 222 | return obj; 223 | } 224 | 225 | 226 | /** 227 | * @example 228 | * htmlParser(htmlString, { 229 | * start: function(tag, attrs, unary) {}, 230 | * end: function(tag) {}, 231 | * chars: function(text) {}, 232 | * comment: function(text) {} 233 | * }); 234 | * 235 | * @param {string} html string 236 | * @param {object} handler 237 | */ 238 | function htmlParser( html, handler ) { 239 | var index, chars, match, stack = [], last = html; 240 | stack.last = function() { return stack[ stack.length - 1 ]; }; 241 | 242 | while ( html ) { 243 | chars = true; 244 | 245 | // Make sure we're not in a script or style element 246 | if ( !stack.last() || !specialElements[ stack.last() ] ) { 247 | 248 | // Comment 249 | if ( html.indexOf("", index) === index) { 254 | if (handler.comment) handler.comment( html.substring( 4, index ) ); 255 | html = html.substring( index + 3 ); 256 | chars = false; 257 | } 258 | // DOCTYPE 259 | } else if ( DOCTYPE_REGEXP.test(html) ) { 260 | match = html.match( DOCTYPE_REGEXP ); 261 | 262 | if ( match ) { 263 | html = html.replace( match[0] , ''); 264 | chars = false; 265 | } 266 | // end tag 267 | } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 268 | match = html.match( END_TAG_REGEXP ); 269 | 270 | if ( match ) { 271 | html = html.substring( match[0].length ); 272 | match[0].replace( END_TAG_REGEXP, parseEndTag ); 273 | chars = false; 274 | } 275 | 276 | // start tag 277 | } else if ( BEGIN_TAG_REGEXP.test(html) ) { 278 | match = html.match( START_TAG_REGEXP ); 279 | 280 | if ( match ) { 281 | html = html.substring( match[0].length ); 282 | match[0].replace( START_TAG_REGEXP, parseStartTag ); 283 | chars = false; 284 | } 285 | } 286 | 287 | if ( chars ) { 288 | index = html.indexOf("<"); 289 | 290 | var text = index < 0 ? html : html.substring( 0, index ); 291 | html = index < 0 ? "" : html.substring( index ); 292 | 293 | if (handler.chars) handler.chars( decodeEntities(text) ); 294 | } 295 | 296 | } else { 297 | html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), 298 | function(all, text){ 299 | text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); 300 | 301 | if (handler.chars) handler.chars( decodeEntities(text) ); 302 | 303 | return ""; 304 | }); 305 | 306 | parseEndTag( "", stack.last() ); 307 | } 308 | 309 | if ( html == last ) { 310 | throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + 311 | "of html: {0}", html); 312 | } 313 | last = html; 314 | } 315 | 316 | // Clean up any remaining tags 317 | parseEndTag(); 318 | 319 | function parseStartTag( tag, tagName, rest, unary ) { 320 | tagName = angular.lowercase(tagName); 321 | if ( blockElements[ tagName ] ) { 322 | while ( stack.last() && inlineElements[ stack.last() ] ) { 323 | parseEndTag( "", stack.last() ); 324 | } 325 | } 326 | 327 | if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 328 | parseEndTag( "", tagName ); 329 | } 330 | 331 | unary = voidElements[ tagName ] || !!unary; 332 | 333 | if ( !unary ) 334 | stack.push( tagName ); 335 | 336 | var attrs = {}; 337 | 338 | rest.replace(ATTR_REGEXP, 339 | function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { 340 | var value = doubleQuotedValue 341 | || singleQuotedValue 342 | || unquotedValue 343 | || ''; 344 | 345 | attrs[name] = decodeEntities(value); 346 | }); 347 | if (handler.start) handler.start( tagName, attrs, unary ); 348 | } 349 | 350 | function parseEndTag( tag, tagName ) { 351 | var pos = 0, i; 352 | tagName = angular.lowercase(tagName); 353 | if ( tagName ) 354 | // Find the closest opened tag of the same type 355 | for ( pos = stack.length - 1; pos >= 0; pos-- ) 356 | if ( stack[ pos ] == tagName ) 357 | break; 358 | 359 | if ( pos >= 0 ) { 360 | // Close all the open elements, up the stack 361 | for ( i = stack.length - 1; i >= pos; i-- ) 362 | if (handler.end) handler.end( stack[ i ] ); 363 | 364 | // Remove the open elements from the stack 365 | stack.length = pos; 366 | } 367 | } 368 | } 369 | 370 | var hiddenPre=document.createElement("pre"); 371 | var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/; 372 | /** 373 | * decodes all entities into regular string 374 | * @param value 375 | * @returns {string} A string with decoded entities. 376 | */ 377 | function decodeEntities(value) { 378 | if (!value) { return ''; } 379 | 380 | // Note: IE8 does not preserve spaces at the start/end of innerHTML 381 | // so we must capture them and reattach them afterward 382 | var parts = spaceRe.exec(value); 383 | var spaceBefore = parts[1]; 384 | var spaceAfter = parts[3]; 385 | var content = parts[2]; 386 | if (content) { 387 | hiddenPre.innerHTML=content.replace(//g, '>'); 413 | } 414 | 415 | /** 416 | * create an HTML/XML writer which writes to buffer 417 | * @param {Array} buf use buf.jain('') to get out sanitized html string 418 | * @returns {object} in the form of { 419 | * start: function(tag, attrs, unary) {}, 420 | * end: function(tag) {}, 421 | * chars: function(text) {}, 422 | * comment: function(text) {} 423 | * } 424 | */ 425 | function htmlSanitizeWriter(buf, uriValidator){ 426 | var ignore = false; 427 | var out = angular.bind(buf, buf.push); 428 | return { 429 | start: function(tag, attrs, unary){ 430 | tag = angular.lowercase(tag); 431 | if (!ignore && specialElements[tag]) { 432 | ignore = tag; 433 | } 434 | if (!ignore && validElements[tag] === true) { 435 | out('<'); 436 | out(tag); 437 | angular.forEach(attrs, function(value, key){ 438 | var lkey=angular.lowercase(key); 439 | var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); 440 | if (validAttrs[lkey] === true && 441 | (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { 442 | out(' '); 443 | out(key); 444 | out('="'); 445 | out(encodeEntities(value)); 446 | out('"'); 447 | } 448 | }); 449 | out(unary ? '/>' : '>'); 450 | } 451 | }, 452 | end: function(tag){ 453 | tag = angular.lowercase(tag); 454 | if (!ignore && validElements[tag] === true) { 455 | out(''); 458 | } 459 | if (tag == ignore) { 460 | ignore = false; 461 | } 462 | }, 463 | chars: function(chars){ 464 | if (!ignore) { 465 | out(encodeEntities(chars)); 466 | } 467 | } 468 | }; 469 | } 470 | 471 | 472 | // define ngSanitize module and register $sanitize service 473 | angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); 474 | 475 | /* global sanitizeText: false */ 476 | 477 | /** 478 | * @ngdoc filter 479 | * @name ngSanitize.filter:linky 480 | * @function 481 | * 482 | * @description 483 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 484 | * plain email address links. 485 | * 486 | * Requires the {@link ngSanitize `ngSanitize`} module to be installed. 487 | * 488 | * @param {string} text Input text. 489 | * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. 490 | * @returns {string} Html-linkified text. 491 | * 492 | * @usage 493 | 494 | * 495 | * @example 496 | 497 | 498 | 509 |
510 | Snippet: 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 522 | 525 | 526 | 527 | 528 | 531 | 534 | 535 | 536 | 537 | 538 | 539 | 540 |
FilterSourceRendered
linky filter 520 |
<div ng-bind-html="snippet | linky">
</div>
521 |
523 |
524 |
linky target 529 |
<div ng-bind-html="snippetWithTarget | linky:'_blank'">
</div>
530 |
532 |
533 |
no filter
<div ng-bind="snippet">
</div>
541 | 542 | 543 | it('should linkify the snippet with urls', function() { 544 | expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 545 | toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + 546 | 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); 547 | expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); 548 | }); 549 | 550 | it('should not linkify snippet without the linky filter', function() { 551 | expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). 552 | toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + 553 | 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); 554 | expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); 555 | }); 556 | 557 | it('should update', function() { 558 | element(by.model('snippet')).clear(); 559 | element(by.model('snippet')).sendKeys('new http://link.'); 560 | expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 561 | toBe('new http://link.'); 562 | expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); 563 | expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) 564 | .toBe('new http://link.'); 565 | }); 566 | 567 | it('should work with the target property', function() { 568 | expect(element(by.id('linky-target')). 569 | element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). 570 | toBe('http://angularjs.org/'); 571 | expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); 572 | }); 573 | 574 | 575 | */ 576 | angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { 577 | var LINKY_URL_REGEXP = 578 | /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/, 579 | MAILTO_REGEXP = /^mailto:/; 580 | 581 | return function(text, target) { 582 | if (!text) return text; 583 | var match; 584 | var raw = text; 585 | var html = []; 586 | var url; 587 | var i; 588 | while ((match = raw.match(LINKY_URL_REGEXP))) { 589 | // We can not end in these as they are sometimes found at the end of the sentence 590 | url = match[0]; 591 | // if we did not match ftp/http/mailto then assume mailto 592 | if (match[2] == match[3]) url = 'mailto:' + url; 593 | i = match.index; 594 | addText(raw.substr(0, i)); 595 | addLink(url, match[0].replace(MAILTO_REGEXP, '')); 596 | raw = raw.substring(i + match[0].length); 597 | } 598 | addText(raw); 599 | return $sanitize(html.join('')); 600 | 601 | function addText(text) { 602 | if (!text) { 603 | return; 604 | } 605 | html.push(sanitizeText(text)); 606 | } 607 | 608 | function addLink(url, text) { 609 | html.push(''); 618 | addText(text); 619 | html.push(''); 620 | } 621 | }; 622 | }]); 623 | 624 | 625 | })(window, window.angular); 626 | -------------------------------------------------------------------------------- /www/lib/ionic/js/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.12 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(p,h,q){'use strict';function E(a){var e=[];s(e,h.noop).chars(a);return e.join("")}function k(a){var e={};a=a.split(",");var d;for(d=0;d=c;d--)e.end&&e.end(f[d]);f.length=c}}var b,g,f=[],l=a;for(f.last=function(){return f[f.length-1]};a;){g=!0;if(f.last()&&x[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(b,a){a=a.replace(H,"$1").replace(I,"$1");e.chars&&e.chars(r(a));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&&e.comment(a.substring(4,b)),a=a.substring(b+3),g=!1);else if(y.test(a)){if(b=a.match(y))a= 8 | a.replace(b[0],""),g=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,c),g=!1}else K.test(a)&&(b=a.match(A))&&(a=a.substring(b[0].length),b[0].replace(A,d),g=!1);g&&(b=a.indexOf("<"),g=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(r(g)))}if(a==l)throw L("badparse",a);l=a}c()}function r(a){if(!a)return"";var e=M.exec(a);a=e[1];var d=e[3];if(e=e[2])n.innerHTML=e.replace(//g,">")}function s(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,g,f){a=h.lowercase(a);!d&&x[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(g,function(d,f){var g=h.lowercase(f),k="img"===a&&"src"===g||"background"===g;!0!==O[g]||!0===D[g]&&!e(d,k)||(c(" "),c(f),c('="'),c(B(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""));a==d&&(d=!1)},chars:function(a){d|| 10 | c(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,z=/^<\s*\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^]*?)>/i,I=/]/,d=/^mailto:/;return function(c,b){function g(a){a&&m.push(E(a))}function f(a,c){m.push("');g(c);m.push("")}if(!c)return c;for(var l,k=c,m=[],n,p;l=k.match(e);)n=l[0],l[2]==l[3]&&(n="mailto:"+n),p=l.index,g(k.substr(0,p)),f(n,l[0].replace(d,"")),k=k.substring(p+l[0].length);g(k);return a(m.join(""))}}])})(window,window.angular); 14 | //# sourceMappingURL=angular-sanitize.min.js.map 15 | -------------------------------------------------------------------------------- /www/lib/ionic/js/ionic-angular.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2014 Drifty Co. 3 | * http://drifty.com/ 4 | * 5 | * Ionic, v1.0.0-beta.1 6 | * A powerful HTML5 mobile app framework. 7 | * http://ionicframework.com/ 8 | * 9 | * By @maxlynch, @benjsperry, @adamdbradley <3 10 | * 11 | * Licensed under the MIT license. Please see LICENSE for more information. 12 | * 13 | */ 14 | 15 | !function(){function e(e){return["$log",function(t){function n(e){this.handle=e}var i=this,o=this._instances=[];this._registerInstance=function(e,t){return t||(t=ionic.Utils.nextUid()),e.$$delegateHandle=t,o.push(e),function(){var t=o.indexOf(e);-1!==t&&o.splice(t,1)}},this.$getByHandle=function(e){return e?new n(e):i},e.forEach(function(e){function r(e,t,n){var i,o;return e.forEach(function(e,r){o=e[t].apply(e,n),0===r&&(i=o)}),i}n.prototype[e]=function(){var n=this.handle,i=o.filter(function(e){return e.$$delegateHandle===n});return i.length?r(i,e,arguments):t.warn('Delegate for handle "'+this.handle+'" could not find a','corresponding element with delegate-handle="'+this.handle+'"!',e,"was not called!")},i[e]=function(){return r(o,e,arguments)}})}]}angular.module("ionic.service",["ionic.service.bind","ionic.service.platform","ionic.service.actionSheet","ionic.service.gesture","ionic.service.loading","ionic.service.modal","ionic.service.popup","ionic.service.templateLoad","ionic.service.view","ionic.decorator.location"]),angular.module("ionic.ui",["ionic.ui.checkbox","ionic.ui.content","ionic.ui.header","ionic.ui.list","ionic.ui.navBar","ionic.ui.popup","ionic.ui.radio","ionic.ui.scroll","ionic.ui.sideMenu","ionic.ui.slideBox","ionic.ui.tabs","ionic.ui.toggle","ionic.ui.touch","ionic.ui.viewState"]),angular.module("ionic",["ionic.service","ionic.ui","ngAnimate","ngSanitize","ui.router"]),angular.element.prototype.addClass=function(e){var t,n,i,o,r,a;if(e&&"ng-scope"!=e&&"ng-isolate-scope"!=e)for(t=0;t')(c),l=angular.element(s[0].querySelector(".action-sheet-wrapper")),u=function(e){l.removeClass("action-sheet-up"),e&&o(function(){r.cancel()},200),i.removeClass(s,"active",function(){c.$destroy()}),t[0].body.classList.remove("action-sheet-open"),c.$deregisterBackButton&&c.$deregisterBackButton()};c.$deregisterBackButton=a.registerBackButtonAction(function(){u()},300),c.cancel=function(){u(!0)},c.buttonClicked=function(e){(r.buttonClicked&&r.buttonClicked(e))===!0&&u(!1)},c.destructiveButtonClicked=function(){(r.destructiveButtonClicked&&r.destructiveButtonClicked())===!0&&u(!1)},t[0].body.appendChild(s[0]),t[0].body.classList.add("action-sheet-open");var d=new ionic.views.ActionSheet({el:s[0]});return c.sheet=d,i.addClass(s,"active"),o(function(){l.addClass("action-sheet-up")},20),d}}}]),angular.module("ionic.service.bind",[]).factory("$ionicBind",["$parse","$interpolate",function(e,t){var n=/^\s*([@=&])(\??)\s*(\w*)\s*$/;return function(i,o,r){angular.forEach(r||{},function(r,a){var c,s,l=r.match(n)||[],u=l[3]||a,d=l[1];switch(d){case"@":if(!o[u])return;o.$observe(u,function(e){i[a]=e}),o[u]&&(i[a]=t(o[u])(i));break;case"=":if(!o[u])return;s=i.$watch(o[u],function(e){i[a]=e}),i.$on("$destroy",s);break;case"&":if(o[u]&&o[u].match(RegExp(a+"(.*?)")))throw new Error('& expression binding "'+a+'" looks like it will recursively call "'+o[u]+'" and cause a stack overflow! Please choose a different scopeName.');c=e(o[u]),i[a]=function(e){return c(i,e)}}})}}]),angular.module("ionic.service.gesture",[]).factory("$ionicGesture",[function(){return{on:function(e,t,n){return window.ionic.onGesture(e,t,n[0])},off:function(e,t,n){return window.ionic.offGesture(e,t,n)}}}]),angular.module("ionic.service.loading",["ionic.ui.loading"]).factory("$ionicLoading",["$rootScope","$document","$compile",function(e,t,n){return{show:function(i){var o={content:"",animation:"fade-in",showBackdrop:!0,maxWidth:200,showDelay:0};i=angular.extend(o,i);var r=e.$new(!0);angular.extend(r,i);var a=angular.element(t[0].querySelector(".loading-backdrop"));a.length&&a.remove();var c=n(""+i.content+"")(r);t[0].body.appendChild(c[0]);var s=new ionic.views.Loading({el:c[0],maxWidth:i.maxWidth,showDelay:i.showDelay});return s.show(),r.loading=s,s}}}]),angular.module("ionic.service.modal",["ionic.service.templateLoad","ionic.service.platform","ionic.ui.modal"]).factory("$ionicModal",["$rootScope","$document","$compile","$timeout","$ionicPlatform","$ionicTemplateLoader",function(e,t,n,i,o,r){var a=ionic.views.Modal.inherit({initialize:function(e){ionic.views.Modal.prototype.initialize.call(this,e),this.animation=e.animation||"slide-in-up"},show:function(){var e=this,n=angular.element(e.modalEl);e.el.classList.remove("hide"),t[0].body.classList.add("modal-open"),e._isShown=!0,e.el.parentElement||(n.addClass(e.animation),t[0].body.appendChild(e.el)),n.addClass("ng-enter active").removeClass("ng-leave ng-leave-active"),i(function(){n.addClass("ng-enter-active"),e.scope.$parent&&e.scope.$parent.$broadcast("modal.shown"),e.el.classList.add("active")},20),e._deregisterBackButton=o.registerBackButtonAction(function(){e.hide()},200),ionic.views.Modal.prototype.show.call(e)},hide:function(){var e=this;e._isShown=!1;var n=angular.element(e.modalEl);e.el.classList.remove("active"),n.addClass("ng-leave"),i(function(){n.addClass("ng-leave-active").removeClass("ng-enter ng-enter-active active")},20),i(function(){t[0].body.classList.remove("modal-open"),e.el.classList.add("hide")},350),ionic.views.Modal.prototype.hide.call(e),e.scope.$parent&&e.scope.$parent.$broadcast("modal.hidden"),e._deregisterBackButton&&e._deregisterBackButton()},remove:function(){var e=this;e.hide(),e.scope.$parent&&e.scope.$parent.$broadcast("modal.removed"),i(function(){e.scope.$destroy(),e.el&&e.el.parentElement&&e.el.parentElement.removeChild(e.el)},750)},isShown:function(){return!!this._isShown}}),c=function(t,i){var o=i.scope&&i.scope.$new()||e.$new(!0),r=n(""+t+"")(o);i.el=r[0],i.modalEl=i.el.querySelector(".modal");var c=new a(i);return c.scope=o,i.scope||(o.modal=c),c};return{fromTemplate:function(e,t){var n=c(e,t||{});return n},fromTemplateUrl:function(e,t,n){var i;return angular.isFunction(t)&&(i=t,t=n),r.load(e).then(function(e){var n=c(e,t||{});return i&&i(n),n})}}}]),function(e){"use strict";angular.module("ionic.service.platform",[]).provider("$ionicPlatform",function(){return{$get:["$q","$rootScope",function(t,n){return{onHardwareBackButton:function(t){e.Platform.ready(function(){document.addEventListener("backbutton",t,!1)})},offHardwareBackButton:function(t){e.Platform.ready(function(){document.removeEventListener("backbutton",t)})},registerBackButtonAction:function(t,i,o){var r=this;r._hasBackButtonHandler||(n.$backButtonActions={},r.onHardwareBackButton(r.hardwareBackButtonClick),r._hasBackButtonHandler=!0);var a={id:o?o:e.Utils.nextUid(),priority:i?i:0,fn:t};return n.$backButtonActions[a.id]=a,function(){delete n.$backButtonActions[a.id]}},hardwareBackButtonClick:function(e){var t,i;for(i in n.$backButtonActions)(!t||n.$backButtonActions[i].priority>=t.priority)&&(t=n.$backButtonActions[i]);return t?(t.fn(e),t):void 0},is:function(t){return e.Platform.is(t)},ready:function(n){var i=t.defer();return e.Platform.ready(function(){i.resolve(),n()}),i.promise}}}]}})}(ionic),function(e){"use strict";angular.module("ionic.service.popup",["ionic.service.templateLoad"]).factory("$ionicPopup",["$rootScope","$q","$document","$compile","$timeout","$ionicTemplateLoader",function(t,n,i,o,r,a){var c={stackPushDelay:50},s=function(e){e.el.style.marginLeft=-e.el.offsetWidth/2+"px",e.el.style.marginTop=-e.el.offsetHeight/2+"px"},l=function(e){var t=e.el.querySelector(".popup-body");t&&""==t.innerHTML.trim()&&(t.style.display="none")},u=function(e){var t,n;t=e.el.querySelectorAll("button"),n=t[t.length-1],n&&n.focus()},d=function(t){e.requestAnimationFrame(function(){l(t),s(t),t.el.classList.remove("popup-hidden"),t.el.classList.add("popup-showing"),t.el.classList.add("active"),u(t)})},f=function(t){e.requestAnimationFrame(function(){t.el.classList.remove("popup-hidden"),t.el.classList.add("popup-showing"),t.el.classList.add("active"),u(t)})},v=function(t){e.requestAnimationFrame(function(){t.el.classList.remove("active"),t.el.classList.add("popup-hidden")})},p=function(e){e.el.offsetWidth,e.el.classList.remove("active"),e.el.classList.add("popup-hidden"),r(function(){e.el.remove()},400)},g=[],h=null,m=function(){var e=o("")(t.$new(!0));i[0].body.appendChild(e[0]),h=e,i[0].body.classList.add("popup-open")},$=function(){h.remove(),r(function(){i[0].body.classList.remove("popup-open")},300)},b=function(e){var t=g[g.length-1];g.push(e),1==g.length&&m(),t?(v(t),r(function(){d(e)},c.stackPushDelay)):d(e)},w=function(){var e=g.pop(),t=g[g.length-1];p(e),t?f(t):$()},y=function(e,t){var n={el:e[0],scope:t,close:function(){w(this)}};return t.popup=n,n},k=function(e,t){return''+(t||"")+""},S=function(e,r){var c=n.defer(),s={title:"",animation:"fade-in"};e=angular.extend(s,e);var l=e.scope&&e.scope.$new()||t.$new(!0);if(angular.extend(l,e),l.onClose=function(e,t){w(l.popup),r.resolve(t)},e.templateUrl)a.load(e.templateUrl).then(function(t){var n=k(e,t),r=o(n)(l);i[0].body.appendChild(r[0]),c.resolve(y(r,l))},function(e){c.reject(e)});else{var u=k(e,e.content),d=o(u)(l);i[0].body.appendChild(d[0]),c.resolve(y(d,l))}return c.promise};return{showPopup:function(e){var t=n.defer();return S(e,t).then(function(n){t.notify(n),b(n,e)},function(){}),t.promise},show:function(e){return this.showPopup(e)},alert:function(e){return this.showPopup({content:e.content||"",title:e.title||"",buttons:[{text:e.okText||"OK",type:e.okType||"button-positive",onTap:function(){return!0}}]})},confirm:function(e){return this.showPopup({content:e.content||"",title:e.title||"",buttons:[{text:e.cancelText||"Cancel",type:e.cancelType||"button-default",onTap:function(e){e.preventDefault()}},{text:e.okText||"OK",type:e.okType||"button-positive",onTap:function(){return!0}}]})},prompt:function(e){var n=t.$new(!0);return n.data={},this.showPopup({content:e.content||'',title:e.title||"",subTitle:e.subTitle||"",scope:n,buttons:[{text:e.cancelText||"Cancel",type:e.cancelType||"button-default",onTap:function(e){e.preventDefault()}},{text:e.okText||"OK",type:e.okType||"button-positive",onTap:function(){return n.data.response}}]})}}}])}(ionic),angular.module("ionic.service.templateLoad",[]).factory("$ionicTemplateLoader",["$q","$http","$templateCache",function(e,t,n){return{load:function(e){return t.get(e,{cache:n}).then(function(e){return e.data&&e.data.trim()})}}}]),angular.module("ionic.service.view",["ui.router","ionic.service.platform"]).run(["$rootScope","$state","$location","$document","$animate","$ionicPlatform",function(e,t,n,i,o,r){function a(t){return e.$viewHistory.backView?e.$viewHistory.backView.go():ionic.Platform.exitApp(),t.preventDefault(),!1}e.$viewHistory={histories:{root:{historyId:"root",parentHistoryId:null,stack:[],cursor:-1}},views:{},backView:null,forwardView:null,currentView:null,disabledRegistrableTagNames:[]},e.$on("viewState.changeHistory",function(i,o){if(o){var r=o.historyId?e.$viewHistory.histories[o.historyId]:null;if(r&&r.cursor>-1&&r.cursor-1&&c.stack.length>0&&c.cursor=u.index;g--)p.stack[g].destroy(),p.stack.splice(g)}}else f.navAction="initialView";o.views[f.viewId]=this.createView({viewId:f.viewId,index:c.stack.length,historyId:c.historyId,backViewId:s&&s.viewId?s.viewId:null,forwardViewId:null,stateId:a,stateName:this.getCurrentStateName(),stateParams:this.getCurrentStateParams(),url:n.url()}),"moveBack"==f.navAction&&e.$emit("$viewHistory.viewBack",s.viewId,f.viewId),c.stack.push(o.views[f.viewId])}return d&&(d.disableAnimate&&(f.navDirection=null),d.disableBack&&(o.views[f.viewId].backViewId=null),this.nextViewOptions(null)),this.setNavViews(f.viewId),c.cursor=o.currentView.index,f},setNavViews:function(t){var n=e.$viewHistory;n.currentView=this._getViewById(t),n.backView=this._getBackView(n.currentView),n.forwardView=this._getForwardView(n.currentView),e.$broadcast("$viewHistory.historyChange",{showBack:n.backView&&n.backView.historyId===n.currentView.historyId})},registerHistory:function(e){e.$historyId=ionic.Utils.nextUid()},createView:function(e){var t=new c;return t.initialize(e)},getCurrentView:function(){return e.$viewHistory.currentView},getBackView:function(){return e.$viewHistory.backView},getForwardView:function(){return e.$viewHistory.forwardView},getNavDirection:function(){return e.$viewHistory.navDirection},getCurrentStateName:function(){return t&&t.current?t.current.name:null},isCurrentStateNavView:function(e){return t&&t.current&&t.current.views&&t.current.views[e]?!0:!1},getCurrentStateParams:function(){var e;if(t&&t.params)for(var n in t.params)t.params.hasOwnProperty(n)&&(e=e||{},e[n]=t.params[n]);return e},getCurrentStateId:function(){var e;if(t&&t.current&&t.current.name){if(e=t.current.name,t.params)for(var n in t.params)t.params.hasOwnProperty(n)&&t.params[n]&&(e+="_"+n+"="+t.params[n]);return e}return ionic.Utils.nextUid()},goToHistoryRoot:function(t){if(t){var n=e.$viewHistory.histories[t];if(n&&n.stack.length){if(e.$viewHistory.currentView&&e.$viewHistory.currentView.viewId===n.stack[0].viewId)return;e.$viewHistory.forcedNav={viewId:n.stack[0].viewId,navAction:"moveBack",navDirection:"back"},n.stack[0].go()}}},_getViewById:function(t){return t?e.$viewHistory.views[t]:null},_getBackView:function(e){return e?this._getViewById(e.backViewId):null},_getForwardView:function(e){return e?this._getViewById(e.forwardViewId):null},_getHistoryById:function(t){return t?e.$viewHistory.histories[t]:null},_getHistory:function(t){var n=this._getParentHistoryObj(t);return e.$viewHistory.histories[n.historyId]||(e.$viewHistory.histories[n.historyId]={historyId:n.historyId,parentHistoryId:this._getParentHistoryObj(n.scope.$parent).historyId,stack:[],cursor:-1}),e.$viewHistory.histories[n.historyId]},_getParentHistoryObj:function(t){for(var n=t;n;){if(n.hasOwnProperty("$historyId"))return{historyId:n.$historyId,scope:n};n=n.$parent}return{historyId:"root",scope:e}},nextViewOptions:function(e){return arguments.length?void(this._nextOpts=e):this._nextOpts},getRenderer:function(e,t,n){function i(e){for(var t="";!t&&e;)t=e.getAttribute("animation"),e=e.parentElement;return t}function o(){l&&e[0].classList.add(l),"back"===r.navDirection?e[0].classList.add("reverse"):e[0].classList.remove("reverse")}var r,c,s=this,l=angular.isDefined(n.$nextAnimation)?n.$nextAnimation:i(e[0]);return n.$nextAnimation=void 0,function(t){return{enter:function(n){return c&&t?(o(),n.addClass("ng-enter"),document.body.classList.add("disable-pointer-events"),void a.enter(n,e,null,function(){document.body.classList.remove("disable-pointer-events"),l&&e[0].classList.remove(l)})):void e.append(n)},leave:function(){var n=e.contents();return c&&t?(o(),void a.leave(n,function(){n.remove()})):void n.remove()},register:function(e){return r=s.register(n,e),c=null!==l&&null!==r.navDirection,r}}}},disableRegisterByTagName:function(t){e.$viewHistory.disabledRegistrableTagNames.push(t.toUpperCase())},isTagNameRegistrable:function(t){var n,i,o=e.$viewHistory.disabledRegistrableTagNames;for(n=0;n
{{titleText}}
'}}])}(),function(e){"use strict";function t(){return["$ionicScrollDelegate",function(t){return{restrict:"E",link:function(n,i){function o(n){if(!e.DomUtil.getParentOrSelfWithClass(n.target,"button",4)){var o=n.gesture&&n.gesture.touches[0]||n.detail.touches[0],r=i[0].getBoundingClientRect();e.DomUtil.rectContains(o.pageX,o.pageY,r.left,r.top-20,r.left+r.width,r.top+r.height)&&t.scrollTop(!0)}}e.on("tap",o,i[0]),n.$on("$destroy",function(){e.off("tap",o,i[0])})}}}]}function n(t){return[function(){return{restrict:"E",compile:function(n){function i(n,i,o){var r=(new e.views.HeaderBar({el:i[0],alignTitle:o.alignTitle||"center"}),i[0]),a=n.$parent||n;t?(n.$watch(function(){return r.className},function(e){var t=-1!==e.indexOf("bar-subheader");a.$hasHeader=!t,a.$hasSubheader=t}),n.$on("$destroy",function(){a.$hasHeader=a.$hasSubheader=null})):(n.$watch(function(){return r.className},function(e){var t=-1!==e.indexOf("bar-subfooter");a.$hasFooter=!t,a.$hasSubfooter=t}),n.$on("$destroy",function(){a.$hasFooter=a.$hasSubfooter=null}),n.$watch("$hasTabs",function(e){i.toggleClass("has-tabs",!!e)}))}return n.addClass(t?"bar bar-header":"bar bar-footer"),{pre:i}}}}]}angular.module("ionic.ui.header",["ngAnimate","ngSanitize"]).directive("ionNavBar",t()).directive("ionHeaderBar",t()).directive("ionHeaderBar",n(!0)).directive("ionFooterBar",n(!1))}(ionic),function(){"use strict";angular.module("ionic.ui.checkbox",[]).directive("ionCheckbox",function(){return{restrict:"E",replace:!0,require:"?ngModel",scope:{ngModel:"=?",ngValue:"=?",ngChecked:"=?",ngChange:"&"},transclude:!0,template:'
',compile:function(e,t){var n=e.find("input");t.name&&n.attr("name",t.name),t.ngChecked&&n.attr("ng-checked","ngChecked"),t.ngTrueValue&&n.attr("ng-true-value",t.ngTrueValue),t.ngFalseValue&&n.attr("ng-false-value",t.ngFalseValue)}}})}(),function(){"use strict";angular.module("ionic.ui.content",["ionic.ui.scroll"]).directive("ionPane",function(){return{restrict:"E",link:function(e,t){t.addClass("pane")}}}).directive("ionContent",["$timeout","$controller","$ionicBind",function(e,t,n){return{restrict:"E",require:"^?ionNavView",scope:!0,compile:function(e,i){function o(e,o,a){e.$watch(function(){return(e.$hasHeader?" has-header":"")+(e.$hasSubheader?" has-subheader":"")+(e.$hasFooter?" has-footer":"")+(e.$hasSubfooter?" has-subfooter":"")+(e.$hasTabs?" has-tabs":"")+(e.$hasTabsTop?" has-tabs-top":"")},function(e,t){o.removeClass(t),o.addClass(e)}),n(e,a,{$onScroll:"&onScroll",$onScrollComplete:"&onScrollComplete",hasBouncing:"@",scroll:"@",padding:"@",hasScrollX:"@",hasScrollY:"@",scrollbarX:"@",scrollbarY:"@",startX:"@",startY:"@",scrollEventInterval:"@"}),angular.isDefined(a.padding)&&e.$watch(a.padding,function(e){r.toggleClass("padding",!!e)}),"false"===e.scroll||("true"===i.overflowScroll?o.addClass("overflow-scroll"):t("$ionicScroll",{$scope:e,scrollViewOptions:{el:o[0],delegateHandle:i.delegateHandle,bouncing:e.$eval(e.hasBouncing),startX:e.$eval(e.startX)||0,startY:e.$eval(e.startY)||0,scrollbarX:e.$eval(e.scrollbarX)!==!1,scrollbarY:e.$eval(e.scrollbarY)!==!1,scrollingX:e.$eval(e.hasScrollX)===!0,scrollingY:e.$eval(e.hasScrollY)!==!1,scrollEventInterval:parseInt(e.scrollEventInterval,10)||20,scrollingComplete:function(){e.$onScrollComplete({scrollTop:this.__scrollTop,scrollLeft:this.__scrollLeft})}}}))}e.addClass("scroll-content");var r=angular.element('
');return r.append(e.contents()),e.append(r),{pre:o}}}}]).directive("ionRefresher",["$ionicBind",function(e){return{restrict:"E",replace:!0,require:"^$ionicScroll",template:'
',compile:function(t,n){return angular.isUndefined(n.pullingIcon)&&n.$set("pullingIcon","ion-arrow-down-c"),angular.isUndefined(n.refreshingIcon)&&n.$set("refreshingIcon","ion-loading-d"),function(t,n,i,o){e(t,i,{pullingIcon:"@",pullingText:"@",refreshingIcon:"@",refreshingText:"@",$onRefresh:"&onRefresh",$onPulling:"&onPulling"}),o._setRefresher(t,n[0]),t.$on("scroll.refreshComplete",function(){n[0].classList.remove("active"),o.scrollView.finishPullToRefresh()})}}}}]).directive("ionInfiniteScroll",["$timeout",function(e){return{restrict:"E",require:["^$ionicScroll","ionInfiniteScroll"],template:'
',scope:!0,controller:["$scope","$attrs",function(e,t){this.isLoading=!1,this.scrollView=null,this.getMaxScroll=function(){var e=t.distance||"1%";return e.indexOf("%")>-1?this.scrollView.getScrollMax().top*(1-parseInt(e,10)/100):this.scrollView.getScrollMax().top-parseInt(e,10)}}],link:function(t,n,i,o){var r=o[0],a=o[1],c=a.scrollView=r.scrollView;t.icon=function(){return angular.isDefined(i.icon)?i.icon:"ion-loading-d"},t.$on("scroll.infiniteScrollComplete",function(){n[0].classList.remove("active"),e(function(){c.resize()},0,!1),a.isLoading=!1}),r.$element.on("scroll",ionic.animationFrameThrottle(function(){!a.isLoading&&c.getValues().top>=a.getMaxScroll()&&(n[0].classList.add("active"),a.isLoading=!0,t.$parent.$apply(i.onInfinite||""))}))}}}])}(),function(){"use strict";angular.module("ionic.ui.list",["ngAnimate"]).directive("ionItem",["$timeout","$parse",function(){return{restrict:"E",require:"?^ionList",replace:!0,transclude:!0,scope:{item:"=",itemType:"@",canDelete:"@",canReorder:"@",canSwipe:"@",onDelete:"&",optionButtons:"&",deleteIcon:"@",reorderIcon:"@"},template:'
',link:function(e,t,n,i){if(i){var o=i.scope,r=i.attrs;n.$observe("href",function(t){t&&(e.href=t.trim())}),e.itemType||(e.itemType=o.itemType),t.addClass(e.itemType||o.itemType),e.itemClass=e.itemType,"false"!==(n.canDelete?e.canDelete:o.canDelete)&&(n.onDelete||r.onDelete)&&(e.deleteClick=function(){n.onDelete?e.onDelete({item:e.item,index:e.$parent.$index}):r.onDelete&&o.onDelete({item:e.item,index:e.$parent.$index})},e.deleteIconClass=e.deleteIcon||o.deleteIcon||"ion-minus-circled",t.addClass("item-left-editable")),"true"===(n.canReorder?e.canReorder:o.canReorder)&&(e.reorderIconClass=e.reorderIcon||o.reorderIcon||"ion-navicon",t.addClass("item-right-editable")),"false"!==(n.canSwipe?e.canSwipe:o.canSwipe)&&(e.itemOptionButtons=e.optionButtons(),"undefined"==typeof e.itemOptionButtons&&(e.itemOptionButtons=o.optionButtons()),t.addClass("item-swipeable"))}}}}]).directive("ionList",["$timeout",function(e){return{restrict:"E",replace:!0,transclude:!0,require:"^?$ionicScroll",scope:{itemType:"@",canDelete:"@",canReorder:"@",canSwipe:"@",showDelete:"=",showReorder:"=",onDelete:"&",onReorder:"&",optionButtons:"&",deleteIcon:"@",reorderIcon:"@"},template:"
",controller:["$scope","$attrs",function(e,t){this.scope=e,this.attrs=t}],link:function(t,n,i,o){t.listView=new ionic.views.ListView({canSwipe:"false"!==t.canSwipe&&!!t.optionButtons(),el:n[0],listEl:n[0].children[0],scrollEl:o&&o.element,scrollView:o&&o.scrollView,onReorder:function(e,n,i){t.$apply(function(){t.onReorder({el:e,start:n,end:i})})}}),i.animation&&n[0].classList.add(i.animation);var r=t.$watch("showReorder",function(i){i?(n[0].classList.add("item-options-hide"),t.listView&&t.listView.clearDragEffects()):i===!1&&e(function(){n[0].classList.remove("item-options-hide")},250)});t.$on("$destroy",function(){r()})}}}])}(),function(){"use strict";angular.module("ionic.ui.loading",[]).directive("ionLoading",function(){return{restrict:"E",replace:!0,transclude:!0,link:function(e,t){t.addClass(e.animation||"")},template:'
'}})}(),function(){"use strict";angular.module("ionic.ui.modal",[]).directive("ionModal",[function(){return{restrict:"E",transclude:!0,replace:!0,template:''}}])}(),angular.module("ionic.ui.navBar",["ionic.service.view","ngSanitize"]).service("$ionicNavBarDelegate",e(["back","align","showBackButton","showBar","setTitle","changeTitle","getTitle","getPreviousTitle"])).controller("$ionicNavBar",["$scope","$element","$attrs","$ionicViewService","$animate","$compile","$ionicNavBarDelegate",function(e,t,n,i,o,r,a){t.parent().data("$ionNavBarController",this);var c=a._registerInstance(this,n.delegateHandle);e.$on("$destroy",c);var s=this;this.leftButtonsElement=angular.element(t[0].querySelector(".buttons.left-buttons")),this.rightButtonsElement=angular.element(t[0].querySelector(".buttons.right-buttons")),this.back=function(e){var t=i.getBackView();return t&&t.go(),e&&(e.alreadyHandled=!0),!1},this.align=function(e){this._headerBarView.align(e)},this.showBackButton=function(t){e.backButtonShown=!!t},this.showBar=function(t){e.isInvisible=!t},this.setTitle=function(t){e.oldTitle=e.title,e.title=t||""},this.changeTitle=function(t,n){return e.title===t?!1:(this.setTitle(t),e.isReverse="back"==n,e.shouldAnimate=!!n,e.shouldAnimate?this._animateTitles():this._headerBarView.align(),!0)},this.getTitle=function(){return e.title||""},this.getPreviousTitle=function(){return e.oldTitle||""},this._animateTitles=function(){var n,i,a;a=t[0].querySelectorAll(".title"),a.length&&(n=r('

')(e),angular.element(a[0]).replaceWith(n)),i=r('

')(e),ionic.requestAnimationFrame(function(){n&&o.leave(angular.element(n));var r=n&&angular.element(n)||null;o.enter(i,t,r,function(){s._headerBarView.align()}),angular.forEach(a,function(e){e&&e.parentNode&&angular.element(e).remove()}),e.$digest(),ionic.requestAnimationFrame(function(){i[0].classList.remove("invisible")})})}}]).directive("ionNavBar",["$ionicViewService","$rootScope","$animate","$compile",function(){return{restrict:"E",controller:"$ionicNavBar",scope:!0,compile:function(e){function t(e,t,n,i){i._headerBarView=new ionic.views.HeaderBar({el:t[0],alignTitle:n.alignTitle||"center"}),e.backButtonShown=!1,e.shouldAnimate=!0,e.isReverse=!1,e.isInvisible=!0,e.$parent.$hasHeader=!0,e.$on("$destroy",function(){e.$parent.$hasHeader=!1}),e.$watch(function(){return(e.isReverse?" reverse":"")+(e.isInvisible?" invisible":"")+(e.shouldAnimate?"":" no-animation")},function(e,n){t.removeClass(n),t.addClass(e)})}return e.addClass("bar bar-header nav-bar").append('

'),{pre:t} 16 | }}}]).directive("ionNavBackButton",["$ionicNgClick",function(e){return{restrict:"E",require:"^ionNavBar",compile:function(t){return t.addClass("button back-button"),function(t,n,i,o){i.ngClick||(t.$navBack=o.back,e(t,n,"$navBack($event)"));var r=t.$parent.$on("$viewHistory.historyChange",function(e,n){t.hasBackButton=!!n.showBack});t.$on("$destroy",r),t.$watch("!!(backButtonShown && hasBackButton)",function(e){n.toggleClass("hide",!e)})}}}}]).directive("ionNavButtons",["$compile","$animate",function(e,t){return{require:"^ionNavBar",restrict:"E",compile:function(n){var i=n.contents().remove();return function(n,o,r,a){var c="right"===r.side?a.rightButtonsElement:a.leftButtonsElement,s=angular.element("
").append(i);o.append(s),e(s)(n),t.enter(s,c),n.$on("$destroy",function(){t.leave(s)}),o.css("display","none")}}}}]),function(){"use strict";angular.module("ionic.ui.popup",[]).directive("ionPopupBackdrop",function(){return{restrict:"E",replace:!0,template:''}}).directive("ionPopup",["$ionicBind",function(e){return{restrict:"E",replace:!0,transclude:!0,scope:!0,template:'',link:function(t,n,i){e(t,i,{title:"@",buttons:"=",$onButtonTap:"&onButtonTap",$onClose:"&onClose"}),t._buttonTapped=function(e,n){var i=e.onTap&&e.onTap(n);return n.defaultPrevented?t.$onClose({button:e,result:!1,event:n}):i?t.$onClose({button:e,result:i,event:n}):void t.$onButtonTap({button:e,event:n})}}}}])}(),function(e){"use strict";angular.module("ionic.ui.radio",[]).directive("ionRadio",function(){return{restrict:"E",replace:!0,require:"?ngModel",scope:{ngModel:"=?",ngValue:"=?",ngChange:"&",icon:"@"},transclude:!0,template:'',compile:function(e,t){t.name&&e.children().eq(0).attr("name",t.name),t.icon&&e.children().eq(2).removeClass("ion-checkmark").addClass(t.icon)}}}).directive("ionRadioButtons",function(){return{restrict:"E",replace:!0,require:"?ngModel",scope:{value:"@"},transclude:!0,template:'
',controller:["$scope","$element",function(e,t){this.select=function(e){for(var n,i=t.children(),o=0;o=0,scrollingY:e.direction.indexOf("y")>=0};s&&(l.speedMultiplier=.8,l.bouncing=!1),c=t("$ionicScroll",{$scope:e,scrollViewOptions:l}),a=e.$parent.scrollView=c.scrollView}e.addClass("scroll-view");var o=angular.element('
');return o.append(e.contents()),e.append(o),{pre:i}}}}])}(),function(){"use strict";angular.module("ionic.ui.sideMenu",["ionic.service.gesture","ionic.service.view"]).run(["$ionicViewService",function(e){e.disableRegisterByTagName("ion-side-menus")}]).service("$ionicSideMenuDelegate",e(["toggleLeft","toggleRight","isOpenLeft","isOpenRight","canDragContent"])).directive("ionSideMenus",function(){return{restrict:"ECA",controller:["$scope","$attrs","$ionicSideMenuDelegate",function(e,t,n){angular.extend(this,ionic.controllers.SideMenuController.prototype),ionic.controllers.SideMenuController.call(this,{left:{width:275},right:{width:275}}),this.canDragContent=function(t){return arguments.length&&(e.dragContent=!!t),e.dragContent},e.sideMenuContentTranslateX=0;var i=n._registerInstance(this,t.delegateHandle);e.$on("$destroy",i)}],replace:!0,transclude:!0,template:'
'}}).directive("ionSideMenuContent",["$timeout","$ionicGesture",function(e,t){return{restrict:"EA",require:"^ionSideMenus",scope:!0,compile:function(n,i){function o(n,o,r,a){function c(e){0!==a.getOpenAmount()&&(a.close(),e.gesture.srcEvent.preventDefault())}o.addClass("menu-content pane"),angular.isDefined(i.dragContent)?n.$watch(i.dragContent,function(e){a.canDragContent(e)}):a.canDragContent(!0);var s=!1,l=!1;ionic.on("tap",c,o[0]);var u=function(e){if(n.dragContent){if(s||e.gesture.srcEvent.defaultPrevented)return;l=!0,a._handleDrag(e),e.gesture.srcEvent.preventDefault()}},d=function(e){l&&e.gesture.srcEvent.preventDefault()},f=t.on("dragright",u,o),v=t.on("dragleft",u,o),p=t.on("dragup",d,o),g=t.on("dragdown",d,o),h=function(e){l=!1,s||a._endDrag(e),s=!1},m=t.on("release",h,o);a.setContent({onDrag:function(){},endDrag:function(){},getTranslateX:function(){return n.sideMenuContentTranslateX||0},setTranslateX:ionic.animationFrameThrottle(function(t){o[0].style[ionic.CSS.TRANSFORM]="translate3d("+t+"px, 0, 0)",e(function(){n.sideMenuContentTranslateX=t})}),enableAnimation:function(){n.animationEnabled=!0,o[0].classList.add("menu-animated")},disableAnimation:function(){n.animationEnabled=!1,o[0].classList.remove("menu-animated")}}),n.$on("$destroy",function(){t.off(v,"dragleft",u),t.off(f,"dragright",u),t.off(p,"dragup",u),t.off(g,"dragdown",u),t.off(m,"release",h),ionic.off("tap",c,o[0])})}return{pre:o}}}}]).directive("ionSideMenu",function(){return{restrict:"E",require:"^ionSideMenus",scope:!0,compile:function(e,t){return angular.isUndefined(t.isEnabled)&&t.$set("isEnabled","true"),angular.isUndefined(t.width)&&t.$set("width","275"),e.addClass("menu menu-"+t.side),function(e,t,n,i){e.side=n.side||"left";var o=i[e.side]=new ionic.views.SideMenu({width:275,el:t[0],isEnabled:!0});e.$watch(n.width,function(e){var t=+e;t&&t==e&&o.setWidth(+e)}),e.$watch(n.isEnabled,function(e){o.setIsEnabled(!!e)})}}}}).directive("menuToggle",["$ionicViewService",function(){return{restrict:"AC",require:"^ionSideMenus",link:function(e,t,n,i){var o=e.$eval(n.menuToggle)||"left";t.bind("click",function(){"left"===o?i.toggleLeft():"right"===o&&i.toggleRight()})}}}]).directive("menuClose",["$ionicViewService",function(){return{restrict:"AC",require:"^ionSideMenus",link:function(e,t,n,i){t.bind("click",function(){i.close()})}}}])}(),function(){"use strict";angular.module("ionic.ui.slideBox",[]).service("$ionicSlideBoxDelegate",e(["update","slide","previous","next","stop","currentIndex","slidesCount"])).directive("ionSlideBox",["$timeout","$compile","$ionicSlideBoxDelegate",function(e,t,n){return{restrict:"E",replace:!0,transclude:!0,scope:{doesContinue:"@",slideInterval:"@",showPager:"@",disableScroll:"@",onSlideChanged:"&",activeSlide:"=?"},controller:["$scope","$element","$attrs",function(t,i,o){var r=t.$eval(t.doesContinue)===!0,a=r?t.$eval(t.slideInterval)||4e3:0,c=new ionic.views.Slider({el:i[0],auto:a,disableScroll:t.$eval(t.disableScroll)===!0||!1,continuous:r,startSlide:t.activeSlide,slidesChanged:function(){t.currentSlide=c.currentIndex(),e(function(){})},callback:function(n){t.currentSlide=n,t.onSlideChanged({index:t.currentSlide}),t.$parent.$broadcast("slideBox.slideChanged",n),t.activeSlide=n,e(function(){})}});t.$watch("activeSlide",function(e){angular.isDefined(e)&&c.slide(e)}),t.$on("slideBox.nextSlide",function(){c.next()}),t.$on("slideBox.prevSlide",function(){c.prev()}),t.$on("slideBox.setSlide",function(e,t){c.slide(t)}),this.__slider=c;var s=n._registerInstance(c,o.delegateHandle);t.$on("$destroy",s),this.slidesCount=function(){return c.slidesCount()},e(function(){c.load()})}],template:'
',link:function(e,n){if(e.$eval(e.showPager)!==!1){var i=e.$new(),o=angular.element("");n.append(o),t(o)(i)}}}}]).directive("ionSlide",function(){return{restrict:"E",require:"^ionSlideBox",compile:function(e){return e.addClass("slider-slide"),function(){}}}}).directive("ionPager",function(){return{restrict:"E",replace:!0,require:"^ionSlideBox",template:'
',link:function(e,t,n,i){var o=function(e){for(var n=t[0].children,i=n.length,o=0;i>o;o++)o==e?n[o].classList.add("active"):n[o].classList.remove("active")};e.numSlides=function(){return new Array(i.slidesCount())},e.$watch("currentSlide",function(e){o(e)})}}})}(),angular.module("ionic.ui.tabs",["ionic.service.view"]).run(["$ionicViewService",function(e){e.disableRegisterByTagName("ion-tabs")}]).service("$ionicTabsDelegate",e(["select","selectedIndex"])).controller("ionicTabs",["$scope","$ionicViewService","$element",function(e,t){var n=null,i=this;i.tabs=[],i.selectedIndex=function(){return i.tabs.indexOf(n)},i.selectedTab=function(){return n},i.add=function(e){t.registerHistory(e),i.tabs.push(e),1===i.tabs.length&&i.select(e)},i.remove=function(e){var t=i.tabs.indexOf(e);if(-1!==t){if(e.$tabSelected)if(i.deselect(e),1===i.tabs.length);else{var n=t===i.tabs.length-1?t-1:t+1;i.select(i.tabs[n])}i.tabs.splice(t,1)}},i.deselect=function(e){e.$tabSelected&&(n=null,e.$tabSelected=!1,(e.onDeselect||angular.noop)())},i.select=function(o,r){var a;if(angular.isNumber(o)?(a=o,o=i.tabs[a]):a=i.tabs.indexOf(o),!o||-1==a)throw new Error('Cannot select tab "'+a+'"!');if(n&&n.$historyId==o.$historyId)r&&t.goToHistoryRoot(o.$historyId);else if(angular.forEach(i.tabs,function(e){i.deselect(e)}),n=o,o.$tabSelected=!0,(o.onSelect||angular.noop)(),r){var c={type:"tab",tabIndex:a,historyId:o.$historyId,navViewName:o.navViewName,hasNavView:!!o.navViewName,title:o.title,url:o.href,uiSref:o.uiSref};e.$emit("viewState.changeHistory",c)}}}]).directive("ionTabs",["$ionicViewService","$ionicTabsDelegate",function(e,t){return{restrict:"E",scope:!0,controller:"ionicTabs",compile:function(e){function n(e,n,i,o){var r=t._registerInstance(o,i.delegateHandle);e.$on("$destroy",r),o.$scope=e,o.$element=n,o.$tabsElement=angular.element(n[0].querySelector(".tabs"));var a=n[0];e.$watch(function(){return a.className},function(t){var n=-1!==t.indexOf("tabs-top"),i=-1!==t.indexOf("tabs-item-hide");e.$hasTabs=!n&&!i,e.$hasTabsTop=n&&!i}),e.$on("$destroy",function(){e.$hasTabs=e.$hasTabsTop=null})}e.addClass("view");var i=angular.element('
');return i.append(e.contents()),e.append(i),{pre:n}}}}]).controller("ionicTab",["$scope","$ionicViewService","$rootScope","$element",function(e){this.$scope=e}]).directive("ionTab",["$rootScope","$animate","$ionicBind","$compile","$ionicViewService",function(e,t,n,i,o){function r(e,t){return angular.isDefined(t)?" "+e+'="'+t+'"':""}return{restrict:"E",require:["^ionTabs","ionTab"],replace:!0,controller:"ionicTab",scope:!0,compile:function(e,a){var c=e[0].querySelector("ion-nav-view")||e[0].querySelector("data-ion-nav-view"),s=c&&c.getAttribute("name"),l=(angular.element(e[0].querySelector("ion-tab-nav")||e[0].querySelector("data-ion-tab-nav")).remove(),angular.element('
').append(e.contents().remove()));return function(e,c,u,d){function f(){o.isCurrentStateNavView(e.navViewName)&&tabsCtrl.select(e)}var v,p,g;tabsCtrl=d[0],tabCtrl=d[1],c[0].removeAttribute("title"),n(e,u,{animate:"=",onSelect:"&",onDeselect:"&",title:"@",uiSref:"@",href:"@"}),tabsCtrl.add(e),e.$on("$destroy",function(){tabsCtrl.remove(e),g.isolateScope().$destroy(),g.remove()}),s&&(e.navViewName=s,e.$on("$stateChangeSuccess",f),f()),g=angular.element(""),g.data("$ionTabsController",tabsCtrl),g.data("$ionTabController",tabCtrl),tabsCtrl.$tabsElement.append(i(g)(e)),e.$watch("$tabSelected",function(n){v&&v.$destroy(),v=null,p&&t.leave(p),p=null,n&&(v=e.$new(),p=l.clone(),t.enter(p,tabsCtrl.$element),i(p)(v))})}}}}]).directive("ionTabNav",["$ionicNgClick",function(e){return{restrict:"E",replace:!0,require:["^ionTabs","^ionTab"],template:'{{badge}}',scope:{title:"@",icon:"@",iconOn:"@",iconOff:"@",badge:"=",badgeStyle:"@"},compile:function(){return function(t,n,i,o){var r=o[0],a=o[1];n[0].removeAttribute("title"),t.selectTab=function(e){e.preventDefault(),r.select(a.$scope,!0)},i.ngClick||e(t,n,"selectTab($event)"),t.getIconOn=function(){return t.iconOn||t.icon},t.getIconOff=function(){return t.iconOff||t.icon},t.isTabActive=function(){return r.selectedTab()===a.$scope}}}}}]),function(e){"use strict";angular.module("ionic.ui.toggle",[]).directive("ionToggle",["$ionicGesture","$timeout",function(){return{restrict:"E",replace:!0,require:"?ngModel",scope:{ngModel:"=?",ngValue:"=?",ngChecked:"=?",ngChange:"&",ngDisabled:"=?"},transclude:!0,template:'
',compile:function(t,n){var i=t.find("input");return n.name&&i.attr("name",n.name),n.ngChecked&&i.attr("ng-checked","ngChecked"),n.ngTrueValue&&i.attr("ng-true-value",n.ngTrueValue),n.ngFalseValue&&i.attr("ng-false-value",n.ngFalseValue),function(t,n){var i,o,r,a;i=n[0].getElementsByTagName("label")[0],o=i.children[0],r=i.children[1],a=r.children[0];var c=angular.element(o).controller("ngModel");t.toggle=new e.views.Toggle({el:i,track:r,checkbox:o,handle:a,onChange:function(){c.$setViewValue(o.checked?!0:!1),t.$apply()}}),t.$on("$destroy",function(){t.toggle.destroy()})}}}}])}(window.ionic),function(e,t){"use strict";e.module("ionic.ui.touch",[]).config(["$provide",function(e){e.decorator("ngClickDirective",["$delegate",function(e){return e.shift(),e}])}]).factory("$ionicNgClick",["$parse",function(e){function n(e){t.tapElement(e.target,e)}return function(i,o,r){var a=e(r);o.on("click",function(e){i.$apply(function(){a(i,{$event:e})})}),t.on("release",n,o[0]),o.onclick=function(){},i.$on("$destroy",function(){t.off("release",n,o[0])})}}]).directive("ngClick",["$ionicNgClick",function(e){return function(t,n,i){e(t,n,i.ngClick)}}]).directive("ionStopEvent",function(){function e(e){e.stopPropagation()}return{restrict:"A",link:function(t,n,i){n.bind(i.ionStopEvent,e)}}})}(window.angular,window.ionic),function(){"use strict";angular.module("ionic.ui.viewState",["ionic.service.view","ionic.service.gesture","ngSanitize"]).directive("ionView",["$ionicViewService","$rootScope","$animate",function(){return{restrict:"EA",priority:1e3,require:"^?ionNavBar",compile:function(e){return e.addClass("pane"),e[0].removeAttribute("title"),function(e,t,n,i){if(i){var o=n.title;i.changeTitle(o,e.$navDirection),n.$observe("title",function(e){e!==o&&i.setTitle(e)}),e.$watch(n.hideBackButton,function(e){i.showBackButton(!e)}),e.$watch(n.hideNavBar,function(e){i.showBar(!e)})}}}}}]).directive("ionNavView",["$ionicViewService","$state","$compile","$controller","$animate",function(e,t,n,i,o){var r=!1,a={restrict:"E",terminal:!0,priority:2e3,transclude:!0,controller:[function(){}],compile:function(c,s,l){return function(c,s,u){function d(r){o.enabled()===!1&&(r=!1);var a=t.$current&&t.$current.locals[p];if(a!==v){var l=e.getRenderer(s,u,c);if(f&&(f.$destroy(),f=null),!a)return v=null,$.state=null,s.append(h);var d=angular.element("
").html(a.$template).contents(),m=l().register(d);l(r).leave(),v=a,$.state=a.$$state,l(r).enter(d);var b=n(d);if(f=c.$new(),f.$navDirection=m.navDirection,a.$$controller){a.$scope=f;var w=i(a.$$controller,a);s.children().data("$ngControllerController",w)}b(f);var y=e._getViewById(m.viewId)||{};f.$broadcast("$viewContentLoaded",y),g&&f.$eval(g),d=null}}var f,v,p=u[a.name]||u.name||"",g=u.onload||"",h=l(c);s.append(h);var m=s.parent().inheritedData("$uiView");p.indexOf("@")<0&&(p=p+"@"+(m?m.state.name:""));var $={name:p,state:null};s.data("$uiView",$);var b=function(){if(!r){r=!0;try{d(!0)}catch(e){throw r=!1,e}r=!1}};c.$on("$stateChangeSuccess",b),c.$on("$viewContentLoading",b),d(!1)}}};return a}]).directive("navClear",["$ionicViewService",function(e){return{restrict:"AC",link:function(t,n){n.bind("click",function(){e.nextViewOptions({disableAnimate:!0,disableBack:!0})})}}}])}(),angular.module("ionic.ui.scroll").service("$ionicScrollDelegate",e(["resize","scrollTop","scrollBottom","scrollTo","anchorScroll","rememberScrollPosition","forgetScrollPosition","scrollToRememberedPosition"])).factory("$$scrollValueCache",function(){return{}}).controller("$ionicScroll",["$scope","scrollViewOptions","$timeout","$window","$$scrollValueCache","$location","$rootScope","$document","$ionicScrollDelegate",function(e,t,n,i,o,r,a,c,s){var l=this;this._scrollViewOptions=t;var u=this.element=t.el,d=this.$element=angular.element(u),f=this.scrollView=new ionic.views.Scroll(t);(d.parent().length?d.parent():d).data("$$ionicScrollController",this);var v=s._registerInstance(this,t.delegateHandle);angular.isDefined(t.bouncing)||ionic.Platform.ready(function(){f.options.bouncing=!ionic.Platform.isAndroid()});var p=angular.bind(f,f.resize);ionic.on("resize",p,i);var g=angular.noop;e.$on("$destroy",function(){v(),ionic.off("resize",p,i),i.removeEventListener("resize",p),g(),l._rememberScrollId&&(o[l._rememberScrollId]=f.getValues())}),d.on("scroll",function(t){var n=(t.originalEvent||t).detail||{};e.$onScroll&&e.$onScroll({event:t,scrollTop:n.scrollTop||0,scrollLeft:n.scrollLeft||0})}),e.$on("$viewContentLoaded",function(e,t){if(!e.defaultPrevented){e.preventDefault();var n=t&&t.viewId;n&&(l.rememberScrollPosition(n),l.scrollToRememberedPosition(),g=a.$on("$viewHistory.viewBack",function(e,t){n===t&&l.forgetScrollPosition()}))}}),n(function(){f.run()}),this._rememberScrollId=null,this.resize=function(){return n(p)},this.scrollTop=function(e){this.resize().then(function(){f.scrollTo(0,0,!!e)})},this.scrollBottom=function(e){this.resize().then(function(){var t=f.getScrollMax();f.scrollTo(t.left,t.top,!!e)})},this.scrollTo=function(e,t,n){this.resize().then(function(){f.scrollTo(e,t,!!n)})},this.anchorScroll=function(e){this.resize().then(function(){var t=r.hash(),n=t&&c[0].getElementById(t);if(t&&n){var i=ionic.DomUtil.getPositionInParent(n,l.$element);f.scrollTo(i.left,i.top,!!e)}else f.scrollTo(0,0,!!e)})},this.rememberScrollPosition=function(e){if(!e)throw new Error("Must supply an id to remember the scroll by!");this._rememberScrollId=e},this.forgetScrollPosition=function(){delete o[this._rememberScrollId],this._rememberScrollId=null},this.scrollToRememberedPosition=function(e){var t=o[this._rememberScrollId];t&&this.resize().then(function(){f.scrollTo(+t.left,+t.top,e)})},this._setRefresher=function(e,t){var n=this.refresher=t,i=l.refresher.clientHeight||0;f.activatePullToRefresh(i,function(){n.classList.add("active"),e.$onPulling()},function(){n.classList.remove("refreshing"),n.classList.remove("active")},function(){n.classList.add("refreshing"),e.$onRefresh()})}}])}(); -------------------------------------------------------------------------------- /www/oauthcallback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /www/templates/all-friend-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{friend.name}} 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /www/templates/feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 |
15 |
16 | 17 |

{{item.from.name}}

18 |

{{item.created_time | date:'MMM d, y h:mma'}}

19 |
20 |
21 |

{{item.story}}

22 | 23 |

{{item.message}}

24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 | 32 |
-------------------------------------------------------------------------------- /www/templates/friend-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{friend.name}} 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /www/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
-------------------------------------------------------------------------------- /www/templates/logout.html: -------------------------------------------------------------------------------- 1 | Logout... -------------------------------------------------------------------------------- /www/templates/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Sociogram

13 |
14 | 15 | 16 | 17 | 18 | Feed 19 | 20 | 21 | 22 | Share 23 | 24 | 25 | 26 | Friends 27 | 28 | 29 | 30 | Taggable Friends 31 | 32 | 33 | 34 | Profile 35 | 36 | 37 | 38 | Logout 39 | 40 | 41 | 42 | Revoke Permissions 43 | 44 | 45 | 46 |
47 | 48 |
49 | -------------------------------------------------------------------------------- /www/templates/mutual-friend-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{friend.name}} 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /www/templates/person.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |

{{user.name}} {{user.birthday}} {{user.gender}}

8 |
9 |
10 | 11 |
12 | 13 | 14 | Mutual Friends 15 | 16 |
17 | 18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /www/templates/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |

{{user.name}} {{user.birthday}} {{user.gender}}

12 |

{{user.email}}

13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /www/templates/share.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 14 | 15 | 18 | 19 |

{{status}}

20 | 21 |
22 | 23 |
24 | 25 |
--------------------------------------------------------------------------------