From 2f58dce502fd6a48bef0f66ecc8c9a79b8811cbe Mon Sep 17 00:00:00 2001
From: blavenie <benoit.lavenier@e-is.pro>
Date: Fri, 6 Oct 2017 12:16:07 +0200
Subject: [PATCH] [fix] ES: Do not try to parse tags on settings, invitation,
 message, etc. [fix] ES: do not try to save sttings remotelly if not auth (no
 box keypair) [enh] ES: compute box keypair once, on auth [enh] Login: better
 message when bad pubkey [enh] display loadingmessage when already login but
 need to load full data [fix] Group: allow to delete a existing group

---
 www/i18n/locale-fr-FR.json                    |  2 +-
 www/js/app.js                                 |  2 +
 www/js/controllers/app-controllers.js         | 29 +++-----
 www/js/controllers/login-controllers.js       |  2 +
 www/js/services/device-services.js            |  2 +-
 www/js/services/settings-services.js          |  2 +-
 www/js/services/wallet-services.js            | 19 +++---
 www/plugins/es/i18n/locale-en-GB.json         |  6 ++
 www/plugins/es/i18n/locale-en.json            |  6 ++
 www/plugins/es/i18n/locale-fr-FR.json         |  9 ++-
 .../es/js/controllers/group-controllers.js    | 28 +++++++-
 .../es/js/controllers/profile-controllers.js  |  2 +-
 .../es/js/services/comment-services.js        | 10 +--
 www/plugins/es/js/services/group-services.js  |  6 +-
 www/plugins/es/js/services/http-services.js   | 19 +++---
 .../es/js/services/message-services.js        |  2 +-
 .../es/js/services/profile-services.js        | 47 +++++++++++--
 .../es/js/services/registry-services.js       |  6 +-
 .../es/js/services/settings-services.js       | 67 ++++++-------------
 www/plugins/es/js/services/wallet-services.js | 25 +++++--
 .../js/controllers/synchro-controllers.js     |  1 -
 www/plugins/map/js/services/wot-services.js   |  2 +-
 www/templates/login/form_scrypt.html          | 12 ++--
 23 files changed, 191 insertions(+), 115 deletions(-)

diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index ebc4acbd5..df661cee1 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -626,7 +626,7 @@
     "WALLET_REVOKED": "Votre identité a été <b>révoquée</b> : ni votre pseudonyme ni votre clef publique ne pourront être utilisés à l'avenir pour un compte membre.",
     "WALLET_HAS_NO_SELF": "Votre identité doit d'abord avoir été publiée, et ne pas être expirée.",
     "AUTH_REQUIRED": "Authentification requise.",
-    "AUTH_INVALID_PUBKEY": "La clé publique ne correspond pas au compte connecté.",
+    "AUTH_INVALID_PUBKEY": "La clef attendue est <i class=\"ion-key\"></i> {{pubkey|formatPubkey}}...",
     "AUTH_INVALID_SCRYPT": "Identifiant ou mot de passe invalide.",
     "AUTH_INVALID_FILE": "Fichier de trousseau invalide.",
     "AUTH_FILE_ERROR": "Echec de l'ouverture du fichier de trousseau",
diff --git a/www/js/app.js b/www/js/app.js
index 283c61b7c..7497c864f 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -103,6 +103,8 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht
         options = next.data.minData ? {minData: true} : undefined;
         if (!csWallet.isDataLoaded(options)) {
           event.preventDefault();
+          // Show loading message, when full load
+          if (!options || !options.minData) UIUtils.loading.show();
           return csWallet.loadData(options)
             .then(function() {
               preventStateChange = false;
diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js
index 6c42b1e1d..91987bd83 100644
--- a/www/js/controllers/app-controllers.js
+++ b/www/js/controllers/app-controllers.js
@@ -209,15 +209,9 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $
     if (options.auth && !csWallet.isAuth()) {
       return csWallet.auth(options)
         .then(function (walletData) {
-          if (walletData) {
-            // Force full load, even if min data asked
-            // Because user can wait when just filled login (by modal)
-            if (options && options.minData) options.minData = false;
-            return $scope.loadWalletData(options);
-          }
-          else { // failed to auth
-            throw 'CANCELLED';
-          }
+          if (walletData) return walletData;
+          // failed to auth
+          throw 'CANCELLED';
         });
     }
 
@@ -225,15 +219,9 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $
     else if (!csWallet.isLogin()) {
       return csWallet.login(options)
         .then(function (walletData) {
-          if (walletData) {
-            // Force full load, even if min data asked
-            // Because user can wait when just filled login (by modal)
-            if (options && options.minData) options.minData = false;
-            return $scope.loadWalletData(options);
-          }
-          else { // failed to login
-            throw 'CANCELLED';
-          }
+          if (walletData) return walletData;
+          // failed to login
+          throw 'CANCELLED';
         });
     }
 
@@ -341,8 +329,11 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $
     $scope.login = false;
     $rootScope.walletData = {};
   });
-  csWallet.api.data.on.auth($scope, function() {
+  csWallet.api.data.on.auth($scope, function(data, deferred) {
+    deferred = deferred || $q.defer();
     $scope.auth = true;
+    deferred.resolve();
+    return deferred.promise;
   });
   csWallet.api.data.on.unauth($scope, function() {
     $scope.auth = false;
diff --git a/www/js/controllers/login-controllers.js b/www/js/controllers/login-controllers.js
index 5184cbd42..8487c8333 100644
--- a/www/js/controllers/login-controllers.js
+++ b/www/js/controllers/login-controllers.js
@@ -406,6 +406,8 @@ function LoginModalController($scope, $timeout, $q, $ionicPopover, CryptoUtils,
     }
   };
 
+
+
   // Default action
   $scope.init();
 
diff --git a/www/js/services/device-services.js b/www/js/services/device-services.js
index 5c893f869..54db54fa9 100644
--- a/www/js/services/device-services.js
+++ b/www/js/services/device-services.js
@@ -94,7 +94,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
             }
           },
           function(err) {
-            console.log('XXX -> ' + err);
+            console.error('[device] Error while using barcode scanner -> ' + err);
             deferred.reject(err);
           },
           n);
diff --git a/www/js/services/settings-services.js b/www/js/services/settings-services.js
index 0828792d1..811afb466 100644
--- a/www/js/services/settings-services.js
+++ b/www/js/services/settings-services.js
@@ -163,7 +163,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
     return promise
       .then(function() {
         if (data.useLocalStorage) {
-          console.debug('[setting] Saved');
+          console.debug('[setting] Saved locally');
         }
 
         // Emit event on store
diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js
index c325d0bb2..6f637d59f 100644
--- a/www/js/services/wallet-services.js
+++ b/www/js/services/wallet-services.js
@@ -166,18 +166,21 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
 
           // Send auth event (if need)
           if (needAuth || isAuth()) {
-            api.data.raise.auth();
-
             // Check if need to start/stop auth idle
             checkAuthIdle(true);
-          }
 
-          // Load data
-          if (!data.loaded) {
-            var loadOptions = options && angular.isDefined(options.minData) ? {minData: true} : undefined;
-            return loadData(loadOptions);
+            return api.data.raisePromise.auth(keepAuth ? data : authData);
           }
         }).then(function() {
+          // Load data if need
+          // If user just login, force data full load (even if min data asked)
+          // because the user can wait (after the login modal)
+          var loadOptions = !needLogin && options && options.minData ? {minData: true} : undefined;
+          if (!isDataLoaded(loadOptions)) {
+            return loadData(loadOptions);
+          }
+        })
+        .then(function() {
           if (options && options.silent) {
             UIUtils.loading.hide();
           }
@@ -327,7 +330,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
           jobs.push(localStorage.put(constants.OLD_STORAGE_KEY, null));
 
           return $q.all(jobs).then(function() {
-            console.debug('[wallet] saved');
+            console.debug('[wallet] Saved locally');
           });
         }
         else {
diff --git a/www/plugins/es/i18n/locale-en-GB.json b/www/plugins/es/i18n/locale-en-GB.json
index 2ae21e5f8..6ab370203 100644
--- a/www/plugins/es/i18n/locale-en-GB.json
+++ b/www/plugins/es/i18n/locale-en-GB.json
@@ -8,6 +8,7 @@
     "BTN_PUBLISH": "Publish",
     "BTN_PICTURE_DELETE": "Delete",
     "BTN_PICTURE_FAVORISE": "Default",
+    "BTN_PICTURE_ROTATE": "Rotate",
     "BTN_ADD_PICTURE": "Add picture",
     "NOTIFICATIONS": {
       "TITLE": "Notifications",
@@ -49,6 +50,11 @@
       "TITLE": "Ask certifications",
       "HELP": "Select recipients"
     },
+    "SEARCH": {
+      "DIVIDER_PROFILE": "Accounts",
+      "DIVIDER_PAGE": "Pages",
+      "DIVIDER_GROUP": "Groups"
+    },
     "CONFIRM": {
       "SUGGEST_CERTIFICATIONS": "Etes-vous sûr de vouloir <b>envoyer ces suggestions de certification</b> ?",
       "ASK_CERTIFICATION": "Etes-vous sûr de vouloir <b>envoyer une demande de certification</b> ?",
diff --git a/www/plugins/es/i18n/locale-en.json b/www/plugins/es/i18n/locale-en.json
index 2ae21e5f8..6ab370203 100644
--- a/www/plugins/es/i18n/locale-en.json
+++ b/www/plugins/es/i18n/locale-en.json
@@ -8,6 +8,7 @@
     "BTN_PUBLISH": "Publish",
     "BTN_PICTURE_DELETE": "Delete",
     "BTN_PICTURE_FAVORISE": "Default",
+    "BTN_PICTURE_ROTATE": "Rotate",
     "BTN_ADD_PICTURE": "Add picture",
     "NOTIFICATIONS": {
       "TITLE": "Notifications",
@@ -49,6 +50,11 @@
       "TITLE": "Ask certifications",
       "HELP": "Select recipients"
     },
+    "SEARCH": {
+      "DIVIDER_PROFILE": "Accounts",
+      "DIVIDER_PAGE": "Pages",
+      "DIVIDER_GROUP": "Groups"
+    },
     "CONFIRM": {
       "SUGGEST_CERTIFICATIONS": "Etes-vous sûr de vouloir <b>envoyer ces suggestions de certification</b> ?",
       "ASK_CERTIFICATION": "Etes-vous sûr de vouloir <b>envoyer une demande de certification</b> ?",
diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json
index f3760e57c..561c25407 100644
--- a/www/plugins/es/i18n/locale-fr-FR.json
+++ b/www/plugins/es/i18n/locale-fr-FR.json
@@ -237,7 +237,8 @@
     },
     "VIEW": {
       "POPOVER_SHARE_TITLE": "{{title}}",
-      "MENU_TITLE": "Options"
+      "MENU_TITLE": "Options",
+      "REMOVE_CONFIRMATION" : "Êtes-vous sûr de vouloir supprimer ce groupe ?<br/><br/>Cette opération est irréversible."
     },
     "EDIT": {
       "TITLE": "Groupe",
@@ -248,7 +249,11 @@
       "RECORD_DESCRIPTION_HELP": "Description"
     },
     "ERROR": {
-      "SEARCH_GROUPS_FAILED": "Echec de la recherche de groupes"
+      "SEARCH_GROUPS_FAILED": "Echec de la recherche de groupes",
+      "REMOVE_RECORD_FAILED": "Erreur de la suppression du groupe"
+    },
+    "INFO": {
+      "RECORD_REMOVED" : "Group supprimé"
     }
   },
   "REGISTRY": {
diff --git a/www/plugins/es/js/controllers/group-controllers.js b/www/plugins/es/js/controllers/group-controllers.js
index 489a59b1b..7b3f31c0c 100644
--- a/www/plugins/es/js/controllers/group-controllers.js
+++ b/www/plugins/es/js/controllers/group-controllers.js
@@ -178,7 +178,8 @@ function ESGroupListController($scope, UIUtils, $state, csWallet, esGroup, Modal
 }
 
 
-function ESGroupViewController($scope, $state, $ionicPopover, UIUtils, csConfig, esGroup, csWallet) {
+function ESGroupViewController($scope, $state, $ionicPopover, $ionicHistory, $translate,
+                               UIUtils, csConfig, esGroup, csWallet) {
   'ngInject';
 
   $scope.formData = {};
@@ -233,6 +234,31 @@ function ESGroupViewController($scope, $state, $ionicPopover, UIUtils, csConfig,
     $state.go('app.edit_group', {id: $scope.id});
   };
 
+  $scope.delete = function() {
+    $scope.hideActionsPopover();
+
+    // translate
+    var translations;
+    $translate(['GROUP.VIEW.REMOVE_CONFIRMATION', 'GROUP.INFO.RECORD_REMOVED'])
+      .then(function(res) {
+        translations = res;
+        return UIUtils.alert.confirm(res['GROUP.VIEW.REMOVE_CONFIRMATION']);
+      })
+      .then(function(confirm) {
+        if (confirm) {
+          esGroup.record.remove($scope.id)
+            .then(function () {
+              $ionicHistory.nextViewOptions({
+                historyRoot: true
+              });
+              $state.go('app.groups');
+              UIUtils.toast.show(translations['GROUP.INFO.RECORD_REMOVED']);
+            })
+            .catch(UIUtils.onError('GROUP.ERROR.REMOVE_RECORD_FAILED'));
+        }
+      });
+  };
+
   /* -- modals & popover -- */
 
   $scope.showActionsPopover = function(event) {
diff --git a/www/plugins/es/js/controllers/profile-controllers.js b/www/plugins/es/js/controllers/profile-controllers.js
index 218ddd2f0..1fb02982b 100644
--- a/www/plugins/es/js/controllers/profile-controllers.js
+++ b/www/plugins/es/js/controllers/profile-controllers.js
@@ -197,7 +197,7 @@ function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $
         }
 
         $scope.walletData.profile = angular.copy(formData);
-        $scope.walletData.profile.description = esHttp.util.trustAsHtml(formData.description);
+        $scope.walletData.profile.description = esHttp.util.parseAsHtml(formData.description);
       }
     };
 
diff --git a/www/plugins/es/js/services/comment-services.js b/www/plugins/es/js/services/comment-services.js
index a0268b2bb..7be2621ab 100644
--- a/www/plugins/es/js/services/comment-services.js
+++ b/www/plugins/es/js/services/comment-services.js
@@ -83,7 +83,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
               var comment = data.mapById[hit._id];
               comment.copyFromJson(hit._source);
               // Parse URL and hashtags
-              comment.html = esHttp.util.trustAsHtml(comment.message);
+              comment.html = esHttp.util.parseAsHtml(comment.message);
               delete incompleteCommentIdByParentIds[comment.id];
             });
 
@@ -132,7 +132,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
             data.result = res.hits.hits.reduce(function (result, hit) {
               var comment = new Comment(hit._id, hit._source);
               // Parse URL and hashtags
-              comment.html = esHttp.util.trustAsHtml(comment.message);
+              comment.html = esHttp.util.parseAsHtml(comment.message);
               // fill map by id
               data.mapById[comment.id] = comment;
               return result.concat(comment);
@@ -218,7 +218,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
                   if (comment) {
                     comment.copyFromJson(change._source);
                     // Parse URL and hashtags
-                    comment.html = esHttp.util.trustAsHtml(comment.message);
+                    comment.html = esHttp.util.parseAsHtml(comment.message);
                     exports.raw.refreshTreeLinks(data);
                   }
                   // create (if not in pending comment)
@@ -227,7 +227,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
                     comment.addOnRemoveListener(onRemoveListener);
                     comment.isnew = true;
                     // Parse URL and hashtags
-                    comment.html = esHttp.util.trustAsHtml(comment.message);
+                    comment.html = esHttp.util.parseAsHtml(comment.message);
                     // fill map by id
                     data.mapById[change._id] = comment;
                     exports.raw.refreshTreeLinks(data)
@@ -298,7 +298,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
         }
 
         // Parse URL and hashtags
-        entity.html = esHttp.util.trustAsHtml(entity.message);
+        entity.html = esHttp.util.parseAsHtml(entity.message);
 
         // Send add request
         if (!id) {
diff --git a/www/plugins/es/js/services/group-services.js b/www/plugins/es/js/services/group-services.js
index 849dfb969..f0a216c18 100644
--- a/www/plugins/es/js/services/group-services.js
+++ b/www/plugins/es/js/services/group-services.js
@@ -84,7 +84,7 @@ angular.module('cesium.es.group.services', ['cesium.platform', 'cesium.es.http.s
 
     // description
     if (html) {
-      record.description = esHttp.util.trustAsHtml(record.description);
+      record.description = esHttp.util.parseAsHtml(record.description);
     }
 
     // avatar
@@ -260,8 +260,8 @@ angular.module('cesium.es.group.services', ['cesium.platform', 'cesium.es.http.s
       last: getLastGroups,
       search: searchGroups,
       load: loadData,
-      add: esHttp.record.post('/group/record'),
-      update: esHttp.record.post('/group/record/:id/_update'),
+      add: esHttp.record.post('/group/record', {tagFields: ['title', 'description']}),
+      update: esHttp.record.post('/group/record/:id/_update', {tagFields: ['title', 'description']}),
       remove: esHttp.record.remove('group', 'record'),
       fields: {
         commons: fields.commons
diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js
index 51edeb755..9f1fa42c7 100644
--- a/www/plugins/es/js/services/http-services.js
+++ b/www/plugins/es/js/services/http-services.js
@@ -193,8 +193,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
     }
 
-    function trustAsHtml(text, options) {
-
+    function parseAsHtml(text, options) {
 
       var content = text ? escape(text.trim()) : undefined;
       if (content) {
@@ -208,6 +207,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
         // Replace URL in description
         var urls = parseUrlsFromText(content);
         _.forEach(urls, function(url){
+          // Redirect URL to the function 'openLink', to open a new window if need (e.g. desktop app)
           var link = '<a ng-click=\"openLink($event, \'{0}\')\">{1}</a>'.format(url, url);
           content = content.replace(url, link);
         });
@@ -225,8 +225,6 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
           var link = '<a ui-sref=\"{0}({uid: \'{1}\'})\">@{2}</a>'.format(options.uidState, uid, uid);
           content = content.replace('@'+uid, link);
         });
-
-        //$sce.trustAsHtml(content);
       }
       return content;
     }
@@ -234,10 +232,11 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     function fillRecordTags(record, fieldNames) {
       fieldNames = fieldNames || ['title', 'description'];
 
-      _.forEach(fieldNames, function(fieldName) {
+      record.tags = fieldNames.reduce(function(res, fieldName) {
         var value = record[fieldName];
-        record.tags = parseTagsFromText(value);
-      });
+        var tags = value && parseTagsFromText(value);
+        return tags ? res.concat(tags) : res;
+      }, []);
     }
 
     function postRecord(path, options) {
@@ -261,7 +260,9 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
             obj.issuer = walletData.pubkey;
 
             // Fill tags
-            fillRecordTags(obj);
+            if (options.tagFields) {
+              fillRecordTags(obj, options.tagFields);
+            }
 
             var str = JSON.stringify(obj);
 
@@ -421,7 +422,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       },
       util: {
         parseTags: parseTagsFromText,
-        trustAsHtml: trustAsHtml
+        parseAsHtml: parseAsHtml
       },
       constants: constants
     };
diff --git a/www/plugins/es/js/services/message-services.js b/www/plugins/es/js/services/message-services.js
index fd2f24bc4..ea0b88247 100644
--- a/www/plugins/es/js/services/message-services.js
+++ b/www/plugins/es/js/services/message-services.js
@@ -324,7 +324,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.platform',
                   fillSummary(message);
                 }
                 else if (content){
-                  message.html = esHttp.util.trustAsHtml(content);
+                  message.html = esHttp.util.parseAsHtml(content);
                 }
               })
               .catch(function(err){
diff --git a/www/plugins/es/js/services/profile-services.js b/www/plugins/es/js/services/profile-services.js
index cc70f0761..744f6787c 100644
--- a/www/plugins/es/js/services/profile-services.js
+++ b/www/plugins/es/js/services/profile-services.js
@@ -71,7 +71,7 @@ angular.module('cesium.es.profile.services', ['cesium.services', 'cesium.es.http
 
           // description
           if (!options.raw) {
-            profile.description = esHttp.util.trustAsHtml(profile.source.description);
+            profile.description = esHttp.util.parseAsHtml(profile.source.description);
           }
 
           // Social url must be unique in socials links - Workaround for issue #306:
@@ -125,6 +125,44 @@ angular.module('cesium.es.profile.services', ['cesium.services', 'cesium.es.http
     }
   }
 
+  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] [wallet] Waiting crypto lib loading...');
+      return $timeout(function() {
+        return onWalletLogin(data, deferred);
+      }, 50);
+    }
+
+    console.debug('[ES] [wallet] Loading user avatar+name...');
+    var now = new Date().getTime();
+
+    esProfile.getAvatarAndName(data.pubkey)
+      .then(function(profile) {
+        if (profile) {
+          data.name = profile.name;
+          data.avatarStyle = profile.avatarStyle;
+          data.avatar = profile.avatar;
+          console.debug('[ES] [profile] Loaded user avatar+name in '+ (new Date().getTime()-now) +'ms');
+        }
+        else {
+          console.debug('[ES] [profil] No user avatar+name found');
+        }
+        deferred.resolve(data);
+      })
+      .catch(function(err){
+        deferred.reject(err);
+      });
+
+    return deferred.promise;
+  }
+
   function onWotSearch(text, datas, pubkeyAtributeName, deferred) {
     deferred = deferred || $q.defer();
     if (!text && (!datas || !datas.length)) {
@@ -144,9 +182,10 @@ angular.module('cesium.es.profile.services', ['cesium.services', 'cesium.es.http
       _source: ["title", "avatar._content_type"]
     };
 
-    var mixedSearch = esSettings.wot.isMixedSearchEnable();
+    var mixedSearch = text && esSettings.wot.isMixedSearchEnable();
     if (mixedSearch) {
       request._source = request._source.concat(["description", "thumbnail._content_type", "city", "creationTime", "membersCount"]);
+      console.debug("[ES] [profile] Mixed search: enable");
     }
 
     if (datas.length > 0) {
@@ -373,8 +412,8 @@ angular.module('cesium.es.profile.services', ['cesium.services', 'cesium.es.http
   return {
     getAvatarAndName: getAvatarAndName,
     get: getProfile,
-    add: esHttp.record.post('/user/profile'),
-    update: esHttp.record.post('/user/profile/:id/_update'),
+    add: esHttp.record.post('/user/profile', {tagFields: ['title', 'description']}),
+    update: esHttp.record.post('/user/profile/:id/_update', {tagFields: ['title', 'description']}),
     avatar: esHttp.get('/user/profile/:id?_source=avatar'),
     fillAvatars: fillAvatars
   };
diff --git a/www/plugins/es/js/services/registry-services.js b/www/plugins/es/js/services/registry-services.js
index 8acd1f5ba..0e00954cb 100644
--- a/www/plugins/es/js/services/registry-services.js
+++ b/www/plugins/es/js/services/registry-services.js
@@ -148,7 +148,7 @@ angular.module('cesium.es.registry.services', ['ngResource', 'cesium.services',
 
         // parse description as Html
         if (!options.raw) {
-          record.description = esHttp.util.trustAsHtml(record.description);
+          record.description = esHttp.util.parseAsHtml(record.description);
         }
 
         // Load issuer (avatar, name, uid, etc.)
@@ -170,8 +170,8 @@ angular.module('cesium.es.registry.services', ['ngResource', 'cesium.services',
     exports.record = {
         search: search,
         load: loadData,
-        add: esHttp.record.post('/page/record'),
-        update: esHttp.record.post('/page/record/:id/_update'),
+        add: esHttp.record.post('/page/record', {tagFields: ['title', 'description']}),
+        update: esHttp.record.post('/page/record/:id/_update', {tagFields: ['title', 'description']}),
         remove: esHttp.record.remove('registry', 'record'),
         fields: {
           commons: fields.commons
diff --git a/www/plugins/es/js/services/settings-services.js b/www/plugins/es/js/services/settings-services.js
index 703720db6..98462476a 100644
--- a/www/plugins/es/js/services/settings-services.js
+++ b/www/plugins/es/js/services/settings-services.js
@@ -107,29 +107,25 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
   }
 
   // Load settings
-  function loadSettings(pubkey, keypair) {
+  function loadSettings(pubkey, boxKeypair) {
     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;
-            }
-          })])
+    return 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)');
+          console.debug('[ES] [settings] Loaded in '+ (new Date().getTime()-now) +'ms, but already up to date');
           return;
         }
         var nonce = CryptoUtils.util.decode_base58(record.nonce);
@@ -138,13 +134,12 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
           .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');
-            console.debug(settings);
+            console.debug('[ES] [settings] Loaded and decrypted 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'));
+            console.error('[ES] [settings] Could not load remote settings: ' + (err && err.message || 'decryption error'));
             // make sure to remove time, to be able to save it again
             delete csSettings.data.time;
             return null;
@@ -159,21 +154,13 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
     return deferred.promise;
   }
 
-  function onWalletLogin(data, deferred) {
+  function onWalletAuth(data, deferred) {
     deferred = deferred || $q.defer();
-    if (!data || !data.pubkey || !data.keypair || !data.keypair.signSk) {
+    if (!data || !data.pubkey || !data.keypair || !data.keypair.signSk || !data.keypair.boxSk) {
       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
@@ -185,7 +172,7 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
         // Remember for comparison
         previousRemoteData = settings;
 
-        console.debug('[ES] [settings] Successfully load settings from ES');
+        console.debug('[ES] [settings] Applied');
         return storeSettingsLocally();
       })
     .then(function() {
@@ -211,7 +198,7 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
     if (csWallet.isAuth()) {
       if (!wasEnable && isEnable) {
 
-        onWalletLogin(csWallet.data);
+        onWalletAuth(csWallet.data);
       }
       else {
         storeSettingsRemotely(data);
@@ -233,26 +220,16 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
   }
 
   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();
+    var time = esHttp.date.now(); // always update time
     console.debug('[ES] [settings] Saving user settings... at time ' + time);
 
     return $q.all([
-        CryptoUtils.box.keypair.fromSignKeypair(csWallet.data.keypair),
+        csWallet.getKeypair(), // same result as esWallet.box.getKeypair(), because box keypair computed on auth
         CryptoUtils.util.random_nonce()
       ])
       .then(function(res) {
@@ -303,7 +280,7 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
     // Extend csWallet.login()
     listeners = [
       csSettings.api.data.on.reset($rootScope, onSettingsReset, this),
-      csWallet.api.data.on.login($rootScope, onWalletLogin, this)
+      csWallet.api.data.on.auth($rootScope, onWalletAuth, this)
     ];
   }
 
@@ -335,8 +312,8 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
             console.debug("[ES] [settings] Enable");
             addListeners();
 
-            if (csWallet.isLogin()) {
-              return onWalletLogin(csWallet.data)
+            if (csWallet.isAuth()) {
+              return onWalletAuth(csWallet.data)
                 .then(function() {
                   // Emit event
                   api.state.raise.changed(enable);
diff --git a/www/plugins/es/js/services/wallet-services.js b/www/plugins/es/js/services/wallet-services.js
index c881c8360..9d92c52ae 100644
--- a/www/plugins/es/js/services/wallet-services.js
+++ b/www/plugins/es/js/services/wallet-services.js
@@ -18,6 +18,20 @@ angular.module('cesium.es.wallet.services', ['ngResource', 'cesium.platform', 'c
       }
     }
 
+    function onWalletAuth(data, deferred) {
+      deferred = deferred || $q.defer();
+
+      // Generate box keypair
+      esCrypto.box.getKeypair(data.keypair)
+        .then(function(res) {
+          csWallet.data.keypair.boxSk = res.boxSk;
+          csWallet.data.keypair.boxPk = res.boxPk;
+          console.debug("[ES] [wallet] Box keypair successfully computed");
+          deferred.resolve();
+        });
+      return deferred.promise;
+    }
+
     function onWalletUnauth(data) {
       data = data || csWallet.data;
       if (data.keypair) {
@@ -95,11 +109,11 @@ angular.module('cesium.es.wallet.services', ['ngResource', 'cesium.platform', 'c
     }
 
     function getBoxKeypair() {
-      if (!csWallet.isLogin()) {
-        throw new Error('Unable to get box keypair: user not connected !');
+      if (!csWallet.isAuth()) {
+        throw new Error('Unable to get box keypair: user not authenticated !');
       }
 
-      return csWallet.getKeypair()
+      return csWallet.getKeypair({silent: true})
         .then(function(keypair) {
           if (keypair && keypair.boxPk && keypair.boxSk) {
             return $q.when(csWallet.data.keypair);
@@ -109,7 +123,7 @@ angular.module('cesium.es.wallet.services', ['ngResource', 'cesium.platform', 'c
         .then(function(res) {
           csWallet.data.keypair.boxSk = res.boxSk;
           csWallet.data.keypair.boxPk = res.boxPk;
-          console.debug("[ES] [wallet] Secret box keypair successfully computed");
+          console.debug("[ES] [wallet] Box keypair successfully computed");
           return csWallet.data.keypair;
         });
     }
@@ -121,7 +135,8 @@ angular.module('cesium.es.wallet.services', ['ngResource', 'cesium.platform', 'c
         csWallet.api.data.on.load($rootScope, onWalletLoad, this),
         csWallet.api.data.on.init($rootScope, onWalletReset, this),
         csWallet.api.data.on.reset($rootScope, onWalletReset, this),
-        csWallet.api.data.on.unauth($rootScope, onWalletUnauth, this)
+        csWallet.api.data.on.unauth($rootScope, onWalletUnauth, this),
+        csWallet.api.data.on.auth($rootScope, onWalletAuth, this)
       ];
     }
 
diff --git a/www/plugins/graph/js/controllers/synchro-controllers.js b/www/plugins/graph/js/controllers/synchro-controllers.js
index f41782fd6..b770cef3f 100644
--- a/www/plugins/graph/js/controllers/synchro-controllers.js
+++ b/www/plugins/graph/js/controllers/synchro-controllers.js
@@ -160,7 +160,6 @@ function GpSynchroController($scope, $controller, $q, $translate, gpColor, gpDat
       };
 
       result = result[1];
-      console.log("TODO", result);
       if (!result || !result.times) return; // no data
       $scope.times = result.times;
 
diff --git a/www/plugins/map/js/services/wot-services.js b/www/plugins/map/js/services/wot-services.js
index 02aab8ad5..81edba0e9 100644
--- a/www/plugins/map/js/services/wot-services.js
+++ b/www/plugins/map/js/services/wot-services.js
@@ -174,7 +174,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
       }
 
       // Description
-      item.description = hit._source.description && esHttp.util.trustAsHtml(hit._source.description);
+      item.description = hit._source.description && esHttp.util.parseAsHtml(hit._source.description);
 
       return item.geoPoint ? res.concat(item) : res;
     }, []);
diff --git a/www/templates/login/form_scrypt.html b/www/templates/login/form_scrypt.html
index a752945ef..752b84828 100644
--- a/www/templates/login/form_scrypt.html
+++ b/www/templates/login/form_scrypt.html
@@ -57,13 +57,17 @@
     </div>
     <a class="button button-icon positive button-small-padding icon ion-ios-help-outline animate-show-hide "
        ng-click="showHelpModal('login-pubkey')"
+       ng-if="!expectedPubkey"
        ng-show="showPubkey">
     </a>
+    <span class="button button-icon balanced button-small-padding icon ion-checkmark animate-show-hide"
+       ng-if="expectedPubkey"
+       ng-show="showPubkey && !computing && !pubkeyError">
+    </span>
   </div>
-  <div class="form-errors"
-       ng-show="pubkeyError">
-    <div class="form-error" >
-      <span translate="ERROR.AUTH_INVALID_PUBKEY"></span>
+  <div class="form-errors" ng-if="expectedPubkey">
+    <div class="form-error" ng-show="pubkeyError">
+      <span trust-as-html="::'ERROR.AUTH_INVALID_PUBKEY'|translate:{pubkey: expectedPubkey}"></span>
     </div>
   </div>
 
-- 
GitLab