Browse Source

Supported mentions

master
Igor Zhukov 9 years ago
parent
commit
d8acf048d2
  1. 15
      app/css/app.css
  2. 34
      app/js/controllers.js
  3. 8
      app/js/directives.js
  4. 179
      app/js/message_composer.js
  5. 9
      app/js/services.js
  6. 2
      app/partials/desktop/im.html

15
app/css/app.css

@ -2190,7 +2190,7 @@ a.composer_emoji_btn:hover { @@ -2190,7 +2190,7 @@ a.composer_emoji_btn:hover {
color: #52719a;
}
.composer_dropdown li a:hover,
.composer_dropdown li a.composer_emoji_option_active {
.composer_dropdown li a.composer_autocomplete_option_active {
color: #52719a;
background: #f2f6fa;
}
@ -2200,6 +2200,19 @@ a.composer_emoji_btn:hover { @@ -2200,6 +2200,19 @@ a.composer_emoji_btn:hover {
margin-left: 15px;
line-height: 20px;
}
.composer_mention_option {
line-height: 20px;
}
.composer_user_mention {
color: #808080;
margin-left: 10px;
}
.composer_dropdown li a:hover .composer_user_mention,
.composer_dropdown li a.composer_autocomplete_option_active .composer_user_mention {
color: #698192;
}
.composer_sticker_btn {
width: 67px;
height: 67px;

34
app/js/controllers.js

@ -1519,7 +1519,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -1519,7 +1519,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('user_update', angular.noop);
})
.controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppPeersManager, AppDocsManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
.controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
$scope.$watch('curDialog.peer', resetDraft);
$scope.$on('user_update', angular.noop);
@ -1529,6 +1529,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -1529,6 +1529,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('ui_typing', onTyping);
$scope.draftMessage = {text: '', send: sendMessage, replyClear: replyClear};
$scope.mentions = {};
$scope.$watch('draftMessage.text', onMessageChange);
$scope.$watch('draftMessage.files', onFilesSelected);
$scope.$watch('draftMessage.sticker', onStickerSelected);
@ -1573,8 +1574,38 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -1573,8 +1574,38 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return cancelEvent(e);
}
function updateMentions () {
var peerID = $scope.curDialog.peerID;
if (!peerID || peerID > 0) {
safeReplaceObject($scope.mentions, {});
$scope.$broadcast('mentions_update');
return;
}
AppChatsManager.getChatFull(-peerID).then(function (chatFull) {
var participantsVector = (chatFull.participants || {}).participants || [];
var mentionUsers = [];
var mentionIndex = SearchIndexManager.createIndex();
angular.forEach(participantsVector, function (participant) {
var user = AppUsersManager.getUser(participant.user_id);
if (user.username) {
mentionUsers.push(user);
SearchIndexManager.indexObject(user.id, AppUsersManager.getUserSearchText(user.id), mentionIndex);
}
});
safeReplaceObject($scope.mentions, {
users: mentionUsers,
index: mentionIndex
});
$scope.$broadcast('mentions_update');
});
}
function resetDraft (newPeer) {
updateMentions();
replyClear();
if (newPeer) {
@ -1651,7 +1682,6 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -1651,7 +1682,6 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var doc = AppDocsManager.getDoc(newVal);
if (doc.id && doc.access_hash) {
console.log('sticker', doc);
var inputMedia = {
_: 'inputMediaDocument',
id: {

8
app/js/directives.js

@ -1150,7 +1150,8 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1150,7 +1150,8 @@ angular.module('myApp.directives', ['myApp.filters'])
return {
link: link,
scope: {
draftMessage: '='
draftMessage: '=',
mentions: '='
}
};
@ -1201,6 +1202,7 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1201,6 +1202,7 @@ angular.module('myApp.directives', ['myApp.filters'])
getSendOnEnter: function () {
return sendOnEnter;
},
mentions: $scope.mentions,
onMessageSubmit: onMessageSubmit,
onFilePaste: onFilePaste
});
@ -1300,6 +1302,10 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1300,6 +1302,10 @@ angular.module('myApp.directives', ['myApp.filters'])
})
});
$scope.$on('mentions_update', function () {
composer.onMentionsUpdated();
});
var sendAwaiting = false;
$scope.$on('ui_message_before_send', function () {
sendAwaiting = true;

179
app/js/message_composer.js

@ -234,7 +234,7 @@ EmojiTooltip.prototype.createTooltip = function () { @@ -234,7 +234,7 @@ EmojiTooltip.prototype.createTooltip = function () {
this.contentEl.on('mousedown', function (e) {
e = e.originalEvent || e;
var target = $(e.target), code, sticker;
if (target.hasClass('emoji') || target.hasClass('composer_sticker_image')) {
if (target[0].tagName != 'A') {
target = $(target[0].parentNode);
}
if (code = target.attr('data-code')) {
@ -382,7 +382,7 @@ function EmojiPanel (containerEl, options) { @@ -382,7 +382,7 @@ function EmojiPanel (containerEl, options) {
this.containerEl.on('mousedown', function (e) {
e = e.originalEvent || e;
var target = $(e.target), code;
if (target.hasClass('emoji')) {
if (target[0].tagName != 'A') {
target = $(target[0].parentNode);
}
if (code = target.attr('data-code')) {
@ -436,8 +436,8 @@ function MessageComposer (textarea, options) { @@ -436,8 +436,8 @@ function MessageComposer (textarea, options) {
var self = this;
this.autoCompleteEl.on('mousedown', function (e) {
e = e.originalEvent || e;
var target = $(e.target), code;
if (target.hasClass('emoji') || target.hasClass('composer_emoji_shortcut')) {
var target = $(e.target), mention, code;
if (target[0].tagName != 'A') {
target = $(target[0].parentNode);
}
if (code = target.attr('data-code')) {
@ -446,6 +446,11 @@ function MessageComposer (textarea, options) { @@ -446,6 +446,11 @@ function MessageComposer (textarea, options) {
}
EmojiHelper.pushPopularEmoji(code);
}
if (mention = target.attr('data-mention')) {
if (self.onMentionSelected) {
self.onMentionSelected(mention);
}
}
return cancelEvent(e);
});
@ -455,6 +460,7 @@ function MessageComposer (textarea, options) { @@ -455,6 +460,7 @@ function MessageComposer (textarea, options) {
this.onMessageSubmit = options.onMessageSubmit;
this.getSendOnEnter = options.getSendOnEnter;
this.onFilePaste = options.onFilePaste;
this.mentions = options.mentions;
}
MessageComposer.prototype.setUpInput = function () {
@ -463,6 +469,7 @@ MessageComposer.prototype.setUpInput = function () { @@ -463,6 +469,7 @@ MessageComposer.prototype.setUpInput = function () {
} else {
this.setUpPlaintext();
}
this.autoCompleteRegEx = /(?:\s|^)(:|@)([A-Za-z0-9\-\+\*_]*)$/;
}
MessageComposer.prototype.setUpRich = function () {
@ -530,34 +537,39 @@ MessageComposer.prototype.onKeyEvent = function (e) { @@ -530,34 +537,39 @@ MessageComposer.prototype.onKeyEvent = function (e) {
if (this.autocompleteShown) {
if (e.keyCode == 38 || e.keyCode == 40) { // UP / DOWN
var next = e.keyCode == 40;
var currentSelected = $(this.autoCompleteEl).find('.composer_emoji_option_active');
var currentSelected = $(this.autoCompleteEl).find('.composer_autocomplete_option_active');
if (currentSelected.length) {
var currentSelectedWrap = currentSelected[0].parentNode;
var nextWrap = currentSelectedWrap[next ? 'nextSibling' : 'previousSibling'];
currentSelected.removeClass('composer_emoji_option_active');
currentSelected.removeClass('composer_autocomplete_option_active');
if (nextWrap) {
$(nextWrap).find('a').addClass('composer_emoji_option_active');
$(nextWrap).find('a').addClass('composer_autocomplete_option_active');
return cancelEvent(e);
}
}
var childNodes = this.autoCompleteEl[0].childNodes;
var nextWrap = childNodes[next ? 0 : childNodes.length - 1];
$(nextWrap).find('a').addClass('composer_emoji_option_active');
$(nextWrap).find('a').addClass('composer_autocomplete_option_active');
return cancelEvent(e);
}
if (e.keyCode == 13) { // ENTER
var currentSelected = $(this.autoCompleteEl).find('.composer_emoji_option_active')/* ||
$(this.autoCompleteEl).childNodes[0].find('a')*/;
var code = currentSelected.attr('data-code');
if (code) {
var currentSelected = $(this.autoCompleteEl).find('.composer_autocomplete_option_active');
var code, mention;
if (code = currentSelected.attr('data-code')) {
this.onEmojiSelected(code, true);
EmojiHelper.pushPopularEmoji(code);
return cancelEvent(e);
}
if (mention = currentSelected.attr('data-mention')) {
if (this.onMentionSelected) {
this.onMentionSelected(mention);
return cancelEvent(e);
}
}
checkSubmit = true;
}
}
@ -638,38 +650,65 @@ MessageComposer.prototype.checkAutocomplete = function () { @@ -638,38 +650,65 @@ MessageComposer.prototype.checkAutocomplete = function () {
value = value.substr(0, pos);
var matches = value.match(/(?:\s|^):([A-Za-z0-9\-\+\*_]*)$/);
var matches = value.match(this.autoCompleteRegEx);
if (matches) {
if (this.previousQuery == matches[0]) {
return;
}
this.previousQuery = matches[0];
var query = SearchIndexManager.cleanSearchText(matches[1]);
EmojiHelper.getPopularEmoji((function (popular) {
if (query.length) {
var found = EmojiHelper.searchEmojis(query);
if (found.length) {
var popularFound = [],
code, pos;
for (var i = 0, len = popular.length; i < len; i++) {
code = popular[i].code;
pos = found.indexOf(code);
if (pos >= 0) {
popularFound.push(code);
found.splice(pos, 1);
if (!found.length) {
break;
}
var query = SearchIndexManager.cleanSearchText(matches[2]);
if (matches[1] == '@') { // mentions
if (this.mentions && this.mentions.index) {
if (query.length) {
var foundObject = SearchIndexManager.search(query, this.mentions.index);
var foundUsers = [];
var user;
for (var i = 0, length = this.mentions.users.length; i < length; i++) {
user = this.mentions.users[i];
if (foundObject[user.id]) {
foundUsers.push(user);
}
}
this.showEmojiSuggestions(popularFound.concat(found));
} else {
var foundUsers = this.mentions.users;
}
if (foundUsers.length) {
this.showMentionSuggestions(foundUsers);
} else {
this.hideSuggestions();
}
} else {
this.showEmojiSuggestions(popular);
this.hideSuggestions();
}
}).bind(this));
}
else { // emoji
EmojiHelper.getPopularEmoji((function (popular) {
if (query.length) {
var found = EmojiHelper.searchEmojis(query);
if (found.length) {
var popularFound = [],
code, pos;
for (var i = 0, len = popular.length; i < len; i++) {
code = popular[i].code;
pos = found.indexOf(code);
if (pos >= 0) {
popularFound.push(code);
found.splice(pos, 1);
if (!found.length) {
break;
}
}
}
this.showEmojiSuggestions(popularFound.concat(found));
} else {
this.hideSuggestions();
}
} else {
this.showEmojiSuggestions(popular);
}
}).bind(this));
}
}
else {
delete this.previousQuery;
@ -819,6 +858,65 @@ MessageComposer.prototype.onEmojiSelected = function (code, autocomplete) { @@ -819,6 +858,65 @@ MessageComposer.prototype.onEmojiSelected = function (code, autocomplete) {
this.onChange();
}
MessageComposer.prototype.onMentionsUpdated = function (username) {
delete this.previousQuery;
if (this.isActive) {
this.checkAutocomplete();
}
}
MessageComposer.prototype.onMentionSelected = function (username) {
if (this.richTextareaEl) {
var textarea = this.richTextareaEl[0];
if (!this.isActive) {
if (!this.restoreSelection()) {
setRichFocus(textarea);
}
}
var valueCaret = getRichValueWithCaret(textarea);
var fullValue = valueCaret[0];
var pos = valueCaret[1] >= 0 ? valueCaret[1] : fullValue.length;
var suffix = fullValue.substr(pos);
var prefix = fullValue.substr(0, pos);
var matches = prefix.match(/@([A-Za-z0-9\-\+\*_]*)$/);
var newValuePrefix;
if (matches && matches[0]) {
newValuePrefix = prefix.substr(0, matches.index) + '@' + username;
} else {
newValuePrefix = prefix + '@' + username;
}
textarea.value = newValue;
this.selId = (this.selId || 0) + 1;
var html = this.getRichHtml(newValuePrefix) + '&nbsp;<span id="composer_sel' + this.selId + '"></span>' + this.getRichHtml(suffix);
this.richTextareaEl.html(html);
setRichFocus(textarea, $('#composer_sel' + this.selId)[0]);
}
else {
var textarea = this.textareaEl[0];
var fullValue = textarea.value;
var pos = this.isActive ? getFieldSelection(textarea) : fullValue.length;
var suffix = fullValue.substr(pos);
var prefix = fullValue.substr(0, pos);
var matches = prefix.match(/@([A-Za-z0-9\-\+\*_]*)$/);
if (matches && matches[0]) {
var newValue = prefix.substr(0, matches.index) + '@' + username + ' ' + suffix;
var newPos = matches.index + username.length + 2;
} else {
var newValue = prefix + ':' + username + ': ' + suffix;
var newPos = prefix.length + username.length + 2;
}
textarea.value = newValue;
setFieldSelection(textarea, newPos);
}
this.hideSuggestions();
this.onChange();
}
MessageComposer.prototype.onChange = function (e) {
if (this.richTextareaEl) {
delete this.keyupStarted;
@ -900,6 +998,23 @@ MessageComposer.prototype.showEmojiSuggestions = function (codes) { @@ -900,6 +998,23 @@ MessageComposer.prototype.showEmojiSuggestions = function (codes) {
this.autocompleteShown = true;
}
MessageComposer.prototype.showMentionSuggestions = function (users) {
var html = [];
var user;
var count = Math.min(5, users.length);
var i;
for (i = 0; i < count; i++) {
user = users[i];
html.push('<li><a class="composer_mention_option" data-mention="' + user.username + '"><span class="composer_user_name">' + user.rFullName + '</span><span class="composer_user_mention">@' + user.username + '</span></a></li>');
}
this.autoCompleteEl.html(html.join(''));
this.autoCompleteEl.show();
this.updatePosition();
this.autocompleteShown = true;
}
MessageComposer.prototype.updatePosition = function () {
var offset = (this.richTextareaEl || this.textareaEl).offset();
var height = this.autoCompleteEl.outerHeight();

9
app/js/services.js

@ -111,7 +111,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -111,7 +111,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (apiUser.first_name) {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true});
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true});
apiUser.rFullName = apiUser.last_name ? RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}) : apiUser.rFirstName;
} else {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || _('user_first_name_deleted');
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || _('user_name_deleted');
@ -926,8 +926,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -926,8 +926,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
dialog.top_message > maxSeenID
) {
var message = getMessage(dialog.top_message);
var notifyPeer = message.flags & 16 ? message.from_id : peerID;
if (message.unread && !message.out) {
NotificationsManager.getPeerMuted(peerID).then(function (muted) {
NotificationsManager.getPeerMuted(notifyPeer).then(function (muted) {
if (!muted) {
Storage.get('notify_nopreview').then(function (no_preview) {
notifyAboutMessage(message, no_preview);
@ -1233,7 +1234,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1233,7 +1234,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
pts_count: affectedMessages.pts_count
}
});
return deletedMessageIDs;
return messageIDs;
});
}
@ -1386,8 +1387,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1386,8 +1387,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
function sendText(peerID, text, options) {
console.log(peerID, text, options);
if (!angular.isString(text) || !text.length) {
return;
}

2
app/partials/desktop/im.html

@ -162,7 +162,7 @@ @@ -162,7 +162,7 @@
</a>
<a class="pull-left im_panel_own_photo" my-peer-photolink="ownID" img-class="im_panel_own_photo" watch="true" ng-click="openSettings()" no-open="true"></a>
<form my-send-form draft-message="draftMessage" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length}">
<form my-send-form draft-message="draftMessage" mentions="mentions" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length}">
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMessage != null">
<a class="im_send_reply_cancel" ng-click="draftMessage.replyClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>

Loading…
Cancel
Save