diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index 6985a1d58635e512dd726728e37d0c8f6dd655ac..acc8819fcd1228d5b57dd8a3998a438f955ae2b1 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -114,6 +114,7 @@ "PEER": "Adresse du nœud Duniter", "USE_LOCAL_STORAGE": "Activer le stockage local", "ENABLE_HELPTIP": "Activer les bulles d'aide contextuelles", + "ENABLE_UI_EFFECTS": "Activer les effets visuels", "HISTORY_SETTINGS": "Mon compte", "DISPLAY_UD_HISTORY": "Afficher les dividendes produits ?", "AUTHENTICATION_SETTINGS": "Authentification", diff --git a/www/index.html b/www/index.html index 3ae85bf09286f11af671ff285aaffae8a8300bd4..99cd88368dc4a76de74d35e0bcdb718bd1883513 100644 --- a/www/index.html +++ b/www/index.html @@ -115,6 +115,7 @@ <script src="dist/dist_js/plugins/es/js/services.js"></script> <script src="dist/dist_js/plugins/es/js/services/comment-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/http-services.js"></script> + <script src="dist/dist_js/plugins/es/js/services/settings-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/market-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/registry-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/social-services.js"></script> @@ -123,6 +124,7 @@ <script src="dist/dist_js/plugins/es/js/services/message-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/modal-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/blockchain-services.js"></script> + <script src="dist/dist_js/plugins/es/js/services/group-services.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/common-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/app-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/settings-controllers.js"></script> @@ -136,6 +138,7 @@ <script src="dist/dist_js/plugins/es/js/controllers/notification-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/blockchain-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/network-controllers.js"></script> + <script src="dist/dist_js/plugins/es/js/controllers/group-controllers.js"></script> <!--endRemoveIf(no-plugin)--> diff --git a/www/js/config.js b/www/js/config.js index 5142bccf37f25925e9c72d0ed4a6ffb9a3779429..401aab98d70952733dda01bc8a987a4b816e1354 100644 --- a/www/js/config.js +++ b/www/js/config.js @@ -10,32 +10,35 @@ angular.module("cesium.config", []) .constant("csConfig", { "cacheTimeMs": 60000, - "fallbackLanguage": "en", - "rememberMe": false, + "fallbackLanguage": "fr-FR", + "defaultLanguage": "fr-FR", + "rememberMe": true, "showUDHistory": false, - "timeout": 10000, + "timeout": 6000, "timeWarningExpireMembership": 5184000, "timeWarningExpire": 7776000, "useLocalStorage": true, "useRelative": true, "initPhase": false, - "expertMode": false, - "decimalCount": 4, - "httpsMode": false, + "expertMode": true, "helptip": { - "enable": true, - "installDocUrl": "https://github.com/duniter/duniter/blob/master/doc/install-a-node.md" + "enable": false, + "installDocUrl": { + "fr-FR": "http://www.le-sou.org/devenir-noeud/", + "en": "https://github.com/duniter/duniter/blob/master/doc/install-a-node.md" + } }, "node": { - "host": "gtest.duniter.org", + "host": "gtest.duniter.fr", "port": "10900" }, "plugins": { "es": { "enable": true, "askEnable": false, - "host": "data.gtest.duniter.fr", - "port": "80", + "host": "localhost", + "port": 9200, + "wsPort": 9400, "notifications": { "txSent": true, "txReceived": true, @@ -45,7 +48,7 @@ angular.module("cesium.config", []) } }, "version": "0.9.34", - "build": "2017-02-18T08:58:50.508Z", + "build": "2017-02-23T14:40:58.183Z", "newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug" }) diff --git a/www/js/controllers/settings-controllers.js b/www/js/controllers/settings-controllers.js index 1c4b32be6210e28f2889472ef469173b61d3ae13..4c93074f0618071c80475ef8ba52d9514c9e14b3 100644 --- a/www/js/controllers/settings-controllers.js +++ b/www/js/controllers/settings-controllers.js @@ -62,8 +62,10 @@ function SettingsController($scope, $q, $ionicPopup, $timeout, $translate, csHtt if ($scope.actionsPopover) { $scope.actionsPopover.hide(); } + $scope.pendingSaving = true; csSettings.reset(); angular.merge($scope.formData, csSettings.data); + $scope.pendingSaving = false; }; $scope.changeLanguage = function(langKey) { diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js index f7b5fbd24c656c158316310899f3abf3cbe1c9f4..bf116cd5d2f322af10f0bb98f0e2257890d8a470 100644 --- a/www/js/controllers/wot-controllers.js +++ b/www/js/controllers/wot-controllers.js @@ -383,7 +383,7 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $i if (!$scope.search.results.length) return; // Motion - if (res.length > 0) { + if (res.length > 0 && $scope.motion) { $scope.motion.show({selector: '.lookupForm .item.ink'}); } }; @@ -425,6 +425,7 @@ function WotLookupModalController($scope, $controller, $focus){ $scope.search.loading = false; $scope.enableFilter = false; + $scope.wotSearchTextId = 'wotSearchTextModal'; $scope.cancel = function(){ $scope.closeModal(); @@ -437,6 +438,10 @@ function WotLookupModalController($scope, $controller, $focus){ }); }; + $scope.doRefreshLocationHref = function() { + // Do NOT change location href + }; + $scope.showHelpTip = function() { // silent }; @@ -459,7 +464,7 @@ function WotLookupModalController($scope, $controller, $focus){ * @param csWallet * @constructor */ -function WotIdentityAbstractController($scope, $rootScope, $state, $timeout, $translate, UIUtils, Modals, csConfig, csWot, csWallet) { +function WotIdentityAbstractController($scope, $rootScope, $state, $translate, UIUtils, Modals, csConfig, csWot, csWallet) { 'ngInject'; $scope.formData = {}; diff --git a/www/js/services/crypto-services.js b/www/js/services/crypto-services.js index 6110bd3a854b3f1a57cbcc57c753538ead6c38eb..c710479e03bd6cf1125c12ff3adde4f5227b3edb 100644 --- a/www/js/services/crypto-services.js +++ b/www/js/services/crypto-services.js @@ -629,7 +629,7 @@ angular.module('cesium.crypto.services', ['ngResource', 'cesium.device.services' }) .then(function() { service.copy(serviceImpl); - console.debug('[crypto] Loaded \'{0}\' implementation in {1}ms'.format(service.id, new Date().getTime() - now)); + console.debug('[crypto] Loaded \'{0}\' implementation in {1}ms'.format(service.id, new Date().getTime() - now)); }); }); diff --git a/www/js/services/device-services.js b/www/js/services/device-services.js index 64456c56fdba70e7a229a093e2219d849c0d43c4..ee980fb1df76262f55284b929838970b4bb3de58 100644 --- a/www/js/services/device-services.js +++ b/www/js/services/device-services.js @@ -29,7 +29,17 @@ angular.module('cesium.device.services', ['ngResource', 'cesium.utils.services'] function ready() { if (!readyPromise) { readyPromise = $ionicPlatform.ready().then(function(){ - console.debug('[ionic] Platform is ready'); + + var enableCamera = !!navigator.camera; + exports.enable = enableCamera; + + if (exports.enable){ + var enableBarcodeScanner = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner; + console.debug('[device] Ionic platform ready, with [barcodescanner={0}] [camera={1}]'.format(enableBarcodeScanner, enableCamera)); + } + else { + console.debug('[device] Ionic platform ready - no device detected.'); + } }); } return readyPromise; @@ -128,21 +138,6 @@ angular.module('cesium.device.services', ['ngResource', 'cesium.utils.services'] return deferred.promise; } - // On platform ready: check if device could be used - ready().then(function() { - var enableCamera = !!navigator.camera; - - exports.enable = enableCamera; - - if (exports.enable){ - var enableBarcodeScanner = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner; - console.debug('[device] Ready with [barcodescanner={0}] [camera={1}]'.format(enableBarcodeScanner, enableCamera)); - } - else { - console.debug('[device] No device detected'); - } - }); - exports.ready = ready; exports.clipboard = {copy: copy}; exports.camera = { diff --git a/www/js/services/http-services.js b/www/js/services/http-services.js index 764a0b8220e6fa98bf2b9bb79469bd9dc483e5fb..0e276ba9549ef23054611d879ce241779536f617 100644 --- a/www/js/services/http-services.js +++ b/www/js/services/http-services.js @@ -18,8 +18,8 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services']) return !host ? null : (host + (port ? ':' + port : '')); } - function getUrl(host, port, path) { - var protocol = (port == 443 ? 'https' : 'http'); + function getUrl(host, port, path, useSsl) { + var protocol = (port == 443 || useSsl) ? 'https' : 'http'; return protocol + '://' + getServer(host, port) + (path ? path : ''); } @@ -48,7 +48,7 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services']) _.forEach(pkeys, function(pkey){ var prevURI = newUri; - newUri = newUri.replace(new RegExp(':' + pkey), params[pkey]); + newUri = newUri.replace(':' + pkey, params[pkey]); if (prevURI == newUri) { queryParams[pkey] = params[pkey]; } @@ -57,7 +57,6 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services']) return callback(newUri, config); } - function getResource(host, port, path) { var url = getUrl(host, port, path); return function(params) { @@ -137,33 +136,38 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services']) }; } - function ws(uri) { + function ws(host, port, path, useSsl) { + var uri = ((port == 443 || useSsl) ? 'wss' : 'ws') + '://' + getServer(host, port) + path; var sock = null; var callbacks = []; function _waitOpen() { if (!sock) throw new Error('Websocket not opened'); - if (sock && sock.readyState === 1) { + if (sock.readyState == 1) { return $q.when(sock); } + if (sock.readyState == 3) { + return $q.reject('Unable to connect to Websocket ['+sock.url+']'); + } return $timeout(_waitOpen, 100); } function _open(self, callback, params) { if (!sock) { prepare(uri, params, {}, function(uri) { + console.debug('[http] Listening on websocket ['+path+']...'); sock = new WebSocket(uri); + sock.onerror = function(e) { + sock.readyState=3; + }; + sock.onmessage = function(e) { + var obj = JSON.parse(e.data); + _.forEach(callbacks, function(callback) { + callback(obj); + }); + }; sockets.push(self); }); - sock.onerror = function(e) { - console.error(e); - }; - sock.onmessage = function(e) { - var obj = JSON.parse(e.data); - _.forEach(callbacks, function(callback) { - callback(obj); - }); - }; } if (callback) callbacks.push(callback); return _waitOpen(); @@ -184,6 +188,7 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services']) }, close: function() { if (sock) { + console.debug('[http] Stopping websocket ['+path+']...'); sock.close(); sock = null; callbacks = []; @@ -247,6 +252,7 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services']) }; } + return factory(csSettings.data.timeout); }) ; diff --git a/www/js/services/settings-services.js b/www/js/services/settings-services.js index 038d6b9c5d284fc2ca5a14ff8d26903eb9b06cfe..6c763ba3a2d1a96fb2de4c4180396b6858bf9234 100644 --- a/www/js/services/settings-services.js +++ b/www/js/services/settings-services.js @@ -4,7 +4,7 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi .factory('csSettings', function($q, Api, localStorage, $translate, csConfig, Device) { 'ngInject'; - CSSettings = function(id) { + function Factory() { // Define app locales var locales = [ @@ -81,11 +81,13 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi }, csConfig), data = angular.copy(defaultSettings), + previousData, - api = new Api(this, "csSettings-" + id), + api = new Api(this, "csSettings"), reset = function() { angular.merge(data, defaultSettings); + store(); }, getByPath = function(path, defaultValue) { @@ -101,6 +103,14 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi return obj; }, + emitChangedEvent = function() { + var hasChanged = previousData && !angular.equals(previousData, data); + previousData = angular.copy(data); + if (hasChanged) { + api.data.raise.changed(data); + } + }, + store = function() { if (data.useLocalStorage) { localStorage.setObject(constants.STORAGE_KEY, data); @@ -111,29 +121,26 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi // Emit event on store return api.data.raisePromise.store(data) - .then(function() { - // Emit event on changed - api.data.raise.changed(data); - }); + .then(emitChangedEvent); }, restore = function(first) { + var now = new Date().getTime(); return $q(function(resolve, reject){ - console.debug("[settings] Trying to restore settings..."); + console.debug("[settings] Loading from local storage..."); var storedData = localStorage.getObject(constants.STORAGE_KEY); var finishRestore = function() { - console.debug("[settings] Restored"); - - // Emit event on changed - api.data.raise.changed(data); + emitChangedEvent(); resolve(); }; // No settings stored if (!storedData) { - if (defaultSettings.locale.id !== $translate.use()) { - $translate.use(defaultSettings.locale.id); + console.debug("[settings] No settings in local storage"); + if (data.locale.id !== $translate.use()) { + console.debug("[settings] Changing locale to [{0}]...".format(data.locale.id)); + $translate.use(data.locale.id); finishRestore(); } return; @@ -186,6 +193,7 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi $translate.use(fixLocale(data.locale.id)); } + console.debug('[settings] Loaded from local storage in '+(new Date().getTime()-now)+'ms'); finishRestore(); }); }; @@ -195,7 +203,6 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi api.registerEvent('data', 'ready'); return { - id: id, data: data, getByPath: getByPath, reset: reset, @@ -206,11 +213,9 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi api: api, locales: locales }; - }; - - var service = CSSettings('default'); + } - service.instance = CSSettings; + var service = Factory(); service.restore() .then(function() { diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index 2a11ec31f1eb0ef60dc196f2ce723270c9cdac07..d480adbcec4a376de92c5b3d1d95e00fa98da408 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -4,7 +4,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser .factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, Api, localStorage, - CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob) { + CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob, csWot) { 'ngInject'; factory = function(id) { @@ -1014,19 +1014,19 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser } // Add TX to pendings - BMA.wot.member.get(destPub) - .then(function(member) { - data.tx.pendings.unshift({ - time: (Math.floor(moment().utc().valueOf() / 1000)), - amount: -amount, - pubkey: destPub, - uid: member ? member.uid : null, - comment: comments, - isUD: false, - hash: res.hash, - locktime: 0, - block_number: null - }); + var pendingTx = { + time: (Math.floor(moment().utc().valueOf() / 1000)), + amount: -amount, + pubkey: destPub, + comment: comments, + isUD: false, + hash: res.hash, + locktime: 0, + block_number: null + }; + csWot.extendAll([pendingTx], 'pubkey') + .then(function() { + data.tx.pendings.unshift(pendingTx); store(); // save pendings in local storage resolve(); }).catch(function(err){reject(err);}); diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js index 13f266ab31a74000e5ad100b92fd29619485f7da..4b06cd29e25348eaf97c1dfe87915e80bf5e06ac 100644 --- a/www/js/services/wot-services.js +++ b/www/js/services/wot-services.js @@ -520,7 +520,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic if (pubkey) { data = withCache ? identityCache.get(pubkey) : null; if (data && (!uid || data.uid == uid)) { - console.debug("[wot] Found cached identity " + pubkey.substring(0, 8)); + console.debug("[wot] Identity " + pubkey.substring(0, 8) + " found in cache"); return $q.when(data); } console.debug("[wot] Loading identity " + pubkey.substring(0, 8) + "..."); @@ -618,7 +618,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic if (!data.pubkey) return undefined; // not found delete data.lookup; // not need anymore identityCache.put(data.pubkey, data); // add to cache - console.debug('[wallet] Identity '+ data.pubkey.substring(0, 8) +' loaded in '+ (new Date().getTime()-now) +'ms'); + console.debug('[wot] Identity '+ data.pubkey.substring(0, 8) +' loaded in '+ (new Date().getTime()-now) +'ms'); return data; }); }, diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json index eb17ff208925b2b0c57db0a2c7d7797bb7ed1f24..9c2e50753955a154acb5c980daf19fb6cdad418c 100644 --- a/www/plugins/es/i18n/locale-fr-FR.json +++ b/www/plugins/es/i18n/locale-fr-FR.json @@ -31,6 +31,9 @@ }, "EVENT": { "MEMBER_WITHOUT_PROFILE": "Pour obtenir vos certifications plus rapidement, completez <a href=\"#/app/user/profile/edit\">votre profil utilisateur</a>. Les membres accorderont plus facilement leur confiance à une identité vérifiable." + }, + "ERROR": { + "WS_CONNECTION_FAILED": "Cesium ne peut plus recevoir les nouvelles notifications, à cause d'une erreur technique (connexion au noeud de noeud Cesium+).<br/><br/>Si le problème persiste, veuillez <b>changer de noeud de données</b> dans les paramètres de l'extension Cesium+." } }, "COMMENTS": { @@ -149,7 +152,11 @@ "OPEN_GROUP": "Groupe ouvert", "OPEN_GROUP_HELP": "Un groupe ouvert est accessible par n'importe quel membre de la monnaie.", "MANAGED_GROUP": "Groupe administré", - "MANAGED_GROUP_HELP": "un groupe administré est gérer par des administrateurs et des modérateurs, qui peuvent accepter, refuser ou exclure un membre en son sein." + "MANAGED_GROUP_HELP": "un groupe administré est gérer par des administrateurs et des modérateurs, qui peuvent accepter, refuser ou exclure un membre en son sein.", + "ENUM": { + "OPEN": "Groupe ouvert", + "MANAGED": "Groupe administré" + } }, "EDIT": { "TITLE": "Groupe", diff --git a/www/plugins/es/js/controllers/group-controllers.js b/www/plugins/es/js/controllers/group-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..525b654fcbee9a78d4f12213060aef1c38150ba1 --- /dev/null +++ b/www/plugins/es/js/controllers/group-controllers.js @@ -0,0 +1,505 @@ +angular.module('cesium.es.group.controllers', ['cesium.es.services']) + + .config(function($stateProvider) { + 'ngInject'; + + $stateProvider + + .state('app.groups', { + url: "/group?type&location", + views: { + 'menuContent': { + templateUrl: "plugins/es/templates/group/lookup.html", + controller: 'ESGroupListCtrl' + } + } + }) + + .state('app.add_group', { + url: "/group/add/:type", + views: { + 'menuContent': { + templateUrl: "plugins/es/templates/group/edit_group.html", + controller: 'ESGroupEditCtrl' + } + } + }) + + .state('app.edit_group', { + url: "/group/edit/:id", + views: { + 'menuContent': { + templateUrl: "plugins/es/templates/group/edit_group.html", + controller: 'ESGroupEditCtrl' + } + } + }) + + + .state('app.view_group', { + url: "/group/view/:id", + views: { + 'menuContent': { + templateUrl: "plugins/es/templates/group/view_record.html", + controller: 'ESGroupViewCtrl' + } + } + }) + + ; + }) + + .controller('ESGroupListCtrl', ESGroupListController) + + .controller('ESGroupViewCtrl', ESGroupViewController) + + .controller('ESGroupEditCtrl', ESGroupEditController) + + .controller('PopoverGroupCtrl', PopoverGroupController) + +; + +function ESGroupListController($scope, UIUtils, $state, csWallet, esGroup, ModalUtils) { + 'ngInject'; + + var defaultSearchLimit = 40; + + $scope.search = { + loading : true, + results: null, + type: 'last', + hasMore : false, + loadingMore : false, + limit: defaultSearchLimit + }; + $scope.enableFilter = !UIUtils.screen.isSmall(); + + $scope.$on('$ionicView.enter', function() { + if ($scope.search.loading) { + $scope.doSearch(); + } + }); + + $scope.doSearch = function(from, size) { + var options = {}; + options.from = options.from || from || 0; + options.size = options.size || size || defaultSearchLimit; + $scope.search.loading = true; + return esGroup.record.search(options) + .then(function(res) { + if (!from) { + $scope.search.results = res || []; + } + else if (res){ + $scope.search.results = $scope.search.results.concat(res); + } + $scope.search.loading = false; + $scope.search.hasMore = $scope.search.results.length >= $scope.search.limit; + $scope.updateView(); + }) + .catch(function(err) { + $scope.search.loading = false; + if (!from) { + $scope.search.results = []; + } + $scope.search.hasMore = false; + UIUtils.onError('GROUP.ERROR.SEARCH_GROUPS_FAILED')(err); + }); + }; + + $scope.updateView = function() { + + $scope.$broadcast('$$rebind::rebind'); // notify binder + $scope.motion.show({selector: '.list.{0} .item'.format($scope.motion.ionListClass)}); + }; + + $scope.select = function(item) { + if (item && item.id) $state.go('app.view_group', {id: item.id}); + }; + + $scope.showMore = function() { + $scope.search.limit = $scope.search.limit || defaultSearchLimit; + $scope.search.limit += defaultSearchLimit; + if ($scope.search.limit < defaultSearchLimit) { + $scope.search.limit = defaultSearchLimit; + } + $scope.search.loadingMore = true; + $scope.load( + $scope.search.results.length, // from + $scope.search.limit) + .then(function() { + $scope.search.loadingMore = false; + $scope.$broadcast('scroll.infiniteScrollComplete'); + }); + }; + + $scope.resetData = function() { + if ($scope.search.loading) return; + console.debug("[ES] [group] Resetting data (settings or account may have changed)"); + $scope.search.hasMore = false; + $scope.search.results = []; + $scope.search.loading = true; + delete $scope.search.limit; + }; + // When logout: force reload + csWallet.api.data.on.logout($scope, $scope.resetData); + + /* -- modals and views -- */ + + $scope.showNewRecordModal = function() { + $scope.loadWallet({minData: true}) + .then(function(walletData) { + UIUtils.loading.hide(); + $scope.walletData = walletData; + ModalUtils.show('plugins/es/templates/group/modal_record_type.html') + .then(function(type){ + if (type) { + $state.go('app.add_group', {type: type}); + } + }); + }); + }; +} + +function PopoverGroupController($scope, $timeout, UIUtils, $state, csWallet, esNotification, esGroup, esModals) { + 'ngInject'; + + var defaultSearchLimit = 40; + + $scope.search = { + loading : true, + results: null, + hasMore : false, + loadingMore : false, + limit: defaultSearchLimit, + options: { + codes: { + includes: esNotification.constants.GROUP_CODES + } + } + }; + + $scope.load = function(from, size) { + var options = angular.copy($scope.search.options); + options.from = options.from || from || 0; + options.size = options.size || size || defaultSearchLimit; + + return esNotification.load(csWallet.data.pubkey, options) + .then(function(notifications) { + if (!from) { + $scope.search.results = notifications; + } + else { + $scope.search.results = $scope.search.results.concat(notifications); + } + $scope.search.loading = false; + $scope.search.hasMore = ($scope.search.results && $scope.search.results.length >= $scope.search.limit); + $scope.updateView(); + }) + .catch(function(err) { + $scope.search.loading = false; + if (!from) { + $scope.search.results = []; + } + $scope.search.hasMore = false; + UIUtils.onError('MESSAGE.ERROR.LOAD_NOTIFICATIONS_FAILED')(err); + }); + }; + + $scope.updateView = function() { + + $timeout(function() { + UIUtils.ink({selector: '.popover-notification .item.ink'}); + }, 100); + }; + + $scope.showMore = function() { + $scope.search.limit = $scope.search.limit || defaultSearchLimit; + $scope.search.limit = $scope.search.limit * 2; + if ($scope.search.limit < defaultSearchLimit) { + $scope.search.limit = defaultSearchLimit; + } + $scope.search.loadingMore = true; + $scope.load( + $scope.search.results.length, // from + $scope.search.limit) + .then(function() { + $scope.search.loadingMore = false; + $scope.$broadcast('scroll.infiniteScrollComplete'); + }); + }; + + $scope.onNewNotification = function(notification) { + if (!$scope.search.loading && !$scope.search.loadingMore && notification.isMessage) { + console.debug("[popover] detected new message (from notification service)"); + + if (notification.reference) { + console.log("[popover] new message has a reference !"); + } + $scope.search.results.splice(0,0,notification); + $scope.updateView(); + } + }; + + $scope.select = function(notification) { + if (!notification.read) notification.read = true; + $state.go('app.user_view_message', {id: notification.id}); + $scope.closePopover(notification); + }; + + $scope.resetData = function() { + if ($scope.search.loading) return; + console.debug("[ES] [messages] Resetting data (settings or account may have changed)"); + $scope.search.hasMore = false; + $scope.search.results = []; + $scope.search.loading = true; + delete $scope.search.limit; + }; + + csWallet.api.data.on.logout($scope, $scope.resetData); + + /* -- Modals -- */ + + $scope.showNewMessageModal = function(parameters) { + $scope.closePopover(); + return esModals.showMessageCompose(parameters) + .then(function(sent) { + if (sent) UIUtils.toast.show('MESSAGE.INFO.MESSAGE_SENT'); + }); + }; + + esNotification.api.data.on.new($scope, $scope.onNewNotification); + + /* -- default popover action -- */ + if ($scope.search.loading) { + $scope.load(); + } + +} + + +function ESGroupViewController($scope, $state, UIUtils, esGroup, csWallet) { + 'ngInject'; + + $scope.formData = {}; + $scope.id = null; + $scope.pictures = []; + $scope.loading = true; + + $scope.$on('$ionicView.enter', function(e, state) { + if (state.stateParams && state.stateParams.id) { // Load by id + if ($scope.loading || state.stateParams.refresh) { // prevent reload if same id (if not forced) + $scope.load(state.stateParams.id, state.stateParams.anchor); + } + UIUtils.loading.hide(); + $scope.$broadcast('$recordView.enter', state); + } + else { + $state.go('app.groups'); + } + }); + + $scope.load = function(id) { + esGroup.record.load(id, { + fetchPictures: true + }) + .then(function (data) { + $scope.id = data.id; + $scope.formData = data.record; + $scope.issuer= data.issuer; + $scope.canEdit = csWallet.isUserPubkey($scope.formData.issuer); + + $scope.pictures = data.record.pictures || []; + delete data.record.pictures; // remove, as already stored in $scope.pictures + + $scope.loading = false; + UIUtils.loading.hide(); + $scope.updateView(); + + }) + .catch(UIUtils.onError('GROUP.ERROR.LOAD_RECORD_FAILED')); + }; + + $scope.updateView = function() { + $scope.motion.show(); + }; + + // Edit click + $scope.edit = function() { + UIUtils.loading.show(); + $state.go('app.edit_group', {id: $scope.id}); + }; +} + +function ESGroupEditController($scope, esGroup, UIUtils, $state, $q, Device, + $ionicHistory, ModalUtils, $focus, $timeout, esHttp) { + 'ngInject'; + + $scope.walletData = {}; + $scope.formData = {}; + $scope.id = null; + $scope.pictures = []; + $scope.loading = true; + + $scope.setForm = function(form) { + $scope.form = form; + }; + + $scope.$on('$ionicView.enter', function(e, state) { + $scope.loadWallet({minData: true}) + .then(function(walletData) { + $scope.walletData = walletData; + if (state.stateParams && state.stateParams.id) { // Load by id + $scope.load(state.stateParams.id); + } + else { + if (state.stateParams && state.stateParams.type) { + $scope.formData.type=state.stateParams.type; + } + $scope.loading = false; + UIUtils.loading.hide(); + $scope.updateView(); + } + // removeIf(device) + $focus('group-record-title'); + // endRemoveIf(device) + }); + }); + + $scope.load = function(id) { + esGroup.record.load(id, { + fetchPictures: true, + html: false + }) + .then(function (data) { + $scope.formData = data.record; + $scope.issuer= data.issuer; + $scope.id= data.id; + + $scope.pictures = data.record.pictures || []; + delete data.record.pictures; // remove, as already stored in $scope.pictures + + $scope.loading = false; + UIUtils.loading.hide(); + $scope.updateView(); + + }) + .catch(UIUtils.onError('GROUP.ERROR.LOAD_RECORD_FAILED')); + }; + + $scope.updateView = function() { + $scope.motion.show({selector: '.list.{0} .item, .card-gallery'.format($scope.motion.ionListClass)}); + }; + + $scope.save = function() { + $scope.form.$submitted=true; + if($scope.saving || // avoid multiple save + !$scope.form.$valid || + ($scope.formData.type !== 'managed' && $scope.formData.type !== 'open')) { + return; + } + $scope.saving = true; + return UIUtils.loading.show() + + .then(function(){ + var json = $scope.formData; + json.time = esHttp.date.now(); + + // Resize pictures + json.picturesCount = $scope.pictures.length; + if (json.picturesCount > 0) { + json.pictures = $scope.pictures.reduce(function(res, pic) { + return res.concat({file: esHttp.image.toAttachment(pic)}); + }, []); + return UIUtils.image.resizeSrc($scope.pictures[0].src, true) // resize thumbnail + .then(function(imageSrc) { + json.thumbnail = esHttp.image.toAttachment({src: imageSrc}); + return json; + }); + } + else { + if (json.thumbnail) { + // FIXME: this is a workaround to allow content deletion + // Is it a bug in the ES attachment-mapper ? + json.thumbnail = { + _content: '', + _content_type: '' + }; + } + json.pictures = []; + return json; + } + }) + .then(function(json){ + // Create + if (!$scope.id) { + json.creationTime = esHttp.date.now(); + return esGroup.record.add(json); + } + // Update + return esGroup.record.update(json, {id: $scope.id}); + }) + + .then(function(id) { + $scope.id = $scope.id || id; + $scope.saving = false; + $ionicHistory.clearCache($ionicHistory.currentView().stateId); // clear current view + $ionicHistory.nextViewOptions({historyRoot: true}); + return $state.go('app.view_group', {id: $scope.id, refresh: true}); + }) + + .catch(function(err) { + $scope.saving = false; + UIUtils.onError('GROUP.ERROR.SAVE_RECORD_FAILED')(err); + }); + }; + + $scope.openPicturePopup = function() { + Device.camera.getPicture() + .then(function(imageData) { + $scope.pictures.push({src: "data:image/png;base64," + imageData}); + $scope.$apply(); + }) + .catch(UIUtils.onError('ERROR.TAKE_PICTURE_FAILED')); + }; + + $scope.fileChanged = function(event) { + UIUtils.loading.show(); + return $q(function(resolve, reject) { + var file = event.target.files[0]; + UIUtils.image.resizeFile(file) + .then(function(imageData) { + $scope.pictures.push({src: imageData}); + UIUtils.loading.hide(); + resolve(); + }); + }); + }; + + $scope.removePicture = function(index){ + $scope.pictures.splice(index, 1); + }; + + $scope.favoritePicture = function(index){ + if (index > 0) { + var item = $scope.pictures[index]; + $scope.pictures.splice(index, 1); + $scope.pictures.splice(0, 0, item); + } + }; + + $scope.cancel = function() { + $ionicHistory.goBack(); + }; + + /* -- modals -- */ + $scope.showRecordTypeModal = function() { + ModalUtils.show('plugins/es/templates/group/modal_record_type.html') + .then(function(type){ + if (type) { + $scope.formData.type = type; + } + }); + }; + +} diff --git a/www/plugins/es/js/controllers/notification-controllers.js b/www/plugins/es/js/controllers/notification-controllers.js index fb73cb71250421cf4c9a6121278ee693aa7c48e2..586b8b805b80c3424dcd00375c2271e4d5e211b8 100644 --- a/www/plugins/es/js/controllers/notification-controllers.js +++ b/www/plugins/es/js/controllers/notification-controllers.js @@ -17,8 +17,6 @@ angular.module('cesium.es.notification.controllers', ['cesium.es.services']) } }) ; - - }) .controller('NotificationsCtrl', NotificationsController) @@ -27,7 +25,7 @@ angular.module('cesium.es.notification.controllers', ['cesium.es.services']) ; -function NotificationsController($scope, $rootScope, $timeout, UIUtils, $state, csWallet, esNotification) { +function NotificationsController($scope, $rootScope, UIUtils, $state, csWallet, esNotification) { 'ngInject'; var defaultSearchLimit = 40; diff --git a/www/plugins/es/js/controllers/registry-controllers.js b/www/plugins/es/js/controllers/registry-controllers.js index 297bc77dd8f9a890e12b0723616da8cb34665503..e0cc8f5abee2f7a10ea8218525a42e5b6754f5fe 100644 --- a/www/plugins/es/js/controllers/registry-controllers.js +++ b/www/plugins/es/js/controllers/registry-controllers.js @@ -282,14 +282,7 @@ function ESRegistryLookupController($scope, $state, $focus, $timeout, esRegistry $scope.search.loading = false; if (records.length > 0) { - // Set Motion - $timeout(function() { - UIUtils.motion.ripple({ - startVelocity: 3000 - }); - // Set Ink - UIUtils.ink(); - }, 10); + $scope.motion.show(); } }) .catch(function(err) { @@ -382,6 +375,7 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo $scope.pictures = []; $scope.canEdit = false; $scope.loading = true; + $scope.motion = UIUtils.motion.fadeSlideIn; $scope.$on('$ionicView.enter', function(e, state) { if (state.stateParams && state.stateParams.id) { // Load by id @@ -410,14 +404,7 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo UIUtils.loading.hide(); $scope.loading = false; // Set Motion (only direct children, to exclude .lazy-load children) - $timeout(function() { - UIUtils.motion.fadeSlideIn({ - selector: '.list > .item, .list > ng-if > .item', - startVelocity: 3000 - }); - UIUtils.ink({ - selector: '.list .item.ink'}); - }, 10); + $scope.motion.show({selector: '.list > .item, .list > ng-if > .item'}); }) .catch(function(err) { // Retry (ES could have error) diff --git a/www/plugins/es/js/controllers/settings-controllers.js b/www/plugins/es/js/controllers/settings-controllers.js index c78a9055f944e6f52fb3c00e9e1fcd4504b06d82..ff6ca7ba8601c8248a02287cdf8fd83f30f1cade 100644 --- a/www/plugins/es/js/controllers/settings-controllers.js +++ b/www/plugins/es/js/controllers/settings-controllers.js @@ -48,7 +48,7 @@ function ESExtendSettingsController ($scope, PluginService, csSettings) { /* * Settings extend controller */ -function ESPluginSettingsController ($scope, $q, $translate, $ionicPopup, UIUtils, Modals, esHttp, csSettings, csHttp, esUser) { +function ESPluginSettingsController ($scope, $q, $translate, $ionicPopup, UIUtils, Modals, csHttp, csSettings, esUser, esHttp) { 'ngInject'; $scope.formData = {}; @@ -81,23 +81,22 @@ function ESPluginSettingsController ($scope, $q, $translate, $ionicPopup, UIUti } UIUtils.loading.show(); - return esHttp.get(newNode.host, newNode.port, '/node/summary')() // ping the node - .then(function(json) { - var valid = json && json.duniter && json.duniter.software === 'duniter4j-elasticsearch'; - if (!valid) throw 'stop'; + var newEsNode = esHttp.instance(newNode.host, newNode.port); + return newEsNode.isAlive() // ping the node + .then(function(alive) { + if (!alive) { + UIUtils.loading.hide(); + return UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY') + .then(function(){ + $scope.changeEsNode(newNode); // loop + }); + } UIUtils.loading.hide(); $scope.formData.host = newNode.host; $scope.formData.port = newNode.port; - esUser.copy(esUser.instance('default', newNode.host, newNode.port)); - }) - .catch(function(err) { - UIUtils.loading.hide(); - UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY') - .then(function(){ - $scope.changeEsNode(newNode); // loop - }); + esHttp.copy(newEsNode); }); }); }; diff --git a/www/plugins/es/js/controllers/user-controllers.js b/www/plugins/es/js/controllers/user-controllers.js index 22546d0177f1160801c3aad6b9066403c11163fd..ba76816c293db6696f2a198bae350d1aebec95f0 100644 --- a/www/plugins/es/js/controllers/user-controllers.js +++ b/www/plugins/es/js/controllers/user-controllers.js @@ -255,6 +255,7 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl .then(function(imageData) { if (!imageData) return; $scope.avatar = {src: imageData}; + $scope.avatarStyle={'background-image':'url("'+imageData+'")'}; $scope.dirty = true; }); } diff --git a/www/plugins/es/js/plugin.js b/www/plugins/es/js/plugin.js index daf538695d4173ee762fe49ec3b99df8644179e6..f1a7d239316ec931a447d3486f90d48207dda76d 100644 --- a/www/plugins/es/js/plugin.js +++ b/www/plugins/es/js/plugin.js @@ -13,6 +13,8 @@ angular.module('cesium.es.plugin', [ 'cesium.es.message.controllers', 'cesium.es.notification.controllers', 'cesium.es.blockchain.controllers', - 'cesium.es.network.controllers' + 'cesium.es.network.controllers', + 'cesium.es.registry.controllers', + 'cesium.es.group.controllers' ]) ; diff --git a/www/plugins/es/js/services.js b/www/plugins/es/js/services.js index c66b12a63a53b52462190dfcac3ac4e5415fb520..18ebff6271227bda6f47ff02d3fc808c53519f8a 100644 --- a/www/plugins/es/js/services.js +++ b/www/plugins/es/js/services.js @@ -4,10 +4,13 @@ angular.module('cesium.es.services', [ 'cesium.es.http.services', 'cesium.es.comment.services', 'cesium.es.social.services', + 'cesium.es.settings.services', 'cesium.es.user.services', 'cesium.es.notification.services', 'cesium.es.message.services', 'cesium.es.modal.services', - 'cesium.es.blockchain.services' + 'cesium.es.blockchain.services', + 'cesium.es.registry.services', + 'cesium.es.group.services' ]) ; diff --git a/www/plugins/es/js/services/blockchain-services.js b/www/plugins/es/js/services/blockchain-services.js index 70eddb31369c144076adc857e4ac9179da63c2c3..f99c66252c906a70b816e39505cbf9ae754e5485 100644 --- a/www/plugins/es/js/services/blockchain-services.js +++ b/www/plugins/es/js/services/blockchain-services.js @@ -1,14 +1,4 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.http.services', 'cesium.es.user.services']) -.config(function(PluginServiceProvider, csConfig) { - 'ngInject'; - - var enable = csConfig.plugins && csConfig.plugins.es; - if (enable) { - // Will force to load this service - PluginServiceProvider.registerEagerLoadingService('esBlockchain'); - } - - }) .factory('esBlockchain', function($rootScope, $q, $timeout, esHttp, csConfig, csSettings, esUser) { 'ngInject'; @@ -24,7 +14,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h MINIMAL: ['number', 'hash', 'medianTime', 'issuer'], COMMONS: ['number', 'hash', 'medianTime', 'issuer', 'currency', 'version', 'powMin', 'dividend', 'membersCount', 'identities', 'joiners', 'actives', 'leavers', 'revoked', 'excluded', 'certifications', 'transactions'] }, - listeners, exports = { node: {}, block: {}, @@ -45,7 +34,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h } function copy(otherNode) { - removeListeners(); if (!!this.instance) { var instance = this.instance; angular.copy(otherNode, this); @@ -54,7 +42,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h else { angular.copy(otherNode, this); } - addListeners(); } exports.node.parseEndPoint = function(endpoint) { @@ -140,47 +127,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h .then(exports.raw.block.processSearchResult); }; - - function removeListeners() { - console.debug('[ES] [blockchain] Disable'); - _.forEach(listeners, function(remove){ - remove(); - }); - listeners = []; - } - - function addListeners() { - console.debug('[ES] [blockchain] Enable'); - - listeners = [ - //csWot.api.data.on.search($rootScope, onWotSearch, this) - ]; - } - - function isEnable() { - return csSettings.data.plugins && - csSettings.data.plugins.es && - host && csSettings.data.plugins.es.enable; - } - - function refreshListeners() { - var enable = isEnable(); - if (!enable && listeners && listeners.length > 0) { - removeListeners(); - } - else if (enable && (!listeners || listeners.length === 0)) { - addListeners(); - } - } - - // Listen for settings changed - csSettings.api.data.on.changed($rootScope, function(){ - refreshListeners(); - }); - - // Default action - refreshListeners(); - return exports; } diff --git a/www/plugins/es/js/services/comment-services.js b/www/plugins/es/js/services/comment-services.js index 3db1bc0cfd59e800ae4ede9ecdcf1531b7d5a7ca..1e9f15507b3df0ccaf815d4fc70e0fa47e0316d2 100644 --- a/www/plugins/es/js/services/comment-services.js +++ b/www/plugins/es/js/services/comment-services.js @@ -6,6 +6,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.bma.services function factory(host, port, wsPort, index) { var + that = this, DEFAULT_SIZE = 20, fields = { commons: ["issuer", "time", "message", "reply_to"], @@ -16,11 +17,11 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.bma.services commons: fields.commons }, raw: { - search: esHttp.post(host, port, '/'+index+'/comment/_search'), - remove: esHttp.record.remove(host, port, index, 'comment'), - wsChanges: esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://' + esHttp.getServer(host, wsPort) + '/ws/_changes'), - add: new esHttp.record.post(host, port, '/'+index+'/comment'), - update: new esHttp.record.post(host, port, '/'+index+'/comment/:id/_update') + search: esHttp.post('/'+index+'/comment/_search'), + remove: esHttp.record.remove(index, 'comment'), + wsChanges: esHttp.ws('/ws/_changes'), + add: new esHttp.record.post('/'+index+'/comment'), + update: new esHttp.record.post('/'+index+'/comment/:id/_update') } }; diff --git a/www/plugins/es/js/services/group-services.js b/www/plugins/es/js/services/group-services.js new file mode 100644 index 0000000000000000000000000000000000000000..e247c7db9190e5ed59f18680fded03c2552826a4 --- /dev/null +++ b/www/plugins/es/js/services/group-services.js @@ -0,0 +1,258 @@ +angular.module('cesium.es.group.services', ['ngResource', 'cesium.services', 'cesium.es.http.services', + 'cesium.es.user.services', 'cesium.es.notification.services', 'cesium.es.comment.services']) + .config(function(PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.es; + if (enable) { + // Will force to load this service + PluginServiceProvider.registerEagerLoadingService('esGroup'); + } + + }) + +.factory('esGroup', function($q, $rootScope, csSettings, esHttp, CryptoUtils, esUser, csWallet, esNotification, esComment) { + 'ngInject'; + + function factory(host, port, wsPort) { + + var + listeners, + defaultLoadSize = 50, + fields = { + list: ["issuer", "title"], + commons: ["issuer", "title", "description", "creationTime", "time", "signature"], + notifications: ["issuer", "time", "hash", "read_signature"] + }, + exports = { + _internal: {}, + node: { + host: host, + port: port, + server: esHttp.getServer(host, port) + } + }; + + function copy(otherNode) { + if (!!this.instance) { + var instance = this.instance; + angular.copy(otherNode, this); + this.instance = instance; + } + else { + angular.copy(otherNode, this); + } + } + + function onWalletInit(data) { + data.groups = data.groups || {}; + data.groups.unreadCount = null; + } + + function onWalletReset(data) { + if (data.groups) { + delete data.groups; + } + } + + function onWalletLogin(data, deferred) { + deferred = deferred || $q.defer(); + if (!data || !data.pubkey) { + deferred.resolve(); + return deferred.promise; + } + + // Count unread notifications + esNotification.unreadCount(data.pubkey, {codes: { + includes: ['GROUP_INVITATION'], + excludes: [] + }}) + .then(function(unreadCount){ + data.groups = data.groups || {}; + data.groups.unreadCount = unreadCount; + console.debug('[ES] [group] Detecting ' + unreadCount + ' unread notifications'); + deferred.resolve(data); + }) + .catch(function(err){ + console.error('Error while counting group notifications: ' + (err.message ? err.message : err)); + deferred.resolve(data); + }); + return deferred.promise; + } + + function readRecordFromHit(hit, html) { + if (!hit) return; + var record = hit._source; + if (html && hit.highlight) { + if (hit.highlight.title) { + record.title = hit.highlight.title[0]; + } + if (hit.highlight.description) { + record.description = hit.highlight.description[0]; + } + if (hit.highlight.location) { + record.location = hit.highlight.location[0]; + } + if (hit.highlight.tags) { + data.tags = hit.highlight.tags.reduce(function(res, tag){ + return res.concat(tag.replace('<em>', '').replace('</em>', '')); + },[]); + } + } + + // description + if (html) { + record.description = esHttp.util.trustAsHtml(record.description); + } + + // thumbnail + record.thumbnail = esHttp.image.fromHit(hit, 'thumbnail'); + + // pictures + if (hit._source.pictures && hit._source.pictures.reduce) { + record.pictures = hit._source.pictures.reduce(function(res, pic) { + return res.concat(esHttp.image.fromAttachment(pic.file)); + }, []); + } + + return record; + } + + exports._internal.search = esHttp.post('/group/record/_search'); + + function searchGroups(options) { + if (!csWallet.isLogin()) { + return $q.when([]); + } + + options = options || {}; + options.from = options.from || 0; + options.size = options.size || defaultLoadSize; + options._source = options._source || fields.list; + var request = { + sort: { + "time" : "desc" + }, + from: options.from, + size: options.size, + _source: options._source + }; + + return exports._internal.search(request) + .then(function(res) { + if (!res || !res.hits || !res.hits.total) { + return []; + } + var groups = res.hits.hits.reduce(function(res, hit) { + var record = readRecordFromHit(hit, true/*html*/); + record.id = hit._id; + return record ? res.concat(record) : res; + }, []); + + console.debug('[ES] [group] Loading {0} {1} messages'.format(groups.length, options.type)); + + return groups; + }); + } + + exports._internal.get = esHttp.get('/group/record/:id'); + exports._internal.getCommons = esHttp.get('/group/record/:id?_source=' + fields.commons.join(',')); + + function loadData(id, options) { + options = options || {}; + options.fecthPictures = angular.isDefined(options.fetchPictures) ? options.fetchPictures : false; + options.html = angular.isDefined(options.html) ? options.html : true; + + // Do get source + var promise = options.fecthPictures ? + exports._internal.get({id: id}) : + exports._internal.getCommons({id: id}); + + return promise + .then(function(hit) { + var record = readRecordFromHit(hit, options.html); + + // Load issuer (avatar, name, uid, etc.) + return esUser.profile.fillAvatars([{pubkey: record.issuer}]) + .then(function(idties) { + var data = { + id: hit._id, + issuer: idties[0], + record: record + }; + return data; + }); + }); + } + + function removeListeners() { + console.debug("[ES] [group] Disable"); + + _.forEach(listeners, function(remove){ + remove(); + }); + listeners = []; + } + + function addListeners() { + console.debug("[ES] [group] Enable"); + + // Extend csWallet.loadData() + listeners = [ + csWallet.api.data.on.login($rootScope, onWalletLogin, this), + csWallet.api.data.on.init($rootScope, onWalletInit, this), + csWallet.api.data.on.reset($rootScope, onWalletReset, this), + ]; + } + + function isEnable() { + return csSettings.data.plugins && + csSettings.data.plugins.es && + host && csSettings.data.plugins.es.enable; + } + + function refreshListeners() { + var enable = isEnable(); + if (!enable && listeners && listeners.length > 0) { + removeListeners(); + } + else if (enable && (!listeners || listeners.length === 0)) { + addListeners(); + } + } + + // Listen for settings changed + csSettings.api.data.on.changed($rootScope, function(){ + refreshListeners(); + if (isEnable() && !csWallet.data.messages) { + onWalletLogin(csWallet.data); + } + }); + + // Default action + refreshListeners(); + + return { + record: { + search: searchGroups, + load: loadData, + add: esHttp.record.post('/group/record'), + update: esHttp.record.post('/group/record/:id/_update'), + remove: esHttp.record.remove('group', 'record'), + fields: { + commons: fields.commons + }, + picture: { + all: esHttp.get('/group/record/:id?_source=pictures') + }, + comment: new esComment.instance(wsPort, 'group') + } + }; + } + + var service = factory(); + + service.instance = factory; + return service; +}) +; diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js index cd6c44052fa122dd59cb18523f968b26e13fcf8b..6f6e4e8f6301fac80ca223aa4c4781d1f325acab 100644 --- a/www/plugins/es/js/services/http-services.js +++ b/www/plugins/es/js/services/http-services.js @@ -1,39 +1,148 @@ -angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'cesium.config']) +angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.services', 'cesium.config']) /** * Elastic Search Http */ -.factory('esHttp', function($q, CryptoUtils, csHttp, $rootScope, $state, $sce, csConfig, csSettings, csWallet) { +.factory('esHttp', function($q, $timeout, CryptoUtils, csHttp, $rootScope, $state, $sce, csConfig, csSettings, csWallet, Api) { 'ngInject'; - function factory() { + function Factory(host, port, wsPort, useSsl) { var - that, + that = this, regex = { IMAGE_SRC: exact('data:([A-Za-z//]+);base64,(.+)'), - HASH_TAG: new RegExp('#([\\wḡĞğ]+)'), - USER_TAG: new RegExp('@(\\w+)') + HASH_TAG: match('#([\\wḡĞğ]+)'), + USER_TAG: match('@(\\w+)') }; + this.cache = _emptyCache(); + that.alive = false; + that.host = host; + that.port = port || 80; + that.wsPort = wsPort || 80; + that.useSsl = angular.isDefined(useSsl) ? useSsl : false; + that.server = csHttp.getServer(host, port); + that.date = {}; + that.api = new Api(this, "esHttp"); function exact(regexpContent) { return new RegExp('^' + regexpContent + '$'); } + function match(regexpContent) { + return new RegExp(regexpContent); + } + + function _emptyCache() { + return { + getByPath: {}, + postByPath: {}, + wsByPath: {} + }; + } + + that.cleanCache = function() { + _.keys(that.cache.wsByPath).forEach(function(key) { + var sock = that.cache.wsByPath[key]; + sock.close(); + }); + console.debug('[ES] [http] Cleaning requests cache'); + that.cache = _emptyCache(); + }; + + that.copy = function(otherNode) { + that.host = otherNode.host; + that.port = otherNode.port; + that.wsPort = otherNode.wsPort || otherNode.port; + that.useSsl = otherNode.useSsl; + that.server = csHttp.getServer(host, port); + return that.restart(); + }; // Get time (UTC) - function getTimeNow() { + that.date.now = function() { // TODO : use the block chain time return Math.floor(moment().utc().valueOf() / 1000); - } + }; - function get(host, node, path) { - return csHttp.get(host, node, path); - } + that.getUrl = function(path) { + return csHttp.getUrl(that.host, that.port, path, that.useSsl); + }; - function post(host, node, path) { - return csHttp.post(host, node, path); - } + that.get = function (path) { + return function(params) { + var request = that.cache.getByPath[path]; + if (!request) { + request = csHttp.get(that.host, that.port, path, that.useSsl); + that.cache.getByPath[path] = request; + } + return request(params); + }; + }; + + that.post = function(path) { + return function(obj, params) { + var request = that.cache.postByPath[path]; + if (!request) { + request = csHttp.post(that.host, that.port, path, that.useSsl); + that.cache.postByPath[path] = request; + } + return request(obj, params); + }; + }; + + that.ws = function(path) { + return function() { + var sock = that.cache.wsByPath[path]; + if (!sock) { + sock = csHttp.ws(that.host, that.wsPort, path, that.useSsl); + that.cache.wsByPath[path] = sock; + } + return sock; + }; + }; + + that.isAlive = function() { + return that.node.summary() + .then(function(json) { + return json && json.duniter && json.duniter.software == 'duniter4j-elasticsearch'; + }) + .catch(function() { + return false; + }); + }; + + that.start = function() { + + console.debug('[ES] [http] Starting on [{0}]...'.format(that.server)); + var now = new Date().getTime(); + + return that.isAlive() + .then(function(alive) { + that.alive = alive; + if (!alive) { + console.error('[ES] [http] Could not start [{0}]: node unreachable'.format(that.server)); + return false; + } + console.debug('[ES] [http] Started in '+(new Date().getTime()-now)+'ms'); + that.api.node.raise.start(); + return true; + }); + }; + + that.stop = function() { + console.debug('[ES] [http] Stopped'); + that.alive = false; + that.cleanCache(); + that.api.node.raise.stop(); + }; + + that.restart = function() { + that.stop(); + return $timeout(function() { + that.start(); + }, 200); + }; function parseTagsFromText(value, prefix) { prefix = prefix || '#'; @@ -91,12 +200,8 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces }); } - function postRecord(host, node, path) { - var that = this; - that.raw = that.raw || {}; - that.raw.post = that.raw.post || {}; - that.raw.post[path] = csHttp.post(host, node, path); - + function postRecord(path) { + var postRequest = that.post(path); return function(record, params) { if (!csWallet.isLogin()) { var deferred = $q.defer(); @@ -105,7 +210,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces } if (!record.time) { - record.time = getTimeNow(); + record.time = that.date.now(); } var keypair = $rootScope.walletData.keypair; var obj = {}; @@ -125,7 +230,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces .then(function(signature) { obj.hash = hash; obj.signature = signature; - return that.raw.post[path](obj, params) + return postRequest(obj, params) .then(function (id){ return id; }); @@ -134,11 +239,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces }; } - function removeRecord(host, node, index, type) { - var that = this; - that.raw = that.raw || {}; - that.raw.delete = csHttp.post(host, node, '/history/delete'); - + function removeRecord(index, type) { return function(id) { if (!csWallet.isLogin()) { var deferred = $q.defer(); @@ -152,7 +253,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces type: type, id: id, issuer: $rootScope.walletData.pubkey, - time: getTimeNow() + time: that.date.now() }; var str = JSON.stringify(obj); return CryptoUtils.util.hash(str) @@ -161,7 +262,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces .then(function(signature) { obj.hash = hash; obj.signature = signature; - return that.raw.delete(obj) + return that.post('/history/delete')(obj) .then(function (id){ return id; }); @@ -170,6 +271,8 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces }; } + that.image = {}; + function imageFromAttachment(attachment) { if (!attachment || !attachment._content_type || !attachment._content || attachment._content.length === 0) { return null; @@ -211,7 +314,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces * @param imageField * @returns {{}} */ - function imageFromHit(host, port, hit, imageField) { + that.image.fromHit = function(hit, imageField) { if (!hit || !hit._source) return; var attachment = hit._source[imageField]; if (!attachment || !attachment._content_type || !attachment._content_type.startsWith("image/")) return; @@ -225,7 +328,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces var extension = attachment._content_type.substr(6); var path = [hit._index, hit._type, hit._id, '_image', imageField].join('/'); path = '/' + path + '.' + extension; - image.src = csHttp.getUrl(host, port, path); + image.src = that.getUrl(path); } if (attachment._title) { image.title = attachment._title; @@ -234,7 +337,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces image.name = attachment._name; } return image; - } + }; function login(host, node, keypair) { return $q(function(resolve, reject) { @@ -290,18 +393,19 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces }; } - that = { - get: get, - post: post, - getUrl : csHttp.getUrl, + that.api.registerEvent('node', 'start'); + that.api.registerEvent('node', 'stop'); + + var exports = { getServer: csHttp.getServer, - ws: csHttp.ws, + node: { + summary: that.get('/node/summary') + }, record: { post: postRecord, remove: removeRecord }, image: { - fromHit : imageFromHit, fromAttachment: imageFromAttachment, toAttachment: imageToAttachment }, @@ -316,17 +420,23 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces parseTags: parseTagsFromText, trustAsHtml: trustAsHtml }, - date: { - now: getTimeNow - }, constants: { regexp: regex } }; - return that; + angular.merge(that, exports); } - var service = factory(); + var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null; + var port = host ? csSettings.data.plugins.es.port : null; + var wsPort = host ? csSettings.data.plugins.es.wsPort : port; + + var service = new Factory(host, port, wsPort); + + service.instance = function(host, port, wsPort) { + return new Factory(host, port, wsPort); + }; + return service; }) ; diff --git a/www/plugins/es/js/services/message-services.js b/www/plugins/es/js/services/message-services.js index 819037935d54e7efec99691fd98e9ae8c1725bec..9a8afcf20042c20621b4d722edefe69a959fbd2f 100644 --- a/www/plugins/es/js/services/message-services.js +++ b/www/plugins/es/js/services/message-services.js @@ -10,10 +10,10 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' }) -.factory('esMessage', function($q, $rootScope, csSettings, esHttp, CryptoUtils, esUser, csWallet) { +.factory('esMessage', function($q, $rootScope, csSettings, esHttp, CryptoUtils, esUser, csWallet, Device) { 'ngInject'; - function factory(host, port) { + function Factory() { var listeners, @@ -21,19 +21,13 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' fields = { commons: ["issuer", "recipient", "title", "content", "time", "nonce", "read_signature"], notifications: ["issuer", "time", "hash", "read_signature"] + }, + raw = { + postSearch: esHttp.post('/message/inbox/_search'), + getByTypeAndId : esHttp.get('/message/:type/:id'), + postReadById: esHttp.post('/message/inbox/:id/_read') }; - function copy(otherNode) { - if (!!this.instance) { - var instance = this.instance; - angular.copy(otherNode, this); - this.instance = instance; - } - else { - angular.copy(otherNode, this); - } - } - function onWalletInit(data) { data.messages = data.messages || {}; data.messages.unreadCount = null; @@ -56,12 +50,15 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' return deferred.promise; } + console.debug('[ES] [message] Loading count...'); + var now = new Date().getTime(); + // Count unread messages countUnreadMessages(data.pubkey) .then(function(unreadCount){ data.messages = data.messages || {}; data.messages.unreadCount = unreadCount; - console.debug('[ES] [message] Detecting ' + unreadCount + ' unread messages'); + console.debug('[ES] [message] Loaded count (' + unreadCount + ') in '+(new Date().getTime()-now)+'ms'); deferred.resolve(data); }) .catch(function(err){ @@ -105,7 +102,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' } }; - return esHttp.post(host, port, '/message/inbox/_count')(request) + return esHttp.post('/message/inbox/_count')(request) .then(function(res) { return res.count; }); @@ -159,7 +156,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' ]) .then(function(cypherTexts){ // Send message - return esHttp.record.post(host, port, boxPath)({ + return esHttp.record.post(boxPath)({ issuer: message.issuer, recipient: message.recipient, title: cypherTexts[0], @@ -187,7 +184,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' _source: fields.notifications }; - return esHttp.post(host, port, '/message/inbox/_search')(request) + return raw.postSearch(request) .then(function(res) { if (!res || !res.hits || !res.hits.total) { return []; @@ -232,7 +229,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' request.query = {bool: {filter: {term: {issuer: csWallet.data.pubkey}}}}; } - return esHttp.post(host, port, '/message/:type/_search')(request, {type: options.type}) + return esHttp.post('/message/:type/_search')(request, {type: options.type}) .then(function(res) { if (!res || !res.hits || !res.hits.total) { return []; @@ -286,7 +283,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' function getAndDecrypt(params, keypair) { params.type = params.type || 'inbox'; var avatarField = (params.type == 'inbox') ? 'issuer' : 'recipient'; - return esHttp.get(host, port, '/message/:type/:id')(params) + return raw.getByTypeAndId(params) .then(function(hit) { if (!hit.found) { return; @@ -363,7 +360,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' function removeMessage(id, type) { type = type || 'inbox'; - return esHttp.record.remove(host, port, 'message', type)(id) + return esHttp.record.remove('message', type)(id) .then(function(res) { // update message count if (type == 'inbox') { @@ -384,7 +381,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' // Remove each messages return $q.all(res.reduce(function(res, msg) { - return res.concat(esHttp.record.remove(host, port, 'message', type)(msg.id)); + return res.concat(esHttp.record.remove('message', type)(msg.id)); }, [])); }) .then(function() { @@ -411,7 +408,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' // Send read request .then(function(signature){ - return esHttp.post(host, port, '/message/inbox/:id/_read')(signature, {id:message.id}); + return raw.postReadById(signature, {id:message.id}); }) // Update message count @@ -441,7 +438,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' CryptoUtils.sign(message.hash, csWallet.data.keypair) // then send read request .then(function(signature){ - return esHttp.post(host, port, '/message/inbox/:id/_read')(signature, {id:message.id}); + return raw.postReadById(signature, {id:message.id}); })); }, [])); }) @@ -453,7 +450,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' } function removeListeners() { - console.debug("[ES] Disable message extension"); + console.debug("[ES] [message] Disable"); _.forEach(listeners, function(remove){ remove(); @@ -472,39 +469,28 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' ]; } - function isEnable() { - return csSettings.data.plugins && - csSettings.data.plugins.es && - host && csSettings.data.plugins.es.enable; - } - function refreshListeners() { - var enable = isEnable(); + var enable = esHttp.alive; if (!enable && listeners && listeners.length > 0) { removeListeners(); } else if (enable && (!listeners || listeners.length === 0)) { addListeners(); + if (csWallet.isLogin() && !csWallet.data.messages) { + onWalletLogin(csWallet.data); + } } } - // Listen for settings changed - csSettings.api.data.on.changed($rootScope, function(){ + // Default action + Device.ready().then(function() { refreshListeners(); - if (isEnable() && !csWallet.data.messages) { - onWalletLogin(csWallet.data); - } + esHttp.api.node.on.start($rootScope, refreshListeners, this); + esHttp.api.node.on.stop($rootScope, refreshListeners, this); }); - // Default action - refreshListeners(); - return { - copy: copy, - node: { - server: esHttp.getServer(host, port) - }, - search: esHttp.post(host, port, '/message/inbox/_search'), + search: raw.postSearch, notifications: { load: loadMessageNotifications }, @@ -521,12 +507,6 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' }; } - var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null; - var port = host ? csSettings.data.plugins.es.port : null; - - var service = factory(host, port); - - service.instance = factory; - return service; + return Factory(); }) ; diff --git a/www/plugins/es/js/services/notification-services.js b/www/plugins/es/js/services/notification-services.js index 146ba7c823e2d44e4985581b994b8ad1b0833945..9d3469a6af53c0f9db2e7ae10194fa99c6f552bd 100644 --- a/www/plugins/es/js/services/notification-services.js +++ b/www/plugins/es/js/services/notification-services.js @@ -13,7 +13,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es .factory('esNotification', function($rootScope, $q, $timeout, esHttp, csConfig, csSettings, csWallet, csWot, UIUtils, BMA, CryptoUtils, Device, Api, esUser) { 'ngInject'; - function factory(id, host, port, wsPort) { + function Factory() { var listeners, defaultLoadSize = 20, @@ -24,9 +24,20 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es fields = { commons: ["type", "code", "params", "reference", "recipient", "time", "hash", "read_signature"] }, - api = new Api(this, 'esNotification-' + id) + that = this, + api = new Api(this, 'esNotification') ; + that.raw = { + postCount: esHttp.post('/user/event/_count'), + postSearch: esHttp.post('/user/event/_search'), + postReadById: esHttp.post('/user/event/:id/_read'), + ws: { + getUserEvent: esHttp.ws('/ws/event/user/:pubkey/:locale'), + getChanges: esHttp.ws('/ws/_changes') + } + }; + // Create the filter query function createFilterQuery(pubkey, options) { options = options || {}; @@ -83,7 +94,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es }; // Filter unread only request.query.bool.must.push({missing: { field : "read_signature" }}); - return esHttp.post(host, port, '/user/event/_count')(request) + return that.raw.postCount(request) .then(function(res) { return res.count; }); @@ -104,7 +115,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es _source: fields.commons }; - return esHttp.post(host, port, '/user/event/_search')(request) + return that.raw.postSearch(request) .then(function(res) { if (!res.hits || !res.hits.total) return []; var notifications = res.hits.hits.reduce(function(res, hit) { @@ -117,34 +128,29 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es }); } - function listenNewNotification(data) { - esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/event/user/:pubkey/:locale') - .on(function(event) { - $rootScope.$apply(function() { - var notification = new Notification(event, markNotificationAsRead); - esUser.profile.fillAvatars([notification]) - .then(function() { - var isMessage = _.contains(constants.MESSAGE_CODES, event.code); - var isGroup = _.contains(constants.GROUP_CODES, event.code); - notification.isMessage = isMessage; - if (isMessage) { - data.messages = data.messages || {}; - data.messages.unreadCount++; - } - else if (isGroup) { - data.groups = data.groups || {}; - data.groups.unreadCount++; - } - else { - data.notifications = data.notifications || {}; - data.notifications.unreadCount++; - } - api.data.raise.new(notification); - }); - }); - }, - {pubkey: data.pubkey, locale: csSettings.data.locale.id} - ); + function onNewNotification(event, data) { + data = data || (csWallet.isLogin() ? csWallet.data : undefined); + if (!data) return; + var notification = new Notification(event, markNotificationAsRead); + return esUser.profile.fillAvatars([notification]) + .then(function() { + var isMessage = _.contains(constants.MESSAGE_CODES, event.code); + var isGroup = _.contains(constants.GROUP_CODES, event.code); + notification.isMessage = isMessage; + if (isMessage) { + data.messages = data.messages || {}; + data.messages.unreadCount++; + } + else if (isGroup) { + data.groups = data.groups || {}; + data.groups.unreadCount++; + } + else { + data.notifications = data.notifications || {}; + data.notifications.unreadCount++; + } + api.data.raise.new(notification); + }); } // Mark a notification as read @@ -153,7 +159,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es notification.read = true; CryptoUtils.sign(notification.hash, csWallet.data.keypair) .then(function(signature){ - return esHttp.post(host, port, '/user/event/:id/_read')(signature, {id:notification.id}); + return postReadById(signature, {id:notification.id}); }) .catch(function(err) { console.error('Error while trying to mark event as read:' + (err.message ? err.message : err)); @@ -163,6 +169,8 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es function onWalletReset(data) { data.notifications = data.notifications || {}; data.notifications.unreadCount = null; + // Stop listening notification + that.raw.ws.getUserEvent().close(); } function onWalletLogin(data, deferred) { @@ -172,7 +180,8 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es return deferred.promise; } - console.debug('[ES] [notification] Loading count from ES node...'); + console.debug('[ES] [notification] Loading count...'); + var now = new Date().getTime(); // Load unread notifications count loadUnreadNotificationsCount( @@ -183,7 +192,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es .then(function(unreadCount) { data.notifications = data.notifications || {}; data.notifications.unreadCount = unreadCount; - console.debug('[ES] [notification] Successfully load count from ES node'); + console.debug('[ES] [notification] Loaded count (' + unreadCount + ') in '+(new Date().getTime()-now)+'ms'); deferred.resolve(data); }) .catch(function(err){ @@ -192,7 +201,22 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es // Listen new events .then(function(){ - listenNewNotification(data); + console.debug('[ES] [notification] Starting listen user event...'); + var userEventWs = that.raw.ws.getUserEvent(); + listeners.push(userEventWs.close); + return userEventWs.on( + function(){ + $rootScope.$apply(onNewNotification); + }, + {pubkey: data.pubkey, locale: csSettings.data.locale.id} + ) + .catch(function(err) { + console.error('[ES] [notification] Unable to listen user event'); + + // TODO : send a event to csHttp instead ? + // And display such connectivity errors in UI + UIUtils.alert.error('ACCOUNT.ERROR.WS_CONNECTION_FAILED'); + }); }); return deferred.promise; @@ -210,7 +234,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es function addListeners() { console.debug("[ES] [notification] Enable"); - // Extend csWallet.loadData() and csWot.loadData() + // Listen some events listeners = [ csWallet.api.data.on.login($rootScope, onWalletLogin, this), csWallet.api.data.on.init($rootScope, onWalletReset, this), @@ -218,50 +242,41 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es ]; } - function isEnable() { - return csSettings.data.plugins && - csSettings.data.plugins.es && - host && csSettings.data.plugins.es.enable; - } - function refreshListeners() { - var enable = isEnable(); + var enable = esHttp.alive; if (!enable && listeners && listeners.length > 0) { removeListeners(); } else if (enable && (!listeners || listeners.length === 0)) { addListeners(); + if (csWallet.isLogin()) { + return onWalletLogin(csWallet.data); + } } } - // Default action - refreshListeners(); - // Register extension points api.registerEvent('data', 'new'); + // Default actions + Device.ready().then(function() { + esHttp.api.node.on.start($rootScope, refreshListeners, this); + esHttp.api.node.on.stop($rootScope, refreshListeners, this); + return refreshListeners(); + }); + return { load: loadNotifications, unreadCount: loadUnreadNotificationsCount, api: api, websocket: { - event: function() { - return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/event/user/:pubkey/:locale'); - }, - change: function() { - return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/_changes'); - } + event: that.raw.ws.getUserEvent, + change: that.raw.ws.getChanges }, constants: constants }; } - var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null; - var port = host ? csSettings.data.plugins.es.port : null; - var wsPort = host && csSettings.data.plugins.es.wsPort ? csSettings.data.plugins.es.wsPort : port; - - var service = factory('default', host, port, wsPort); - service.instance = factory; - return service; + return new Factory(); }) ; diff --git a/www/plugins/es/js/services/settings-services.js b/www/plugins/es/js/services/settings-services.js new file mode 100644 index 0000000000000000000000000000000000000000..b835fa1e2275ca031e1bf0931e19574348f52fa8 --- /dev/null +++ b/www/plugins/es/js/services/settings-services.js @@ -0,0 +1,326 @@ +angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.http.services']) +.config(function(PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.es; + if (enable) { + // Will force to load this service + PluginServiceProvider.registerEagerLoadingService('esSettings'); + } + + }) + +.factory('esSettings', function($rootScope, $q, $timeout, esHttp, + csConfig, csSettings, CryptoUtils, Device, UIUtils, csWallet) { + 'ngInject'; + + function Factory() { + + var + SETTINGS_SAVE_SPEC = { + includes: ['locale', 'showUDHistory', 'useRelative', 'useLocalStorage', 'expertMode'], + excludes: ['time'], + plugins: { + es: { + excludes: ['enable', 'host', 'port', 'wsPort'] + } + }, + helptip: { + excludes: ['installDocUrl'] + } + }, + that = this, + previousRemoteData, + listeners, + ignoreSettingsChanged = false + ; + + that.get = esHttp.get('/user/settings/:id'); + that.add = esHttp.record.post('/user/settings'); + that.update = esHttp.record.post('/user/settings/:id/_update'); + + that.isEnable = function() { + return csSettings.data.plugins && + csSettings.data.plugins.es && + csSettings.data.plugins.es.enable && + !!csSettings.data.plugins.es.host; + }; + + function copyUsingSpec(data, copySpec) { + var result = {}; + + // Add implicit includes + if (copySpec.includes) { + _.forEach(_.keys(copySpec), function(key) { + if (key != "includes" && key != "excludes") { + copySpec.includes.push(key); + } + }); + } + + _.forEach(_.keys(data), function(key) { + if ((!copySpec.includes || _.contains(copySpec.includes, key)) && + (!copySpec.excludes || !_.contains(copySpec.excludes, key))) { + if (data[key] && (typeof data[key] == 'object') && + copySpec[key] && (typeof copySpec[key] == 'object')) { + result[key] = copyUsingSpec(data[key], copySpec[key]); + } + else { + result[key] = data[key]; + } + } + }); + return result; + } + + // Load settings + function loadSettings(pubkey, keypair) { + var now = new Date().getTime(); + return $q.all([ + CryptoUtils.box.keypair.fromSignKeypair(keypair), + that.get({id: pubkey}) + .catch(function(err){ + if (err && err.ucode && err.ucode == 404) { + return null; // not found + } + else { + throw err; + } + })]) + .then(function(res) { + var boxKeypair = res[0]; + res = res[1]; + if (!res || !res._source) { + return; + } + var record = res._source; + // Do not apply if same version + if (record.time === csSettings.data.time) { + console.debug('[ES] [settings] Loaded user settings in '+ (new Date().getTime()-now) +'ms (no update need)'); + return; + } + var nonce = CryptoUtils.util.decode_base58(record.nonce); + // Decrypt settings content + return CryptoUtils.box.open(record.content, nonce, boxKeypair.boxPk, boxKeypair.boxSk) + .then(function(json) { + var settings = JSON.parse(json || '{}'); + settings.time = record.time; + console.debug('[ES] [settings] Loaded user settings in '+ (new Date().getTime()-now) +'ms'); + return settings; + }) + // if error: skip stored content + .catch(function(err){ + console.error('[ES] [settings] Could not read stored settings: ' + (err && err.message || 'decryption error')); + // make sure to remove time, to be able to save it again + delete csSettings.data.time; + return null; + }); + }); + } + + function onWalletLogin(data, deferred) { + deferred = deferred || $q.defer(); + if (!data || !data.pubkey || !data.keypair) { + deferred.resolve(); + return deferred.promise; + } + + // Waiting to load crypto libs + if (!CryptoUtils.isLoaded()) { + console.debug('[ES] [settings] Waiting crypto lib loading...'); + return $timeout(function() { + return onWalletLogin(data, deferred); + }, 50); + } + + console.debug('[ES] [settings] Loading user settings...'); + + // Load settings + loadSettings(data.pubkey, data.keypair) + .then(function(settings) { + if (!settings) return; // not found or up to date + angular.merge(csSettings.data, settings); + + // Remember for comparison + previousRemoteData = settings; + + console.debug('[ES] [settings] Successfully load settings from ES'); + return storeSettingsLocally(); + }) + .then(function() { + deferred.resolve(data); + }) + .catch(function(err){ + deferred.reject(err); + }); + + return deferred.promise; + } + + // Listen for settings changed + function onSettingsChanged(data) { + // avoid recursive call, because storeSettingsLocally() could emit event again + if (ignoreSettingsChanged) return; + + var wasEnable = listeners && listeners.length > 0; + + refreshListeners(); + + var isEnable = that.isEnable(); + if (csWallet.isLogin()) { + if (!wasEnable && isEnable) { + + onWalletLogin(csWallet.data); + } + else { + storeSettingsRemotely(data); + } + } + } + + function storeSettingsLocally() { + if (ignoreSettingsChanged) return $q.when(); + ignoreSettingsChanged = true; + return csSettings.store() + .then(function(){ + ignoreSettingsChanged = false; + }) + .catch(function(err) { + ignoreSettingsChanged = false; + throw err; + }); + } + + function storeSettingsRemotely(data) { + if (!csWallet.isLogin()) return $q.when(); + + var filteredData = copyUsingSpec(data, SETTINGS_SAVE_SPEC); + if (previousRemoteData && angular.equals(filteredData, previousRemoteData)) { + return $q.when(); + } + + // Waiting to load crypto libs + if (!CryptoUtils.isLoaded()) { + console.debug('[ES] [settings] Waiting crypto lib loading...'); + return $timeout(function() { + return storeSettingsRemotely(); + }, 50); + } + + var time = esHttp.date.now(); + console.debug('[ES] [settings] Saving user settings... at time ' + time); + + return $q.all([ + CryptoUtils.box.keypair.fromSignKeypair(csWallet.data.keypair), + CryptoUtils.util.random_nonce() + ]) + .then(function(res) { + var boxKeypair = res[0]; + var nonce = res[1]; + + var record = { + issuer: csWallet.data.pubkey, + nonce: CryptoUtils.util.encode_base58(nonce), + time: time + }; + + var json = JSON.stringify(filteredData); + + return CryptoUtils.box.pack(json, nonce, boxKeypair.boxPk, boxKeypair.boxSk) + .then(function(cypherText) { + record.content = cypherText; + // create or update + return !data.time ? + that.add(record) : + that.update(record, {id: record.issuer}); + }); + }) + .then(function() { + // Update settings version, then store (on local store only) + csSettings.data.time = time; + previousRemoteData = filteredData; + console.debug('[ES] [settings] Saved user settings in ' + (esHttp.date.now() - time) + 'ms'); + return storeSettingsLocally(); + }) + .catch(function(err) { + console.error(err); + throw err; + }) + ; + } + + function removeListeners() { + console.debug("[ES] [settings] Disable"); + _.forEach(listeners, function(remove){ + remove(); + }); + listeners = []; + } + + function addListeners() { + console.debug("[ES] [settings] Enable"); + + // Extend csWallet.login() + listeners = [ + csWallet.api.data.on.login($rootScope, onWalletLogin, this), + + ]; + } + + function refreshListeners() { + var enable = that.isEnable(); + if (!enable && listeners && listeners.length > 0) { + removeListeners(); + return esHttp.stop(); + } + else if (enable && (!listeners || listeners.length === 0)) { + return esHttp.start() + .then(function(started) { + if (!started) { + // TODO : alert user ? + console.error('ES node could not be started !!'); + } + else { + addListeners(); + if (csWallet.isLogin()) { + return onWalletLogin(csWallet.data); + } + } + }); + } + } + + Device.ready().then(function() { + + csSettings.api.data.on.changed($rootScope, onSettingsChanged, this); + esHttp.api.node.on.stop($rootScope, function() { + previousRemoteData = null; + }, this); + return refreshListeners(); + }) + + .then(function() { + // Ask (once) user to enable ES plugin + if (csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.askEnable && // if config ask enable + csSettings.data.plugins.es && !csSettings.data.plugins.es.enable && // AND user settings has disable plugin + csSettings.data.plugins.es.askEnable // AND user has not yet answer 'NO' + ) { + return UIUtils.alert.confirm('ES_SETTINGS.CONFIRM.ASK_ENABLE', 'ES_SETTINGS.CONFIRM.ASK_ENABLE_TITLE', + { + cancelText: 'COMMON.BTN_NO', + okText: 'COMMON.BTN_YES' + }) + .then(function (confirm) { + if (confirm) { + csSettings.data.plugins.es.enable = true; + } + csSettings.data.plugins.es.askEnable = false; + return csSettings.store(); + }); + } + }); + } + + return new Factory(); +}) +; diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js index 8721bc94362f00b08bc26eb8d241c0e616de01a7..fed979b3a7d0756b27257bd2f6725780e7a6f390 100644 --- a/www/plugins/es/js/services/user-services.js +++ b/www/plugins/es/js/services/user-services.js @@ -11,91 +11,30 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se }) .factory('esUser', function($rootScope, $q, $timeout, esHttp, $state, $sce, $sanitize, - csConfig, csSettings, CryptoUtils, Device, UIUtils, csWallet, csWot, BMA) { + esSettings, CryptoUtils, UIUtils, csWallet, csWot, BMA, Device) { 'ngInject'; - function factory(id, host, port, wsPort) { + function Factory() { var + that = this, CONSTANTS = { contentTypeImagePrefix: "image/", ES_USER_API_ENDPOINT: "ES_USER_API( ([a-z_][a-z0-9-_.]*))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))" }, REGEX = { ES_USER_API_ENDPOINT: exact(CONSTANTS.ES_USER_API_ENDPOINT) - }, - SETTINGS_SAVE_SPEC = { - includes: ['locale', 'showUDHistory', 'useRelative', 'useLocalStorage', 'expertMode'], - excludes: ['time'], - plugins: { - es: { - excludes: ['enable', 'host', 'port', 'wsPort'] - } - }, - helptip: { - excludes: ['installDocUrl'] - } }; var listeners, - restoringSettings = false, - getRequestFields = esHttp.get(host, port, '/user/profile/:id?&_source_exclude=avatar._content&_source=:fields'), - getRequest = esHttp.get(host, port, '/user/profile/:id?&_source_exclude=avatar._content') + getRequestFields = esHttp.get('/user/profile/:id?&_source_exclude=avatar._content&_source=:fields'), + getRequest = esHttp.get('/user/profile/:id?&_source_exclude=avatar._content') ; function exact(regexpContent) { return new RegExp("^" + regexpContent + "$"); } - function copy(otherNode) { - removeListeners(); - if (!!this.instance) { - var instance = this.instance; - angular.copy(otherNode, this); - this.instance = instance; - } - else { - angular.copy(otherNode, this); - } - addListeners(); - } - - function copyUsingSpec(data, copySpec) { - var result = {}; - - // Add implicit includes - if (copySpec.includes) { - _.forEach(_.keys(copySpec), function(key) { - if (key != "includes" && key != "excludes") { - copySpec.includes.push(key); - } - }); - } - - _.forEach(_.keys(data), function(key) { - if ((!copySpec.includes || _.contains(copySpec.includes, key)) && - (!copySpec.excludes || !_.contains(copySpec.excludes, key))) { - if (data[key] && (typeof data[key] == 'object') && - copySpec[key] && (typeof copySpec[key] == 'object')) { - result[key] = copyUsingSpec(data[key], copySpec[key]); - } - else { - result[key] = data[key]; - } - } - }); - return result; - } - - function readAvatarFromSource(source) { - var extension = source.avatar && source.avatar._content_type && source.avatar._content_type.startsWith(CONSTANTS.contentTypeImagePrefix) ? - source.avatar._content_type.substr(CONSTANTS.contentTypeImagePrefix.length) : null; - if (extension) { - return esHttp.getUrl(host, port, '/user/profile/' + pubkey + '/_image/avatar.' + extension); - } - return null; - } - function loadProfileAvatarAndName(pubkey) { return getRequestFields({id: pubkey, fields: 'title,avatar._content_type'}) .then(function(res) { @@ -104,7 +43,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se // name profile = {name: res._source.title}; // avatar - profile.avatar = esHttp.image.fromHit(host, port, res, 'avatar'); + profile.avatar = esHttp.image.fromHit(res, 'avatar'); } return profile; }) @@ -131,7 +70,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se profile.source = res._source; // avatar - profile.avatar = esHttp.image.fromHit(host, port, res, 'avatar'); + profile.avatar = esHttp.image.fromHit(res, 'avatar'); delete profile.source.avatar; // not need anymore profile.source.description = esHttp.util.trustAsHtml(profile.source.description); @@ -154,47 +93,6 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se return onWotSearch(null, datas, pubkeyAtributeName); } - // Load settings - function loadSettings(pubkey, keypair) { - return $q.all([ - CryptoUtils.box.keypair.fromSignKeypair(keypair), - esHttp.get(host, port, '/user/settings/:id')({id: pubkey}) - .catch(function(err){ - if (err && err.ucode && err.ucode == 404) { - return null; // not found - } - else { - throw err; - } - })]) - .then(function(res) { - var boxKeypair = res[0]; - res = res[1]; - if (!res || !res._source) { - return; - } - var record = res._source; - // Do not apply if same version - if (record.time === csSettings.data.time) { - console.debug('[ES] [user] Local settings already up to date'); - return; - } - var nonce = CryptoUtils.util.decode_base58(record.nonce); - // Decrypt settings content - return CryptoUtils.box.open(record.content, nonce, boxKeypair.boxPk, boxKeypair.boxSk) - .then(function(json) { - var settings = JSON.parse(json || '{}'); - settings.time = record.time; - return settings; - }) - // if error: skip stored content - .catch(function(err){ - console.error('[ES] [user] Could not read stored settings: ' + (err && err.message || 'decryption error')); - return null; - }); - }); - } - function onWalletReset(data) { data.avatar = null; data.avatarStyle = null; @@ -212,45 +110,30 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se // Waiting to load crypto libs if (!CryptoUtils.isLoaded()) { console.debug('[ES] [user] Waiting crypto lib loading...'); - //throw 'stop'; - $timeout(function() { - onWalletLogin(data, deferred); + return $timeout(function() { + return onWalletLogin(data, deferred); }, 50); - return; } - console.debug('[ES] [user] Loading user data from ES node...'); - $q.all([ - // Load settings - loadSettings(data.pubkey, data.keypair) - .then(function(settings) { - if (!settings) { // not found - // make sure to remove save timestamp - delete csSettings.data.time; - return; - } - angular.merge(csSettings.data, settings); - restoringSettings = true; - csSettings.store(); - }), + console.debug('[ES] [user] Loading user avatar+name...'); + var now = new Date().getTime(); - // Load profile avatar and name - loadProfileAvatarAndName(data.pubkey) - .then(function(profile) { - if (profile) { - data.name = profile.name; - data.avatarStyle = profile.avatarStyle; - data.avatar = profile.avatar; - } - }) - ]) - .then(function() { - console.debug('[ES] [user] Successfully loaded user data from ES node'); - deferred.resolve(data); - }) - .catch(function(err){ - deferred.reject(err); - }); + loadProfileAvatarAndName(data.pubkey) + .then(function(profile) { + if (profile) { + data.name = profile.name; + data.avatarStyle = profile.avatarStyle; + data.avatar = profile.avatar; + console.debug('[ES] [user] Loaded user avatar+name in '+ (new Date().getTime()-now) +'ms'); + } + else { + console.debug('[ES] [user] No user avatar+name found'); + } + deferred.resolve(data); + }) + .catch(function(err){ + deferred.reject(err); + }); return deferred.promise; } @@ -262,6 +145,9 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se data.events.push({type:'info',message: 'ACCOUNT.EVENT.MEMBER_WITHOUT_PROFILE'}); } + console.debug('[ES] [user] Loading full user profile...'); + var now = new Date().getTime(); + // Load full profile loadProfile(data.pubkey) .then(function(profile) { @@ -280,7 +166,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se return social.url; }); } - + console.debug('[ES] [user] Loaded full user profile in '+ (new Date().getTime()-now) +'ms'); } deferred.resolve(); }); @@ -288,6 +174,16 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se return deferred.promise; } + function onWalletLoadTx(tx, deferred) { + fillAvatars((tx.history || []).concat(tx.pendings||[]), 'pubkey') + .then(function() { + deferred.resolve(); + }) + .catch(function(err) { + console.error(err); + deferred.resolve(); // silent + }); + } function onWotSearch(text, datas, pubkeyAtributeName, deferred) { deferred = deferred || $q.defer(); @@ -380,7 +276,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se .then(function(res){ uidsByPubkey = res; }), - esHttp.post(host, port, '/user/profile/_search')(request) + esHttp.post('/user/profile/_search')(request) .then(function(res){ hits = res.hits; }) @@ -395,7 +291,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se values=[value]; datas.push(value); } - var avatar = esHttp.image.fromHit(host, port, hit, 'avatar'); + var avatar = esHttp.image.fromHit(hit, 'avatar'); _.forEach(values, function(data) { // name (basic or highlighted) data.name = hit._source.title; @@ -488,73 +384,6 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se return deferred.promise; } - function onSettingsChanged(data) { - if (!csWallet.isLogin()) return; - - // Waiting to load crypto libs - if (!CryptoUtils.isLoaded()) { - console.debug('[ES] [user] Waiting crypto lib loading...'); - $timeout(function() { - onSettingsChanged(data); - }, 200); - return; - } - - console.debug('[ES] [user] Saving user settings to ES...'); - - return $q.all([ - CryptoUtils.box.keypair.fromSignKeypair(csWallet.data.keypair), - CryptoUtils.util.random_nonce() - ]) - .then(function(res) { - var boxKeypair = res[0]; - var nonce = res[1]; - var record = { - issuer: csWallet.data.pubkey, - nonce: CryptoUtils.util.encode_base58(nonce), - time: esHttp.date.now() - }; - - var filteredData = copyUsingSpec(data, SETTINGS_SAVE_SPEC); - - var json = JSON.stringify(filteredData); - - return CryptoUtils.box.pack(json, nonce, boxKeypair.boxPk, boxKeypair.boxSk) - .then(function(cypherText) { - record.content = cypherText; - return !data.time ? - // create - esHttp.record.post(host, port, '/user/settings')(record) : - // or update - esHttp.record.post(host, port, '/user/settings/:pubkey/_update')(record, {pubkey: record.issuer}); - }) - .then(function() { - // Change settings version - csSettings.data.time = record.time; - restoringSettings = true; - csSettings.store(); - console.debug('[ES] [user] User settings saved in ES'); - }); - }) - .catch(function(err) { - console.error(err); - throw err; - }) - ; - } - - function onWalletLoadTx(tx, deferred) { - fillAvatars((tx.history || []).concat(tx.pendings||[]), 'pubkey') - .then(function() { - deferred.resolve(); - }) - .catch(function(err) { - console.error(err); - deferred.resolve(); // silent - }); - } - - function removeListeners() { console.debug("[ES] [user] Disable"); _.forEach(listeners, function(remove){ @@ -578,19 +407,19 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se ]; } - function isEnable() { - return csSettings.data.plugins && - csSettings.data.plugins.es && - host && csSettings.data.plugins.es.enable; - } - function refreshListeners() { - var enable = isEnable(); + var enable = esHttp.alive; if (!enable && listeners && listeners.length > 0) { removeListeners(); + if (csWallet.isLogin()) { + return onWalletReset(csWallet.data); + } } else if (enable && (!listeners || listeners.length === 0)) { addListeners(); + if (csWallet.isLogin()) { + return onWalletLogin(csWallet.data); + } } } @@ -605,86 +434,46 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se }; } - // Listen for settings changed - csSettings.api.data.on.changed($rootScope, function(data){ - if (restoringSettings) { - restoringSettings = false; - return; - } - - var wasEnable = listeners && listeners.length > 0; - - refreshListeners(); - - if (!wasEnable && isEnable()) { - return onWalletLogin(csWallet.data); - } - else { - onSettingsChanged(data); - } - }); - - // Ask (once) user to enable ES plugin + // Default actions Device.ready().then(function() { - - if (csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.askEnable && // if config ask enable - csSettings.data.plugins.es && !csSettings.data.plugins.es.enable && // AND user settings has disable plugin - csSettings.data.plugins.es.askEnable // AND user has not yet answer 'NO' - ) { - UIUtils.alert.confirm('ES_SETTINGS.CONFIRM.ASK_ENABLE', 'ES_SETTINGS.CONFIRM.ASK_ENABLE_TITLE', { - cancelText: 'COMMON.BTN_NO', - okText: 'COMMON.BTN_YES' - }) - .then(function (confirm) { - if (confirm) { - csSettings.data.plugins.es.enable = true; - } - csSettings.data.plugins.es.askEnable = false; - csSettings.store(); - }); - } + esHttp.api.node.on.start($rootScope, refreshListeners, this); + esHttp.api.node.on.stop($rootScope, refreshListeners, this); + return refreshListeners(); }); - // Default action - refreshListeners(); - - return { - copy: copy, + var exports = { node: { - server: esHttp.getServer(host, port), - summary: esHttp.get(host, port, '/node/summary'), + summary: esHttp.get('/node/summary'), parseEndPoint: parseEndPoint }, profile: { - get: esHttp.get(host, port, '/user/profile/:id'), - add: esHttp.record.post(host, port, '/user/profile'), - update: esHttp.record.post(host, port, '/user/profile/:id/_update'), - avatar: esHttp.get(host, port, '/user/profile/:id?_source=avatar'), + get: esHttp.get('/user/profile/:id'), + add: esHttp.record.post('/user/profile'), + update: esHttp.record.post('/user/profile/:id/_update'), + avatar: esHttp.get('/user/profile/:id?_source=avatar'), fillAvatars: fillAvatars }, settings: { - get: esHttp.get(host, port, '/user/settings/:id'), - add: esHttp.record.post(host, port, '/user/settings'), - update: esHttp.record.post(host, port, '/user/settings/:id/_update'), + get: esHttp.get('/user/settings/:id'), + add: esHttp.record.post('/user/settings'), + update: esHttp.record.post('/user/settings/:id/_update'), }, websocket: { event: function() { - return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/event/user/:pubkey/:locale'); + return esHttp.ws('/ws/event/user/:pubkey/:locale'); }, change: function() { - return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/_changes'); + return esHttp.ws('/ws/_changes'); } }, constants: CONSTANTS }; - } - var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null; - var port = host ? csSettings.data.plugins.es.port : null; - var wsPort = host ? csSettings.data.plugins.es.wsPort : port; + _.keys(exports).forEach(function(key) { + that[key] = exports[key]; + }); + } - var service = factory('default', host, port, wsPort); - service.instance = factory; - return service; + return new Factory(); }) ; diff --git a/www/plugins/es/templates/group/edit_group.html b/www/plugins/es/templates/group/edit_group.html new file mode 100644 index 0000000000000000000000000000000000000000..7b0b089384d13986ad3b15ec742cf69c5d1e7709 --- /dev/null +++ b/www/plugins/es/templates/group/edit_group.html @@ -0,0 +1,111 @@ +<ion-view left-buttons="leftButtons"> + <ion-nav-title> + <span class="visible-xs" ng-if="id" ng-bind-html="formData.title"></span> + <span class="visible-xs" ng-if="!loading && !id" translate>GROUP.EDIT.TITLE_NEW</span> + </ion-nav-title> + + <ion-nav-buttons side="secondary"> + <button class="button button-icon button-clear visible-xs visible-sm" + ng-class="{'ion-android-send':!id, 'ion-android-done': id}" + ng-click="save()"> + </button> + </ion-nav-buttons> + + <ion-content scroll="true"> + <div class="row no-padding"> + + <div class="col col-20 hidden-xs hidden-sm"> </div> + + <div class="col"> + <!-- loading --> + <div class="center padding" ng-if="loading"> + <ion-spinner icon="android"></ion-spinner> + </div> + + <form name="recordForm" novalidate="" ng-submit="save()"> + + <!-- --> + <div class="list" + ng-class="motion.ionListClass" + ng-init="setForm(recordForm)"> + + <div class="item hidden-xs"> + <h1 ng-if="id" ng-bind-html="formData.title"></h1> + <h1 ng-if="!id" translate>GROUP.EDIT.TITLE_NEW</h1> + <h2 class="balanced" ng-if="!id"> + <i class="icon ion-android-people"></i> + <i class="icon ion-android-lock" ng-if="formData.type=='managed'"></i> + {{'GROUP.TYPE.ENUM.'+formData.type|upper|translate}} + </h2> + </div> + <div class="item" ng-if="id"> + <h4 class="gray"> + <i class="icon ion-calendar"></i> + {{'COMMON.LAST_MODIFICATION_DATE'|translate}} {{formData.time | formatDate}} + </h4> + <div class="badge badge-balanced badge-editable" ng-click="showRecordTypeModal()"> + {{'GROUP.TYPE.ENUM.'+formData.type|upper|translate}} + </div> + </div> + + <!-- pictures --> + <ng-include src="'plugins/es/templates/common/edit_pictures.html'"></ng-include> + + <div class="item item-divider" translate>GROUP.GENERAL_DIVIDER</div> + + <!-- title --> + <div class="item item-input item-floating-label" + ng-class="{'item-input-error': form.$submitted && form.title.$invalid}"> + <span class="input-label" translate>GROUP.EDIT.RECORD_TITLE</span> + <input type="text" placeholder="{{'GROUP.EDIT.RECORD_TITLE_HELP'|translate}}" + name="title" + id="group-record-title" + ng-model="formData.title" + ng-minlength="3" + ng-required="true"/> + </div> + <div class="form-errors" + ng-if="form.$submitted && form.title.$error" + ng-messages="form.title.$error"> + <div class="form-error" ng-message="required"> + <span translate="ERROR.FIELD_REQUIRED"></span> + </div> + <div class="form-error" ng-message="minlength"> + <span translate="ERROR.FIELD_TOO_SHORT"></span> + </div> + </div> + + <!-- description --> + <div class="item item-input item-floating-label"> + <span class="input-label" translate>GROUP.EDIT.RECORD_DESCRIPTION</span> + <textarea placeholder="{{'GROUP.EDIT.RECORD_DESCRIPTION_HELP'|translate}}" + ng-model="formData.description" + rows="8" cols="10"> + </textarea> + </div> + + <!-- social networks --> + <ng-include src="'plugins/es/templates/common/edit_socials.html'"></ng-include> + + </div> + + <div class="padding hidden-xs hidden-sm text-right"> + <button class="button button-clear button-dark ink" ng-click="cancel()" type="button" translate> + COMMON.BTN_CANCEL + </button> + <button class="button button-positive button-raised ink" type="submit" ng-if="!id" translate> + COMMON.BTN_PUBLISH + </button> + <button class="button button-assertive button-raised ink" type="submit" ng-if="id" translate> + COMMON.BTN_SAVE + </button> + </div> + </form> + </div> + + <div class="col col-20 hidden-xs hidden-sm"> </div> + + </div> + </div> + </ion-content> +</ion-view> diff --git a/www/plugins/es/templates/group/item_group.html b/www/plugins/es/templates/group/item_group.html new file mode 100644 index 0000000000000000000000000000000000000000..313f7bc9a72644cac38ee0c4d2d94e5c401fc475 --- /dev/null +++ b/www/plugins/es/templates/group/item_group.html @@ -0,0 +1,27 @@ +<a name="group-{{:rebind:group.hash}}"></a> +<ion-item id="group-{{:rebind:block.hash}}" + class="item item-icon-left item-group {{ionItemClass}}" + ng-click="select(group)"> + + <i class="icon ion-cube stable" ng-if=":rebind:(!group.empty && !group.avatar)"></i> + <i class="avatar" ng-if=":rebind:!group.empty && group.avatar" style="background-image: url('{{:rebind:block.avatar.src}}')"></i> + + <div class="row no-padding"> + <div class="col"> + <h4 class="dark"> + <i class="ion-clock"></i> + {{:rebind:group.creationTime|formatDate}} + </h4> + <h4> + <!-- membersCount --> + <i class="dark ion-person"></i> + <span class="dark" ng-if=":rebind:group.membersCount">+{{:rebind:group.membersCount}}</span> + </h4> + </div> + + <div class="col col-33 positive hidden-md"> + <h4><i class="ion-person"></i> <span ng-bind-html=":rebind:group.title"></span></h4> + </div> + + </div> +</ion-item> diff --git a/www/plugins/es/templates/group/items_groups.html b/www/plugins/es/templates/group/items_groups.html new file mode 100644 index 0000000000000000000000000000000000000000..b3df8b122220a96d25b98bc294dfa3476dfd5379 --- /dev/null +++ b/www/plugins/es/templates/group/items_groups.html @@ -0,0 +1,29 @@ + + +<div class="item row row-header hidden-xs hidden-sm" ng-if="expertMode"> + + <a class="no-padding dark col col-header" + ng-click="toggleSort('medianTime')"> + <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'medianTime'"></cs-sort-icon> + {{'GROUP.LOOKUP.HEADER_CREATION_TIME' | translate}} + </a> + <a class="no-padding dark col col-header" + ng-click="toggleSort('issuer')"> + <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'issuer'"></cs-sort-icon> + {{'GROUP.LOOKUP.HEADER_ISSUER' | translate}} + </a> + <div class="col col-20"> + </div> + <a class="no-padding dark col col-20 col-header" ng-click="toggleSort('number')"> + <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'number'"></cs-sort-icon> + {{'GROUP.LOOKUP.HEADER_NAME' | translate}} + </a> +</div> + +<div class="padding gray" ng-if=":rebind:!search.loading && !search.results.length" translate> + COMMON.SEARCH_NO_RESULT +</div> + +<ng-repeat ng-repeat="group in :rebind:search.results" + ng-include="'plugins/es/templates/group/item_group.html'"> +</ng-repeat> diff --git a/www/plugins/es/templates/group/list.html b/www/plugins/es/templates/group/list.html new file mode 100644 index 0000000000000000000000000000000000000000..95e49b6a9d35fa7230b1f97337c1f0ff7ff11b84 --- /dev/null +++ b/www/plugins/es/templates/group/list.html @@ -0,0 +1,25 @@ +<ion-list class="{{::motion.ionListClass}}"> + + <ion-item + ng-repeat="notification in search.results" + class="item-border-large item-text-wrap ink item-avatar" + ng-class="{'unread': !notification.read}" + ng-click="select(notification)"> + + <i ng-if="!notification.avatar" class="item-image icon {{::notification.avatarIcon}}"></i> + <i ng-if="notification.avatar" class="item-image avatar" style="background-image: url({{::notification.avatar.src}})"></i> + + <h3 trust-as-html="notification.message | translate:notification"></h3> + <h4> + <i class="icon {{notification.icon}}"></i> <span class="dark">{{notification.time|formatFromNow}}</span> + <span class="gray">| {{notification.time|formatDate}}</span> + </h4> + </ion-item> +</ion-list> + +<ion-infinite-scroll + ng-if="!search.loading && search.hasMore" + spinner="android" + on-infinite="showMore()" + distance="1%"> +</ion-infinite-scroll> diff --git a/www/plugins/es/templates/group/lookup.html b/www/plugins/es/templates/group/lookup.html new file mode 100644 index 0000000000000000000000000000000000000000..46e60cd58d92aefc7174ce49bcec3685941ff244 --- /dev/null +++ b/www/plugins/es/templates/group/lookup.html @@ -0,0 +1,16 @@ +<ion-view class="view-group"> + <ion-nav-title> + <span translate>GROUP.LOOKUP.TITLE</span> + </ion-nav-title> + + <ion-nav-buttons side="secondary"> + + <button class="button button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm" ng-click="showActionsPopover($event)"> + </button> + + </ion-nav-buttons> + + <ion-content class="padding no-padding-xs" scroll="true"> + <ng-include src="'plugins/es/templates/group/lookup_form.html'"></ng-include> + </ion-content> +</ion-view> diff --git a/www/plugins/es/templates/group/lookup_form.html b/www/plugins/es/templates/group/lookup_form.html new file mode 100644 index 0000000000000000000000000000000000000000..0c6b9ae22d1edbb5c773dcddd1df07dc9cfd34c8 --- /dev/null +++ b/www/plugins/es/templates/group/lookup_form.html @@ -0,0 +1,86 @@ +<div class="lookupForm"> + + <button class="button button-small button-positive button-clear ink pull-right padding-right hidden-sm hidden-xs" + ng-click="showNewRecordModal()"> + <i class="icon ion-plus"></i> + {{'GROUP.LOOKUP.BTN_NEW' | translate}} + </button> + + <!-- search text--> + <label class="item item-input"> + <i class="icon ion-search placeholder-icon"></i> + <input type="text" + class="visible-xs visible-sm" + placeholder="{{'GROUP.LOOKUP.SEARCH_HELP'|translate}}" + ng-model="search.text" + ng-model-options="{ debounce: 650 }" + ng-change="doSearchText()"> + <input type="text" + class="hidden-xs hidden-sm" + id="{{searchTextId}}" placeholder="{{'GROUP.LOOKUP.SEARCH_HELP'|translate}}" + ng-model="search.text" + on-return="doSearchText()"> + <div class="helptip-anchor-center"> + <a id="helptip-group-search-text"></a> + </div> + + </label> + + <div class="padding-top padding-xs" style="display: block; height: 60px;"> + <div class="pull-left"> + <h4 + ng-if="search.type=='open'" translate> + GROUP.LOOKUP.OPEN_RESULTS_LIST + </h4> + <h4 + ng-if="search.type=='last'" translate> + GROUP.LOOKUP.LAST_RESULTS_LIST + </h4> + <h4 + ng-if="search.type=='managed'" translate> + GROUP.LOOKUP.MANAGED_RESULTS_LIST + </h4> + <h4 ng-if="search.type=='text'"> + {{'COMMON.RESULTS_LIST'|translate}} + </h4> + <h5 class="dark" ng-if="!search.loading && search.total"> + <span translate="COMMON.RESULTS_COUNT" translate-values="{count: search.total}"></span> + <small class="gray" ng-if=":rebind:search.took && expertMode"> + - {{:rebind:'COMMON.EXECUTION_TIME'|translate: {duration: search.took} }} + </small> + </h5> + <h5 class="gray" ng-if="search.loading" > + <ion-spinner class="icon ion-spinner-small" icon="android"></ion-spinner> + <span translate>COMMON.SEARCHING</span> + <br/> + </h5> + </div> + + <div class=" pull-right hidden-xs hidden-sm"> + <a ng-if="enableFilter" + class="button button-text button-small ink icon ion-clock" + ng-class="{'button-text-positive': search.type=='last'}" + ng-click="doSearchLast()"> + {{'GROUP.LOOKUP.BTN_LAST' | translate}} + </a> + + <button class="button button-small button-stable ink" + ng-click="doSearchText()"> + {{'COMMON.BTN_SEARCH' | translate:search}} + </button> + </div> + </div> + + <ion-list class="list {{ionListClass}}"> + + <ng-include src="'plugins/es/templates/group/items_groups.html'"></ng-include> + + </ion-list> + + <ion-infinite-scroll + ng-if="search.hasMore" + spinner="android" + on-infinite="showMore()" + distance="1%"> + </ion-infinite-scroll> + diff --git a/www/plugins/es/templates/group/modal_record_type.html b/www/plugins/es/templates/group/modal_record_type.html new file mode 100644 index 0000000000000000000000000000000000000000..a64a96bba8c0330f17c64bd5aedfc562ead07c8a --- /dev/null +++ b/www/plugins/es/templates/group/modal_record_type.html @@ -0,0 +1,35 @@ +<ion-modal-view> + <ion-header-bar class="bar-positive"> + <button class="button button-clear" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button> + <h1 class="title" translate>GROUP.TYPE.TITLE</h1> + </ion-header-bar> + + <ion-content class="lookupForm padding"> + <h3 translate>GROUP.TYPE.SELECT_TYPE</h3> + + <div class="list"> + + <!-- open group --> + <div class="item item-complex card stable-bg item-icon-left ink" + ng-click="closeModal('open')"> + <div class="item-content item-text-wrap"> + <i class="item-image icon ion-android-people dark"></i> + <h2 translate>GROUP.TYPE.OPEN_GROUP</h2> + <h4 class="gray" translate>GROUP.TYPE.OPEN_GROUP_HELP</h4> + </div> + </div> + + <!-- managed group --> + <div class="item item-complex card stable-bg item-icon-left ink" + ng-click="closeModal('managed')"> + <div class="item-content item-text-wrap"> + <i class="item-image icon ion-android-people dark"></i> + <i class="icon-secondary ion-android-lock dark" style="left: 10px; top: -8px;"></i> + <h2 translate>GROUP.TYPE.MANAGED_GROUP</h2> + <h4 class="gray" translate>GROUP.TYPE.MANAGED_GROUP_HELP</h4> + </div> + </div> + + </div> +</ion-content> +</ion-modal-view> diff --git a/www/plugins/es/templates/group/popover_group.html b/www/plugins/es/templates/group/popover_group.html new file mode 100644 index 0000000000000000000000000000000000000000..dfb323b0c858e1d40946954720fc1935d978f911 --- /dev/null +++ b/www/plugins/es/templates/group/popover_group.html @@ -0,0 +1,41 @@ +<ion-popover-view class="fit hidden-xs hidden-sm popover-notification" + ng-controller="PopoverGroupCtrl"> + <ion-header-bar class="stable-bg block"> + <div class="title" translate>GROUP.NOTIFICATIONS.TITLE</div> + + <div class="pull-right"> + <a class="positive" + ng-click="markAllAsRead()" + translate>COMMON.NOTIFICATIONS.MARK_ALL_AS_READ</a> + </div> + </ion-header-bar> + <ion-content scroll="true"> + <div class="center" ng-if="search.loading"> + <ion-spinner icon="android"></ion-spinner> + </div> + <div class="padding gray" ng-if="!search.loading && !search.results.length" translate> + COMMON.NOTIFICATIONS.NO_RESULT + </div> + + <ng-include src="'plugins/es/templates/group/list_group.html'"></ng-include> + + </ion-content> + + <ion-footer-bar class="stable-bg block"> + <!-- settings --> + <div class="pull-left"> + <a class="positive" + ui-sref="app.es_settings" + ng-click="closePopover()" + translate>COMMON.NOTIFICATIONS.SETTINGS</a> + </div> + + <!-- show all --> + <div class="pull-right"> + <a class="positive" + ui-sref="app.view_notifications" + ng-click="closePopover()" + translate>COMMON.NOTIFICATIONS.SHOW_ALL</a> + </div> + </ion-footer-bar> +</ion-popover-view> diff --git a/www/plugins/es/templates/group/view_record.html b/www/plugins/es/templates/group/view_record.html new file mode 100644 index 0000000000000000000000000000000000000000..ad3bab2c365b5054a7367b94e8786c4f0f89c947 --- /dev/null +++ b/www/plugins/es/templates/group/view_record.html @@ -0,0 +1,160 @@ +<ion-view left-buttons="leftButtons"> + <ion-nav-title> + + </ion-nav-title> + + <ion-nav-buttons side="secondary"> + <button class="button button-bar button-icon button-clear visible-xs visible-sm" ng-click="edit()" ng-if="canEdit"> + <i class="icon ion-android-create"></i> + </button> + <button class="button button-bar button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm" + ng-click="showActionsPopover($event)"> + </button> + </ion-nav-buttons> + + <ion-content scroll="true"> + <div class="positive-900-bg hero"> + <div class="content" ng-if="!loading"> + <i class="avatar cion-registry-{{formData.type}}" ng-if="!formData.thumbnail"></i> + <i class="avatar" style="background-image: url({{::formData.thumbnail.src}})" ng-if="formData.thumbnail"></i> + <h3 ng-bind-html="formData.title"></h3> + <h4> </h4> + </div> + <h4 class="content light" ng-if="loading"> + <ion-spinner icon="android"></ion-spinner> + </h4> + </div> + + <div class="row no-padding-xs"> + <div class="col col-20 hidden-xs hidden-sm"> + </div> + + <div class="col list item-text-wrap no-padding-xs" ng-class="motion.ionListClass"> + + <div class="item"> + <h2 class="gray"> + <a ng-if="formData.city" ui-sref="app.groups({location:formData.city})"> + <i class="icon ion-location"></i> + <span ng-bind-html="formData.city"></span> + </a> + <span ng-if="formData.city && formData.type"> | </span> + <a ng-if="formData.type" ui-sref="app.groups({type:formData.type})"> + <i class="icon ion-flag"></i> + {{'GROUP.TYPE.ENUM.'+formData.type|upper|translate}} + </a> + </h2> + <h4> + <i class="icon ion-clock" ng-if="formData.time"></i> + <span translate>COMMON.SUBMIT_BY</span> + <a ng-class="{'positive': issuer.uid, 'gray': !issuer.uid}" + ui-sref="app.wot_identity({pubkey:issuer.pubkey, uid: issuer.name||issuer.uid})"> + <ng-if ng-if="issuer.uid"> + <i class="icon ion-person"></i> + {{::issuer.name||issuer.uid}} + </ng-if> + <span ng-if="!issuer.uid"> + <i class="icon ion-key"></i> + {{issuer.pubkey|formatPubkey}} + </span> + </a> + <span > + {{formData.time|formatFromNow}} + <h4 class="gray hidden-xs">| + {{formData.time | formatDate}} + </h4> + </span> + </h4> + </div> + + <!-- Buttons bar--> + <div class="item large-button-bar hidden-xs hidden-sm"> + <button class="button button-stable button-small-padding icon ion-android-share-alt" + ng-click="showSharePopover($event)"> + </button> + <button class="button button-calm ink-dark" + ng-if="formData.pubkey && !isUserPubkey(formData.pubkey)" + ng-click="showTransferModal({pubkey:formData.pubkey, uid: formData.title})"> + {{'COMMON.BTN_SEND_MONEY' | translate}} + </button> + <button class="button button-stable icon-left ink-dark" + ng-if="canEdit" + ng-click="delete()"> + <i class="icon ion-trash-a assertive"></i> + <span class="assertive"> {{'COMMON.BTN_DELETE' | translate}}</span> + </button> + <button class="button button-calm icon-left ion-android-create ink" + ng-if="canEdit" + ng-click="edit()"> + {{'COMMON.BTN_EDIT' | translate}} + </button> + </div> + + <ion-item> + <h2> + <span class="text-keep-lines" ng-bind-html="formData.description"></span> + </h2> + </ion-item> + + <ion-item> + <h4 ng-if="formData.address"> + <span class="gray" translate>REGISTRY.VIEW.LOCATION</span> + <a class="positive" target='_blank' href="https://www.google.fr/maps/?q={{formData.address}},%20{{formData.city}}"> + <span ng-bind-html="formData.address"></span> + <span ng-if="formData.city"> - </span> + <span ng-bind-html="formData.city"></span> + </a> + </h4> + </ion-item> + + <!-- Socials networks --> + <ng-if ng-if="formData.socials && formData.socials.length>0"> + <ion-item class="item-icon-left" + type="no-padding item-text-wrap" + ng-repeat="social in formData.socials track by social.url" + id="social-{{social.url|formatSlug}}"> + <i class="icon ion-social-{{social.type}}" + ng-class="{'ion-bookmark': social.type == 'other', 'ion-link': social.type == 'web', 'ion-email': social.type == 'email'}"></i> + <p ng-if="social.type && social.type != 'web'">{{social.type}}</p> + <h2> + <a href="{{social.url}}" ng-if="social.type != 'email'" target="_blank">{{social.url}}</a> + <a href="mailto:{{social.url}}" ng-if="social.type == 'email'">{{social.url}}</a> + </h2> + </ion-item> + </ng-if> + + <div class="lazy-load"> + + <!-- pictures --> + <ng-include src="'plugins/es/templates/common/view_pictures.html'"></ng-include> + + + <span class="item item-divider" ng-if="formData.pubkey"> + <span translate>REGISTRY.TECHNICAL_DIVIDER</span> + </span> + + <!-- pubkey --> + <div class="item item-icon-left item-text-wrap ink" + ng-if="formData.pubkey" + copy-on-click="{{::formData.pubkey}}"> + <i class="icon ion-key"></i> + <span translate>REGISTRY.EDIT.RECORD_PUBKEY</span> + <h4 class="dark">{{::formData.pubkey}}</h4> + </div> + + <!-- comments --> + <ng-include src="'plugins/es/templates/common/view_comments.html'"></ng-include> + </div> + </div> + + <div class="col col-20 hidden-xs hidden-sm"> + </div> + </div> + </ion-content> + + <button class="button button-fab button-fab-bottom-right button-assertive icon ion-android-send visible-xs visible-sm" + ng-if="formData.pubkey && !isUserPubkey(formData.pubkey)" + ng-click="showTransferModal({pubkey: formData.pubkey, uid: formData.title})"> + </button> + + +</ion-view> diff --git a/www/plugins/es/templates/registry/edit_record.html b/www/plugins/es/templates/registry/edit_record.html index 883c4f81095ff83c6442894b51e02301b8af83a7..be055364d8b0086db3de68cf67c2b376ddd015bc 100644 --- a/www/plugins/es/templates/registry/edit_record.html +++ b/www/plugins/es/templates/registry/edit_record.html @@ -23,7 +23,9 @@ </div> <form name="recordForm" novalidate="" ng-submit="save()"> - <div class="list animate-ripple" ng-init="setForm(recordForm)"> + <div class="list" + ng-class="motion.ionListClass" + ng-init="setForm(recordForm)"> <div class="item hidden-xs"> <h1 ng-if="id" ng-bind-html="formData.title"></h1> diff --git a/www/plugins/es/templates/user/edit_profile.html b/www/plugins/es/templates/user/edit_profile.html index d4ca46695e817477c9c474405f1f636dff0defe6..ffb5f08e642818b9b1f8707e2f6fb71be5d80a83 100644 --- a/www/plugins/es/templates/user/edit_profile.html +++ b/www/plugins/es/templates/user/edit_profile.html @@ -13,7 +13,7 @@ <div class="positive-900-bg hero"> <div class="content"> <i class="avatar" - style="background-image: url('{{avatar.src}}')" + ng-style="avatarStyle" ng-class="{'avatar-wallet': !loading && !avatar && walletData && !walletData.isMember, 'avatar-member': !loading && !avatar && walletData.isMember}"> <button class="button button-positive button-large button-clear flat icon ion-camera visible-xs visible-sm" style="display: inline-block;" @@ -21,17 +21,14 @@ <button class="button button-positive button-large button-clear icon ion-camera hidden-xs hidden-sm" ng-click="showAvatarModal()"></button> </i> - <div ng-if="!loading"> - <h3 class="light" ng-if="!formData.title && walletData && walletData.isMember">{{walletData.uid}}</h3> - <h3 class="light" ng-if="!formData.title && walletData && !walletData.isMember">{{::walletData.pubkey | formatPubkey}}</h3> - <h3 class="light">{{formData.title}}</h3> - <h4 class="light"> </h4> - </div> - <div ng-if="loading"> - <h4 class="light"> - <ion-spinner icon="android"></ion-spinner> - </h4> - </div> + <h3 class="light"> + <ng-if ng-if="!loading && !formData.title && walletData && walletData.isMember">{{walletData.uid}}</ng-if> + <ng-if ng-if="!loading && !formData.title && walletData && !walletData.isMember">{{::walletData.pubkey | formatPubkey}}</ng-if> + <ng-if ng-if="!loading && formData.title">{{formData.title}}</ng-if> + </h3> + <h4 class="light"> + <ion-spinner ng-if="loading" icon="android"></ion-spinner> + </h4> </div> </div> diff --git a/www/plugins/es/templates/wallet/view_wallet_extend.html b/www/plugins/es/templates/wallet/view_wallet_extend.html index 7d069c1ef7be51ea478a3ec45a4900697affe2cd..2ecdef9fe2618917fefd2867c05ec6a00b33e132 100644 --- a/www/plugins/es/templates/wallet/view_wallet_extend.html +++ b/www/plugins/es/templates/wallet/view_wallet_extend.html @@ -1,11 +1,12 @@ <!-- Buttons section --> <ng-if ng-if="enable && extensionPoint === 'buttons'"> - <button class="button button-small-padding" + <!--<button class="button button-small-padding" ui-sref="app.user_edit_profile" title="{{'PROFILE.BTN_EDIT' | translate}}"> - <i class="icon ion-edit"></i> - </button> + <i class="icon ion-person"></i> + <b class="icon-secondary ion-edit" style="left:31px; top: -6px"></b> + </button>--> </ng-if> <!-- General section --> diff --git a/www/templates/settings/settings.html b/www/templates/settings/settings.html index e7da4e24d10b6097a98f5ef7089db7162c6661c4..a07236ed855fa524aece7b146341c6ef477dfabd 100644 --- a/www/templates/settings/settings.html +++ b/www/templates/settings/settings.html @@ -72,6 +72,17 @@ </div> </label> </div> + + <!-- <div class="item item-toggle dark item-text-wrap"> + <div class="input-label" ng-bind-html="'SETTINGS.ENABLE_UI_EFFECTS' | translate"> + </div> + <label class="toggle toggle-royal"> + <input type="checkbox" ng-model="formData.enableUuiEffects" > + <div class="track"> + <div class="handle"></div> + </div> + </label> + </div>--> <!-- Allow extension here --> <cs-extension-point name="common"></cs-extension-point> diff --git a/www/templates/wot/view_certifications.html b/www/templates/wot/view_certifications.html index c3767f6b8455d067cfb5a7fec4fb0e2dfaedba4f..effd05feaf988eb55268519bfa36b8e5789e646c 100644 --- a/www/templates/wot/view_certifications.html +++ b/www/templates/wot/view_certifications.html @@ -13,10 +13,6 @@ <ion-content class="certifications certifications-lg"> - <div class="center padding" ng-if="loading"> - <ion-spinner icon="android"></ion-spinner> - </div> - <!-- Buttons bar --> <div class="hidden-xs hidden-sm text-center padding" ng-if="canCertify || canSelectAndCertify"> @@ -39,6 +35,10 @@ </button> </div> + <div class="center padding" ng-if="loading"> + <ion-spinner icon="android"></ion-spinner> + </div> + <!-- certifications tables --> <div class="row responsive-sm responsive-md responsive-lg"> <!-- Received certifications --> @@ -55,7 +55,7 @@ </div> <div class="col text-center no-padding"> <a style="text-decoration: none;" - ui-sref="app.wot_identity({pubkey: formData.pubkey, uid: formData.name||formData.uid})"> + ui-sref="app.wot_identity({pubkey: formData.pubkey, uid: formData.uid})"> <i class="avatar avatar-large" ng-if="!formData.avatar" ng-class="{'avatar-wallet': !formData.isMember, 'avatar-member': formData.isMember}"></i>