Added desktop notifications.
This commit is contained in:
parent
a28dc17b32
commit
84d30e4ee0
11 changed files with 124 additions and 46 deletions
|
@ -550,6 +550,25 @@ class App.Controller extends Spine.Controller
|
|||
'meta-task-update'
|
||||
)
|
||||
|
||||
locationVerify: (e, callback) =>
|
||||
newLocation = $(e.currentTarget).attr 'href'
|
||||
@log 'debug', "new location #{newLocation}"
|
||||
return if !newLocation
|
||||
currentLocation = Spine.Route.getPath()
|
||||
@log 'debug', "current location #{currentLocation}"
|
||||
return if newLocation.replace(/#/, '') isnt currentLocation
|
||||
@locationExecute(newLocation, callback)
|
||||
|
||||
locationExecute: (location, callback) =>
|
||||
if callback
|
||||
callback()
|
||||
location = location.replace(/#/, '')
|
||||
@log 'debug', "execute controller again for '#{location}' because of same hash"
|
||||
Spine.Route.matchRoutes(location)
|
||||
|
||||
logoUrl: ->
|
||||
"#{@Config.get('image_path')}/#{@Config.get('product_logo')}"
|
||||
|
||||
class App.ControllerPermanent extends App.Controller
|
||||
constructor: ->
|
||||
super
|
||||
|
|
|
@ -150,11 +150,9 @@ class App.SettingsAreaLogo extends App.Controller
|
|||
@render()
|
||||
|
||||
render: ->
|
||||
logoFile = App.Config.get('product_logo')
|
||||
logoUrl = App.Config.get('image_path') + "/#{logoFile}"
|
||||
@html App.view('settings/logo')(
|
||||
setting: @setting
|
||||
logoUrl: logoUrl
|
||||
logoUrl: @logoUrl()
|
||||
)
|
||||
|
||||
onLogoPick: (event) =>
|
||||
|
|
|
@ -94,6 +94,10 @@ class App.CustomerChat extends App.Controller
|
|||
# do not play sound on initial load
|
||||
if counter > 0 && @lastWaitingChatCount isnt undefined
|
||||
@sounds.chat_new.play()
|
||||
@notifyDesktop(
|
||||
title: "#{counter} #{App.i18n.translateInline('Waiting Customers')}",
|
||||
url: '#customer_chat'
|
||||
)
|
||||
@lastWaitingChatCount = counter
|
||||
|
||||
# collect chat window messages
|
||||
|
@ -152,7 +156,6 @@ class App.CustomerChat extends App.Controller
|
|||
addChat: (session) ->
|
||||
return if @chatWindows[session.session_id]
|
||||
chat = new ChatWindow
|
||||
name: "#{session.created_at}"
|
||||
session: session
|
||||
removeCallback: @removeChat
|
||||
messageCallback: @updateNavMenu
|
||||
|
@ -234,6 +237,9 @@ class ChatWindow extends App.Controller
|
|||
@isAgentTyping = false
|
||||
@resetUnreadMessages()
|
||||
|
||||
chat = App.Chat.find(@session.chat_id)
|
||||
@name = "#{chat.displayName()} [##{@session.id}]"
|
||||
|
||||
@on 'layout-change', @scrollToBottom
|
||||
|
||||
@bind('chat_session_typing', (data) =>
|
||||
|
@ -252,10 +258,14 @@ class ChatWindow extends App.Controller
|
|||
@addStatusMessage("<strong>#{data.realname}</strong> has left the conversation")
|
||||
@goOffline()
|
||||
)
|
||||
@bind('chat_focus', (data) =>
|
||||
return if data.session_id isnt @session.session_id
|
||||
@focus()
|
||||
)
|
||||
|
||||
render: ->
|
||||
@html App.view('customer_chat/chat_window')
|
||||
name: @options.name
|
||||
name: @name
|
||||
|
||||
@el.one 'transitionend', @onTransitionend
|
||||
|
||||
|
@ -389,6 +399,13 @@ class ChatWindow extends App.Controller
|
|||
@addUnreadMessages()
|
||||
@updateModified(true)
|
||||
@sounds.message.play()
|
||||
@notifyDesktop(
|
||||
title: @name
|
||||
body: message
|
||||
url: '#customer_chat'
|
||||
callback: =>
|
||||
App.Event.trigger('chat_focus', { session_id: @session.session_id })
|
||||
)
|
||||
|
||||
unreadMessages: =>
|
||||
@unreadMessagesCounter
|
||||
|
|
|
@ -305,13 +305,10 @@ class Base extends App.Wizard
|
|||
else
|
||||
url = "#{http_type}://#{fqdn}"
|
||||
|
||||
logoFile = App.Config.get('product_logo')
|
||||
logoUrl = App.Config.get('image_path') + "/#{logoFile}"
|
||||
|
||||
organization = App.Config.get('organization')
|
||||
@html App.view('getting_started/base')(
|
||||
url: url
|
||||
logoUrl: logoUrl
|
||||
logoUrl: @logoUrl()
|
||||
organization: organization
|
||||
)
|
||||
@$('input, select').first().focus()
|
||||
|
|
|
@ -53,12 +53,9 @@ class Index extends App.ControllerContent
|
|||
if @Config.get( provider.config ) is true || @Config.get( provider.config ) is 'true'
|
||||
auth_providers.push provider
|
||||
|
||||
logoFile = App.Config.get('product_logo')
|
||||
logoUrl = App.Config.get('image_path') + "/#{logoFile}"
|
||||
|
||||
@html App.view('login')(
|
||||
item: data
|
||||
logoUrl: logoUrl
|
||||
logoUrl: @logoUrl()
|
||||
auth_providers: auth_providers
|
||||
)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class App.TaskbarWidget extends App.Controller
|
||||
events:
|
||||
'click .js-close': 'remove'
|
||||
'click .js-locationVerify': 'location'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -74,6 +75,10 @@ class App.TaskbarWidget extends App.Controller
|
|||
|
||||
@el.sortable(dndOptions)
|
||||
|
||||
location: (e) =>
|
||||
return if !$(e.currentTarget).hasClass('is-modified')
|
||||
@locationVerify(e)
|
||||
|
||||
remove: (e, key = false, force = false) =>
|
||||
e.preventDefault()
|
||||
if !key
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
class App.Notify extends App.ControllerWidgetPermanent
|
||||
desktopNotify: {}
|
||||
desktopNotifyCounter: 0
|
||||
|
||||
events:
|
||||
'click .alert': 'destroy'
|
||||
|
||||
|
@ -12,17 +15,48 @@ class App.Notify extends App.ControllerWidgetPermanent
|
|||
@log 'notify:removeall', @
|
||||
@destroyAll()
|
||||
|
||||
@bind 'notifyDesktop', (data) ->
|
||||
@bind 'notifyDesktop', (data) =>
|
||||
return if !window.Notification
|
||||
|
||||
if !data['icon']
|
||||
data['icon'] = 'unknown'
|
||||
notify.createNotification( data.msg, data )
|
||||
data['icon'] = @logoUrl()
|
||||
|
||||
timeout = 60000 * 60 * 24
|
||||
if document.hasFocus()
|
||||
timeout = 4000
|
||||
|
||||
@desktopNotifyCounter += 1
|
||||
counter = @desktopNotifyCounter
|
||||
notification = new window.Notification(data.title, data)
|
||||
@desktopNotify[counter] = notification
|
||||
|
||||
notification.onclose = (e) =>
|
||||
delete @desktopNotify[counter]
|
||||
|
||||
notification.onclick = (e) =>
|
||||
window.focus()
|
||||
if data.url
|
||||
@locationExecute(data.url)
|
||||
if data.callback
|
||||
data.callback()
|
||||
|
||||
if data.timeout || timeout
|
||||
App.Delay.set(
|
||||
-> notification.close()
|
||||
data.timeout || timeout
|
||||
)
|
||||
|
||||
# request desktop notification after login
|
||||
@bind 'auth', (data) ->
|
||||
if !_.isEmpty(data)
|
||||
notify.requestPermission()
|
||||
return if !window.Notification
|
||||
window.Notification.requestPermission()
|
||||
|
||||
notify.config( pageVisibility: false )
|
||||
$(window).focus(
|
||||
=>
|
||||
for counter, notification of @desktopNotify
|
||||
notification.close()
|
||||
)
|
||||
|
||||
render: (data) ->
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class App.OnlineNotificationWidget extends App.Controller
|
||||
alreadyShown: {}
|
||||
|
||||
elements:
|
||||
'.js-toggleNotifications': 'toggle'
|
||||
|
||||
|
@ -102,6 +104,7 @@ class App.OnlineNotificationWidget extends App.Controller
|
|||
|
||||
fetch: =>
|
||||
load = (items) =>
|
||||
@fetchedData = true
|
||||
App.OnlineNotification.refresh( items, { clear: true } )
|
||||
@updateContent()
|
||||
App.OnlineNotification.fetchFull(load)
|
||||
|
@ -133,17 +136,21 @@ class App.OnlineNotificationWidget extends App.Controller
|
|||
|
||||
notificationsContainer = $('.js-notificationsContainer .popover-content')
|
||||
|
||||
# generate desktop notifications
|
||||
for item in items
|
||||
if !@alreadyShown[item.id]
|
||||
@alreadyShown[item.id] = true
|
||||
if @fetchedData
|
||||
word = "#{item.type}d"
|
||||
title = "#{item.created_by.displayName()} #{App.i18n.translateInline(word)} #{App.i18n.translateInline(item.object_name)} #{item.title}"
|
||||
@notifyDesktop(
|
||||
url: item.link
|
||||
title: title
|
||||
)
|
||||
|
||||
# execute controller again of already open (because hash hasn't changed, we need to do it manually)
|
||||
notificationsContainer.find('.js-locationVerify').on('click', (e) =>
|
||||
newLocation = $(e.target).attr 'href'
|
||||
if !newLocation
|
||||
newLocation = $(e.target).closest('.js-locationVerify').attr 'href'
|
||||
return if !newLocation
|
||||
currentLocation = Spine.Route.getPath()
|
||||
return if newLocation.replace(/#/, '') isnt currentLocation
|
||||
@hidePopover()
|
||||
@log 'debug', "execute controller again for '#{currentLocation}' because of same hash"
|
||||
Spine.Route.matchRoutes(currentLocation)
|
||||
@locationVerify(e, @hidePopover)
|
||||
)
|
||||
|
||||
# close notification list on click
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<% for item in @taskItems: %>
|
||||
<a href="<%- item.meta.url %>" title="<%= item.meta.title %>" class="nav-tab task <%= item.meta.class %><% if item.task.active: %> is-active<% end %><% if item.task.notify: %> is-modified<% end %>" data-key="<%- item.task.key %>">
|
||||
<a href="<%- item.meta.url %>" title="<%= item.meta.title %>" class="nav-tab task js-locationVerify <%= item.meta.class %><% if item.task.active: %> is-active<% end %><% if item.task.notify: %> is-modified<% end %>" data-key="<%- item.task.key %>">
|
||||
<div class="nav-tab-icon" title="<%- @Ti(item.meta.iconTitle) %>">
|
||||
<% if item.meta.type is 'task': %>
|
||||
<% if item.task.notify: %>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="activity-body">
|
||||
<a class="activity-message js-locationVerify" href="<%- item.link %>">
|
||||
<span class="activity-text">
|
||||
<%= item.created_by.displayName() %> <%- @T( item.type ) %> <%- @T( item.object_name ) %><% if item.title: %> <strong><%= item.title %></strong><% end %>
|
||||
<%= item.created_by.displayName() %> <%- @T( "#{item.type}d" ) %> <%- @T( item.object_name ) %><% if item.title: %> <strong><%= item.title %></strong><% end %>
|
||||
</span>
|
||||
<%- @humanTime(item.created_at, false, 'activity-time') %>
|
||||
</a>
|
||||
|
|
|
@ -502,13 +502,28 @@ class ChatTest < TestCase
|
|||
end
|
||||
|
||||
def test_timeouts
|
||||
agent = browser_instance
|
||||
login(
|
||||
browser: agent,
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all(
|
||||
browser: agent,
|
||||
)
|
||||
click(
|
||||
browser: agent,
|
||||
css: 'a[href="#customer_chat"]',
|
||||
)
|
||||
agent.find_elements( { css: '.active .chat-window .js-close' } ).each(&:click)
|
||||
|
||||
# no customer action, hide widget
|
||||
customer = browser_instance
|
||||
location(
|
||||
browser: customer,
|
||||
url: "#{browser_url}/assets/chat/znuny.html?port=#{ENV['WS_PORT']}",
|
||||
)
|
||||
|
||||
# no customer action, hide widget
|
||||
watch_for(
|
||||
browser: customer,
|
||||
css: '.zammad-chat',
|
||||
|
@ -557,22 +572,11 @@ class ChatTest < TestCase
|
|||
browser: customer,
|
||||
css: '.js-chat-open',
|
||||
)
|
||||
|
||||
agent = browser_instance
|
||||
login(
|
||||
watch_for(
|
||||
browser: agent,
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
css: '.js-chatMenuItem .counter',
|
||||
value: '1',
|
||||
)
|
||||
tasks_close_all(
|
||||
browser: agent,
|
||||
)
|
||||
click(
|
||||
browser: agent,
|
||||
css: 'a[href="#customer_chat"]',
|
||||
)
|
||||
agent.find_elements( { css: '.active .chat-window .js-close' } ).each(&:click)
|
||||
click(
|
||||
browser: agent,
|
||||
css: '.active .js-acceptChat',
|
||||
|
|
Loading…
Reference in a new issue