Browse Source

Commands autocomplete draft

master
Igor Zhukov 10 years ago
parent
commit
fa3e97c6a4
  1. 47
      app/js/controllers.js
  2. 8
      app/js/directives.js
  3. 99
      app/js/message_composer.js
  4. 32
      app/js/services.js
  5. 50
      app/less/app.less
  6. 10
      app/less/desktop.less
  7. 2
      app/partials/desktop/im.html

47
app/js/controllers.js

@ -1746,7 +1746,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('user_update', angular.noop); $scope.$on('user_update', angular.noop);
}) })
.controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager) { .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager) {
$scope.$watch('curDialog.peer', resetDraft); $scope.$watch('curDialog.peer', resetDraft);
$scope.$on('user_update', angular.noop); $scope.$on('user_update', angular.noop);
@ -1757,6 +1757,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.draftMessage = {text: '', send: sendMessage, replyClear: replyClear}; $scope.draftMessage = {text: '', send: sendMessage, replyClear: replyClear};
$scope.mentions = {}; $scope.mentions = {};
$scope.commands = {};
$scope.$watch('draftMessage.text', onMessageChange); $scope.$watch('draftMessage.text', onMessageChange);
$scope.$watch('draftMessage.files', onFilesSelected); $scope.$watch('draftMessage.files', onFilesSelected);
$scope.$watch('draftMessage.sticker', onStickerSelected); $scope.$watch('draftMessage.sticker', onStickerSelected);
@ -1831,8 +1832,52 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
} }
function updateCommands () {
var peerID = $scope.curDialog.peerID;
AppProfileManager.getPeerBots(peerID).then(function (peerBots) {
if (!peerBots.length) {
safeReplaceObject($scope.commands, {});
$scope.$broadcast('mentions_update');
return;
}
var needMentions = peerBots.length > 1;
var commandsList = [];
var commandsIndex = SearchIndexManager.createIndex();
angular.forEach(peerBots, function (peerBot) {
var mention = '';
if (needMentions) {
var bot = AppUsersManager.getUser(peerBot.id);
if (bot && bot.username) {
mention += '@' + bot.username;
}
}
var botSearchText = AppUsersManager.getUserSearchText(peerBot.id);
angular.forEach(peerBot.commands, function (description, command) {
var value = '/' + command + mention;
commandsList.push({
botID: peerBot.id,
value: value,
description: description
});
SearchIndexManager.indexObject(value, botSearchText + ' ' + command + ' ' + description, commandsIndex);
})
});
console.log(commandsList, commandsIndex);
safeReplaceObject($scope.commands, {
list: commandsList,
index: commandsIndex
});
$scope.$broadcast('mentions_update');
});
}
function resetDraft (newPeer) { function resetDraft (newPeer) {
updateMentions(); updateMentions();
updateCommands();
replyClear(); replyClear();
if (newPeer) { if (newPeer) {

8
app/js/directives.js

@ -1295,7 +1295,8 @@ angular.module('myApp.directives', ['myApp.filters'])
link: link, link: link,
scope: { scope: {
draftMessage: '=', draftMessage: '=',
mentions: '=' mentions: '=',
commands: '='
} }
}; };
@ -1363,8 +1364,8 @@ angular.module('myApp.directives', ['myApp.filters'])
getSendOnEnter: function () { getSendOnEnter: function () {
return sendOnEnter; return sendOnEnter;
}, },
getPeerImage: function (element, peerID) { getPeerImage: function (element, peerID, noReplace) {
if (cachedPeerPhotos[peerID]) { if (cachedPeerPhotos[peerID] && !noReplace) {
element.replaceWith(cachedPeerPhotos[peerID]); element.replaceWith(cachedPeerPhotos[peerID]);
return; return;
} }
@ -1376,6 +1377,7 @@ angular.module('myApp.directives', ['myApp.filters'])
}); });
}, },
mentions: $scope.mentions, mentions: $scope.mentions,
commands: $scope.commands,
onMessageSubmit: onMessageSubmit, onMessageSubmit: onMessageSubmit,
onFilePaste: onFilePaste onFilePaste: onFilePaste
}); });

99
app/js/message_composer.js

@ -487,7 +487,13 @@ function MessageComposer (textarea, options) {
this.setUpInput(); this.setUpInput();
this.autoCompleteEl = $('<ul class="composer_dropdown dropdown-menu"></ul>').appendTo(document.body); this.autoCompleteWrapEl = $('<div class="composer_dropdown_wrap"></div>').appendTo(document.body);
this.autoCompleteScrollerEl = $('<div class="composer_dropdown_scroller nano"></div>').appendTo(this.autoCompleteWrapEl);
this.autoCompleteEl = $('<ul class="composer_dropdown dropdown-menu nano-content"></ul>').appendTo(this.autoCompleteScrollerEl);
if (!Config.Mobile) {
this.autoCompleteScrollerEl.nanoScroller({preventPageScrolling: true, tabIndex: -1});
}
var self = this; var self = this;
this.autoCompleteEl.on('mousedown', function (e) { this.autoCompleteEl.on('mousedown', function (e) {
@ -517,6 +523,7 @@ function MessageComposer (textarea, options) {
this.getSendOnEnter = options.getSendOnEnter; this.getSendOnEnter = options.getSendOnEnter;
this.onFilePaste = options.onFilePaste; this.onFilePaste = options.onFilePaste;
this.mentions = options.mentions; this.mentions = options.mentions;
this.commands = options.commands;
this.getPeerImage = options.getPeerImage; this.getPeerImage = options.getPeerImage;
} }
@ -526,7 +533,7 @@ MessageComposer.prototype.setUpInput = function () {
} else { } else {
this.setUpPlaintext(); this.setUpPlaintext();
} }
this.autoCompleteRegEx = /(?:\s|^)(:|@)([A-Za-z0-9\-\+\*_]*)$/; this.autoCompleteRegEx = /(?:\s|^)(:|@|\/)([A-Za-z0-9\-\+\*@_]*)$/;
} }
MessageComposer.prototype.setUpRich = function () { MessageComposer.prototype.setUpRich = function () {
@ -602,12 +609,18 @@ MessageComposer.prototype.onKeyEvent = function (e) {
currentSelected.removeClass('composer_autocomplete_option_active'); currentSelected.removeClass('composer_autocomplete_option_active');
if (nextWrap) { if (nextWrap) {
$(nextWrap).find('a').addClass('composer_autocomplete_option_active'); $(nextWrap).find('a').addClass('composer_autocomplete_option_active');
if (!Config.Mobile) {
this.autoCompleteScrollerEl.nanoScroller({scrollTop: nextWrap.offsetTop})
}
return cancelEvent(e); return cancelEvent(e);
} }
} }
var childNodes = this.autoCompleteEl[0].childNodes; var childNodes = this.autoCompleteEl[0].childNodes;
var nextWrap = childNodes[next ? 0 : childNodes.length - 1]; var nextWrap = childNodes[next ? 0 : childNodes.length - 1];
if (!Config.Mobile) {
this.autoCompleteScrollerEl.nanoScroller({scrollTop: nextWrap.offsetTop})
}
$(nextWrap).find('a').addClass('composer_autocomplete_option_active'); $(nextWrap).find('a').addClass('composer_autocomplete_option_active');
return cancelEvent(e); return cancelEvent(e);
@ -742,6 +755,30 @@ MessageComposer.prototype.checkAutocomplete = function () {
this.hideSuggestions(); this.hideSuggestions();
} }
} }
else if (matches[1] == '/') { // commands
if (this.commands && this.commands.index) {
if (query.length) {
var foundObject = SearchIndexManager.search(query, this.commands.index);
var foundCommands = [];
var command;
for (var i = 0, length = this.commands.list.length; i < length; i++) {
command = this.commands.list[i];
if (foundObject[command.value]) {
foundCommands.push(command);
}
}
} else {
var foundCommands = this.commands.list;
}
if (foundCommands.length) {
this.showCommandsSuggestions(foundCommands);
} else {
this.hideSuggestions();
}
} else {
this.hideSuggestions();
}
}
else { // emoji else { // emoji
EmojiHelper.getPopularEmoji((function (popular) { EmojiHelper.getPopularEmoji((function (popular) {
if (query.length) { if (query.length) {
@ -1028,6 +1065,21 @@ MessageComposer.prototype.focus = function () {
} }
} }
MessageComposer.prototype.renderSuggestions = function (html) {
this.autoCompleteEl.html(html.join(''));
this.autoCompleteWrapEl.show();
var self = this;
if (!Config.Mobile) {
self.autoCompleteScrollerEl.nanoScroller({scroll: 'top'});
setTimeout(function () {
self.autoCompleteScrollerEl.nanoScroller();
}, 100);
}
this.updatePosition();
this.autocompleteShown = true;
}
MessageComposer.prototype.showEmojiSuggestions = function (codes) { MessageComposer.prototype.showEmojiSuggestions = function (codes) {
var html = []; var html = [];
@ -1052,10 +1104,7 @@ MessageComposer.prototype.showEmojiSuggestions = function (codes) {
} }
} }
this.autoCompleteEl.html(html.join('')); this.renderSuggestions(html);
this.autoCompleteEl.show();
this.updatePosition();
this.autocompleteShown = true;
} }
MessageComposer.prototype.showMentionSuggestions = function (users) { MessageComposer.prototype.showMentionSuggestions = function (users) {
@ -1069,27 +1118,49 @@ MessageComposer.prototype.showMentionSuggestions = function (users) {
html.push('<li><a class="composer_mention_option" data-mention="' + user.username + '"><span class="composer_user_photo" data-user-id="' + user.id + '"></span><span class="composer_user_name">' + user.rFullName + '</span><span class="composer_user_mention">@' + user.username + '</span></a></li>'); html.push('<li><a class="composer_mention_option" data-mention="' + user.username + '"><span class="composer_user_photo" data-user-id="' + user.id + '"></span><span class="composer_user_name">' + user.rFullName + '</span><span class="composer_user_mention">@' + user.username + '</span></a></li>');
} }
this.autoCompleteEl.html(html.join('')); this.renderSuggestions(html);
var self = this; var self = this;
this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) { this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) {
self.getPeerImage($(element), element.getAttribute('data-user-id')); self.getPeerImage($(element), element.getAttribute('data-user-id'));
}); });
}
this.autoCompleteEl.show(); MessageComposer.prototype.showCommandsSuggestions = function (commands) {
this.updatePosition(); var html = [];
this.autocompleteShown = true; var command;
var count = Math.min(5, commands.length);
var i;
for (i = 0; i < count; i++) {
command = commands[i];
html.push('<li><a class="composer_command_option" data-command="' + command.value + '"><span class="composer_user_photo" data-user-id="' + command.botID + '"></span><span class="composer_command_value">' + command.value + '</span><span class="composer_command_desc">' + command.description + '</span></a></li>');
}
this.renderSuggestions(html);
var self = this;
var usedImages = {};
this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) {
var noReplace = true;
var botID = element.getAttribute('data-user-id');
if (!usedImages[botID]) {
usedImages[botID] = true;
noReplace = false;
}
self.getPeerImage($(element), botID, noReplace);
});
} }
MessageComposer.prototype.updatePosition = function () { MessageComposer.prototype.updatePosition = function () {
var offset = (this.richTextareaEl || this.textareaEl).offset(); var offset = (this.richTextareaEl || this.textareaEl).offset();
var height = this.autoCompleteEl.outerHeight(); var height = this.autoCompleteWrapEl.outerHeight();
var width = (this.richTextareaEl || this.textareaEl).outerWidth(); var width = (this.richTextareaEl || this.textareaEl).outerWidth();
this.autoCompleteEl.css({top: offset.top - height, left: offset.left, width: width - 2}); this.autoCompleteWrapEl.css({top: offset.top - height, left: offset.left, width: width - 2});
} }
MessageComposer.prototype.hideSuggestions = function () { MessageComposer.prototype.hideSuggestions = function () {
this.autoCompleteEl.hide(); return;
this.autoCompleteWrapEl.hide();
delete this.autocompleteShown; delete this.autocompleteShown;
} }

32
app/js/services.js

@ -837,7 +837,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
}) })
.service('AppProfileManager', function (AppBotsManager, AppUsersManager, AppPhotosManager, NotificationsManager, MtpApiManager, RichTextProcessor) { .service('AppProfileManager', function ($q, AppUsersManager, AppChatsManager, AppPhotosManager, NotificationsManager, MtpApiManager, RichTextProcessor) {
var botInfos = {}; var botInfos = {};
@ -851,6 +851,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
commands[botCommand.command] = botCommand.description; commands[botCommand.command] = botCommand.description;
}) })
return botInfos[botID] = { return botInfos[botID] = {
id: botID,
version: botInfo.version, version: botInfo.version,
shareText: botInfo.share_text, shareText: botInfo.share_text,
description: botInfo.description, description: botInfo.description,
@ -884,23 +885,13 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
} }
return { function getPeerBots (peerID) {
getProfile: getProfile
}
})
.service('AppBotsManager', function (AppUsersManager, AppChatsManager) {
return {
getPeerBots: function (peerID) {
var peerBots = []; var peerBots = [];
if (peerID > 0) { if (peerID >= 0) {
var user = AppUsersManager.getUser(peerID); if (!AppUsersManager.isBot(peerID)) {
if (!user.pFlags.bot) {
return $q.when(peerBots); return $q.when(peerBots);
} }
return AppUsersManager.getUserFull(peerID).then(function (userFull) { return getProfile(peerID).then(function (userFull) {
var botInfo = userFull.bot_info; var botInfo = userFull.bot_info;
if (botInfo && botInfo._ != 'botInfoEmpty') { if (botInfo && botInfo._ != 'botInfoEmpty') {
peerBots.push(botInfo); peerBots.push(botInfo);
@ -909,15 +900,20 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
} }
return AppChatsManager.getFullChat(-peerID).then(function (chatFull) { return AppChatsManager.getChatFull(-peerID).then(function (chatFull) {
angular.forEach(chatFull.bot_info, function (botInfo) { angular.forEach(chatFull.bot_info, function (botInfo) {
peerBots.push(botInfo); peerBots.push(saveBotInfo(botInfo));
}); });
return peerBots; return peerBots;
}); });
} }
};
return {
getProfile: getProfile,
getPeerBots: getPeerBots
}
}) })
.service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, AppProfileManager, FileManager, TelegramMeWebService, ErrorService, StatusManager, _) { .service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, AppProfileManager, FileManager, TelegramMeWebService, ErrorService, StatusManager, _) {

50
app/less/app.less

@ -2413,15 +2413,33 @@ a.composer_emoji_btn {
&.emoji-spritesheet-4 { background-size: 884px 182px; } &.emoji-spritesheet-4 { background-size: 884px 182px; }
} }
.composer_dropdown {
.composer_dropdown_wrap {
background: #FFF;
display: none; display: none;
padding: 6px 0; position: absolute;
border: 0; border: 0;
.box-shadow(0px 1px 1px 0px rgba(60,75,87,0.27)); .box-shadow(0px 1px 1px 0px rgba(60,75,87,0.27));
border-radius: 0; border-radius: 0;
margin-top: -5px; margin-top: -5px;
height: 180px;
}
.composer_dropdown_scroller {
}
.composer_dropdown {
position: static;
display: block;
float: none;
top: auto;
left: auto;
border: 0;
border-radius: 0;
padding: 0;
margin: 0;
z-index: auto;
& > li > a { & > li > a {
display: block; display: block;
@ -2473,6 +2491,7 @@ a.composer_emoji_btn {
img& { img& {
width: 32px; width: 32px;
height: 32px; height: 32px;
vertical-align: top;
} }
span& { span& {
@ -2490,6 +2509,33 @@ a.composer_emoji_btn {
vertical-align: top; vertical-align: top;
} }
.composer_dropdown a.composer_command_option {
color: #808080;
line-height: 32px;
padding-right: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.composer_dropdown .composer_command_value {
color: #52719a;
display: inline;
}
.composer_dropdown .composer_command_desc {
display: inline;
color: #808080;
padding-left: 7px;
font-weight: normal;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
a.composer_command_option:hover .composer_command_desc,
a.composer_command_option.composer_autocomplete_option_active .composer_command_desc {
color: #698192;
}
.composer_stickerset_title { .composer_stickerset_title {
display: block; display: block;
// clear: both; // clear: both;

10
app/less/desktop.less

@ -482,7 +482,8 @@
} }
} }
.composer_emoji_tooltip { .composer_emoji_tooltip,
.composer_dropdown_wrap {
z-index: 1001; z-index: 1001;
.nano > .nano-pane { .nano > .nano-pane {
@ -491,11 +492,18 @@
& > .nano-slider { & > .nano-slider {
background: #d1d1d1; background: #d1d1d1;
background : rgba(0,0,0,0.17);
margin: 0 3px 0 4px; margin: 0 3px 0 4px;
} }
} }
} }
.composer_dropdown_wrap .nano > .nano-pane {
top: 3px;
bottom: 3px;
right: -1px;
}
.countries_modal_col { .countries_modal_col {
.nano { .nano {
& > .nano-pane { & > .nano-pane {

2
app/partials/desktop/im.html

@ -162,7 +162,7 @@
</a> </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> <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" mentions="mentions" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length}"> <form my-send-form draft-message="draftMessage" mentions="mentions" commands="commands" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length}">
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMessage != null"> <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> <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