From fcf00ae13ecf4e1540698ef030a5a82a7d452c4b Mon Sep 17 00:00:00 2001
From: blavenie <benoit.lavenier@e-is.pro>
Date: Thu, 12 Oct 2017 15:34:00 +0200
Subject: [PATCH] [fix] Page: fix deletion [enh] Page: edit avatar using a
 modal [enh] Page: add fields for position (lat/lon) [fix] Comment: send new
 comment when not auth : hide loading toast

---
 www/plugins/es/i18n/locale-en-GB.json         |   3 +-
 www/plugins/es/i18n/locale-en.json            |   3 +-
 www/plugins/es/i18n/locale-fr-FR.json         |   3 +-
 .../es/js/controllers/common-controllers.js   | 198 ++++++++++++
 .../es/js/controllers/profile-controllers.js  | 178 +---------
 .../es/js/controllers/registry-controllers.js | 303 +++++++++++++++---
 .../es/js/services/comment-services.js        |   4 +-
 www/plugins/es/js/services/http-services.js   |   4 +-
 .../es/js/services/registry-services.js       |  11 +-
 .../es/templates/common/edit_position.html    | 102 ++++++
 .../es/templates/group/edit_group.html        |   2 +-
 .../es/templates/registry/edit_record.html    |  67 ++--
 .../es/templates/registry/view_record.html    |   4 +-
 .../es/templates/user/edit_profile.html       | 105 +-----
 .../es/templates/user/modal_edit_avatar.html  |  57 ++--
 15 files changed, 641 insertions(+), 403 deletions(-)
 create mode 100644 www/plugins/es/templates/common/edit_position.html

diff --git a/www/plugins/es/i18n/locale-en-GB.json b/www/plugins/es/i18n/locale-en-GB.json
index 6f3ae1c34..3e4ee8c6c 100644
--- a/www/plugins/es/i18n/locale-en-GB.json
+++ b/www/plugins/es/i18n/locale-en-GB.json
@@ -269,7 +269,8 @@
       "FAILED_REMOVE_COMMENT": "Deleting comment failed"
     },
     "INFO": {
-      "RECORD_REMOVED" : "Reference successfully deleted"
+      "RECORD_REMOVED" : "Page successfully deleted",
+      "RECORD_SAVED": "Page successfully saved"
     }
   },
   "PROFILE": {
diff --git a/www/plugins/es/i18n/locale-en.json b/www/plugins/es/i18n/locale-en.json
index 6f3ae1c34..3e4ee8c6c 100644
--- a/www/plugins/es/i18n/locale-en.json
+++ b/www/plugins/es/i18n/locale-en.json
@@ -269,7 +269,8 @@
       "FAILED_REMOVE_COMMENT": "Deleting comment failed"
     },
     "INFO": {
-      "RECORD_REMOVED" : "Reference successfully deleted"
+      "RECORD_REMOVED" : "Page successfully deleted",
+      "RECORD_SAVED": "Page successfully saved"
     }
   },
   "PROFILE": {
diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json
index 9df790aa5..50e803dd3 100644
--- a/www/plugins/es/i18n/locale-fr-FR.json
+++ b/www/plugins/es/i18n/locale-fr-FR.json
@@ -320,7 +320,8 @@
       "FAILED_REMOVE_COMMENT": "Erreur lors de la suppression du commentaire"
     },
     "INFO": {
-      "RECORD_REMOVED" : "Page supprimée"
+      "RECORD_REMOVED" : "Page supprimée",
+      "RECORD_SAVED": "Page sauvegardée"
     }
   },
   "PROFILE": {
diff --git a/www/plugins/es/js/controllers/common-controllers.js b/www/plugins/es/js/controllers/common-controllers.js
index 5ea4c65b3..284ece732 100644
--- a/www/plugins/es/js/controllers/common-controllers.js
+++ b/www/plugins/es/js/controllers/common-controllers.js
@@ -12,6 +12,10 @@ angular.module('cesium.es.common.controllers', ['ngResource', 'cesium.es.service
 
  .controller('ESCategoryModalCtrl', ESCategoryModalController)
 
+ .controller('ESAvatarModalCtrl', ESAvatarModalController)
+
+ .controller('ESPositionEditCtrl', ESPositionEditController)
+
 ;
 
 
@@ -358,3 +362,197 @@ function ESSocialsViewController($scope)  {
   };
 
 }
+
+
+
+function ESAvatarModalController($scope) {
+
+  $scope.formData = {
+    initCrop: false,
+    imageCropStep: 0,
+    imgSrc: undefined,
+    result: undefined,
+    resultBlob: undefined
+  };
+
+  $scope.openFileSelector = function() {
+    var fileInput = angular.element(document.querySelector('.modal-avatar #fileInput'));
+    if (fileInput && fileInput.length > 0) {
+      fileInput[0].click();
+    }
+  };
+
+  $scope.fileChanged = function(e) {
+
+    var files = e.target.files;
+    var fileReader = new FileReader();
+    fileReader.readAsDataURL(files[0]);
+
+    fileReader.onload = function(e) {
+      $scope.formData.imgSrc = this.result;
+      $scope.$apply();
+    };
+  };
+
+  $scope.doNext = function() {
+    if ($scope.formData.imageCropStep == 2) {
+      $scope.doCrop();
+    }
+    else if ($scope.formData.imageCropStep == 3) {
+      $scope.closeModal($scope.formData.result);
+    }
+  };
+
+  $scope.doCrop = function() {
+    $scope.formData.initCrop = true;
+  };
+
+  $scope.clear = function() {
+    $scope.formData = {
+      initCrop: false,
+      imageCropStep: 1,
+      imgSrc: undefined,
+      result: undefined,
+      resultBlob: undefined
+    };
+  };
+
+}
+
+
+function ESPositionEditController($scope, $q, $translate,
+                                  csConfig, UIUtils, esGeo, ModalUtils) {
+  'ngInject';
+
+  // The default country used for address localisation
+  var defaultCountry = csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.defaultCountry;
+
+  //$scope.formData = $scope.formData || {};
+  //$scope.formData.geoPoint = $scope.formData.geoPoint || {};
+
+  $scope.localizeByAddress = function() {
+
+    return UIUtils.loading.show()
+      .then($scope.searchPositions)
+      .then(function(res) {
+        UIUtils.loading.hide();
+
+        if (!res) return; // no result, or city value just changed
+        if (res.length == 1) {
+          return res[0];
+        }
+
+        return ModalUtils.show('plugins/es/templates/common/modal_category.html', 'ESCategoryModalCtrl as ctrl',
+          {
+            categories : res,
+            title: 'PROFILE.MODAL_LOCATIONS.TITLE'
+          },
+          {focusFirstInput: true}
+        );
+      })
+      .then(function(res) {
+        if (res && res.lat && res.lon) {
+          $scope.formData.geoPoint = $scope.formData.geoPoint || {};
+          $scope.formData.geoPoint.lat =  parseFloat(res.lat);
+          $scope.formData.geoPoint.lon =  parseFloat(res.lon);
+        }
+      })
+      .catch(UIUtils.onError('PROFILE.ERROR.ADDRESS_LOCATION_FAILED'));
+  };
+
+  $scope.searchPositions = function(query) {
+
+    // Build the query
+    if (!query) {
+      if (!$scope.formData.city) {
+        return $q.when(); // nothing to search
+      }
+
+      var cityPart = $scope.formData.city.split(',');
+      var city = cityPart[0];
+
+      var country = cityPart.length > 1 ? cityPart[1].trim() : defaultCountry;
+      var street = $scope.formData.address ? angular.copy($scope.formData.address.trim()) : undefined;
+      if (street) {
+        // Search with AND without street
+        return $q.all([
+          $scope.searchPositions({
+            street: street,
+            city: city,
+            country: country
+          }),
+          $scope.searchPositions({
+            city: city,
+            country: country
+          })
+        ])
+          .then(function(res){
+            return res[0].concat(res[1]);
+          });
+      }
+      else {
+        return $scope.searchPositions({
+          city: city,
+          country: country
+        });
+      }
+    }
+
+    var queryString = (query.street ? query.street + ', ' : '') +
+      query.city +
+      (query.country ? ', ' + query.country : '');
+    // Execute the given query
+    return $q.all([
+      $translate('PROFILE.MODAL_LOCATIONS.RESULT_DIVIDER', {address: queryString}),
+      esGeo.point.searchByAddress(query)
+    ])
+      .then(function(res) {
+        var dividerText = res[0];
+        res = res[1];
+        if (!res) return $q.when(); // no result
+
+        // Ask user to choose
+        var parent = {name: dividerText};
+        var hits = res.reduce(function(res, hit){
+          if (hit.class == 'waterway') return res;
+          return res.concat({
+            name: hit.display_name,
+            parent: parent,
+            lat: hit.lat,
+            lon: hit.lon
+          });
+        }, [parent]);
+
+        if (hits.length == 1) return $q.when(); // no result (after filtering)
+
+        return hits;
+      });
+  };
+
+  $scope.localizeMe = function() {
+    return esGeo.point.current()
+      .then(function(position) {
+        if (!position || !position.lat || !position.lon) return;
+        $scope.formData.geoPoint = $scope.formData.geoPoint || {};
+        $scope.formData.geoPoint.lat =  parseFloat(position.lat);
+        $scope.formData.geoPoint.lon =  parseFloat(position.lon);
+      })
+      .catch(UIUtils.onError('PROFILE.ERROR.GEO_LOCATION_FAILED'));
+  };
+
+  $scope.removeLocalisation = function() {
+    if ($scope.formData.geoPoint) {
+      $scope.formData.geoPoint.lat = null;
+      $scope.formData.geoPoint.lon = null;
+    }
+  };
+
+  $scope.onCityChanged = function() {
+    if ($scope.loading) return;
+    var hasGeoPoint = $scope.formData.geoPoint && $scope.formData.geoPoint.lat && $scope.formData.geoPoint.lon;
+    if (!hasGeoPoint) {
+      return $scope.localizeByAddress();
+    }
+  };
+
+}
diff --git a/www/plugins/es/js/controllers/profile-controllers.js b/www/plugins/es/js/controllers/profile-controllers.js
index 1fb02982b..76b0bce0e 100644
--- a/www/plugins/es/js/controllers/profile-controllers.js
+++ b/www/plugins/es/js/controllers/profile-controllers.js
@@ -20,26 +20,25 @@ angular.module('cesium.es.profile.controllers', ['cesium.es.services'])
 
  .controller('ESViewEditProfileCtrl', ESViewEditProfileController)
 
- .controller('ESAvatarModalCtrl', ESAvatarModalController)
 
 ;
 
-function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $focus, $translate, $ionicHistory,
-                           csConfig, UIUtils, esHttp, esProfile, esGeo, ModalUtils, Device) {
+function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $focus, $translate, $controller, $ionicHistory,
+                                     UIUtils, esHttp, esProfile, ModalUtils, Device) {
   'ngInject';
 
-  // The default country used for address localisation
-  var defaultCountry = csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.defaultCountry;
+  // Initialize the super class and extend it.
+  angular.extend(this, $controller('ESPositionEditCtrl', {$scope: $scope}));
 
-  $scope.loading = true;
-  $scope.dirty = false;
-  $scope.walletData = null;
   $scope.formData = {
     title: null,
     description: null,
     socials: [],
     geoPoint: {}
   };
+  $scope.loading = true;
+  $scope.dirty = false;
+  $scope.walletData = null;
   $scope.avatar = null;
   $scope.existing = false;
   $scope.socialData = {
@@ -100,7 +99,7 @@ function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $
             }
           })
           .catch(function(err) {
-
+            // Silent
           });
       }
     }
@@ -108,7 +107,9 @@ function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $
 
   $scope.load = function(walletData) {
     $scope.loading = true; // to avoid the call of doSave()
-    return esProfile.get(walletData.pubkey, {raw: true})
+    return esProfile.get(walletData.pubkey, {
+        raw: true
+      })
       .then(function(profile) {
         if (profile) {
           $scope.avatar = esHttp.image.fromAttachment(profile.source.avatar);
@@ -336,164 +337,7 @@ function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $
       });
   };
 
-  $scope.localizeByAddress = function() {
-
-    return UIUtils.loading.show()
-      .then($scope.searchPositions)
-      .then(function(res) {
-        UIUtils.loading.hide();
-
-        if (!res) return; // no result, or city value just changed
-        if (res.length == 1) {
-          return res[0];
-        }
-
-        return ModalUtils.show('plugins/es/templates/common/modal_category.html', 'ESCategoryModalCtrl as ctrl',
-          {
-            categories : res,
-            title: 'PROFILE.MODAL_LOCATIONS.TITLE'
-          },
-          {focusFirstInput: true}
-        );
-      })
-      .then(function(res) {
-        if (res && res.lat && res.lon) {
-          $scope.formData.geoPoint = $scope.formData.geoPoint || {};
-          $scope.formData.geoPoint.lat =  parseFloat(res.lat);
-          $scope.formData.geoPoint.lon =  parseFloat(res.lon);
-        }
-      })
-      .catch(UIUtils.onError('PROFILE.ERROR.ADDRESS_LOCATION_FAILED'));
-  };
-
-  $scope.searchPositions = function(query) {
-
-    // Build the query
-    if (!query) {
-      if (!$scope.formData.city) {
-        return $q.when(); // nothing to search
-      }
-
-      var cityPart = $scope.formData.city.split(',');
-      var city = cityPart[0];
-
-      var country = cityPart.length > 1 ? cityPart[1].trim() : defaultCountry;
-      var street = $scope.formData.address ? angular.copy($scope.formData.address.trim()) : undefined;
-      if (street) {
-        // Search with AND without street
-        return $q.all([
-          $scope.searchPositions({
-            street: street,
-            city: city,
-            country: country
-          }),
-          $scope.searchPositions({
-            city: city,
-            country: country
-          })
-        ])
-        .then(function(res){
-          return res[0].concat(res[1]);
-        });
-      }
-      else {
-        return $scope.searchPositions({
-          city: city,
-          country: country
-        });
-      }
-    }
-
-    var queryString = (query.street ? query.street + ', ' : '') +
-      query.city +
-      (query.country ? ', ' + query.country : '');
-    // Execute the given query
-    return $q.all([
-      $translate('PROFILE.MODAL_LOCATIONS.RESULT_DIVIDER', {address: queryString}),
-      esGeo.point.searchByAddress(query)
-    ])
-      .then(function(res) {
-        var dividerText = res[0];
-        res = res[1];
-        if (!res) return $q.when(); // no result
-
-        // Ask user to choose
-        var parent = {name: dividerText};
-        var hits = res.reduce(function(res, hit){
-          if (hit.class == 'waterway') return res;
-          return res.concat({
-            name: hit.display_name,
-            parent: parent,
-            lat: hit.lat,
-            lon: hit.lon
-          });
-        }, [parent]);
-
-        if (hits.length == 1) return $q.when(); // no result (after filtering)
-
-        return hits;
-      });
-  };
-
-  $scope.localizeMe = function() {
-    return esGeo.point.current()
-      .then(function(position) {
-        if (!position || !position.lat || !position.lon) return;
-        $scope.formData.geoPoint = $scope.formData.geoPoint || {};
-        $scope.formData.geoPoint.lat =  parseFloat(position.lat);
-        $scope.formData.geoPoint.lon =  parseFloat(position.lon);
-      })
-      .catch(UIUtils.onError('PROFILE.ERROR.GEO_LOCATION_FAILED'));
-  };
-
-  $scope.removeLocalisation = function() {
-    if ($scope.formData.geoPoint) {
-      $scope.formData.geoPoint.lat = null;
-      $scope.formData.geoPoint.lon = null;
-    }
-  };
-
-  $scope.onCityChanged = function() {
-    if ($scope.loading) return;
-    var hasGeoPoint = $scope.formData.geoPoint && $scope.formData.geoPoint.lat && $scope.formData.geoPoint.lon;
-    if (!hasGeoPoint) {
-      return $scope.localizeByAddress();
-    }
-  };
 
 }
 
 
-function ESAvatarModalController($scope) {
-
-  $scope.openFileSelector = function() {
-    var fileInput = angular.element(document.querySelector('.modal-avatar #fileInput'));
-    if (fileInput && fileInput.length > 0) {
-      fileInput[0].click();
-    }
-  };
-
-  $scope.fileChanged = function(e) {
-
-    var files = e.target.files;
-    var fileReader = new FileReader();
-    fileReader.readAsDataURL(files[0]);
-
-    fileReader.onload = function(e) {
-      $scope.imgSrc = this.result;
-      $scope.$apply();
-    };
-  };
-
-  $scope.doCrop = function() {
-    $scope.initCrop = true;
-  };
-
-  $scope.clear = function() {
-    $scope.imageCropStep = 1;
-    delete $scope.imgSrc;
-    delete $scope.result;
-    delete $scope.resultBlob;
-  };
-
-}
diff --git a/www/plugins/es/js/controllers/registry-controllers.js b/www/plugins/es/js/controllers/registry-controllers.js
index 5f9e87abf..fd644d2bf 100644
--- a/www/plugins/es/js/controllers/registry-controllers.js
+++ b/www/plugins/es/js/controllers/registry-controllers.js
@@ -6,7 +6,7 @@ angular.module('cesium.es.registry.controllers', ['cesium.es.services', 'cesium.
     $stateProvider
 
     .state('app.registry_lookup', {
-      url: "/registry?q&category&location&type&reload",
+      url: "/page?q&category&location&type&reload",
       views: {
         'menuContent': {
           templateUrl: "plugins/es/templates/registry/lookup.html",
@@ -19,7 +19,7 @@ angular.module('cesium.es.registry.controllers', ['cesium.es.services', 'cesium.
     })
 
     .state('app.registry_lookup_lg', {
-      url: "/registry/lg?q&category&location&type&reload",
+      url: "/page/lg?q&category&location&type&reload",
       views: {
         'menuContent': {
           templateUrl: "plugins/es/templates/registry/lookup_lg.html",
@@ -29,7 +29,7 @@ angular.module('cesium.es.registry.controllers', ['cesium.es.services', 'cesium.
     })
 
     .state('app.view_page', {
-      url: "/page/:id/:title?refresh",
+      url: "/page/view/:id/:title?refresh",
       views: {
         'menuContent': {
           templateUrl: "plugins/es/templates/registry/view_record.html",
@@ -39,7 +39,7 @@ angular.module('cesium.es.registry.controllers', ['cesium.es.services', 'cesium.
     })
 
     .state('app.view_page_anchor', {
-      url: "/page/:id/:title/:anchor",
+      url: "/page/view/:id/:title/:anchor",
       views: {
         'menuContent': {
           templateUrl: "plugins/es/templates/registry/view_record.html",
@@ -50,7 +50,7 @@ angular.module('cesium.es.registry.controllers', ['cesium.es.services', 'cesium.
 
     .state('app.registry_add_record', {
       cache: false,
-      url: "/registry/add/:type",
+      url: "/page/add/:type",
       views: {
         'menuContent': {
           templateUrl: "plugins/es/templates/registry/edit_record.html",
@@ -65,7 +65,7 @@ angular.module('cesium.es.registry.controllers', ['cesium.es.services', 'cesium.
 
     .state('app.registry_edit_record', {
       cache: false,
-      url: "/registry/edit/:id/:title",
+      url: "/page/edit/:id/:title",
       views: {
         'menuContent': {
           templateUrl: "plugins/es/templates/registry/edit_record.html",
@@ -408,6 +408,10 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo
         $scope.formData = data.record;
         $scope.canEdit = csWallet.isUserPubkey($scope.formData.issuer);
         $scope.issuer = data.issuer;
+        // avatar
+        $scope.avatar = $scope.formData.avatar;
+        $scope.avatarStyle= $scope.formData.avatar && {'background-image':'url("'+$scope.avatar.src+'")'};
+
         UIUtils.loading.hide();
         $scope.loading = false;
         // Set Motion (only direct children, to exclude .lazy-load children)
@@ -438,19 +442,13 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo
       // Load pictures
       esRegistry.record.picture.all({id: id})
         .then(function(hit) {
-          var pictures;
-          if (hit._source.pictures) {
-            pictures = hit._source.pictures.reduce(function(res, pic) {
+
+          $scope.pictures = hit._source.pictures && hit._source.pictures.reduce(function(res, pic) {
               return res.concat(esHttp.image.fromAttachment(pic.file));
             }, []);
-            if (pictures.length > 0) {
-              pictures.splice(0,1); // Remove the avatar
-            }
-          }
-          $scope.pictures = pictures;
 
           // Set Motion
-          if (pictures.length > 0) {
+          if ($scope.pictures.length > 0) {
             $scope.motion.show({
               selector: '.lazy-load .item.card-gallery, .lazy-load .item',
               startVelocity: 3000
@@ -550,15 +548,25 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo
 
 }
 
-function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q, Device,
-  $ionicHistory, ModalUtils, $focus, esHttp) {
+function ESRegistryRecordEditController($scope, $timeout,  $state, $q, $ionicHistory, $focus, $translate, $controller,
+                                        Device, UIUtils, ModalUtils, esHttp, esRegistry) {
   'ngInject';
+  // Initialize the super class and extend it.
+  angular.extend(this, $controller('ESPositionEditCtrl', {$scope: $scope}));
+
+  $scope.formData = {
+    title: null,
+    description: null,
+    socials: [],
+    geoPoint: {}
+  };
 
-  $scope.walletData = {};
-  $scope.formData = {};
+  $scope.loading = true;
+  $scope.dirty = false;
+  $scope.walletData = null;
   $scope.id = null;
+  $scope.avatar = null;
   $scope.pictures = [];
-  $scope.loading = true;
 
   $scope.setForm =  function(form) {
     $scope.form = form;
@@ -573,11 +581,12 @@ function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q,
       }
       else {
         if (state.stateParams && state.stateParams.type) {
-          $scope.formData.type=state.stateParams.type;
+          $scope.updateView({
+            record: {
+              type: state.stateParams.type
+            }
+          });
         }
-        $scope.loading = false;
-        UIUtils.loading.hide();
-        $scope.motion.show();
       }
       // removeIf(device)
       $focus('registry-record-title');
@@ -585,42 +594,158 @@ function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q,
     });
   });
 
+  $scope.$on('$stateChangeStart', function (event, next, nextParams, fromState) {
+    if ($scope.dirty && !$scope.saving) {
+
+      // stop the change state action
+      event.preventDefault();
+
+      if (!$scope.loading) {
+        $scope.loading = true;
+        return UIUtils.alert.confirm('CONFIRM.SAVE_BEFORE_LEAVE',
+          'CONFIRM.SAVE_BEFORE_LEAVE_TITLE', {
+            cancelText: 'COMMON.BTN_NO',
+            okText: 'COMMON.BTN_YES_SAVE'
+          })
+          .then(function(confirmSave) {
+            $scope.loading = false;
+            if (confirmSave) {
+              $scope.form.$submitted=true;
+              return $scope.save(false/*silent*/, true/*haswait debounce*/)
+                .then(function(saved){
+                  if (saved) {
+                    $scope.dirty = false;
+                  }
+                  return saved; // change state only if not error
+                });
+            }
+            else {
+              $scope.dirty = false;
+              return true; // ok, change state
+            }
+          })
+          .then(function(confirmGo) {
+            if (confirmGo) {
+              // continue to the order state
+              $ionicHistory.nextViewOptions({
+                historyRoot: true
+              });
+              $state.go(next.name, nextParams);
+            }
+          })
+          .catch(function(err) {
+            // Silent
+          });
+      }
+    }
+  });
+
   $scope.load = function(id) {
+    $scope.loading = true;
     esRegistry.record.load(id, {
         raw: true
       })
       .then(function (data) {
-        $scope.formData = data.record;
-        $scope.id= data.id;
+        if (data && data.record) {
+          $scope.updateView(data);
+        }
+        else {
+          $scope.updateView({record: {}});
+        }
+      })
+      .catch(function(err) {
+        UIUtils.loading.hide(10);
+        $scope.loading = false;
+        UIUtils.onError('REGISTRY.ERROR.LOAD_RECORD_FAILED')(err);
+      });
+  };
 
-        $scope.pictures = data.record.pictures || [];
-        delete data.record.pictures; // remove, as already stored in $scope.pictures
+  $scope.updateView = function(data) {
+    $scope.formData = data.record || {};
+    $scope.id= data.id;
 
-        $scope.loading = false;
-        UIUtils.loading.hide();
+    // avatar
+    $scope.avatar = $scope.formData.avatar;
+    if ($scope.avatar) {
+      $scope.avatarStyle = $scope.avatar && {'background-image':'url("'+$scope.avatar.src+'")'};
+      $scope.avatarClass = {};
+    }
+    else {
+      $scope.avatarStyle = undefined;
+      $scope.avatarClass = {};
+      $scope.avatarClass['cion-page-' +  $scope.formData.type] = !$scope.avatar;
+    }
 
-        $scope.motion.show({
-          selector: '.animate-ripple .item, .card-gallery',
-          startVelocity: 3000
-        });
-      })
-      .catch(UIUtils.onError('REGISTRY.ERROR.LOAD_RECORD_FAILED'));
+    // geo point
+    $scope.formData.geoPoint = $scope.formData.geoPoint || {};
+
+    // pictures
+    $scope.pictures = data.record && data.record.pictures || [];
+    delete data.record.pictures; // remove, as already stored in $scope.pictures
+
+    $scope.motion.show({
+      selector: '.animate-ripple .item, .card-gallery',
+      startVelocity: 3000
+    });
+    UIUtils.loading.hide();
+
+    // Update loading - done with a delay, to avoid trigger onFormDataChanged()
+    $timeout(function() {
+      $scope.loading = false;
+    }, 1000);
   };
 
+  $scope.onFormDataChanged = function() {
+    if ($scope.loading) return;
+    $scope.dirty = true;
+  };
+  $scope.$watch('formData', $scope.onFormDataChanged, true);
+
+
+
   $scope.needCategory = function() {
     return $scope.formData.type && ($scope.formData.type=='company' || $scope.formData.type=='shop');
   };
 
-  $scope.save = function() {
+  $scope.save = function(silent, hasWaitDebounce) {
     $scope.form.$submitted=true;
     if($scope.saving || // avoid multiple save
        !$scope.form.$valid ||
        (($scope.formData.type === 'shop' || $scope.formData.type === 'company') && (!$scope.formData.category || !$scope.formData.category.id))) {
-      return;
+      return $q.reject();
+    }
+
+    if (!hasWaitDebounce) {
+      console.debug('[ES] [page] Waiting debounce end, before saving...');
+      return $timeout(function() {
+        return $scope.save(silent, true);
+      }, 650);
     }
+
     $scope.saving = true;
-    return UIUtils.loading.show()
+    console.debug('[ES] [page] Saving record...');
+
+    var showSuccessToast = function() {
+      if (!silent) {
+        // removeIf(no-device)
+        UIUtils.loading.hide();
+        // endRemoveIf(no-device)
+
+        return $translate('REGISTRY.INFO.RECORD_SAVED')
+          .then(function(message){
+            UIUtils.toast.show(message);
+          });
+      }
+    };
+
+    var promise = $q.when();
+    // removeIf(no-device)
+    if (!silent) {
+      promise = UIUtils.loading.show();
+    }
+    // endRemoveIf(no-device)
 
+    return promise
       .then(function(){
         var json = $scope.formData;
         if (!$scope.needCategory()) {
@@ -628,35 +753,54 @@ function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q,
         }
         json.time = esHttp.date.now();
 
-        // Resize pictures
+        // geo point
+        if (json.geoPoint && json.geoPoint.lat && json.geoPoint.lon) {
+          json.geoPoint.lat =  parseFloat(json.geoPoint.lat);
+          json.geoPoint.lon =  parseFloat(json.geoPoint.lon);
+        }
+        else{
+          json.geoPoint = null;
+        }
+
+        // Social url must be unique in socials links - Fix #306:
+        if (json.socials && json.socials.length) {
+          json.socials = _.uniq(json.socials, false, function(social) {
+            return social.url;
+          });
+        }
+
+        // Pictures
         json.picturesCount = $scope.pictures.length;
         if (json.picturesCount > 0) {
-          json.pictures = $scope.pictures.reduce(function(res, pic) {
+          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
+        }
+        else {
+          json.pictures = [];
+        }
+
+        // Avatar
+        if ($scope.avatar && $scope.avatar.src) {
+          return UIUtils.image.resizeSrc($scope.avatar.src, true) // resize to thumbnail
             .then(function(imageSrc) {
-              json.thumbnail = esHttp.image.toAttachment({src: imageSrc});
+              json.avatar = esHttp.image.toAttachment({src: imageSrc});
               return json;
             });
         }
         else {
-          if (json.thumbnail) {
-            // Workaround to allow content deletion, because of a bug in the ES attachment-mapper:
-            // get error (in ES node) : MapperParsingException[No content is provided.] - AttachmentMapper.parse(AttachmentMapper.java:471
-            json.thumbnail = {
-              _content: '',
-              _content_type: ''
-            };
-          }
-          json.pictures = [];
+          // Workaround to allow content deletion, because of a bug in the ES attachment-mapper:
+          // get error (in ES node) : MapperParsingException[No content is provided.] - AttachmentMapper.parse(AttachmentMapper.java:471
+          json.avatar = {
+            _content: '',
+            _content_type: ''
+          };
           return json;
         }
       })
       .then(function(json){
         // Create
         if (!$scope.id) {
-          json.creationTime = esHttp.date.now();
           return esRegistry.record.add(json);
         }
         // Update
@@ -664,8 +808,13 @@ function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q,
       })
 
       .then(function(id) {
+        console.info("[ES] [page] Record successfully saved.");
         $scope.id = $scope.id || id;
         $scope.saving = false;
+        $scope.dirty = false;
+
+        showSuccessToast();
+
         $ionicHistory.clearCache($ionicHistory.currentView().stateId); // clear current view
         $ionicHistory.nextViewOptions({historyRoot: true});
         return $state.go('app.view_page', {id: $scope.id, refresh: true});
@@ -685,6 +834,26 @@ function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q,
     .catch(UIUtils.onError('ERROR.TAKE_PICTURE_FAILED'));
   };
 
+
+
+  $scope.rotateAvatar = function(){
+    if (!$scope.avatar || !$scope.avatar.src || $scope.rotating) return;
+
+    $scope.rotating = true;
+
+    return UIUtils.image.rotateSrc($scope.avatar.src)
+      .then(function(imageData){
+        $scope.avatar.src = imageData;
+        $scope.avatarStyle={'background-image':'url("'+imageData+'")'};
+        $scope.dirty = true;
+        $scope.rotating = false;
+      })
+      .catch(function(err) {
+        console.error(err);
+        $scope.rotating = false;
+      });
+  };
+
   $scope.fileChanged = function(event) {
     UIUtils.loading.show();
     return $q(function(resolve, reject) {
@@ -715,11 +884,39 @@ function ESRegistryRecordEditController($scope, esRegistry, UIUtils, $state, $q,
   };
 
   /* -- modals -- */
+  $scope.showAvatarModal = function() {
+    if (Device.camera.enable) {
+      return Device.camera.getPicture()
+        .then(function(imageData) {
+          if (!imageData) return;
+          $scope.avatar = {src: "data:image/png;base64," + imageData};
+          $scope.avatarStyle={'background-image':'url("'+imageData+'")'};
+          $scope.dirty = true;
+          $scope.avatarClass = {};
+        })
+        .catch(UIUtils.onError('ERROR.TAKE_PICTURE_FAILED'));
+    }
+    else {
+      return ModalUtils.show('plugins/es/templates/user/modal_edit_avatar.html','ESAvatarModalCtrl',
+        {})
+        .then(function(imageData) {
+          if (!imageData) return;
+          $scope.avatar = {src: imageData};
+          $scope.avatarStyle={'background-image':'url("'+imageData+'")'};
+          $scope.dirty = true;
+          $scope.avatarClass = {};
+        });
+    }
+  };
+
   $scope.showRecordTypeModal = function() {
     ModalUtils.show('plugins/es/templates/registry/modal_record_type.html')
     .then(function(type){
       if (type) {
         $scope.formData.type = type;
+        if (!$scope.avatar) {
+          $scope.avatarClass['cion-page-' + type] = true;
+        }
       }
     });
   };
diff --git a/www/plugins/es/js/services/comment-services.js b/www/plugins/es/js/services/comment-services.js
index 7be2621ab..6f98c4505 100644
--- a/www/plugins/es/js/services/comment-services.js
+++ b/www/plugins/es/js/services/comment-services.js
@@ -168,8 +168,8 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
           data.result.splice(index, 1);
           delete data.mapById[comment.id];
           // Send deletion request
-          if (comment.issuer === csWallet.data.pubkey) {
-            exports.raw.remove(comment.id, csWallet.data.keypair)
+          if (csWallet.isUserPubkey(comment.issuer)) {
+            exports.raw.remove(comment.id)
               .catch(function(err){
                 console.error(err);
                 throw new Error('MARKET.ERROR.FAILED_REMOVE_COMMENT');
diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js
index 9f1fa42c7..4de5bf990 100644
--- a/www/plugins/es/js/services/http-services.js
+++ b/www/plugins/es/js/services/http-services.js
@@ -282,9 +282,9 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       };
     }
 
-    function removeRecord(index, type, walletData) {
+    function removeRecord(index, type) {
       return function(id) {
-        return (!walletData ? csWallet.auth() : $q.when(walletData))
+        return (csWallet.isAuth() ? $q.when(csWallet.data) : csWallet.auth({silent: true, minData: true}))
           .then(function(walletData) {
 
             var obj = {
diff --git a/www/plugins/es/js/services/registry-services.js b/www/plugins/es/js/services/registry-services.js
index 0e00954cb..630fe31f6 100644
--- a/www/plugins/es/js/services/registry-services.js
+++ b/www/plugins/es/js/services/registry-services.js
@@ -7,10 +7,7 @@ angular.module('cesium.es.registry.services', ['ngResource', 'cesium.services',
 
     var
       fields = {
-        commons: ["category", "title", "description", "issuer", "time", "address", "city", "thumbnail._content_type", "picturesCount", "type", "socials", "pubkey"],
-        comment: {
-          commons: ["issuer", "time", "message"],
-        }
+        commons: ["title", "description", "issuer", "time", "address", "city", "creationTime", "avatar._content_type", "picturesCount", "type", "category", "socials", "pubkey"]
       };
     var
       exports = {
@@ -76,7 +73,7 @@ angular.module('cesium.es.registry.services', ['ngResource', 'cesium.services',
       }
 
       // thumbnail
-      record.thumbnail = esHttp.image.fromHit(hit, 'thumbnail');
+      record.avatar = esHttp.image.fromHit(hit, 'avatar');
 
       // pictures
       if (hit._source.pictures && hit._source.pictures.reduce) {
@@ -170,9 +167,9 @@ angular.module('cesium.es.registry.services', ['ngResource', 'cesium.services',
     exports.record = {
         search: search,
         load: loadData,
-        add: esHttp.record.post('/page/record', {tagFields: ['title', 'description']}),
+        add: esHttp.record.post('/page/record', {tagFields: ['title', 'description'], creationTime: true}),
         update: esHttp.record.post('/page/record/:id/_update', {tagFields: ['title', 'description']}),
-        remove: esHttp.record.remove('registry', 'record'),
+        remove: esHttp.record.remove('page', 'record'),
         fields: {
           commons: fields.commons
         },
diff --git a/www/plugins/es/templates/common/edit_position.html b/www/plugins/es/templates/common/edit_position.html
new file mode 100644
index 000000000..04f495c32
--- /dev/null
+++ b/www/plugins/es/templates/common/edit_position.html
@@ -0,0 +1,102 @@
+<div class="item item-divider" translate>REGISTRY.LOCATION_DIVIDER</div>
+
+<!-- address -->
+<ion-item class="item-input item-floating-label item-button-right">
+  <span class="input-label">{{'PROFILE.ADDRESS' | translate}}</span>
+  <textarea placeholder="{{'PROFILE.ADDRESS_HELP' | translate}}"
+            ng-model="formData.address"
+            ng-model-options="{ debounce: 350 }"
+            rows="4" cols="10">
+              </textarea>
+</ion-item>
+
+<!-- city -->
+<div class="item item-input item-floating-label">
+  <span class="input-label" translate>PROFILE.CITY</span>
+  <input type="text" placeholder="{{'PROFILE.CITY_HELP'|translate}}"
+         ng-model="formData.city"
+         ng-model-options="{ updateOn: 'blur' }"
+         ng-change="onCityChanged()">
+</div>
+
+<!-- Position (lat/lon) -->
+<div class="row responsive-md responsive-sm no-padding">
+
+  <!-- lat -->
+  <div class="col no-padding">
+    <label class="item item-input item-floating-label"
+           ng-class="{'item-input-error': form.$submitted && form.latitude.$invalid}">
+      <span class="input-label" translate>PROFILE.LATITUDE</span>
+      <input class="no-padding-right"
+             name="latitude"
+             type="number" placeholder="{{'PROFILE.LATITUDE_HELP'|translate}}"
+             ng-model="formData.geoPoint.lat"
+             ng-model-options="{ debounce: 350 }"
+             ng-change="onFormDataChanged()"
+             min="-90" max="90">
+    </label>
+    <div class="form-errors"
+         ng-show="form.$submitted && form.latitude.$error"
+         ng-messages="form.latitude.$error">
+      <div class="form-error" ng-message="min">
+        <span translate="ERROR.FIELD_MIN" translate-values="{min: -90}"></span>
+      </div>
+      <div class="form-error" ng-message="max">
+        <span translate="ERROR.FIELD_MAX" translate-values="{max: 90}"></span>
+      </div>
+    </div>
+  </div>
+
+  <div class="col-10 no-padding hidden-xs">
+    &nbsp;
+  </div>
+
+  <!-- lon -->
+  <div class="col no-padding">
+    <label class="item item-input item-floating-label"
+           ng-class="{'item-input-error': form.$submitted && form.longitude.$invalid}">
+      <span class="input-label" translate>PROFILE.LONGITUDE</span>
+      <input class="no-padding-right"
+             name="longitude"
+             type="number" placeholder="{{'PROFILE.LONGITUDE_HELP'|translate}}"
+             ng-model="formData.geoPoint.lon"
+             ng-model-options="{ debounce: 350 }"
+             ng-change="onFormDataChanged()"
+             min="-180" max="180">
+    </label>
+    <div class="form-errors"
+         ng-show="form.$submitted && form.longitude.$error"
+         ng-messages="form.longitude.$error">
+      <div class="form-error" ng-message="min">
+        <span translate="ERROR.FIELD_MIN" translate-values="{min: -180}"></span>
+      </div>
+      <div class="form-error" ng-message="max">
+        <span translate="ERROR.FIELD_MAX" translate-values="{max: 180}"></span>
+      </div>
+    </div>
+  </div>
+
+  <div class="col col-40">
+    <div class="row text-center">
+
+      <a class="col button  button-stable button-small-padding icon ion-refresh"
+         title="{{'PROFILE.BTN_GEOLOC_ADDRESS'|translate}}"
+         ng-disabled="!formData.city"
+         ng-click="localizeByAddress()">
+      </a>
+
+      <a class="col button button-stable button-small-padding icon ion-android-locate"
+         title="{{'PROFILE.BTN_GEOLOC_ME'|translate}}"
+         ng-click="localizeMe()">
+      </a>
+
+      <a class="col button button-stable button-small-padding icon ion-close"
+         title="{{'PROFILE.BTN_REMOVE_GEOLOC'|translate}}"
+         ng-disabled="!formData.geoPoint || (!formData.geoPoint.lat && !formData.geoPoint.lon)"
+         ng-click="removeLocalisation()">
+      </a>
+    </div>
+  </div>
+</div>
+
+<cs-extension-point name="after-position"></cs-extension-point>
diff --git a/www/plugins/es/templates/group/edit_group.html b/www/plugins/es/templates/group/edit_group.html
index 7b0b08938..0f3563489 100644
--- a/www/plugins/es/templates/group/edit_group.html
+++ b/www/plugins/es/templates/group/edit_group.html
@@ -85,7 +85,7 @@
               </div>
 
               <!-- social networks -->
-              <ng-include src="'plugins/es/templates/common/edit_socials.html'"></ng-include>
+              <ng-include src="'plugins/es/templates/common/edit_socials.html'" ng-controller="ESSocialsEditCtrl"></ng-include>
 
             </div>
 
diff --git a/www/plugins/es/templates/registry/edit_record.html b/www/plugins/es/templates/registry/edit_record.html
index e8f1438f7..9660fecc2 100644
--- a/www/plugins/es/templates/registry/edit_record.html
+++ b/www/plugins/es/templates/registry/edit_record.html
@@ -12,24 +12,44 @@
   </ion-nav-buttons>
 
   <ion-content scroll="true">
+
+    <div class="positive-900-bg hero">
+      <div class="content">
+        <i class="avatar"
+           ng-class="avatarClass"
+           ng-style="avatarStyle">
+          <button class="button button-positive button-large button-clear flat icon ion-camera visible-xs visible-sm"
+                  style="display: inline-block;"
+                  ng-click="showAvatarModal()"></button>
+          <button ng-if="avatar.src"
+                  class="button button-positive button-large button-clear flat  visible-xs visible-sm"
+                  style="display: inline-block; left: 85px; bottom:15px;"
+                  ng-click="rotateAvatar()">
+            <i class="icon-secondary ion-image" style="left: 24px; top: 3px; font-size: 24px;"></i>
+            <i class="icon-secondary ion-forward" style="left: 26px; top: -13px;"></i>
+          </button>
+          <button class="button button-positive button-large button-clear icon ion-camera hidden-xs hidden-sm"
+                  ng-click="showAvatarModal()"></button>
+        </i>
+        <h3 class="light">
+          <span ng-if="!loading && formData.title">{{formData.title}}</span>
+          <span ng-if="!loading && !id && !formData.title" translate>REGISTRY.EDIT.TITLE_NEW</span>
+        </h3>
+        <h4 class="light">
+          <ion-spinner ng-if="loading" icon="android"></ion-spinner>
+        </h4>
+      </div>
+    </div>
+
       <div class="row no-padding">
 
         <div class="col col-20 hidden-xs hidden-sm">&nbsp;</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 {{::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>REGISTRY.EDIT.TITLE_NEW</h1>
-              </div>
               <div class="item" ng-if="id">
                 <h4 class="gray">
                   <i class="icon ion-calendar"></i>
@@ -94,34 +114,11 @@
 
               <div class="item item-divider" translate>REGISTRY.LOCATION_DIVIDER</div>
 
-              <!-- address -->
-              <div class="item item-floating-label" ng-if="location.enable">
-                <span class="input-label" translate>REGISTRY.EDIT.RECORD_ADDRESS</span>
-                <div class="item-input-inset">
-                  <label class="item-input-wrapper">
-                    <input type="text" placeholder="{{'REGISTRY.EDIT.RECORD_ADDRESS_HELP'|translate}}" ng-model="formData.address">
-                  </label>
-                  <button class="button button-small button-positive" ng-click="localize()" ng-if="location.enable">
-                    <i class="icon ion-android-locate"></i>
-                  </button>
-                </div>
-              </div>
-              <div class="item item-input item-floating-label" ng-if="!location.enable">
-                <span class="input-label" translate>REGISTRY.EDIT.RECORD_ADDRESS</span>
-                <textarea placeholder="{{'REGISTRY.EDIT.RECORD_ADDRESS_HELP' | translate}}"
-                          ng-model="formData.address"
-                          ng-model-options="{ debounce: 350 }"
-                          rows="4" cols="10">
-                </textarea>
-              </div>
-
-              <div class="item item-input item-floating-label">
-                <span class="input-label" translate>REGISTRY.EDIT.RECORD_CITY</span>
-                <textarea placeholder="{{'REGISTRY.EDIT.RECORD_CITY_HELP'|translate}}" ng-model="formData.city"></textarea>
-              </div>
+              <!-- position -->
+              <ng-include src="'plugins/es/templates/common/edit_position.html'"></ng-include>
 
               <!-- social networks -->
-              <ng-include src="'plugins/es/templates/common/edit_socials.html'"></ng-include>
+              <ng-include src="'plugins/es/templates/common/edit_socials.html'" ng-controller="ESSocialsEditCtrl"></ng-include>
 
               <div class="item item-divider" translate>REGISTRY.TECHNICAL_DIVIDER</div>
 
diff --git a/www/plugins/es/templates/registry/view_record.html b/www/plugins/es/templates/registry/view_record.html
index db62b5dff..a1ef5ddcd 100644
--- a/www/plugins/es/templates/registry/view_record.html
+++ b/www/plugins/es/templates/registry/view_record.html
@@ -15,8 +15,8 @@
   <ion-content scroll="true">
     <div class="positive-900-bg hero">
       <div class="content" ng-if="!loading">
-        <i class="avatar cion-page-{{formData.type}}" ng-if="!formData.thumbnail"></i>
-        <i class="avatar" style="background-image: url({{::formData.thumbnail.src}})" ng-if="formData.thumbnail"></i>
+        <i class="avatar cion-page-{{formData.type}}" ng-if="!formData.avatar"></i>
+        <i class="avatar" ng-style="{{avatarStyle}}" ng-if="formData.avatar"></i>
         <h3 ng-bind-html="formData.title"></h3>
         <h4>&nbsp;</h4>
       </div>
diff --git a/www/plugins/es/templates/user/edit_profile.html b/www/plugins/es/templates/user/edit_profile.html
index a4524098e..5a49ea3cc 100644
--- a/www/plugins/es/templates/user/edit_profile.html
+++ b/www/plugins/es/templates/user/edit_profile.html
@@ -92,110 +92,11 @@
               </textarea>
             </ion-item>
 
-            <!-- address -->
-            <ion-item class="item-input item-floating-label item-button-right">
-              <span class="input-label">{{'PROFILE.ADDRESS' | translate}}</span>
-              <textarea placeholder="{{'PROFILE.ADDRESS_HELP' | translate}}"
-                        ng-model="formData.address"
-                        ng-model-options="{ debounce: 350 }"
-                        rows="4" cols="10">
-              </textarea>
-            </ion-item>
-
-            <!-- city -->
-            <div class="item item-input item-floating-label">
-              <span class="input-label" translate>PROFILE.CITY</span>
-              <input type="text" placeholder="{{'PROFILE.CITY_HELP'|translate}}"
-                     ng-model="formData.city"
-                     ng-model-options="{ updateOn: 'blur' }"
-                     ng-change="onCityChanged()">
-            </div>
-
-            <!-- Position (lat/lon) -->
-            <div class="row responsive-md responsive-sm no-padding">
-
-              <!-- lat -->
-              <div class="col no-padding">
-                <label class="item item-input item-floating-label"
-                       ng-class="{'item-input-error': form.$submitted && form.latitude.$invalid}">
-                  <span class="input-label" translate>PROFILE.LATITUDE</span>
-                  <input class="no-padding-right"
-                         name="latitude"
-                         type="number" placeholder="{{'PROFILE.LATITUDE_HELP'|translate}}"
-                         ng-model="formData.geoPoint.lat"
-                         ng-model-options="{ debounce: 350 }"
-                         ng-change="onFormDataChanged()"
-                         min="-90" max="90">
-                </label>
-                <div class="form-errors"
-                     ng-show="form.$submitted && form.latitude.$error"
-                     ng-messages="form.latitude.$error">
-                  <div class="form-error" ng-message="min">
-                    <span translate="ERROR.FIELD_MIN" translate-values="{min: -90}"></span>
-                  </div>
-                  <div class="form-error" ng-message="max">
-                    <span translate="ERROR.FIELD_MAX" translate-values="{max: 90}"></span>
-                  </div>
-                </div>
-              </div>
-
-              <div class="col-10 no-padding hidden-xs">
-                &nbsp;
-              </div>
-
-              <!-- lon -->
-              <div class="col no-padding">
-                <label class="item item-input item-floating-label"
-                       ng-class="{'item-input-error': form.$submitted && form.longitude.$invalid}">
-                  <span class="input-label" translate>PROFILE.LONGITUDE</span>
-                  <input class="no-padding-right"
-                         name="longitude"
-                         type="number" placeholder="{{'PROFILE.LONGITUDE_HELP'|translate}}"
-                         ng-model="formData.geoPoint.lon"
-                         ng-model-options="{ debounce: 350 }"
-                         ng-change="onFormDataChanged()"
-                         min="-180" max="180">
-                </label>
-                <div class="form-errors"
-                     ng-show="form.$submitted && form.longitude.$error"
-                     ng-messages="form.longitude.$error">
-                  <div class="form-error" ng-message="min">
-                    <span translate="ERROR.FIELD_MIN" translate-values="{min: -180}"></span>
-                  </div>
-                  <div class="form-error" ng-message="max">
-                    <span translate="ERROR.FIELD_MAX" translate-values="{max: 180}"></span>
-                  </div>
-                </div>
-              </div>
-
-              <div class="col col-40 no-padding">
-                <div class="item no-padding text-center">
-                  <span class="input-label"></span>
-
-                  <a class="button  button-stable button-small-padding icon ion-refresh"
-                     title="{{'PROFILE.BTN_GEOLOC_ADDRESS'|translate}}"
-                     ng-disabled="!formData.city"
-                     ng-click="localizeByAddress()">
-                  </a>
-
-                  <a class="button button-stable button-small-padding icon ion-android-locate"
-                     title="{{'PROFILE.BTN_GEOLOC_ME'|translate}}"
-                     ng-click="localizeMe()">
-                  </a>
-
-                  <a class="button button-stable button-small-padding icon ion-close"
-                     title="{{'PROFILE.BTN_REMOVE_GEOLOC'|translate}}"
-                     ng-disabled="!formData.geoPoint || (!formData.geoPoint.lat && !formData.geoPoint.lon)"
-                     ng-click="removeLocalisation()">
-                  </a>
-                </div>
-              </div>
-            </div>
-
-            <cs-extension-point name="after-position"></cs-extension-point>
+            <!-- position -->
+            <ng-include src="'plugins/es/templates/common/edit_position.html'" ng-controller="ESPositionEditCtrl"></ng-include>
 
             <!-- social networks -->
-            <ng-include src="'plugins/es/templates/common/edit_socials.html'"></ng-include>
+            <ng-include src="'plugins/es/templates/common/edit_socials.html'" ng-controller="ESSocialsEditCtrl"></ng-include>
 
             <div class="item item-divider">
               {{'PROFILE.TECHNICAL_DIVIDER' | translate}}
diff --git a/www/plugins/es/templates/user/modal_edit_avatar.html b/www/plugins/es/templates/user/modal_edit_avatar.html
index 3c7e84d12..46c9cd0ec 100644
--- a/www/plugins/es/templates/user/modal_edit_avatar.html
+++ b/www/plugins/es/templates/user/modal_edit_avatar.html
@@ -1,28 +1,27 @@
 <ion-modal-view>
   <ion-header-bar class="bar-positive">
     <button class="button button-clear visible-xs visible-sm" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button>
-    <h1 class="title" translate>PROFILE.MODAL_AVATAR.TITLE</h1>
-
-    <div class="buttons buttons-right">
-      <div class="secondary-buttons">
-        <button class="button button-icon button-clear ion-ios-arrow-right visible-xs visible-sm"
-                ng-if="imageCropStep == 2"
-                ng-click="doCrop()">
-        </button>
-        <button class="button button-icon button-clear ion-android-done visible-xs visible-sm"
-                ng-if="imageCropStep == 3"
-                ng-click="closeModal(result)">
-        </button>
-      </div>
-    </div>
 
+    <h1 class="title" translate>PROFILE.MODAL_AVATAR.TITLE</h1>
 
+    <button class="button button-clear icon-right visible-xs"
+            ng-click="doCrop()"
+            ng-disabled="formData.imageCropStep == 1"
+            ng-if="formData.imageCropStep <= 2">
+      <span translate>COMMON.BTN_NEXT</span>
+      <i class="icon ion-ios-arrow-right"></i>
+    </button>
+    <button class="button button-clear icon-right visible-xs"
+            ng-click="closeModal(formData.result)"
+            ng-if="formData.imageCropStep == 3">
+      <i class="icon ion-android-done"></i>
+    </button>
   </ion-header-bar>
 
   <ion-content class="modal-avatar padding">
 
 
-    <div ng-show="imageCropStep == 1">
+    <div ng-show="formData.imageCropStep == 1">
       <p translate>PROFILE.MODAL_AVATAR.SELECT_FILE_HELP</p>
 
       <!-- Add picture button -->
@@ -37,7 +36,7 @@
              style="visibility:hidden; position:absolute;"/>
     </div>
 
-    <div ng-show="imageCropStep == 2">
+    <div ng-show="formData.imageCropStep == 2">
       <p translate>PROFILE.MODAL_AVATAR.RESIZE_HELP</p>
 
       <!-- <image-crop
@@ -58,23 +57,23 @@
           data-height="100"
           data-width="100"
           data-shape="circle"
-          data-step="imageCropStep"
-          src="imgSrc"
-          data-result="result"
-          data-result-blob="resultBlob"
-          crop="initCrop"
+          data-step="formData.imageCropStep"
+          src="formData.imgSrc"
+          data-result="formData.result"
+          data-result-blob="formData.resultBlob"
+          crop="formData.initCrop"
           padding="150"
-          max-size="1024"
-        ></image-crop>
+          max-size="1024">
+        </image-crop>
       </div>
     </div>
 
-    <div ng-show="imageCropStep == 3">
+    <div ng-show="formData.imageCropStep == 3">
       <p translate>PROFILE.MODAL_AVATAR.RESULT_HELP</p>
 
       <div class="item card padding hero" style="height: 110px;">
         <div class="content">
-          <img class="avatar" ng-src="{{result}}" style="height: 88px; width: 88px;">
+          <img class="avatar" ng-src="{{formData.result}}" style="height: 88px; width: 88px;">
         </div>
       </div>
     </div>
@@ -85,12 +84,12 @@
         COMMON.BTN_CANCEL
       </button>
       <button class="button button-calm icon-right ion-chevron-right ink" ng-click="doCrop()" translate
-              ng-disabled="imageCropStep == 1"
-              ng-if="imageCropStep <= 2">
+              ng-disabled="formData.imageCropStep == 1"
+              ng-if="formData.imageCropStep <= 2">
         COMMON.BTN_NEXT
       </button>
-      <button class="button button-positive ink" ng-click="closeModal(result)" translate
-              ng-if="imageCropStep == 3">
+      <button class="button button-positive ink" ng-click="closeModal(formData.result)" translate
+              ng-if="formData.imageCropStep == 3">
         COMMON.BTN_CONTINUE
       </button>
     </div>
-- 
GitLab