Browse Source

inline bots wip

master
Igor Zhukov 9 years ago
parent
commit
078d871626
  1. 26
      app/js/controllers.js
  2. 73
      app/js/directives.js
  3. 145
      app/js/message_composer.js
  4. 27
      app/js/messages_manager.js
  5. 5
      app/js/services.js
  6. 79
      app/less/app.less
  7. 27
      app/partials/desktop/composer_dropdown.html
  8. 5
      app/partials/desktop/im.html
  9. 15
      app/partials/desktop/inline_results.html

26
app/js/controllers.js

@ -2099,6 +2099,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2099,6 +2099,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
};
$scope.mentions = {};
$scope.commands = {};
$scope.inlineResults = {};
$scope.$watch('draftMessage.text', onMessageChange);
$scope.$watch('draftMessage.files', onFilesSelected);
$scope.$watch('draftMessage.sticker', onStickerSelected);
@ -2389,7 +2390,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2389,7 +2390,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return cancelEvent($event);
}
var inlineUsernameRegex = /^@([a-zA-Z\d_]{1,32}) /;
var inlineUsernameRegex = /^@([a-zA-Z\d_]{1,32}) ([\s\S]*)$/;
var lastInlineBot = false;
function onMessageChange(newVal) {
// console.log('ctrl text changed', newVal);
@ -2405,16 +2406,27 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2405,16 +2406,27 @@ angular.module('myApp.controllers', ['myApp.i18n'])
Storage.set(backupDraftObj);
// console.log(dT(), 'draft save', backupDraftObj);
var matches;
if (matches = newVal.match(inlineUsernameRegex)) {
AppPeersManager.resolveInlineMention(matches[1]).then(function (placeholder) {
$scope.draftMessage.inlinePlaceholder = placeholder;
var matches = newVal.match(inlineUsernameRegex);
if (matches) {
$scope.draftMessage.inlineProgress = true;
AppPeersManager.resolveInlineMention(matches[1]).then(function (inlineBot) {
$scope.draftMessage.inlinePlaceholder = inlineBot.placeholder;
AppMessagesManager.getInlineResults(inlineBot.id, matches[2], '').then(function (botResults) {
$scope.inlineResults = botResults;
console.log('results', botResults);
delete $scope.draftMessage.inlineProgress;
}, function () {
})
delete $scope.draftMessage.inlineProgress;
});
}, function () {
delete $scope.draftMessage.inlinePlaceholder;
delete $scope.draftMessage.inlineProgress;
});
}
} else {
Storage.remove('draft' + $scope.curDialog.peerID);
delete $scope.draftMessage.inlinePlaceholder;
delete $scope.draftMessage.inlineProgress;
// console.log(dT(), 'draft delete', 'draft' + $scope.curDialog.peerID);
}
}

73
app/js/directives.js

@ -1517,16 +1517,11 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1517,16 +1517,11 @@ angular.module('myApp.directives', ['myApp.filters'])
getSendOnEnter: function () {
return sendOnEnter;
},
getPeerImage: function (element, peerID, noReplace) {
if (cachedPeerPhotos[peerID] && !noReplace) {
element.replaceWith(cachedPeerPhotos[peerID]);
return;
}
dropdownDirective: function (element, callback) {
var scope = $scope.$new(true);
scope.peerID = peerID;
peerPhotoCompiled(scope, function (clonedElement) {
cachedPeerPhotos[peerID] = clonedElement;
$compile('<div my-composer-dropdown></div>')(scope, function (clonedElement) {
element.replaceWith(clonedElement);
callback(scope, clonedElement);
});
},
mentions: $scope.mentions,
@ -3289,7 +3284,7 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3289,7 +3284,7 @@ angular.module('myApp.directives', ['myApp.filters'])
var width = attrs.width || element.width() || 40;
var stroke = attrs.stroke || (width / 2 * 0.14);
var center = width / 2;
var radius = center - stroke;
var radius = center - (stroke / 2);
// Doesn't work without unique id for every gradient
var curNum = ++num;
@ -3345,3 +3340,63 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3345,3 +3340,63 @@ angular.module('myApp.directives', ['myApp.filters'])
};
})
.directive('myComposerDropdown', function () {
return {
templateUrl: templateUrl('composer_dropdown')
}
})
.directive('myEmojiSuggestions', function () {
return {
link: function($scope, element, attrs) {
$scope.$watchCollection('emojiCodes', function (codes) {
// var codes = $scope.$eval(attrs.myEmojiSuggestions);
var html = [];
var iconSize = Config.Mobile ? 26 : 20;
var emoticonCode, emoticonData, spritesheet, pos, categoryIndex;
var count = Math.min(5, codes.length);
var i, x, y;
for (i = 0; i < count; i++) {
emoticonCode = codes[i];
if (emoticonCode.code) {
emoticonCode = emoticonCode.code;
}
if (emoticonData = Config.Emoji[emoticonCode]) {
spritesheet = EmojiHelper.spritesheetPositions[emoticonCode];
categoryIndex = spritesheet[0];
pos = spritesheet[1];
x = iconSize * spritesheet[3];
y = iconSize * spritesheet[2];
html.push('<li><a class="composer_emoji_option" data-code="' + encodeEntities(emoticonCode) + '"><i class="emoji emoji-w', iconSize, ' emoji-spritesheet-' + categoryIndex + '" style="background-position: -' + x + 'px -' + y + 'px;"></i><span class="composer_emoji_shortcut">:' + encodeEntities(emoticonData[1][0]) + ':</span></a></li>');
}
}
onContentLoaded(function () {
element.html(html);
});
});
}
};
})
.directive('myInlineResults', function () {
return {
templateUrl: templateUrl('inline_results'),
scope: {
botResults: '=myInlineResults'
},
link: function ($scope, element, attrs) {
$scope.$watch('botResults.results.length', function (show) {
console.log($scope.botResults, show);
});
}
}
})

145
app/js/message_composer.js

@ -674,38 +674,19 @@ EmojiPanel.prototype.update = function () { @@ -674,38 +674,19 @@ EmojiPanel.prototype.update = function () {
function MessageComposer (textarea, options) {
var self = this;
this.textareaEl = $(textarea);
this.setUpInput();
this.autoCompleteWrapEl = $('<div class="composer_dropdown_wrap"></div>').appendTo(document.body);
this.autoCompleteEl = $('<ul class="composer_dropdown dropdown-menu"></ul>').appendTo(this.autoCompleteWrapEl);
this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180});
var autoCompleteEl = $('<div></div>').appendTo(this.autoCompleteWrapEl);
var self = this;
this.autoCompleteEl.on('mousedown', function (e) {
e = e.originalEvent || e;
var target = $(e.target), mention, code, command;
if (target[0].tagName != 'A') {
target = $(target[0].parentNode);
}
if (code = target.attr('data-code')) {
if (self.onEmojiSelected) {
self.onEmojiSelected(code, true);
}
EmojiHelper.pushPopularEmoji(code);
}
if (mention = target.attr('data-mention')) {
self.onMentionSelected(mention);
}
if (command = target.attr('data-command')) {
if (self.onCommandSelected) {
self.onCommandSelected(command);
}
self.hideSuggestions();
}
return cancelEvent(e);
options.dropdownDirective(div, function (scope, autoCompleteEl) {
self.autoCompleteEl = autoCompleteEl;
self.autoCompleteScope = scope;
self.setUpAutoComplete();
});
this.isActive = false;
@ -714,10 +695,9 @@ function MessageComposer (textarea, options) { @@ -714,10 +695,9 @@ function MessageComposer (textarea, options) {
this.onMessageSubmit = options.onMessageSubmit;
this.getSendOnEnter = options.getSendOnEnter;
this.onFilePaste = options.onFilePaste;
this.onCommandSend = options.onCommandSend;
this.mentions = options.mentions;
this.commands = options.commands;
this.getPeerImage = options.getPeerImage;
this.onCommandSend = options.onCommandSend;
}
MessageComposer.autoCompleteRegEx = /(\s|^)(:|@|\/)([A-Za-z0-9\-\+\*@_]*)$/;
@ -738,6 +718,35 @@ MessageComposer.prototype.setUpInput = function () { @@ -738,6 +718,35 @@ MessageComposer.prototype.setUpInput = function () {
}
}
MessageComposer.prototype.setUpAutoComplete = function () {
this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180});
var self = this;
this.autoCompleteEl.on('mousedown', function (e) {
e = e.originalEvent || e;
var target = $(e.target), mention, code, command;
if (target[0].tagName != 'A') {
target = $(target[0].parentNode);
}
if (code = target.attr('data-code')) {
if (self.onEmojiSelected) {
self.onEmojiSelected(code, true);
}
EmojiHelper.pushPopularEmoji(code);
}
if (mention = target.attr('data-mention')) {
self.onMentionSelected(mention);
}
if (command = target.attr('data-command')) {
if (self.onCommandSelected) {
self.onCommandSelected(command);
}
self.hideSuggestions();
}
return cancelEvent(e);
});
}
MessageComposer.prototype.setUpRich = function () {
this.textareaEl.hide();
this.richTextareaEl = $('<div class="composer_rich_textarea" contenteditable="true" dir="auto"></div>');
@ -1353,8 +1362,7 @@ MessageComposer.prototype.blur = function () { @@ -1353,8 +1362,7 @@ MessageComposer.prototype.blur = function () {
}
}
MessageComposer.prototype.renderSuggestions = function (html) {
this.autoCompleteEl.html(html.join(''));
MessageComposer.prototype.renderSuggestions = function () {
this.autoCompleteWrapEl.show();
this.scroller.reinit();
this.updatePosition();
@ -1362,72 +1370,35 @@ MessageComposer.prototype.renderSuggestions = function (html) { @@ -1362,72 +1370,35 @@ MessageComposer.prototype.renderSuggestions = function (html) {
}
MessageComposer.prototype.showEmojiSuggestions = function (codes) {
var html = [];
var iconSize = Config.Mobile ? 26 : 20;
var emoticonCode, emoticonData, spritesheet, pos, categoryIndex;
var count = Math.min(5, codes.length);
var i, x, y;
for (i = 0; i < count; i++) {
emoticonCode = codes[i];
if (emoticonCode.code) {
emoticonCode = emoticonCode.code;
}
if (emoticonData = Config.Emoji[emoticonCode]) {
spritesheet = EmojiHelper.spritesheetPositions[emoticonCode];
categoryIndex = spritesheet[0];
pos = spritesheet[1];
x = iconSize * spritesheet[3];
y = iconSize * spritesheet[2];
html.push('<li><a class="composer_emoji_option" data-code="' + encodeEntities(emoticonCode) + '"><i class="emoji emoji-w', iconSize, ' emoji-spritesheet-' + categoryIndex + '" style="background-position: -' + x + 'px -' + y + 'px;"></i><span class="composer_emoji_shortcut">:' + encodeEntities(emoticonData[1][0]) + ':</span></a></li>');
}
}
this.renderSuggestions(html);
var self = this;
this.autoCompleteScope.$apply(function () {
self.autoCompleteScope.type = 'emoji';
self.autoCompleteScope.emojiCodes = codes;
});
onContentLoaded(function () {
self.renderSuggestions();
});
}
MessageComposer.prototype.showMentionSuggestions = function (users) {
var html = [];
var user;
var count = 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_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.renderSuggestions(html);
var self = this;
this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) {
self.getPeerImage($(element), element.getAttribute('data-user-id'));
this.autoCompleteScope.$apply(function () {
self.autoCompleteScope.type = 'mentions';
self.autoCompleteScope.mentionUsers = users;
});
onContentLoaded(function () {
self.renderSuggestions();
});
}
MessageComposer.prototype.showCommandsSuggestions = function (commands) {
var html = [];
var command;
var count = Math.min(200, commands.length);
var i;
for (i = 0; i < count; i++) {
command = commands[i];
html.push('<li><a class="composer_command_option" data-command="' + encodeEntities(command.value) + '"><span class="composer_user_photo" data-user-id="' + command.botID + '"></span><span class="composer_command_value">' + encodeEntities(command.value) + '</span><span class="composer_command_desc">' + command.rDescription + '</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);
this.autoCompleteScope.$apply(function () {
self.autoCompleteScope.type = 'commands';
self.autoCompleteScope.commands = commands;
});
onContentLoaded(function () {
self.renderSuggestions();
});
}

27
app/js/messages_manager.js

@ -3010,7 +3010,31 @@ angular.module('myApp.services') @@ -3010,7 +3010,31 @@ angular.module('myApp.services')
};
}
})
})
});
var inlineResults = {};
function getInlineResults (botID, query, offset) {
return MtpApiManager.invokeApi('messages.getInlineBotResults', {
bot: AppUsersManager.getUserInput(botID),
query: query,
offset: offset
}).then(function(botResults) {
var queryID = botResults.query_id;
delete botResults._;
delete botResults.flags;
delete botResults.query_id;
angular.forEach(botResults.results, function (result) {
var qID = queryID + '_' + result.id;
result.qID = qID;
result.rTitle = RichTextProcessor.wrapRichText(result.title, {noLinebreaks: true, noLinks: true});
result.rDescription = RichTextProcessor.wrapRichText(result.description, {noLinebreaks: true, noLinks: true});
inlineResults[qID] = result;
});
return botResults;
});
}
return {
getConversations: getConversations,
@ -3033,6 +3057,7 @@ angular.module('myApp.services') @@ -3033,6 +3057,7 @@ angular.module('myApp.services')
getMessagePeer: getMessagePeer,
getMessageThumb: getMessageThumb,
clearDialogCache: clearDialogCache,
getInlineResults: getInlineResults,
wrapForDialog: wrapForDialog,
wrapForHistory: wrapForHistory,
wrapReplyMarkup: wrapReplyMarkup,

5
app/js/services.js

@ -949,7 +949,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -949,7 +949,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (peerID > 0) {
var bot = AppUsersManager.getUser(peerID);
if (bot.pFlags.bot && bot.bot_inline_placeholder !== undefined) {
return bot.bot_inline_placeholder;
return qSync.when({
id: peerID,
placeholder: bot.bot_inline_placeholder
});
}
}
return $q.reject();

79
app/less/app.less

@ -434,6 +434,10 @@ a { @@ -434,6 +434,10 @@ a {
animation: infinite_rotation 0.8s linear infinite;
}
.composer_progress_icon & {
stroke: rgba(0,0,0,0.3);
}
.progress-arc-percent & {
stroke: #FFF;
stroke: rgba(255,255,255,0.95);
@ -449,14 +453,23 @@ a { @@ -449,14 +453,23 @@ a {
.stop0 {
stop-opacity: 1.0;
stop-color: #68a4d1;
.composer_progress_icon & {
stop-color: rgba(0,0,0,0.3);
}
}
.stop60 {
stop-opacity: 1.0;
stop-color: #68a4d1;
.composer_progress_icon & {
stop-color: rgba(0,0,0,0.3);
}
}
.stop100 {
stop-opacity: 0.0;
stop-color: #68a4d1;
.composer_progress_icon & {
stop-color: rgba(0,0,0,0.3);
}
}
/* Infinite rotation */
@ -2485,7 +2498,27 @@ img.img_fullsize { @@ -2485,7 +2498,27 @@ img.img_fullsize {
}
/* Message composer */
.composer_progress_icon {
display: block;
opacity: 0;
position: absolute;
right: 3px;
top: 2px;
cursor: pointer;
padding: 0;
width: 22px;
height: 22px;
margin-top: 1px;
transition: opacity cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.2s;
pointer-events: none;
.composer_progress_enabled & {
opacity: 1;
}
}
.composer_emoji_insert_btn {
opacity: 1;
display: block;
position: absolute;
right: 3px;
@ -2496,7 +2529,13 @@ img.img_fullsize { @@ -2496,7 +2529,13 @@ img.img_fullsize {
width: 22px;
height: 22px;
margin-top: 1px;
transition: opacity cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.2s;
.composer_progress_enabled & {
opacity: 0;
}
}
.icon-emoji {
display: inline-block;
width: 22px;
@ -3093,6 +3132,46 @@ _:-ms-lang(x), .composer_rich_textarea:empty:focus:before { @@ -3093,6 +3132,46 @@ _:-ms-lang(x), .composer_rich_textarea:empty:focus:before {
}
}
.im_send_form_inline_results {
position: relative;
display: none;
}
.inline_results_wrap {
position: absolute;
bottom: 0;
background: #FFF;
border: 0;
.box-shadow(0px 1px 1px 0px rgba(60,75,87,0.27));
// margin-top: -5px;
// margin-left: -1px;
// position: static;
display: block;
float: none;
top: auto;
left: auto;
border: 0;
border-radius: 0;
padding: 0;
margin: 0;
z-index: auto;
}
.inline_result_wrap {
display: block;
font-size: 13px;
line-height: 15px;
padding: 4px 10px;
color: #52719a;
&:hover,
&.composer_autocomplete_option_active {
color: #52719a;
background: #f2f6fa;
}
}
.error_modal_window {
.modal-dialog {

27
app/partials/desktop/composer_dropdown.html

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
<div ng-switch="type">
<ul ng-switch-when="mentions" class="composer_dropdown">
<li ng-repeat="user in users">
<a class="composer_mention_option" data-mention="{{user.username}}">
<span class="composer_user_photo" my-peer-photolink="user.id" img-class="composer_user_photo"></span>
<span class="composer_user_name" ng-bind-html="user.rFullName"></span>
<span class="composer_user_mention" ng-bind="'@' + user.username"></span>
</a>
</li>
</ul>
<ul ng-switch-when="commands" class="composer_dropdown">
<li ng-repeat="command in commands track by (command.botID + command.value)">
<a class="composer_command_option" data-command="{{command.value}}">
<span class="composer_user_photo" my-peer-photolink="command.botID" img-class="composer_user_photo"></span>
<span class="composer_command_value" ng-bind="command.value"></span>
<span class="composer_command_desc" ng-bind-html="command.rDescription"></span>
</a>
</li>
</ul>
<ul ng-switch-when="emoji" my-emoji-suggestions="emojiCodes" class="composer_dropdown"></ul>
<div ng-switch-when="inline" my-inline-results="botResults"></div>
</div>

5
app/partials/desktop/im.html

@ -182,7 +182,9 @@ @@ -182,7 +182,9 @@
</a>
<a class="pull-left im_panel_own_photo" my-peer-photolink="draftMessage.isBroadcast ? historyPeer.id : 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" commands="commands" 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, composer_progress_enabled: draftMessage.inlineProgress}">
<div class="im_send_form_inline_results" my-inline-results="inlineResults"></div>
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMessage != null">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.replyClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
@ -196,6 +198,7 @@ @@ -196,6 +198,7 @@
<div class="im_send_field_wrap" ng-class="historyState.replyKeyboard._ == 'replyKeyboardMarkup' ? 'im_send_field_wrap_2ndbtn' : ''">
<a class="composer_emoji_insert_btn"><i class="icon icon-emoji"></i></a>
<div class="composer_progress_icon" my-arc-progress width="22" stroke="2.5"></div>
<a class="composer_command_btn" ng-show="!historyState.replyKeyboard && commands.list.length > 0 && (!draftMessage.text.length || draftMessage.text[0] == '/')" ng-mousedown="toggleSlash($event)" ng-class="draftMessage.text[0] == '/' ? 'active' : ''"><i class="icon icon-slash"></i></a>
<a class="composer_keyboard_btn" ng-show="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="replyKeyboardToggle($event)" ng-class="!historyState.replyKeyboard.pFlags.hidden ? 'active' : ''"><i class="icon icon-keyboard"></i></a>

15
app/partials/desktop/inline_results.html

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<div class="inline_results_wrap">
<div class="inline_result_wrap" ng-repeat="result in botResults.results track by result.qID" ng-switch="result.type">
<div ng-switch-default class="inline_result_">
<div class="inline_article_thumb_wrap pull-left" ng-switch="result.thumbUrl.length > 0">
<img ng-switch-when="true" ng-src="{{result.thumbUrl}}"/>
<div ng-switch-default class="inline_article_thumb_initials" ng-bind="result.initials"></div>
</div>
<div class="inline_article_content_wrap">
<div class="inline_article_title" ng-if="::result.title.length > 0" ng-bind-html="::result.rTitle"></div>
<div class="inline_article_description" ng-if="::result.description.length > 0" ng-bind-html="::result.rDescription"></div>
<div class="inline_article_url" ng-if="::result.url.length > 0" ng-bind="::result.url"></div>
</div>
</div>
</div>
</div>
Loading…
Cancel
Save