diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js index b2660b91dc7f4c13d608cdc1b87cade321df9746..f6f0c3f33e8270f31a2e260948043978e73c91a4 100644 --- a/www/js/controllers/wot-controllers.js +++ b/www/js/controllers/wot-controllers.js @@ -5,7 +5,7 @@ angular.module('cesium.wot.controllers', ['cesium.services']) $stateProvider .state('app.wot_lookup', { - url: "/wot?q&newcomers&pendings", + url: "/wot?q&type&hash", views: { 'menuContent': { templateUrl: "templates/wot/lookup.html", @@ -86,7 +86,7 @@ angular.module('cesium.wot.controllers', ['cesium.services']) ; -function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, +function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $ionicHistory, UIUtils, csConfig, csSettings, Device, BMA, csWallet, csWot) { 'ngInject'; @@ -105,7 +105,13 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $scope.$on('$ionicView.enter', function(e, state) { if (!$scope.entered) { if (state.stateParams && state.stateParams.q) { // Query parameter - $scope.search.text=state.stateParams.q; + $scope.search.text = state.stateParams.q; + $timeout(function() { + $scope.doSearch(); + }, 100); + } + else if (state.stateParams && state.stateParams.hash) { // hash tag parameter + $scope.search.text = '#' + state.stateParams.hash; $timeout(function() { $scope.doSearch(); }, 100); @@ -113,10 +119,10 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, else { $timeout(function() { // get new comers - if (!csConfig.initPhase || state.stateParams.newcomers) { + if (!csConfig.initPhase || state.stateParams.type == 'newcomers') { $scope.doGetNewcomers(0, state.stateParams.newcomers); } - else { + else if (csConfig.initPhase || state.stateParams.type == 'pending') { $scope.doGetPending(0, state.stateParams.pendings); } }, 100); @@ -141,6 +147,47 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, }; }; + $scope.doRefreshLocationHref = function() { + + var stateParams = { + q: undefined, + hash: undefined, + type: undefined + }; + + if ($scope.search.type == 'text') { + var text = $scope.search.text.trim(); + if (text.match(/^#\w+$/)) { + stateParams.hash = text.substr(1); + } + else { + stateParams.q = text; + } + } + else { + stateParams.type = $scope.search.type; + } + + // Update location href + $ionicHistory.nextViewOptions({ + disableAnimate: true, + disableBack: true, + historyRoot: true + }); + $state.go('app.wot_lookup', stateParams, + { + reload: false, + inherit: true, + notify: false + }); + }; + + $scope.doSearchText = function() { + + $scope.doSearch(); + $scope.doRefreshLocationHref(); + }; + $scope.doSearch = function() { $scope.search.loading = true; var text = $scope.search.text.trim(); @@ -152,18 +199,18 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, else { $scope.search.type = 'text'; csWot.search(text) - .then(function(idties){ - if ($scope.search.type != 'text') return; // could have change - if ($scope.search.text.trim() !== text) return; // search text has changed before received response + .then(function(idties){ + if ($scope.search.type != 'text') return; // could have change + if ($scope.search.text.trim() !== text) return; // search text has changed before received response - if ((!idties || !idties.length) && BMA.regex.PUBKEY.test(text)) { - $scope.doDisplayResult([{pubkey: text}]); - } - else { - $scope.doDisplayResult(idties); - } - }) - .catch(UIUtils.onError('ERROR.WOT_LOOKUP_FAILED')); + if ((!idties || !idties.length) && BMA.regex.PUBKEY.test(text)) { + $scope.doDisplayResult([{pubkey: text}]); + } + else { + $scope.doDisplayResult(idties); + } + }) + .catch(UIUtils.onError('ERROR.WOT_LOOKUP_FAILED')); } }; @@ -176,7 +223,7 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $scope.search.loading = (offset === 0); $scope.search.type = 'newcomers'; - return csWot.newcomers(offset, size) + csWot.newcomers(offset, size) .then(function(idties){ if ($scope.search.type != 'newcomers') return false; // could have change $scope.doDisplayResult(idties, offset, size); @@ -188,6 +235,9 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $scope.search.hasMore = false; UIUtils.onError('ERROR.LOAD_NEWCOMERS_FAILED')(err); }); + + // Update location href + $scope.doRefreshLocationHref(); }; $scope.doGetPending = function(offset, size) { @@ -203,7 +253,7 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, csWot.all : csWot.pending; - return searchFunction(offset, size) + searchFunction(offset, size) .then(function(idties){ if ($scope.search.type != 'pending') return false; // could have change $scope.doDisplayResult(idties, offset, size); @@ -217,6 +267,9 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $scope.search.hasMore = false; UIUtils.onError('ERROR.LOAD_PENDING_FAILED')(err); }); + + // Update location href + $scope.doRefreshLocationHref(); }; $scope.showMore = function() { diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js index a587ac433294911349d3c1d02fca36d5da1b547d..b32a96b5247260b3b070ba3e318a95acf856bb3e 100644 --- a/www/js/services/wot-services.js +++ b/www/js/services/wot-services.js @@ -560,12 +560,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic }) ]) .then(function() { - if (!data.requirements.uid) - return api.data.raisePromise.load(data) - .catch(function(err) { - console.debug('Error while loading identity data, on extension point.'); - console.error(err); - }); + if (!data.requirements.uid) return; var idtyFullKey = data.requirements.uid + '-' + data.requirements.meta.timestamp; @@ -596,8 +591,10 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic ]); }) .then(function() { - // Add compute some additional requirements (that required all data like certifications) - finishLoadRequirements(data); + if (data.requirements.uid) { + // Add compute some additional requirements (that required all data like certifications) + finishLoadRequirements(data); + } // API extension return api.data.raisePromise.load(data) @@ -619,35 +616,54 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic return $q.when(undefined); } + // Remove first special characters (to avoid request error) + var safeText = text.replace(/(^|\s)#\w+/g, ''); // remove tags + safeText = safeText.replace(/[^a-zA-Z0-9_-\s]+/g, ''); + safeText = safeText.replace(/\s+/g, ' ').trim(); + options = options || {}; options.addUniqueId = angular.isDefined(options.addUniqueId) ? options.addUniqueId : true; options.allowExtension = angular.isDefined(options.allowExtension) ? options.allowExtension : true; - return BMA.wot.lookup({ search: text }) - .then(function(res){ - return res.results.reduce(function(idties, res) { - return idties.concat(res.uids.reduce(function(uids, idty) { - var blocUid = idty.meta.timestamp.split('-', 2); - if (!idty.revoked) { - return uids.concat({ - uid: idty.uid, - pubkey: res.pubkey, - number: blocUid[0], - hash: blocUid[1] - }); - } - return uids; - }, [])); - }, []); - }) - .catch(function(err) { - if (err && err.ucode == BMA.errorCodes.NO_MATCHING_IDENTITY) { - return []; - } - else { - throw err; - } - }) + var promise; + if (!safeText) { + promise = $q.when([]); + } + else { + promise = $q.all( + safeText.split(' ').reduce(function(res, text) { + console.debug('[wot] Will search on: \'' + text + '\''); + return res.concat(BMA.wot.lookup({ search: text })); + }, []) + ).then(function(res){ + return res.reduce(function(idties, res) { + return idties.concat(res.results.reduce(function(idties, res) { + return idties.concat(res.uids.reduce(function(uids, idty) { + var blocUid = idty.meta.timestamp.split('-', 2); + if (!idty.revoked) { + return uids.concat({ + uid: idty.uid, + pubkey: res.pubkey, + number: blocUid[0], + hash: blocUid[1] + }); + } + return uids; + }, [])); + }, [])); + }, []); + }) + .catch(function(err) { + if (err && err.ucode == BMA.errorCodes.NO_MATCHING_IDENTITY) { + return []; + } + else { + throw err; + } + }); + } + + return promise .then(function(idties) { if (!options.allowExtension) { // Add unique id (if enable) diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js index c32d58f5949cafaa56a28c3e9972469a38c5d467..148b281d39ca2f83f7397e521761142e500e3a2b 100644 --- a/www/plugins/es/js/services/http-services.js +++ b/www/plugins/es/js/services/http-services.js @@ -11,12 +11,14 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces var that, regex = { - IMAGE_SRC: exact("data:([A-Za-z//]+);base64,(.+)") + IMAGE_SRC: exact('data:([A-Za-z//]+);base64,(.+)'), + HASH_TAG: new RegExp('#(\\w+)'), + USER_TAG: new RegExp('@(\\w+)') }; function exact(regexpContent) { - return new RegExp("^" + regexpContent + "$"); + return new RegExp('^' + regexpContent + '$'); } // Get time (UTC) @@ -33,6 +35,30 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces return csHttp.post(host, node, path); } + function parseTagsFromText(value) { + var matches = value && regex.HASH_TAG.exec(value); + var tags; + while(matches) { + var tag = matches[1]; + tags = tags || []; + if (!_.contains(tags, tag)) { + tags.push(tag); + } + value = value.substr(matches.index + matches[1].length + 1); + matches = value && regex.HASH_TAG.exec(value); + } + return tags; + } + + function fillRecordTags(record, fieldNames) { + fieldNames = fieldNames || ['title', 'description']; + + _.forEach(fieldNames, function(fieldName) { + var value = record[fieldName]; + record.tags = parseTagsFromText(value); + }); + } + function postRecord(host, node, path) { var that = this; that.raw = that.raw || {}; @@ -55,6 +81,10 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces delete obj.signature; delete obj.hash; obj.issuer = $rootScope.walletData.pubkey; + + // Fill tags + fillRecordTags(obj); + var str = JSON.stringify(obj); return CryptoUtils.util.hash(str) @@ -244,11 +274,14 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces toAttachment: imageToAttachment }, auth: { - login: login, - logout: logout + login: login, + logout: logout }, hit: { - empty: emptyHit + empty: emptyHit + }, + util: { + parseTags: parseTagsFromText }, date: { now: getTimeNow diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js index 62113c7ff7c0eef528a2998a038a4262f645f043..2fbd34e0518ba474b70ffebcc365c3cc91727280 100644 --- a/www/plugins/es/js/services/user-services.js +++ b/www/plugins/es/js/services/user-services.js @@ -10,7 +10,8 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se }) -.factory('esUser', function($rootScope, $q, $timeout, esHttp, csConfig, csSettings, csWallet, csWot, UIUtils, BMA, CryptoUtils, Device, Api) { +.factory('esUser', function($rootScope, $q, $timeout, esHttp, $state, $sce, $sanitize, + csConfig, csSettings, esHttp, CryptoUtils, Device, UIUtils, csWallet, csWot, BMA) { 'ngInject'; function factory(id, host, port, wsPort) { @@ -21,7 +22,8 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se ES_USER_API_ENDPOINT: "ES_USER_API( ([a-z_][a-z0-9-_.]*))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))" }, REGEX = { - ES_USER_API_ENDPOINT: exact(CONSTANTS.ES_USER_API_ENDPOINT) + ES_USER_API_ENDPOINT: exact(CONSTANTS.ES_USER_API_ENDPOINT), + HASH_TAG: new RegExp('#(\\w+)') }, SETTINGS_SAVE_SPEC = { includes: ['locale', 'showUDHistory', 'useRelative', 'useLocalStorage', 'expertMode'], @@ -132,6 +134,18 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se // avatar profile.avatar = esHttp.image.fromHit(host, port, res, 'avatar'); delete profile.source.avatar; // not need anymore + + // Replace tags in description + if (profile.source.description) { + var description = $sanitize(profile.source.description.trim().replace(/\n/g,'<br>')); + _.forEach(esHttp.util.parseTags(description), function(tag){ + var href = $state.href('app.wot_lookup', {hash: tag}); + var link = '<a href=\"{0}">{1}</a>'.format(href, '#'+tag); + description = description.replace('#'+tag, link); + }); + $sce.trustAsHtml(description); + profile.source.description = description; + } } return profile; }) @@ -165,7 +179,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se } })]) .then(function(res) { - boxKeypair = res[0]; + var boxKeypair = res[0]; res = res[1]; if (!res || !res._source) { return; @@ -285,6 +299,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se return deferred.promise; } + function onWotSearch(text, datas, pubkeyAtributeName, deferred) { deferred = deferred || $q.defer(); if (!text && (!datas || !datas.length)) { @@ -297,7 +312,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se var dataByPubkey; var request = { query: {}, - highlight: {fields : {title : {}}}, + highlight: {fields : {title : {}, tags: {}}}, from: 0, size: 100, _source: ["title", "avatar._content_type"] @@ -336,21 +351,35 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se {terms : {_id : pubkeys}}, {bool: { must: [ - {match: {title: text}}, + {match: {title: {query: text, boost: 2}}}, {prefix: {title: text}} ]} } ]}} }; + + // Add tags + var tags = esHttp.util.parseTags(text); + if (tags) { + request.query.constant_score.filter.bool.should.push({terms: {tags: tags}}); + } } } else if (text){ request.query.bool = { should: [ - {match: {title: text}}, + {match: {title: { + query: text, + boost: 2 + }}}, {prefix: {title: text}} ] }; + // Set tags filter + var tags = esHttp.util.parseTags(text); + if (tags) { + request.query.bool.should.push({terms: {tags: tags}}); + } } else { // nothing to search: stop here @@ -392,6 +421,9 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se if (hit.highlight.title) { data.name = hit.highlight.title[0]; } + if (hit.highlight.tags) { + data.tags = hit.highlight.tags; + } } // avatar data.avatar=avatar; diff --git a/www/plugins/es/templates/menu_extend.html b/www/plugins/es/templates/menu_extend.html index a0561b5f31dfd45dee2ce695cdf5d783461254f4..b1517ac231a84ad49a64dee9f55fc92b17a08b42 100644 --- a/www/plugins/es/templates/menu_extend.html +++ b/www/plugins/es/templates/menu_extend.html @@ -13,8 +13,19 @@ class="badge badge-button badge-positive">{{$root.walletData.messages.unreadCount}}</span> </button> + <!-- wot event --> + <button class="button button-clear icon ion-person-stalker hidden-xs hidden-sm" + ng-if="login" + active-link="gray" + active-link-path-prefix="#/app/groups" + ng-click="showGroupsPopover($event)"> + <span + ng-if="$root.walletData.groups.unreadCount" + class="badge badge-button badge-positive">{{$root.walletData.groups.unreadCount}}</span> + </button> + <!-- notifications --> - <button class="button button-clear hidden-xs hidden-sm icon ion-android-notifications" + <button class="button button-clear icon ion-android-notifications hidden-xs hidden-sm" ng-if="login" active-link="gray" active-link-path-prefix="#/app/notifications" @@ -24,6 +35,8 @@ class="badge badge-button badge-positive">{{$root.walletData.notifications.unreadCount}}</span> </button> + + </ng-if> <!-- Main section --> diff --git a/www/plugins/es/templates/user/items_profile.html b/www/plugins/es/templates/user/items_profile.html index 93b7585457ef4d6b4632c52c3163cb82d084776c..e1bd076a9d0809f04e604ac907e2716bdaa35bac 100644 --- a/www/plugins/es/templates/user/items_profile.html +++ b/www/plugins/es/templates/user/items_profile.html @@ -10,8 +10,8 @@ <!-- About me --> <div class="item" ng-if="formData.profile.description"> <span class="gray" translate>PROFILE.DESCRIPTION</span> - <h3 class=" text-keep-lines">{{formData.profile.description}} - </h3> + <!--<h3 class=" text-keep-lines" trust-as-html="formData.profile.description"></h3>--> + <h3 class=" text-keep-lines" ng-bind-html="formData.profile.description"></h3> </div> <!-- Localisation --> diff --git a/www/templates/wallet/popover_actions.html b/www/templates/wallet/popover_actions.html index 3231e717370dbbace9fef25144b9b4be8ef3e6fa..e8c1487c26544b44bec411b6034fe765ca9be999 100644 --- a/www/templates/wallet/popover_actions.html +++ b/www/templates/wallet/popover_actions.html @@ -11,6 +11,14 @@ {{'COMMON.BTN_SHARE' | translate}} </a> + <!-- identity --> + <a class="item item-icon-left ink" + ng-if="walletData.requirements.needSelf" + ng-click="self()"> + <i class="icon ion-flag"></i> + {{'ACCOUNT.BTN_SEND_IDENTITY_DOTS' | translate}} + </a> + <!-- membership in --> <a class="item item-icon-left ink visible-xs visible-sm" ng-if="walletData.requirements.needMembership" @@ -46,13 +54,6 @@ {{'ACCOUNT.BTN_MEMBERSHIP_OUT_DOTS' | translate}} </a> - <a class="item item-icon-left ink" - ng-if="walletData.requirements.needSelf" - ng-click="self()"> - <i class="icon ion-flag"></i> - {{'ACCOUNT.BTN_SEND_IDENTITY_DOTS' | translate}} - </a> - <a class="item item-icon-left ink" ng-click="showSecurityModal()"> <i class="icon ion-android-lock"></i> diff --git a/www/templates/wot/lookup_form.html b/www/templates/wot/lookup_form.html index d5f839d0e061765d04b8ac6893bddff4dd1b707b..822b803562dde67c7888c69f7689cc685f35dcda 100644 --- a/www/templates/wot/lookup_form.html +++ b/www/templates/wot/lookup_form.html @@ -11,7 +11,7 @@ class="hidden-xs hidden-sm" id="{{wotSearchTextId}}" placeholder="{{'WOT.SEARCH_HELP'|translate}}" ng-model="search.text" - on-return="doSearch()"> + on-return="doSearchText()"> <div class="helptip-anchor-center"> <a id="helptip-wot-search-text"></a> </div> @@ -106,12 +106,15 @@ {{::identity.pubkey | formatPubkey}} <span ng-if="!identity.uid" class="assertive" translate>WOT.NOT_MEMBER_PARENTHESIS</span> </h4> - <h4 ng-if="identity.events"> + <h4 ng-if="identity.events||identity.tags"> <span ng-repeat="event in identity.events" class="assertive"> <i class="ion-alert-circled " ng-if="!identity.valid"></i> <span ng-bind-html="event.message|translate:event.messageParams"></span> </span> + <span ng-repeat="tag in identity.tags" class="dark"> + #<ng-bind-html ng-bind-html="tag"></ng-bind-html> + </ng-repeat> </h4> <i class="icon ion-ios-arrow-right "></i> <ion-option-button class="button-positive" ng-click="showTransferModal({pubkey: identity.pubkey, uid: identity.name ||identity.uid})" translate>COMMON.BTN_SEND_MONEY_SHORT</ion-option-button>