From ba25a32b14a89bfdb738e1cc3a5dc127b82c74b9 Mon Sep 17 00:00:00 2001 From: Igor Zhukov Date: Mon, 6 Jul 2015 23:47:59 +0300 Subject: [PATCH] Implemented universal scroller Added keyboard hide/show Added enter slash button Added scroll to keyboard --- app/js/controllers.js | 26 +++- app/js/directives.js | 20 +++ app/js/message_composer.js | 167 +++++++++++++++---------- app/js/services.js | 16 ++- app/less/app.less | 73 +++++++++-- app/less/desktop.less | 109 ++++++++++++++++ app/partials/desktop/im.html | 44 ++++--- app/partials/desktop/reply_markup.html | 8 +- 8 files changed, 358 insertions(+), 105 deletions(-) diff --git a/app/js/controllers.js b/app/js/controllers.js index 9912e915..da11e08c 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -995,6 +995,8 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.selectedFlush = selectedFlush; $scope.botStart = botStart; + $scope.replyKeyboardToggle = replyKeyboardToggle; + $scope.toggleEdit = toggleEdit; $scope.toggleMedia = toggleMedia; $scope.returnToRecent = returnToRecent; @@ -1393,7 +1395,17 @@ angular.module('myApp.controllers', ['myApp.i18n']) } console.log('update reply markup', peerID, replyKeyboard); $scope.historyState.replyKeyboard = replyKeyboard; - $scope.$broadcast('ui_panel_update'); + $scope.$broadcast('ui_keyboard_update'); + } + + function replyKeyboardToggle () { + var replyKeyboard = $scope.historyState.replyKeyboard; + if (!replyKeyboard) { + return; + } + replyKeyboard.pFlags.hidden = !replyKeyboard.pFlags.hidden; + console.log('toggle reply markup', peerID, replyKeyboard); + $scope.$broadcast('ui_keyboard_update'); } function botStart () { @@ -1859,7 +1871,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.$on('user_update', angular.noop); }) - .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager) { + .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager, RichTextProcessor) { $scope.$watch('curDialog.peer', resetDraft); $scope.$on('user_update', angular.noop); @@ -1876,6 +1888,8 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.$watch('draftMessage.sticker', onStickerSelected); $scope.$watch('draftMessage.command', onCommandSelected); + $scope.enterSlash = enterSlash; + function sendMessage (e) { $scope.$broadcast('ui_message_before_send'); @@ -1974,7 +1988,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) commandsList.push({ botID: peerBot.id, value: value, - description: description + rDescription: RichTextProcessor.wrapRichText(description, {noLinks: true, noLineBreaks: true}) }); SearchIndexManager.indexObject(value, botSearchText + ' ' + command + ' ' + description, commandsIndex); }) @@ -2017,6 +2031,12 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.$broadcast('ui_peer_reply'); } + function enterSlash (event) { + $scope.draftMessage.text = '/'; + $scope.$broadcast('ui_peer_draft'); + return cancelEvent(event); + } + function onMessageChange(newVal) { // console.log('ctrl text changed', newVal); // console.trace('ctrl text changed', newVal); diff --git a/app/js/directives.js b/app/js/directives.js index afb793c9..4cd92a48 100755 --- a/app/js/directives.js +++ b/app/js/directives.js @@ -414,9 +414,26 @@ angular.module('myApp.directives', ['myApp.filters']) }; function link ($scope, element, attrs) { + var scrollable = $('.reply_markup', element); + var scroller = new Scroller(scrollable, { + classPrefix: 'reply_markup', + maxHeight: 170 + }); $scope.buttonSend = function (button) { $scope.$emit('reply_button_press', button); } + + $scope.$on('ui_keyboard_update', function () { + onContentLoaded(function () { + scroller.updateHeight(); + scroller.scrollTo(0); + $scope.$emit('ui_panel_update'); + }) + }); + onContentLoaded(function () { + scroller.updateHeight(); + $scope.$emit('ui_panel_update'); + }); } }) @@ -1474,6 +1491,9 @@ angular.module('myApp.directives', ['myApp.filters']) if (!Config.Navigator.touch) { composer.focus(); } + onContentLoaded(function () { + composer.checkAutocomplete(true); + }); if (emojiTooltip) { emojiTooltip.hide(); } diff --git a/app/js/message_composer.js b/app/js/message_composer.js index c2155e7c..ecbd5e43 100644 --- a/app/js/message_composer.js +++ b/app/js/message_composer.js @@ -196,21 +196,6 @@ EmojiTooltip.prototype.onMouseLeave = function (triggerUnshow) { } }; -EmojiTooltip.prototype.getScrollWidth = function() { - var outer = $('
').css({ - position: 'absolute', - width: 100, - height: 100, - overflow: 'scroll', - top: -9999 - }).appendTo($(document.body)); - - var scrollbarWidth = outer[0].offsetWidth - outer[0].clientWidth; - outer.remove(); - - return scrollbarWidth; -}; - EmojiTooltip.prototype.createTooltip = function () { @@ -219,20 +204,12 @@ EmojiTooltip.prototype.createTooltip = function () { } var self = this; - this.tooltipEl = $('
').appendTo(document.body); - - this.tabsEl = $('.composer_emoji_tooltip_tabs', this.tooltip); - this.contentWrapEl = $('.composer_emoji_tooltip_content_wrap', this.tooltip); - this.contentEl = $('.composer_emoji_tooltip_content', this.tooltip); - this.footerEl = $('.composer_emoji_tooltip_footer', this.tooltip); - this.settingsEl = $('.composer_emoji_tooltip_settings', this.tooltip); - - var scrollWidth = this.getScrollWidth(); - if (scrollWidth > 0) { - this.tooltipEl.css({ - width: parseInt(this.tooltipEl.css('width')) + scrollWidth - }); - } + this.tooltipEl = $('
').appendTo(document.body); + + this.tabsEl = $('.composer_emoji_tooltip_tabs', this.tooltipEl); + this.contentEl = $('.composer_emoji_tooltip_content', this.tooltipEl); + this.footerEl = $('.composer_emoji_tooltip_footer', this.tooltipEl); + this.settingsEl = $('.composer_emoji_tooltip_settings', this.tooltipEl); angular.forEach(['recent', 'smile', 'flower', 'bell', 'car', 'grid', 'stickers'], function (tabName, tabIndex) { var tab = $('') @@ -254,9 +231,7 @@ EmojiTooltip.prototype.createTooltip = function () { } }); - if (!Config.Mobile) { - this.contentWrapEl.nanoScroller({preventPageScrolling: true, tabIndex: -1}); - } + this.scroller = new Scroller(this.contentEl, {classPrefix: 'composer_emoji_tooltip'}); this.contentEl.on('mousedown', function (e) { e = e.originalEvent || e; @@ -323,13 +298,7 @@ EmojiTooltip.prototype.updateTabContents = function () { var renderContent = function () { self.contentEl.html(html.join('')); - - if (!Config.Mobile) { - self.contentWrapEl.nanoScroller({scroll: 'top'}); - setTimeout(function () { - self.contentWrapEl.nanoScroller(); - }, 100); - } + self.scroller.reinit(); } if (this.tab == 6) { // Stickers @@ -488,12 +457,9 @@ function MessageComposer (textarea, options) { this.setUpInput(); this.autoCompleteWrapEl = $('
').appendTo(document.body); - this.autoCompleteScrollerEl = $('
').appendTo(this.autoCompleteWrapEl); - this.autoCompleteEl = $('').appendTo(this.autoCompleteScrollerEl); + this.autoCompleteEl = $('').appendTo(this.autoCompleteWrapEl); - if (!Config.Mobile) { - this.autoCompleteScrollerEl.nanoScroller({preventPageScrolling: true, tabIndex: -1}); - } + this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180}); var self = this; this.autoCompleteEl.on('mousedown', function (e) { @@ -614,18 +580,14 @@ MessageComposer.prototype.onKeyEvent = function (e) { currentSelected.removeClass('composer_autocomplete_option_active'); if (nextWrap) { $(nextWrap).find('a').addClass('composer_autocomplete_option_active'); - if (!Config.Mobile) { - scrollToNode(this.autoCompleteEl[0], nextWrap, this.autoCompleteScrollerEl); - } + this.scroller.scrollToNode(nextWrap); return cancelEvent(e); } } var childNodes = this.autoCompleteEl[0].childNodes; var nextWrap = childNodes[next ? 0 : childNodes.length - 1]; - if (!Config.Mobile) { - scrollToNode(this.autoCompleteEl[0], nextWrap, this.autoCompleteScrollerEl); - } + this.scroller.scrollToNode(nextWrap); $(nextWrap).find('a').addClass('composer_autocomplete_option_active'); return cancelEvent(e); @@ -714,7 +676,7 @@ MessageComposer.prototype.restoreSelection = function () { -MessageComposer.prototype.checkAutocomplete = function () { +MessageComposer.prototype.checkAutocomplete = function (forceFull) { if (Config.Mobile) { return false; } @@ -730,7 +692,9 @@ MessageComposer.prototype.checkAutocomplete = function () { var value = textarea.value; } - value = value.substr(0, pos); + if (!forceFull) { + value = value.substr(0, pos); + } var matches = value.match(this.autoCompleteRegEx); if (matches) { @@ -1096,15 +1060,7 @@ 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.scroller.reinit(); this.updatePosition(); this.autocompleteShown = true; } @@ -1161,7 +1117,7 @@ MessageComposer.prototype.showCommandsSuggestions = function (commands) { for (i = 0; i < count; i++) { command = commands[i]; - html.push('
  • ' + encodeEntities(command.value) + '' + encodeEntities(command.description) + '
  • '); + html.push('
  • ' + encodeEntities(command.value) + '' + command.rDescription + '
  • '); } this.renderSuggestions(html); @@ -1182,14 +1138,13 @@ MessageComposer.prototype.showCommandsSuggestions = function (commands) { MessageComposer.prototype.updatePosition = function () { var offset = (this.richTextareaEl || this.textareaEl).offset(); var width = (this.richTextareaEl || this.textareaEl).outerWidth(); - var contentHeight = this.autoCompleteEl[0].firstChild.clientHeight * this.autoCompleteEl[0].childNodes.length; - var height = Math.min(180, contentHeight); + var height = this.scroller.updateHeight(); this.autoCompleteWrapEl.css({ top: offset.top - height, left: offset.left, - width: width - 2, - height: height + width: width - 2 }); + this.scroller.update(); } MessageComposer.prototype.hideSuggestions = function () { @@ -1202,3 +1157,83 @@ MessageComposer.prototype.resetTyping = function () { this.lastLength = 0; } + + +function Scroller(content, options) { + options = options || {}; + var classPrefix = options.classPrefix || 'scroller'; + + this.content = $(content); + this.content.wrap('
    '); + + this.scrollable = $(this.content[0].parentNode); + this.scroller = $(this.scrollable[0].parentNode); + this.wrap = $(this.scroller[0].parentNode); + + this.useNano = options.nano !== undefined ? options.nano : !Config.Mobile; + this.maxHeight = options.maxHeight; + + if (this.useNano) { + this.scrollable.addClass('nano-content'); + this.scroller.addClass('nano'); + this.scroller.nanoScroller({preventPageScrolling: true, tabIndex: -1}); + } else { + if (this.maxHeight) { + this.wrap.css({maxHeight: this.maxHeight}); + } + } + this.updateHeight(); +} + +Scroller.prototype.update = function () { + if (this.useNano) { + $(this.scroller).nanoScroller(); + } +} + +Scroller.prototype.reinit = function () { + this.scrollTo(0); + if (this.useNano) { + setTimeout((function () { + this.updateHeight(); + }).bind(this), 100) + } +} + +Scroller.prototype.updateHeight = function () { + var height; + if (this.maxHeight) { + var contentHeight = this.content[0].offsetHeight; + height = Math.min(this.maxHeight, contentHeight); + this.wrap.css({height: height}); + } else { + height = this.scroller[0].offsetHeight; + } + $(this.scroller).nanoScroller(); + return height; +} + + +Scroller.prototype.scrollTo = function (scrollTop) { + this.scrollable[0].scrollTop = scrollTop; + if (this.useNano) { + $(this.scroller).nanoScroller({flash: true}); + } +} + +Scroller.prototype.scrollToNode = function (node) { + node = node[0] || node; + var elTop = node.offsetTop - 15, + elHeight = node.offsetHeight + 30, + scrollTop = this.scrollable[0].scrollTop, + viewportHeight = this.scrollable[0].clientHeight; + + if (scrollTop > elTop) { // we are below the node to scroll + this.scrollTo(elTop); + } + else if (scrollTop < elTop + elHeight - viewportHeight) { // we are over the node to scroll + this.scrollTo(elTop + elHeight - viewportHeight); + } +} + + diff --git a/app/js/services.js b/app/js/services.js index 1988eb45..f0611a90 100755 --- a/app/js/services.js +++ b/app/js/services.js @@ -1055,7 +1055,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) }); if (historiesStorage[peerID] === undefined) { - historiesStorage[peerID] = {count: null, history: [dialog.top_message], pending: []} + var historyStorage = {count: null, history: [dialog.top_message], pending: []}; + historiesStorage[peerID] = historyStorage; + var message = getMessage(dialog.top_message); + if (mergeReplyKeyboard(historyStorage, message)) { + $rootScope.$broadcast('history_reply_markup', {peerID: peerID}); + } } NotificationsManager.savePeerSettings(peerID, dialog.notify_settings); @@ -1291,7 +1296,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } function getReplyKeyboard (peerID) { - console.log('get', historiesStorage[peerID]); return (historiesStorage[peerID] || {}).reply_markup || false; } @@ -2413,6 +2417,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } function wrapReplyMarkup (replyMarkup) { + if (!replyMarkup || + replyMarkup._ == 'replyKeyboardHide') { + return false; + } if (replyMarkup.wrapped) { return replyMarkup; } @@ -2422,6 +2430,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) markupButton.rText = RichTextProcessor.wrapRichText(markupButton.text, {noLinks: true, noLinebreaks: true}); }) }) + + if (nextRandomInt(1)) { + replyMarkup.rows = replyMarkup.rows.slice(0, 2); + } return replyMarkup; } diff --git a/app/less/app.less b/app/less/app.less index e6f8c976..3aba2e46 100644 --- a/app/less/app.less +++ b/app/less/app.less @@ -2062,11 +2062,14 @@ a.im_message_fwd_photo { .im_send_field_wkeyboard { .im_send_buttons_wrap { - display: none; + // display: none; } } .reply_markup_wrap { - margin: 5px -2px 0; + margin: 7px -2px 0; +} +.reply_markup_scrollable_wrap.has-scrollbar .reply_markup_row { + margin-right: 6px; } .reply_markup_button_wrap { display: inline-block; @@ -2075,11 +2078,12 @@ a.im_message_fwd_photo { .reply_markup_button { display: block; width: 100%; - background: #EEE; + background: #EFEFEF; margin: 0; } .reply_markup_button:hover { - background: #DDD; + // background: #DDD; + background: #f2f6fa; } .reply_markup_button_w1 {width: 100%;} .reply_markup_button_w2 {width: 50%;} @@ -2094,6 +2098,8 @@ a.im_message_fwd_photo { .reply_markup_button_w11 {width: 9.09090909%;} .reply_markup_button_w12 {width: 8.33333333%;} + + .im_history_not_selected, .im_history_empty { visibility: hidden; @@ -2360,14 +2366,11 @@ img.img_fullsize { .composer_emoji_tooltip_tab_stickers {background-position: -9px -361px; } .composer_emoji_tooltip_tab_stickers.active {background-position: -9px -333px; } -.nano.composer_emoji_tooltip_content_wrap { +.composer_emoji_tooltip_scrollable_container { height: 174px; position: relative; } .composer_emoji_tooltip_content { - /*position: relative;*/ - /*overflow: hidden; - overflow-y: auto;*/ padding-right: 8px; outline: 0!important; } @@ -2568,6 +2571,9 @@ a.composer_command_option:hover .composer_command_desc, a.composer_command_option.composer_autocomplete_option_active .composer_command_desc { color: #698192; } +.composer_command_desc .emoji { + vertical-align: text-bottom; +} .composer_stickerset_title { @@ -2654,6 +2660,57 @@ a.composer_command_option.composer_autocomplete_option_active .composer_command_ } } +.composer_command_btn { + display: block; + position: absolute; + right: 23px; + top: 2px; + cursor: pointer; + padding: 0; + + width: 22px; + height: 22px; + margin-top: 1px; +} +.icon-slash { + display: inline-block; + width: 22px; + height: 22px; + vertical-align: top; + opacity: 0.8; + + .image-2x('../img/icons/General.png', 40px, 778px); + background-position: -9px -335px; +} + +.composer_keyboard_btn { + display: block; + position: absolute; + right: 28px; + top: -2px; + cursor: pointer; + padding: 0; + + width: 22px; + height: 22px; + margin-top: 1px; +} +.icon-hide-keyboard { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #BBB; + border-top: 0; + border-left: 0; + .transform(rotate(45deg)); + vertical-align: top; + opacity: 0.8; +} +.composer_keyboard_btn:hover .icon-hide-keyboard { + opacity: 1.0; +} + + .error_modal_window { .modal-dialog { max-width: 350px; diff --git a/app/less/desktop.less b/app/less/desktop.less index 8cc9800f..17bd2170 100644 --- a/app/less/desktop.less +++ b/app/less/desktop.less @@ -702,6 +702,24 @@ a.footer_link.active:active { } } +.reply_markup_scrollable_container { + .nano > .nano-pane { + background: rgba(137, 160, 179, 0.1); + right: 2px; + width: 3px; + top: 2px; + bottom: 2px; + .rounded(1px); + + & > .nano-slider { + .rounded(1px); + background: #d1d1d1; + background: rgba(137, 160, 179, 0.5); + margin: 0; + } + } +} + .im_history { &_no_dialogs_wrap { margin: 122px 170px 60px; @@ -1396,13 +1414,104 @@ a.im_panel_peer_photo .peer_initials { } .im_send_field_wkeyboard { + + a { + &.im_panel_peer_photo, + &.im_panel_own_photo { + display: none; + } + } + + .im_send_field_wrap { + margin-bottom: 0; + } + .composer { &_rich_textarea, &_textarea { min-height: 25px; padding-right: 25px; } + + &_emoji_insert_btn { + top: 0; + right: 0px; + margin-top: -3px; + } + + &_emoji_panel { + display: none; + } + } + + .im_submit { + position: absolute; + top: 0; + left: 100%; + margin: 0 0 0 15px; } + .im_media_attach { + position: absolute; + top: -6px; + left: -43px; + width: 19px; + height: 24px; + } + + .icon-camera { + display: inline-block; + width: 19px; + height: 23px; + vertical-align: text-top; + opacity: 0.8; + margin-top: -1px; + + .image-2x('../img/icons/IconsetW.png', 42px, 1171px); + background-position: -12px -68px; + } + + .im_media_attach { + &:hover .icon-camera, + &:active .icon-camera { + background-position: -12px -100px; + opacity: 1; + } + } + + .im_attach { + display: none; + position: absolute; + top: 0; + right: 100%; + margin: 0; + margin-right: 45px; + margin-top: 1px; + } + + .icon-emoji { + display: inline-block; + width: 22px; + height: 22px; + vertical-align: text-top; + opacity: 1; + margin: 0; + + .image-2x('../img/icons/IconsetW.png', 42px, 1171px); + background-position: -10px -771px; + } + + .composer_emoji_insert_btn:active .icon-emoji, + .is_1x .composer_emoji_insert_btn:active .icon-emoji, + .composer_emoji_insert_btn.composer_emoji_insert_btn_on .icon-emoji, + .is_1x .composer_emoji_insert_btn.composer_emoji_insert_btn_on .icon-emoji { + background-position: -10px -803px; + } + + .im_send_dropbox_wrap { + display: none; + } + + } /* Peer modals */ diff --git a/app/partials/desktop/im.html b/app/partials/desktop/im.html index a87a7b81..705370b6 100644 --- a/app/partials/desktop/im.html +++ b/app/partials/desktop/im.html @@ -158,7 +158,7 @@
    -
    + +
    + + + -
    +
    + +
    -
    - +
    + -
    - +
    + +
    -
    -
    +
    + +
    -
    - - -
    - - -
    - -
    - - -
    - -
    -
    +
    +
    +
    +
    +
    diff --git a/app/partials/desktop/reply_markup.html b/app/partials/desktop/reply_markup.html index 6e581c00..cf6c353d 100644 --- a/app/partials/desktop/reply_markup.html +++ b/app/partials/desktop/reply_markup.html @@ -1,7 +1,9 @@
    -
    -
    - +
    +
    +
    + +
    \ No newline at end of file