Added keyboard support to online notifications. Added browser tests. Improved search keyboard support.
This commit is contained in:
parent
105b36c641
commit
532d562845
9 changed files with 244 additions and 70 deletions
|
@ -82,6 +82,11 @@ Copyright: @leChanteaux <santiago at mural.ly>
|
|||
Mural.ly Dev Team <dev at mural.ly>
|
||||
License: dfyw
|
||||
-----------------------------------------------------------------------------
|
||||
jquery.visible.js
|
||||
Source: https://github.com/customd/jquery-visible
|
||||
Copyright: 2012, Digital Fusion
|
||||
License: MIT license
|
||||
-----------------------------------------------------------------------------
|
||||
marked.js
|
||||
Source: https://github.com/chjj/marked
|
||||
Copyright: 2011-2014, Christopher Jeffrey
|
||||
|
|
|
@ -440,12 +440,11 @@ class App.Controller extends Spine.Controller
|
|||
|
||||
fetch = (params) =>
|
||||
@ajax(
|
||||
type: 'GET',
|
||||
url: @Config.get('api_path') + '/ticket_customer',
|
||||
data: {
|
||||
customer_id: params.user_id,
|
||||
}
|
||||
processData: true,
|
||||
type: 'GET'
|
||||
url: @Config.get('api_path') + '/ticket_customer'
|
||||
data:
|
||||
customer_id: params.user_id
|
||||
processData: true
|
||||
success: (data, status, xhr) ->
|
||||
App.Collection.loadAssets(data.assets)
|
||||
show(params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed })
|
||||
|
@ -712,7 +711,6 @@ class App.ControllerModal extends App.Controller
|
|||
'hide.bs.modal': @onClose
|
||||
'hidden.bs.modal': =>
|
||||
@onClosed()
|
||||
# remove modal from dom
|
||||
$('.modal').remove()
|
||||
|
||||
close: (e) =>
|
||||
|
|
|
@ -195,7 +195,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
|||
@$('form.search').on('submit', (e) ->
|
||||
e.preventDefault()
|
||||
)
|
||||
@$('#global-search').on('keydown', @navigate)
|
||||
@$('#global-search').on('keydown', @listNavigate)
|
||||
|
||||
# bind to empty search
|
||||
@$('.empty-search').on('click', =>
|
||||
|
@ -206,7 +206,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
|||
el: @el
|
||||
)
|
||||
|
||||
navigate: (e) =>
|
||||
listNavigate: (e) =>
|
||||
if e.keyCode is 27 # close on esc
|
||||
@emptyAndClose()
|
||||
return
|
||||
|
@ -217,7 +217,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
|||
@nudge(e, 1)
|
||||
return
|
||||
else if e.keyCode is 13 # enter
|
||||
href = @$('#global-search-result .nav-tab.is-active').attr('href')
|
||||
href = @$('#global-search-result .nav-tab.is-hover').attr('href')
|
||||
@locationExecute(href)
|
||||
@emptyAndClose()
|
||||
return
|
||||
|
@ -229,21 +229,29 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
|||
|
||||
# get current
|
||||
navigationResult = @$('#global-search-result')
|
||||
current = navigationResult.find('.nav-tab.is-active')
|
||||
current = navigationResult.find('.nav-tab.is-hover')
|
||||
if !current.get(0)
|
||||
navigationResult.find('.nav-tab').first().addClass('is-active')
|
||||
navigationResult.find('.nav-tab').first().addClass('is-hover')
|
||||
return
|
||||
|
||||
if position is 1
|
||||
next = current.closest('li').nextAll('li').not('.divider').first().find('.nav-tab')
|
||||
if next.get(0)
|
||||
current.removeClass('is-active').popover('hide')
|
||||
next.addClass('is-active').popover('show')
|
||||
current.removeClass('is-hover').popover('hide')
|
||||
next.addClass('is-hover').popover('show')
|
||||
else
|
||||
prev = current.closest('li').prevAll('li').not('.divider').first().find('.nav-tab')
|
||||
if prev.get(0)
|
||||
current.removeClass('is-active').popover('hide')
|
||||
prev.addClass('is-active').popover('show')
|
||||
current.removeClass('is-hover').popover('hide')
|
||||
prev.addClass('is-hover').popover('show')
|
||||
|
||||
if next
|
||||
element = next.get(0)
|
||||
if prev
|
||||
element = prev.get(0)
|
||||
return if !element
|
||||
return if $(element).visible(true)
|
||||
element.scrollIntoView()
|
||||
|
||||
emptyAndClose: =>
|
||||
@$('#global-search').val('').blur()
|
||||
|
|
|
@ -59,6 +59,7 @@ App.Config.set(
|
|||
description: 'Dashboard'
|
||||
callback: (e) ->
|
||||
e.preventDefault()
|
||||
$('#global-search').blur()
|
||||
App.Event.trigger('keyboard_shortcuts_close')
|
||||
window.location.hash = '#dashboard'
|
||||
}
|
||||
|
@ -68,6 +69,7 @@ App.Config.set(
|
|||
description: 'Overviews'
|
||||
callback: (e) ->
|
||||
e.preventDefault()
|
||||
$('#global-search').blur()
|
||||
App.Event.trigger('keyboard_shortcuts_close')
|
||||
window.location.hash = '#ticket/view'
|
||||
}
|
||||
|
@ -80,12 +82,23 @@ App.Config.set(
|
|||
App.Event.trigger('keyboard_shortcuts_close')
|
||||
$('#global-search').focus()
|
||||
}
|
||||
{
|
||||
key: 'y'
|
||||
hotkeys: true
|
||||
description: 'Notifications'
|
||||
callback: (e) ->
|
||||
e.preventDefault()
|
||||
$('#global-search').blur()
|
||||
App.Event.trigger('keyboard_shortcuts_close')
|
||||
$('#navigation .js-toggleNotifications').click()
|
||||
}
|
||||
{
|
||||
key: 'n'
|
||||
hotkeys: true
|
||||
description: 'New Ticket'
|
||||
callback: (e) ->
|
||||
e.preventDefault()
|
||||
$('#global-search').blur()
|
||||
App.Event.trigger('keyboard_shortcuts_close')
|
||||
window.location.hash = '#ticket/create'
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ class App.OnlineNotificationWidget extends App.Controller
|
|||
release: ->
|
||||
@removeContainer()
|
||||
$(window).off 'click.notifications'
|
||||
$(window).off 'keydown.notifications'
|
||||
App.OnlineNotification.unsubscribe(@subscribeId)
|
||||
|
||||
access: ->
|
||||
|
@ -56,6 +57,48 @@ class App.OnlineNotificationWidget extends App.Controller
|
|||
return true if @isRole('Admin')
|
||||
return false
|
||||
|
||||
listNavigate: (e) =>
|
||||
|
||||
if e.keyCode is 27 # close on esc
|
||||
@hidePopover()
|
||||
return
|
||||
else if e.keyCode is 38 # up
|
||||
@nudge(e, -1)
|
||||
return
|
||||
else if e.keyCode is 40 # down
|
||||
@nudge(e, 1)
|
||||
return
|
||||
else if e.keyCode is 13 # enter
|
||||
$('.js-notificationsContainer .popover-content .activity-entry.is-hover .js-locationVerify').click()
|
||||
|
||||
nudge: (e, position) ->
|
||||
|
||||
# get current
|
||||
navigation = $('.js-notificationsContainer .popover-content')
|
||||
current = navigation.find('.activity-entry.is-hover')
|
||||
if !current.get(0)
|
||||
navigation.find('.activity-entry').first().addClass('is-hover')
|
||||
return
|
||||
|
||||
if position is 1
|
||||
next = current.next('.activity-entry')
|
||||
if next.get(0)
|
||||
current.removeClass('is-hover')
|
||||
next.addClass('is-hover')
|
||||
else
|
||||
prev = current.prev('.activity-entry')
|
||||
if prev.get(0)
|
||||
current.removeClass('is-hover')
|
||||
prev.addClass('is-hover')
|
||||
|
||||
if next
|
||||
element = next.get(0)
|
||||
if prev
|
||||
element = prev.get(0)
|
||||
return if !element
|
||||
return if $(element).visible(true)
|
||||
element.scrollIntoView()
|
||||
|
||||
counterUpdate: (count) =>
|
||||
if !count
|
||||
@el.find('.js-counter').text('')
|
||||
|
@ -100,9 +143,11 @@ class App.OnlineNotificationWidget extends App.Controller
|
|||
|
||||
notificationsContainer.on 'click', @stopPropagation
|
||||
$(window).on 'click.notifications', @hidePopover
|
||||
$(window).on 'keydown.notifications', @listNavigate
|
||||
|
||||
onHide: ->
|
||||
$(window).off 'click.notifications'
|
||||
$(window).off 'keydown.notifications'
|
||||
|
||||
hidePopover: =>
|
||||
@toggle.popover('hide')
|
||||
|
|
68
app/assets/javascripts/app/lib/base/jquery.visible.js
Normal file
68
app/assets/javascripts/app/lib/base/jquery.visible.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
(function($){
|
||||
|
||||
/**
|
||||
* Copyright 2012, Digital Fusion
|
||||
* Licensed under the MIT license.
|
||||
* http://teamdf.com/jquery-plugins/license/
|
||||
*
|
||||
* @author Sam Sehnert
|
||||
* @desc A small plugin that checks whether elements are within
|
||||
* the user visible viewport of a web browser.
|
||||
* only accounts for vertical position, not horizontal.
|
||||
*/
|
||||
var $w = $(window);
|
||||
$.fn.visible = function(partial,hidden,direction){
|
||||
|
||||
if (this.length < 1)
|
||||
return;
|
||||
|
||||
var $t = this.length > 1 ? this.eq(0) : this,
|
||||
t = $t.get(0),
|
||||
vpWidth = $w.width(),
|
||||
vpHeight = $w.height(),
|
||||
direction = (direction) ? direction : 'both',
|
||||
clientSize = hidden === true ? t.offsetWidth * t.offsetHeight : true;
|
||||
|
||||
if (typeof t.getBoundingClientRect === 'function'){
|
||||
|
||||
// Use this native browser method, if available.
|
||||
var rec = t.getBoundingClientRect(),
|
||||
tViz = rec.top >= 0 && rec.top < vpHeight,
|
||||
bViz = rec.bottom > 0 && rec.bottom <= vpHeight,
|
||||
lViz = rec.left >= 0 && rec.left < vpWidth,
|
||||
rViz = rec.right > 0 && rec.right <= vpWidth,
|
||||
vVisible = partial ? tViz || bViz : tViz && bViz,
|
||||
hVisible = partial ? lViz || rViz : lViz && rViz;
|
||||
|
||||
if(direction === 'both')
|
||||
return clientSize && vVisible && hVisible;
|
||||
else if(direction === 'vertical')
|
||||
return clientSize && vVisible;
|
||||
else if(direction === 'horizontal')
|
||||
return clientSize && hVisible;
|
||||
} else {
|
||||
|
||||
var viewTop = $w.scrollTop(),
|
||||
viewBottom = viewTop + vpHeight,
|
||||
viewLeft = $w.scrollLeft(),
|
||||
viewRight = viewLeft + vpWidth,
|
||||
offset = $t.offset(),
|
||||
_top = offset.top,
|
||||
_bottom = _top + $t.height(),
|
||||
_left = offset.left,
|
||||
_right = _left + $t.width(),
|
||||
compareTop = partial === true ? _bottom : _top,
|
||||
compareBottom = partial === true ? _top : _bottom,
|
||||
compareLeft = partial === true ? _right : _left,
|
||||
compareRight = partial === true ? _left : _right;
|
||||
|
||||
if(direction === 'both')
|
||||
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop)) && ((compareRight <= viewRight) && (compareLeft >= viewLeft));
|
||||
else if(direction === 'vertical')
|
||||
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop));
|
||||
else if(direction === 'horizontal')
|
||||
return !!clientSize && ((compareRight <= viewRight) && (compareLeft >= viewLeft));
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -2904,7 +2904,8 @@ footer {
|
|||
}
|
||||
|
||||
.nav-tab.is-active,
|
||||
.nav-tab.nav-tab--search:hover {
|
||||
.nav-tab.nav-tab--search:hover,
|
||||
.nav-tab.nav-tab--search.is-hover {
|
||||
background: #389ed9;
|
||||
color: white;
|
||||
|
||||
|
@ -3893,6 +3894,10 @@ footer {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.is-hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
&.activity-entry--removeable {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
|
|
@ -154,6 +154,38 @@ class KeyboardShortcutsTest < TestCase
|
|||
timeout: 6,
|
||||
)
|
||||
|
||||
# open online notification
|
||||
@browser_agent = browser_instance
|
||||
login(
|
||||
browser: @browser_agent,
|
||||
username: 'agent1@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
ticket2 = ticket_create(
|
||||
browser: @browser_agent,
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'Test Ticket for Shortcuts II - ABC123',
|
||||
body: 'Test Ticket Body for Shortcuts II - ABC123',
|
||||
},
|
||||
)
|
||||
sleep 5
|
||||
shortcut(key: 'y')
|
||||
watch_for(
|
||||
css: '.js-notificationsContainer',
|
||||
value: 'Test Ticket for Shortcuts II',
|
||||
timeout: 10,
|
||||
)
|
||||
window_keys(value: :arrow_down)
|
||||
window_keys(value: :enter)
|
||||
watch_for(
|
||||
css: '.active.content',
|
||||
value: ticket2[:number],
|
||||
timeout: 2,
|
||||
)
|
||||
|
||||
shortcut(key: 'e')
|
||||
watch_for(
|
||||
css: 'body',
|
||||
|
|
Loading…
Reference in a new issue