From 38317bbe8bf164f8cb91456850fee367f6a7db1d Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 25 Feb 2016 13:45:46 +0100 Subject: [PATCH] Keyboard shortcut improvements and browser tests. --- .../_application_controller.coffee | 6 +- .../app/controllers/keyboard_shortcurs.coffee | 32 +- .../widget/keyboard_shortcuts.coffee | 95 ++++- .../app/lib/base/jquery.contenteditable.js | 15 +- .../app/lib/base/jquery.hotkeys.js | 9 +- .../app/lib/bootstrap/popover-enhance.js | 2 +- .../app/views/ticket_zoom/article_new.jst.eco | 2 +- script/build/test_slice_tests.sh | 2 + test/browser/keyboard_shortcuts_test.rb | 163 +++++++++ test/browser_test_helper.rb | 341 ++++++++++-------- 10 files changed, 452 insertions(+), 215 deletions(-) create mode 100644 test/browser/keyboard_shortcuts_test.rb diff --git a/app/assets/javascripts/app/controllers/_application_controller.coffee b/app/assets/javascripts/app/controllers/_application_controller.coffee index 941784e3f..157a17f87 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.coffee @@ -205,7 +205,7 @@ class App.Controller extends Spine.Controller # remember requested url if !checkOnly location = window.location.hash - if location isnt '#login' && location isnt '#logout' + if location isnt '#login' && location isnt '#logout' && location isnt '#keyboard_shortcuts' @Config.set( 'requested_url', location) return false if checkOnly @@ -612,6 +612,7 @@ class App.ControllerContent extends App.Controller @navShow() class App.ControllerModal extends App.Controller + authenticateRequired: false backdrop: true keyboard: true large: false @@ -638,6 +639,9 @@ class App.ControllerModal extends App.Controller constructor: -> super + if @authenticateRequired + return if !@authenticate() + # rerender view, e. g. on langauge change @bind('ui:rerender', => @update() diff --git a/app/assets/javascripts/app/controllers/keyboard_shortcurs.coffee b/app/assets/javascripts/app/controllers/keyboard_shortcurs.coffee index 29df4a210..44e34a68a 100644 --- a/app/assets/javascripts/app/controllers/keyboard_shortcurs.coffee +++ b/app/assets/javascripts/app/controllers/keyboard_shortcurs.coffee @@ -1,32 +1,6 @@ -class Index extends App.ControllerModal - large: true - head: 'Keyboard Shortcuts' - buttonClose: true - buttonCancel: false - buttonSubmit: false - - constructor: (params = {}) -> - delete params.el # do attache to body - super(params) - - return if !@authenticate() - - @bind('keyboard_shortcuts_close', @close) - - content: -> - App.view('keyboard_shortcuts')( - areas: App.Config.get('keyboard_shortcuts') - ) - - onClosed: -> - window.history.go(-1) - - onSubmit: -> - window.history.go(-1) - - onCancel: -> - window.history.go(-1) +class Index + constructor: -> + new App.KeyboardShortcutModal() App.Config.set('keyboard_shortcuts', Index, 'Routes') - App.Config.set('KeyboardShortcuts', { prio: 1700, parent: '#current_user', name: 'Keyboard Shortcuts', translate: true, target: '#keyboard_shortcuts', role: [ 'Admin', 'Agent' ] }, 'NavBarRight') diff --git a/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee b/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee index 7109d1489..e73c05bb7 100644 --- a/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee +++ b/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee @@ -1,3 +1,24 @@ +class App.KeyboardShortcutModal extends App.ControllerModal + authenticateRequired: true + large: true + head: 'Keyboard Shortcuts' + buttonClose: true + buttonCancel: false + buttonSubmit: false + + constructor: -> + super + @bind('keyboard_shortcuts_close', @close) + + content: -> + App.view('keyboard_shortcuts')( + areas: App.Config.get('keyboard_shortcuts') + ) + + exists: => + return true if @el.parents('html').length > 0 + false + class App.KeyboardShortcutWidget extends Spine.Module @include App.LogInclude @@ -5,7 +26,6 @@ class App.KeyboardShortcutWidget extends Spine.Module @observerKeys() observerKeys: => - #jQuery.hotkeys.options.filterInputAcceptingElements = false navigationHotkeys = 'alt+ctrl' areas = App.Config.get('keyboard_shortcuts') for area in areas @@ -37,59 +57,75 @@ App.Config.set( key: 'd' hotkeys: true description: 'Dashboard' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') window.location.hash = '#dashboard' } { key: 'o' hotkeys: true description: 'Overviews' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') window.location.hash = '#ticket/view' } { key: 's' hotkeys: true description: 'Search' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') $('#global-search').focus() } { key: 'n' hotkeys: true description: 'New Ticket' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') window.location.hash = '#ticket/create' } { key: 'e' hotkeys: true description: 'Logout' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') window.location.hash = '#logout' } { key: 'h' hotkeys: true description: 'List of shortcuts' - callback: -> - if window.location.hash is '#keyboard_shortcuts' - App.Event.trigger('keyboard_shortcuts_close') + callback: (e) => + e.preventDefault() + if @dialog && @dialog.exists() + @dialog.close() + @dialog = false return - window.location.hash = '#keyboard_shortcuts' + @dialog = new App.KeyboardShortcutModal() } { key: 'x' hotkeys: true description: 'Close current tab' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') $('#navigation .tasks .is-active .js-close').click() } { key: 'tab' hotkeys: true description: 'Next in tab' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') if $('#navigation .tasks .is-active').get(0) if $('#navigation .tasks .is-active').next().get(0) $('#navigation .tasks .is-active').next().find('div').first().click() @@ -100,7 +136,9 @@ App.Config.set( key: 'shift+tab' hotkeys: true description: 'Previous tab' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') if $('#navigation .tasks .is-active').get(0) if $('#navigation .tasks .is-active').prev().get(0) $('#navigation .tasks .is-active').prev().find('div').first().click() @@ -111,7 +149,9 @@ App.Config.set( key: 'return' hotkeys: true description: 'Confirm/submit dialog' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') # check of primary modal exists dialog = $('body > div.modal') @@ -185,14 +225,19 @@ App.Config.set( key: 'm' hotkeys: true description: 'Open note box' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') + $('.active.content .editControls .js-articleTypes [data-value="note"]').click() $('.active.content .article-new .articleNewEdit-body').first().focus() } { - key: 'r' + key: 'g' hotkeys: true description: 'Reply to last article' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') lastArticleWithReply = $('.active.content .ticket-article .icon-reply').last() lastArticleWithReplyAll = lastArticleWithReply.parent().find('.icon-reply-all') if lastArticleWithReplyAll.get(0) @@ -200,18 +245,30 @@ App.Config.set( return lastArticleWithReply.click() } + { + key: 'j' + hotkeys: true + description: 'Set article to internal/public' + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') + $('.active.content .editControls .js-selectInternalPublic').click() + } #{ # key: 'm' # hotkeys: true # description: 'Open macro selection' - # callback: -> + # callback: (e) -> + # e.preventDefault() # window.location.hash = '#ticket/create' #} { key: 'c' hotkeys: true description: 'Update as closed' - callback: -> + callback: (e) -> + e.preventDefault() + App.Event.trigger('keyboard_shortcuts_close') return if !$('.active.content .edit').get(0) $('.active.content .edit [name="state_id"]').val(4) $('.active.content .js-attributeBar .js-submit').first().click() diff --git a/app/assets/javascripts/app/lib/base/jquery.contenteditable.js b/app/assets/javascripts/app/lib/base/jquery.contenteditable.js index 337b4651b..c3f21ba4b 100644 --- a/app/assets/javascripts/app/lib/base/jquery.contenteditable.js +++ b/app/assets/javascripts/app/lib/base/jquery.contenteditable.js @@ -127,6 +127,12 @@ || e.keyCode == 79 || e.keyCode == 87)) { e.preventDefault() + + // disable rich text b/u/i + if ( _this.options.mode === 'textonly' ) { + return + } + if (e.keyCode == 66) { document.execCommand('Bold') } @@ -234,15 +240,6 @@ document.execCommand('insertHTML', false, text) return true }) - - // disable rich text b/u/i - if ( this.options.mode === 'textonly' ) { - this.$element.on('keydown', function (e) { - if ( _this.richTextKey(e) ) { - e.preventDefault() - } - }) - } } // check if key is allowed, even if length limit is reached diff --git a/app/assets/javascripts/app/lib/base/jquery.hotkeys.js b/app/assets/javascripts/app/lib/base/jquery.hotkeys.js index e7701f39c..b155cded6 100644 --- a/app/assets/javascripts/app/lib/base/jquery.hotkeys.js +++ b/app/assets/javascripts/app/lib/base/jquery.hotkeys.js @@ -121,9 +121,12 @@ textInputTypes: /textarea|input|select/i, options: { - filterInputAcceptingElements: true, - filterTextInputs: true, - filterContentEditable: true + //filterInputAcceptingElements: true, + //filterTextInputs: true, + //filterContentEditable: true + filterInputAcceptingElements: false, + filterTextInputs: false, + filterContentEditable: false } }; diff --git a/app/assets/javascripts/app/lib/bootstrap/popover-enhance.js b/app/assets/javascripts/app/lib/bootstrap/popover-enhance.js index 66fa65122..2fc6476ab 100644 --- a/app/assets/javascripts/app/lib/bootstrap/popover-enhance.js +++ b/app/assets/javascripts/app/lib/bootstrap/popover-enhance.js @@ -59,10 +59,10 @@ var originalShow = $.fn.popover.Constructor.prototype.show; $.fn.popover.Constructor.prototype.show = function(){ originalShow.call(this); - var maxHeight = $(this.options.viewport.selector).height() - 2 * this.options.viewport.padding; // improved error handling - no exeption if no $tip exists if (!this.$tip) { return } + var maxHeight = $(this.options.viewport.selector).height() - 2 * this.options.viewport.padding; this.$tip.find('.popover-body').css('maxHeight', maxHeight); } \ No newline at end of file diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco index 68f9607b6..b09c819f2 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco @@ -21,7 +21,7 @@ <% end %> -