Split of article actions into separate backends.

This commit is contained in:
Martin Edenhofer 2017-11-14 14:35:31 +01:00
parent ab189f46ec
commit f3ff650bfe
9 changed files with 460 additions and 371 deletions

View file

@ -0,0 +1,34 @@
class Delete
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
if article.type.name is 'note'
user = undefined
if App.Session.get('id') == article.created_by_id
user = App.User.find(App.Session.get('id'))
if user.permission('ticket.agent')
actions.push {
name: 'delete'
type: 'delete'
icon: 'trash'
href: '#'
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'delete'
callback = ->
article = App.TicketArticle.find(article.id)
article.destroy()
new App.ControllerConfirm(
message: 'Sure?'
callback: callback
container: ui.el.closest('.content')
)
true
App.Config.set('900-Delete', Delete, 'TicketZoomArticleAction')

View file

@ -0,0 +1,135 @@
class EmailReply extends App.Controller
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
group = ticket.group
if group.email_address_id && (article.type.name is 'email' || article.type.name is 'web')
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
recipients = []
if article.sender.name is 'Customer'
if article.from
localRecipients = emailAddresses.parseAddressList(article.from)
if localRecipients
recipients = recipients.concat localRecipients
if article.to
localRecipients = emailAddresses.parseAddressList(article.to)
if localRecipients
recipients = recipients.concat localRecipients
if article.cc
localRecipients = emailAddresses.parseAddressList(article.cc)
if localRecipients
recipients = recipients.concat localRecipients
# remove system addresses
localAddresses = App.EmailAddress.all()
forgeinRecipients = []
recipientUsed = {}
for recipient in recipients
if !_.isEmpty(recipient.address)
localRecipientAddress = recipient.address.toString().toLowerCase()
if !recipientUsed[localRecipientAddress]
recipientUsed[localRecipientAddress] = true
localAddress = false
for address in localAddresses
if localRecipientAddress is address.email.toString().toLowerCase()
recipientUsed[localRecipientAddress] = true
localAddress = true
if !localAddress
forgeinRecipients.push recipient
# check if reply all is neede
if forgeinRecipients.length > 1
actions.push {
name: 'reply all'
type: 'emailReplyAll'
icon: 'reply-all'
href: '#'
}
if article.sender.name is 'Customer' && article.type.name is 'phone'
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
if article.sender.name is 'Agent' && article.type.name is 'phone'
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'emailReply' && type isnt 'emailReplyAll'
if type isnt 'emailReply'
@emailReply(true, ticket, article, ui)
else if type isnt 'emailReplyAll'
@emailReply(false, ticket, article, ui)
true
@emailReply: (all = false, ticket, article, ui) ->
# get reference article
type = App.TicketArticleType.find(article.type_id)
article_created_by = App.User.find(article.created_by_id)
email_addresses = App.EmailAddress.all()
ui.scrollToCompose()
# empty form
articleNew = App.Utils.getRecipientArticle(ticket, article, article_created_by, type, email_addresses, all)
# get current body
body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
# check if quote need to be added
signaturePosition = 'bottom'
selected = App.ClipBoard.getSelected('html')
if selected
selected = App.Utils.htmlCleanup(selected).html()
if !selected
selected = App.ClipBoard.getSelected('text')
if selected
selected = App.Utils.textCleanup(selected)
selected = App.Utils.text2html(selected)
# full quote, if needed
if !selected && article && App.Config.get('ui_ticket_zoom_article_email_full_quote')
signaturePosition = 'top'
if article.content_type.match('html')
selected = App.Utils.textCleanup(article.body)
if article.content_type.match('plain')
selected = App.Utils.textCleanup(article.body)
selected = App.Utils.text2html(selected)
if selected
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
# add selected text to body
body = selected + body
articleNew.body = body
type = App.TicketArticleType.findByAttribute(name:'email')
App.Event.trigger('ui::ticket::setArticleType', {
ticket: ticket
type: type
article: articleNew
signaturePosition: signaturePosition
})
true
App.Config.set('200-EmailReply', EmailReply, 'TicketZoomArticleAction')

View file

@ -0,0 +1,37 @@
class FacebookReply
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
if article.type.name is 'facebook feed post' || article.type.name is 'facebook feed comment'
actions.push {
name: 'reply'
type: 'facebookFeedReply'
icon: 'reply'
href: '#'
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'facebookFeedReply'
ui.scrollToCompose()
type = App.TicketArticleType.findByAttribute('name', 'facebook feed comment')
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
App.Event.trigger('ui::ticket::setArticleType', {
ticket: ticket
type: type
article: articleNew
})
true
App.Config.set('300-FacebookReply', FacebookReply, 'TicketZoomArticleAction')

View file

@ -0,0 +1,40 @@
class Internal
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
if article.internal is true
actions.push {
name: 'set to public'
type: 'public'
icon: 'lock-open'
}
else
actions.push {
name: 'set to internal'
type: 'internal'
icon: 'lock'
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'internal' && type isnt 'public'
# storage update
internal = true
if article.internal == true
internal = false
ui.lastAttributres.internal = internal
article.updateAttributes(internal: internal)
# runtime update
if internal
articleContainer.addClass('is-internal')
else
articleContainer.removeClass('is-internal')
ui.render()
true
App.Config.set('100-Internal', Internal, 'TicketZoomArticleAction')

View file

@ -0,0 +1,18 @@
class Split
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
actions.push {
name: 'split'
type: 'split'
icon: 'split'
href: "#ticket/create/#{article.ticket_id}/#{article.id}"
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'split'
ui.navigate "#ticket/create/#{article.ticket_id}/#{article.id}"
true
App.Config.set('700-Split', Split, 'TicketZoomArticleAction')

View file

@ -0,0 +1,45 @@
class TelegramReply
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
if article.sender.name is 'Customer' && article.type.name is 'telegram personal-message'
actions.push {
name: 'reply'
type: 'telegramPersonalMessageReply'
icon: 'reply'
href: '#'
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'telegramPersonalMessageReply'
ui.scrollToCompose()
# get reference article
type = App.TicketArticleType.find(article.type_id)
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
if article.message_id
articleNew.in_reply_to = article.message_id
# get current body
articleNew.body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
App.Event.trigger('ui::ticket::setArticleType', {
ticket: ticket
type: type
article: articleNew
position: 'end'
})
true
App.Config.set('300-TelegramReply', TelegramReply, 'TicketZoomArticleAction')

View file

@ -0,0 +1,128 @@
class TwitterReply
@action: (actions, ticket, article, ui) ->
return actions if ui.permissionCheck('ticket.customer')
if article.type.name is 'twitter status'
actions.push {
name: 'reply'
type: 'twitterStatusReply'
icon: 'reply'
href: '#'
}
if article.type.name is 'twitter direct-message'
actions.push {
name: 'reply'
type: 'twitterDirectMessageReply'
icon: 'reply'
href: '#'
}
actions
@perform: (articleContainer, type, ticket, article, ui) ->
return true if type isnt 'twitterStatusReply' && type isnt 'twitterDirectMessageReply'
if type is 'twitterStatusReply'
@twitterStatusReply(ticket, article, ui)
else if type is 'twitterDirectMessageReply'
@twitterDirectMessageReply(ticket, article, ui)
true
@twitterStatusReply: (ticket, article, ui) ->
ui.scrollToCompose()
# get reference article
type = App.TicketArticleType.find(article.type_id)
# empty form
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
if article.message_id
articleNew.in_reply_to = article.message_id
# get current body
body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
articleNew.body = body
recipients = article.from
if article.to
if recipients
recipients += ', '
recipients += article.to
if recipients
recipientString = ''
recipientScreenNames = recipients.split(',')
for recipientScreenName in recipientScreenNames
if recipientScreenName
recipientScreenName = recipientScreenName.trim().toLowerCase()
# exclude already listed screen name
exclude = false
if body && body.toLowerCase().match(recipientScreenName)
exclude = true
# exclude own screen_name
if recipientScreenName is "@#{@ticket.preferences.channel_screen_name}".toLowerCase()
exclude = true
if exclude is false
if recipientString isnt ''
recipientString += ' '
recipientString += recipientScreenName
if body
articleNew.body = "#{recipientString} #{body}&nbsp;"
else
articleNew.body = "#{recipientString}&nbsp;"
App.Event.trigger('ui::ticket::setArticleType', {
ticket: ticket
type: type
article: articleNew
position: 'end'
})
@twitterDirectMessageReply: (ticket, article, ui) ->
# get reference article
type = App.TicketArticleType.find(article.type_id)
sender = App.TicketArticleSender.find(article.sender_id)
customer = App.User.find(article.created_by_id)
ui.scrollToCompose()
# empty form
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
if article.message_id
articleNew.in_reply_to = article.message_id
if sender.name is 'Agent'
articleNew.to = article.to
else
articleNew.to = article.from
if !articleNew.to
articleNew.to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
App.Event.trigger('ui::ticket::setArticleType', {
ticket: ticket
type: type
article: articleNew
})
App.Config.set('300-TwitterReply', TwitterReply, 'TicketZoomArticleAction')

View file

@ -1,21 +1,13 @@
class App.TicketZoomArticleActions extends App.Controller
events:
'click [data-type=public]': 'publicInternal'
'click [data-type=internal]': 'publicInternal'
'click [data-type=emailReply]': 'emailReply'
'click [data-type=emailReplyAll]': 'emailReplyAll'
'click [data-type=twitterStatusReply]': 'twitterStatusReply'
'click [data-type=twitterDirectMessageReply]': 'twitterDirectMessageReply'
'click [data-type=facebookFeedReply]': 'facebookFeedReply'
'click [data-type=telegramPersonalMessageReply]': 'telegramPersonalMessageReply'
'click [data-type=delete]': 'delete'
'click .js-ArticleAction': 'actionPerform'
constructor: ->
super
@render()
render: ->
actions = @actionRow(@article)
actions = @actionRow(@ticket, @article)
if actions
@html App.view('ticket_zoom/article_view_actions')(
@ -25,371 +17,31 @@ class App.TicketZoomArticleActions extends App.Controller
else
@html ''
publicInternal: (e) =>
e.preventDefault()
articleContainer = $(e.target).closest('.ticket-article-item')
article_id = $(e.target).parents('[data-id]').data('id')
# storage update
article = App.TicketArticle.find(article_id)
internal = true
if article.internal == true
internal = false
@lastAttributres.internal = internal
article.updateAttributes(internal: internal)
# runntime update
if internal
articleContainer.addClass('is-internal')
else
articleContainer.removeClass('is-internal')
@render()
actionRow: (article) ->
if @permissionCheck('ticket.customer')
return []
actionRow: (ticket, article) ->
actionConfig = App.Config.get('TicketZoomArticleAction')
keys = _.keys(actionConfig).sort()
actions = []
if article.internal is true
actions = [
{
name: 'set to public'
type: 'public'
icon: 'lock-open'
}
]
else
actions = [
{
name: 'set to internal'
type: 'internal'
icon: 'lock'
}
]
#if @article.type.name is 'note'
# actions.push []
group = @ticket.group
if group.email_address_id && (article.type.name is 'email' || article.type.name is 'web')
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
recipients = []
if article.sender.name is 'Customer'
if article.from
localRecipients = emailAddresses.parseAddressList(article.from)
if localRecipients
recipients = recipients.concat localRecipients
if article.to
localRecipients = emailAddresses.parseAddressList(article.to)
if localRecipients
recipients = recipients.concat localRecipients
if article.cc
localRecipients = emailAddresses.parseAddressList(article.cc)
if localRecipients
recipients = recipients.concat localRecipients
# remove system addresses
localAddresses = App.EmailAddress.all()
forgeinRecipients = []
recipientUsed = {}
for recipient in recipients
if !_.isEmpty(recipient.address)
localRecipientAddress = recipient.address.toString().toLowerCase()
if !recipientUsed[localRecipientAddress]
recipientUsed[localRecipientAddress] = true
localAddress = false
for address in localAddresses
if localRecipientAddress is address.email.toString().toLowerCase()
recipientUsed[localRecipientAddress] = true
localAddress = true
if !localAddress
forgeinRecipients.push recipient
# check if reply all is neede
if forgeinRecipients.length > 1
actions.push {
name: 'reply all'
type: 'emailReplyAll'
icon: 'reply-all'
href: '#'
}
if article.sender.name is 'Customer' && article.type.name is 'phone'
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
if article.sender.name is 'Agent' && article.type.name is 'phone'
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
if article.type.name is 'twitter status'
actions.push {
name: 'reply'
type: 'twitterStatusReply'
icon: 'reply'
href: '#'
}
if article.type.name is 'twitter direct-message'
actions.push {
name: 'reply'
type: 'twitterDirectMessageReply'
icon: 'reply'
href: '#'
}
if article.type.name is 'facebook feed post' || article.type.name is 'facebook feed comment'
actions.push {
name: 'reply'
type: 'facebookFeedReply'
icon: 'reply'
href: '#'
}
if article.sender.name is 'Customer' && article.type.name is 'telegram personal-message'
actions.push {
name: 'reply'
type: 'telegramPersonalMessageReply'
icon: 'reply'
href: '#'
}
actions.push {
name: 'split'
type: 'split'
icon: 'split'
href: '#ticket/create/' + article.ticket_id + '/' + article.id
}
if article.type.name is 'note'
user = undefined
if App.Session.get('id') == article.created_by_id
user = App.User.find(App.Session.get('id'))
if user.permission('ticket.agent')
actions.push {
name: 'delete'
type: 'delete'
icon: 'trash'
href: '#'
}
for key in keys
config = actionConfig[key]
if config
actions = config.action(actions, ticket, article, @)
actions
facebookFeedReply: (e) =>
actionPerform: (e) =>
e.preventDefault()
type = App.TicketArticleType.findByAttribute('name', 'facebook feed comment')
@scrollToCompose()
articleContainer = $(e.target).closest('.ticket-article-item')
type = $(e.currentTarget).attr('data-type')
ticket = App.Ticket.fullLocal(@ticket.id)
article = App.TicketArticle.fullLocal(@article.id)
# empty form
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
twitterStatusReply: (e) =>
e.preventDefault()
# get reference article
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.fullLocal(article_id)
sender = App.TicketArticleSender.find(article.sender_id)
type = App.TicketArticleType.find(article.type_id)
customer = App.User.find(article.created_by_id)
@scrollToCompose()
# empty form
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
if article.message_id
articleNew.in_reply_to = article.message_id
# get current body
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
articleNew.body = body
recipients = article.from
if article.to
if recipients
recipients += ', '
recipients += article.to
if recipients
recipientString = ''
recipientScreenNames = recipients.split(',')
for recipientScreenName in recipientScreenNames
if recipientScreenName
recipientScreenName = recipientScreenName.trim().toLowerCase()
# exclude already listed screen name
exclude = false
if body && body.toLowerCase().match(recipientScreenName)
exclude = true
# exclude own screen_name
if recipientScreenName is "@#{@ticket.preferences.channel_screen_name}".toLowerCase()
exclude = true
if exclude is false
if recipientString isnt ''
recipientString += ' '
recipientString += recipientScreenName
if body
articleNew.body = "#{recipientString} #{body}&nbsp;"
else
articleNew.body = "#{recipientString}&nbsp;"
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew, position: 'end' } )
twitterDirectMessageReply: (e) =>
e.preventDefault()
# get reference article
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.fullLocal(article_id)
type = App.TicketArticleType.find(article.type_id)
sender = App.TicketArticleSender.find(article.sender_id)
customer = App.User.find(article.created_by_id)
@scrollToCompose()
# empty form
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
if article.message_id
articleNew.in_reply_to = article.message_id
if sender.name is 'Agent'
articleNew.to = article.to
else
articleNew.to = article.from
if !articleNew.to
articleNew.to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
emailReplyAll: (e) =>
@emailReply(e, true)
emailReply: (e, all = false) =>
e.preventDefault()
# get reference article
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.fullLocal(article_id)
ticket = App.Ticket.fullLocal(article.ticket_id)
type = App.TicketArticleType.find(article.type_id)
article_created_by = App.User.find(article.created_by_id)
email_addresses = App.EmailAddress.all()
@scrollToCompose()
# empty form
articleNew = App.Utils.getRecipientArticle(ticket, article, article_created_by, type, email_addresses, all)
# get current body
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
# check if quote need to be added
signaturePosition = 'bottom'
selected = App.ClipBoard.getSelected('html')
if selected
selected = App.Utils.htmlCleanup(selected).html()
if !selected
selected = App.ClipBoard.getSelected('text')
if selected
selected = App.Utils.textCleanup(selected)
selected = App.Utils.text2html(selected)
# full quote, if needed
if !selected && article && App.Config.get('ui_ticket_zoom_article_email_full_quote')
signaturePosition = 'top'
if article.content_type.match('html')
selected = App.Utils.textCleanup(article.body)
if article.content_type.match('plain')
selected = App.Utils.textCleanup(article.body)
selected = App.Utils.text2html(selected)
if selected
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
# add selected text to body
body = selected + body
articleNew.body = body
type = App.TicketArticleType.findByAttribute(name:'email')
App.Event.trigger('ui::ticket::setArticleType', {
ticket: @ticket
type: type
article: articleNew
signaturePosition: signaturePosition
})
telegramPersonalMessageReply: (e) =>
e.preventDefault()
# get reference article
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.fullLocal(article_id)
sender = App.TicketArticleSender.find(article.sender_id)
type = App.TicketArticleType.find(article.type_id)
customer = App.User.find(article.created_by_id)
@scrollToCompose()
# empty form
articleNew = {
to: ''
cc: ''
body: ''
in_reply_to: ''
}
if article.message_id
articleNew.in_reply_to = article.message_id
# get current body
articleNew.body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew, position: 'end' } )
delete: (e) =>
e.preventDefault()
callback = ->
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.find(article_id)
article.destroy()
new App.ControllerConfirm(
message: 'Sure?'
callback: callback
container: @el.closest('.content')
)
actionConfig = App.Config.get('TicketZoomArticleAction')
keys = _.keys(actionConfig).sort()
actions = []
for key in keys
config = actionConfig[key]
if config
return if !config.perform(articleContainer, type, ticket, article, @)
scrollToCompose: =>
@el.closest('.content').find('.article-add').ScrollTo()

View file

@ -1,7 +1,7 @@
<div class="article-content article-actions horizontal stretch">
<% for action in @actions: %>
<a href="<%= action.href %>" data-type="<%= action.type %>" class="article-action u-clickable<% if action.class: %> <%= action.class %><% end %>">
<%- @Icon(action.icon, 'article-action-icon') %><%- @T( action.name ) %>
<a href="<%= action.href %>" data-type="<%= action.type %>" class="article-action js-ArticleAction u-clickable<% if action.class: %> <%= action.class %><% end %>">
<%- @Icon(action.icon, 'article-action-icon') %><%- @T(action.name) %>
</a>
<% end %>
</div>